From a40ec5392fb0b1ed46c9e843b56b1100bb24be46 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 18 Aug 2022 13:16:21 -0300 Subject: [PATCH 0001/1965] Add code from "fibers-optional" branch with some modifications: - Create new context for DDP server too. - Removing some console.logs + adding some TODOS. --- npm-packages/meteor-babel/options.js | 12 +- npm-packages/meteor-babel/runtime.js | 25 +-- packages/boilerplate-generator/generator.js | 4 +- packages/callback-hook/hook.js | 4 +- packages/ddp-server/livedata_server.js | 26 +-- packages/meteor/dynamics_nodejs.js | 184 ++++++++++++++------ packages/meteor/fiber_helpers.js | 105 +++++++---- packages/promise/server.js | 15 +- packages/webapp/connect.js | 4 + packages/webapp/webapp_server.js | 2 + tools/static-assets/server/boot.js | 34 +++- 11 files changed, 288 insertions(+), 127 deletions(-) diff --git a/npm-packages/meteor-babel/options.js b/npm-packages/meteor-babel/options.js index 2d582a3102..dc215572b8 100644 --- a/npm-packages/meteor-babel/options.js +++ b/npm-packages/meteor-babel/options.js @@ -185,11 +185,13 @@ function getDefaultsForNode8(features) { // Ensure that async functions run in a Fiber, while also taking // full advantage of native async/await support in Node 8. - combined.plugins.push([require("./plugins/async-await.js"), { - // Do not transform `await x` to `Promise.await(x)`, since Node - // 8 has native support for await expressions. - useNativeAsyncAwait: false - }]); + if (!process.env.DISABLE_FIBERS) { + combined.plugins.push([require("./plugins/async-await.js"), { + // Do not transform `await x` to `Promise.await(x)`, since Node + // 8 has native support for await expressions. + useNativeAsyncAwait: false + }]); + } // Enable async generator functions proposal. combined.plugins.push(require("@babel/plugin-proposal-async-generator-functions")); diff --git a/npm-packages/meteor-babel/runtime.js b/npm-packages/meteor-babel/runtime.js index 43b5c85c85..c4e69bd468 100644 --- a/npm-packages/meteor-babel/runtime.js +++ b/npm-packages/meteor-babel/runtime.js @@ -11,19 +11,22 @@ Module.prototype.resolve = function (id) { require("@meteorjs/reify/lib/runtime").enable(Module.prototype); -require("meteor-promise").makeCompatible( - global.Promise = global.Promise || - require("promise/lib/es6-extensions"), - require("fibers") -); +if (!!!process.env.DISABLE_FIBERS) { + require("meteor-promise").makeCompatible( + global.Promise = global.Promise || + require("promise/lib/es6-extensions"), + require("fibers") + ); // If Promise.asyncApply is defined, use it to wrap calls to // regeneratorRuntime.async so that the entire async function will run in // its own Fiber, not just the code that comes after the first await. -if (typeof Promise.asyncApply === "function") { - var regeneratorRuntime = require("@babel/runtime/regenerator"); - var realAsync = regeneratorRuntime.async; - regeneratorRuntime.async = function (innerFn) { - return Promise.asyncApply(realAsync, regeneratorRuntime, arguments); - }; + if (typeof Promise.asyncApply === "function") { + var regeneratorRuntime = require("@babel/runtime/regenerator"); + var realAsync = regeneratorRuntime.async; + regeneratorRuntime.async = function (innerFn) { + return Promise.asyncApply(realAsync, regeneratorRuntime, arguments); + }; + } } + diff --git a/packages/boilerplate-generator/generator.js b/packages/boilerplate-generator/generator.js index 0b076528f4..b281218354 100644 --- a/packages/boilerplate-generator/generator.js +++ b/packages/boilerplate-generator/generator.js @@ -1,11 +1,11 @@ -import { readFile } from 'fs'; +import {readFileSync} from 'fs'; import { create as createStream } from "combined-stream2"; import WebBrowserTemplate from './template-web.browser'; import WebCordovaTemplate from './template-web.cordova'; // Copied from webapp_server -const readUtf8FileSync = filename => Meteor.wrapAsync(readFile)(filename, 'utf8'); +const readUtf8FileSync = filename => readFileSync(filename, 'utf8'); const identity = value => value; diff --git a/packages/callback-hook/hook.js b/packages/callback-hook/hook.js index 8a8aa29ecd..f99331d71a 100644 --- a/packages/callback-hook/hook.js +++ b/packages/callback-hook/hook.js @@ -99,7 +99,9 @@ export class Hook { // Invoking bindEnvironment'd callbacks outside of a Fiber in Node doesn't // run them to completion (and exceptions thrown from onException are not // propagated), so we need to be in a Fiber. - Meteor._nodeCodeMustBeInFiber(); + if (Meteor._isFibersEnabled()) { + Meteor._nodeCodeMustBeInFiber(); + } const ids = Object.keys(this.callbacks); for (let i = 0; i < ids.length; ++i) { diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index a86f9747b4..02ecdec18b 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1,7 +1,7 @@ DDPServer = {}; var Fiber = Npm.require('fibers'); - +const ASL = global.asyncLocalStorage; // Publication strategies define how we handle data from published cursors at the collection level // This allows someone to: // - Choose a trade-off between client-server bandwidth and server memory usage @@ -330,9 +330,9 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - Fiber(function () { + ASL.run({}, function () { self.startUniversalSubs(); - }).run(); + }); if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -557,9 +557,9 @@ Object.assign(Session.prototype, { // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. if (self.heartbeat) { - Fiber(function () { + ASL.run({}, function () { self.heartbeat.messageReceived(); - }).run(); + }); } if (self.version !== 'pre1' && msg_in.msg === 'ping') { @@ -584,7 +584,7 @@ Object.assign(Session.prototype, { return; } - Fiber(function () { + ASL.run({}, function () { var blocked = true; var unblock = function () { @@ -604,7 +604,7 @@ Object.assign(Session.prototype, { else self.sendError('Bad request', msg); unblock(); // in case the handler didn't already do it - }).run(); + }); }; processNext(); @@ -1475,9 +1475,9 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - Fiber(function () { + ASL.run({}, function () { self._handleConnect(socket, msg); - }).run(); + }); return; } @@ -1494,9 +1494,9 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - Fiber(function () { + ASL.run({}, function () { socket._meteorSession.close(); - }).run(); + }); } }); }); @@ -1674,9 +1674,9 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - Fiber(function() { + ASL.run({}, function() { session._startSubscription(handler); - }).run(); + }); } }); } diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 051ebedb17..4d10d268e9 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -4,6 +4,12 @@ var Fiber = Npm.require('fibers'); var nextSlot = 0; +const getStore = () => global.asyncLocalStorage.getStore(); +const getValueFromStore = key => getStore()[key]; +const updateStore = (key, value) => getStore()[key] = value; + +const IS_FIBERS_ENABLED = !!!process.env.DISABLE_FIBERS; + Meteor._nodeCodeMustBeInFiber = function () { if (!Fiber.current) { throw new Error("Meteor code must always run within a Fiber. " + @@ -31,10 +37,16 @@ var EVp = Meteor.EnvironmentVariable.prototype; * @memberof Meteor.EnvironmentVariable */ EVp.get = function () { - Meteor._nodeCodeMustBeInFiber(); + if (IS_FIBERS_ENABLED) { + Meteor._nodeCodeMustBeInFiber(); - return Fiber.current._meteor_dynamics && - Fiber.current._meteor_dynamics[this.slot]; + return Fiber.current._meteor_dynamics && + Fiber.current._meteor_dynamics[this.slot]; + } + + // TODO -> Meteor tools also use this, so the asyncLocalStorage there will be null. + // When handling with tools, we should always end up with the isFibersEnabled condition. + return global?.asyncLocalStorage?.getStore?.() || null; }; // Most Meteor code ought to run inside a fiber, and the @@ -56,16 +68,7 @@ EVp.getOrNullIfOutsideFiber = function () { return this.get(); }; -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValue - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ -EVp.withValue = function (value, func) { +function withValuesWithFiber(value, func) { Meteor._nodeCodeMustBeInFiber(); if (!Fiber.current._meteor_dynamics) @@ -79,6 +82,34 @@ EVp.withValue = function (value, func) { } finally { currentValues[this.slot] = saved; } +} +/** + * @summary Set the environment variable to the given value while a function is run + * @locus Anywhere + * @method withValue + * @memberof Meteor.EnvironmentVariable + * @param {Any} value Value the environment variable should be set to + * @param {Function} func The function to run + * @return {Any} Return value of function + */ +EVp.withValue = function (value, func) { + if (IS_FIBERS_ENABLED) { + return withValuesWithFiber.call(this, value, func); + } + + let meteorDynamics = getValueFromStore('_meteor_dynamics'); + if (!meteorDynamics) { + meteorDynamics = []; + updateStore('_meteor_dynamics', []); + } + + const saved = meteorDynamics[this.slot]; + try { + updateStore('_meteor_dynamics', value); + return func(); + } finally { + updateStore('_meteor_dynamics', saved); + } }; // Meteor application code is always supposed to be run inside a @@ -107,51 +138,102 @@ EVp.withValue = function (value, func) { * @locus Anywhere * @memberOf Meteor * @param {Function} func Function that is wrapped - * @param {Function} onException + * @param {Function} onException * @param {Object} _this Optional `this` object against which the original function will be invoked * @return {Function} The wrapped function */ Meteor.bindEnvironment = function (func, onException, _this) { - Meteor._nodeCodeMustBeInFiber(); - - var dynamics = Fiber.current._meteor_dynamics; - var boundValues = dynamics ? dynamics.slice() : []; - - if (!onException || typeof(onException) === 'string') { - var description = onException || "callback of async function"; - onException = function (error) { - Meteor._debug( - "Exception in " + description + ":", - error - ); - }; - } else if (typeof(onException) !== 'function') { - throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + if (IS_FIBERS_ENABLED) { + return bindEnvironmentWithFibers({ func, onException, _this }); } - return function (/* arguments */) { - var args = Array.prototype.slice.call(arguments); + const savedValues = getValueFromStore('_meteor_dynamics'); + const boundValues = savedValues ? savedValues.slice() : []; - var runWithEnvironment = function () { - var savedValues = Fiber.current._meteor_dynamics; - try { - // Need to clone boundValues in case two fibers invoke this - // function at the same time - Fiber.current._meteor_dynamics = boundValues.slice(); - var ret = func.apply(_this, args); - } catch (e) { - // note: callback-hook currently relies on the fact that if onException - // throws and you were originally calling the wrapped callback from - // within a Fiber, the wrapped call throws. - onException(e); - } finally { - Fiber.current._meteor_dynamics = savedValues; - } - return ret; - }; + return function(/* arguments */) { + const args = Array.prototype.slice.call(arguments); - if (Fiber.current) - return runWithEnvironment(); - Fiber(runWithEnvironment).run(); + const runWithEnvironment = handleRunWithEnvironment({ + args, + _this, + func, + onException, + savedValues, + boundValues, + handleUpdate(values) { + updateStore('_meteor_dynamics', values); + }, + }); + + runWithEnvironment(); }; }; + +const handleRunWithEnvironment = ({ + args, + _this, + savedValues, + boundValues, + handleUpdate, + onException, + func, +}) => () => { + let handleException = onException; + + if (!onException || typeof handleException === 'string') { + const description = handleException || 'callback of async function'; + handleException = function(error) { + Meteor._debug('Exception in ' + description + ':', error); + }; + } else if (typeof handleException !== 'function') { + throw new Error( + 'onException argument must be a function, string or undefined for Meteor.bindEnvironment().' + ); + } + + let ret; + try { + // Need to clone boundValues in case two fibers invoke this + // function at the same time + handleUpdate(boundValues.slice()); + + ret = func.apply(_this, args); + } catch (e) { + // TODO check the scenario from this comment when we don't have a Fiber + // note: callback-hook currently relies on the fact that if onException + // throws and you were originally calling the wrapped callback from + // within a Fiber, the wrapped call throws. + handleException(e); + } finally { + handleUpdate(savedValues); + } + return ret; +}; + +function bindEnvironmentWithFibers({ func, onException, _this }) { + Meteor._nodeCodeMustBeInFiber(); + + // to keep the function scope and don't lose the Fiber.current reference + const fibersCurrent = Fiber.current; + + const savedValues = fibersCurrent._meteor_dynamics; + const boundValues = savedValues ? savedValues.slice() : []; + + return function(/* arguments */) { + const args = Array.prototype.slice.call(arguments); + const runWithEnvironment = handleRunWithEnvironment({ + args, + _this, + func, + onException, + savedValues, + boundValues, + handleUpdate(values) { + fibersCurrent._meteor_dynamics = values; + }, + }); + + if (Fiber.current) return runWithEnvironment(); + Fiber(runWithEnvironment).run(); + }; +} diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index fd56ee29b7..40bb18d361 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -55,20 +55,19 @@ Meteor._SynchronousQueue = function () { var SQp = Meteor._SynchronousQueue.prototype; -SQp.runTask = function (task) { - var self = this; - +const runTaskWithFibers = ({ task, self }) => { if (!self.safeToRunTask()) { - if (Fiber.current) + 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"); + } else { + throw new Error('Can only call runTask in a Fiber'); + } } - var fut = new Future; - var handle = { - task: Meteor.bindEnvironment(task, function (e) { - Meteor._debug("Exception from task", e); + const fut = new Future(); + const handle = { + task: Meteor.bindEnvironment(task, function(e) { + Meteor._debug('Exception from task', e); throw e; }), future: fut, @@ -81,6 +80,31 @@ SQp.runTask = function (task) { fut.wait(); }; +const runTask = ({ task, self }) => { + const handle = { + task: Meteor.bindEnvironment(task, function(e) { + Meteor._debug('Exception from task', e); + throw e; + }), + name: task.name, + }; + + self._taskHandles.push(handle); + self._scheduleRun(); + // TODO Check this comment: + // Yield. We'll get back here after the task is run (and will throw if the + // task throws). +}; + +SQp.runTask = function(task) { + const self = this; + if (global._isFibersEnabled()) { + runTaskWithFibers({ task, self }); + return; + } + runTask({ task, self }); +}; + SQp.queueTask = function (task) { var self = this; self._taskHandles.push({ @@ -121,10 +145,14 @@ SQp._scheduleRun = function () { return; self._runningOrRunScheduled = true; - setImmediate(function () { - Fiber(function () { - self._run(); - }).run(); + setImmediate(function() { + if (global._isFibersEnabled()) { + Fiber(function() { + self._run(); + }).run(); + return; + } + self._run(); }); }; @@ -142,32 +170,45 @@ SQp._run = function () { } var taskHandle = self._taskHandles.shift(); - // Run the task. - self._currentTaskFiber = Fiber.current; var exception = undefined; - try { - taskHandle.task(); - } catch (err) { - if (taskHandle.future) { - // We'll throw this exception through runTask. - exception = err; - } else { - Meteor._debug("Exception in queued task", err); + function runFiber() { + self._currentTaskFiber = Fiber.current; + try { + taskHandle.task(); + } catch (err) { + if (taskHandle.future) { + // We'll throw this exception through runTask. + exception = err; + } else { + Meteor._debug("Exception in queued task", err); + } + } + self._currentTaskFiber = undefined; + } + // Run the task. + if (Meteor._isFibersEnabled()) { + runFiber(); + } else { + try { + taskHandle.task(); + } catch (err) { + Meteor._debug("Exception in queued task", err); } } - self._currentTaskFiber = undefined; // Soon, run the next task, if there is any. self._runningOrRunScheduled = false; self._scheduleRun(); - // If this was queued with runTask, let the runTask call return (throwing if - // the task threw). - if (taskHandle.future) { - if (exception) - taskHandle.future['throw'](exception); - else - taskHandle.future['return'](); + if (Meteor._isFibersEnabled()) { + // If this was queued with runTask, let the runTask call return (throwing if + // the task threw). + if (taskHandle.future) { + if (exception) + taskHandle.future['throw'](exception); + else + taskHandle.future['return'](); + } } }; diff --git a/packages/promise/server.js b/packages/promise/server.js index e07faeb52b..601473d628 100644 --- a/packages/promise/server.js +++ b/packages/promise/server.js @@ -1,11 +1,14 @@ require("./extensions.js"); -require("meteor-promise").makeCompatible( - Promise, - // Allow every Promise callback to run in a Fiber drawn from a pool of - // reusable Fibers. - require("fibers") -); +if (!!!process.env.DISABLE_FIBERS) { + require("meteor-promise").makeCompatible( + Promise, + // Allow every Promise callback to run in a Fiber drawn from a pool of + // reusable Fibers. + require("fibers") + ); +} + // Reference: https://caniuse.com/#feat=promises require("meteor/modern-browsers").setMinimumBrowserVersions({ diff --git a/packages/webapp/connect.js b/packages/webapp/connect.js index 712e19b35f..8338364688 100644 --- a/packages/webapp/connect.js +++ b/packages/webapp/connect.js @@ -1,6 +1,10 @@ import npmConnect from "connect"; export function connect(...connectArgs) { + if (!Meteor._isFibersEnabled()) { + return npmConnect.apply(this, connectArgs); + } + const handlers = npmConnect.apply(this, connectArgs); const originalUse = handlers.use; diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 03d6de971b..36578d7a7c 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -572,6 +572,7 @@ WebAppInternals.staticFilesMiddleware = async function( res, next ) { + // console.log(String(arguments.callee)); var pathname = parseRequest(req).pathname; try { pathname = decodeURIComponent(pathname); @@ -1121,6 +1122,7 @@ function runWebAppServer() { // Serve static files from the manifest. // This is inspired by the 'static' middleware. app.use(function(req, res, next) { + // console.log(String(arguments.callee)); WebAppInternals.staticFilesMiddleware( WebAppInternals.staticFilesByArch, req, diff --git a/tools/static-assets/server/boot.js b/tools/static-assets/server/boot.js index 2707518670..b0122d9273 100644 --- a/tools/static-assets/server/boot.js +++ b/tools/static-assets/server/boot.js @@ -485,10 +485,32 @@ var runMain = Profile("Run main()", function () { } }); -Fiber(function () { - Profile.run("Server startup", function () { - loadServerBundles(); - callStartupHooks(); - runMain(); +function startServerProcess() { + const { AsyncLocalStorage } = require('async_hooks'); + global.asyncLocalStorage = new AsyncLocalStorage(); + + Profile.run('Server startup', function() { + // TODO the if around loadServerBundles should be enough + if (global._isFibersEnabled()) { + loadServerBundles(); + callStartupHooks(); + runMain(); + } else { + global.asyncLocalStorage.run({}, () => { + loadServerBundles(); + callStartupHooks(); + runMain(); + }); + } }); -}).run(); +} + +if (global._isFibersEnabled()) { + Fiber(function() { + startServerProcess(); + }).run(); + return; +} else { + startServerProcess(); +} + From 26b16b5db3fd4cb8731f2bfc6133dcfdc18d6c68 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 19 Aug 2022 20:03:25 -0300 Subject: [PATCH 0002/1965] Move ASL helpers to it's own file and attach it to Meteor's global object. On tools, however, we still need to use the process.env value (when starting the server in boot.js). --- packages/callback-hook/hook.js | 2 +- packages/ddp-server/livedata_server.js | 12 ++++---- packages/meteor/asl-helpers.js | 8 ++++++ packages/meteor/dynamics_nodejs.js | 39 ++++++++++++-------------- packages/meteor/fiber_helpers.js | 13 +++++---- packages/meteor/helpers.js | 2 -- packages/meteor/package.js | 1 + packages/webapp/connect.js | 2 +- tools/static-assets/server/boot.js | 12 ++------ 9 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 packages/meteor/asl-helpers.js diff --git a/packages/callback-hook/hook.js b/packages/callback-hook/hook.js index f99331d71a..ca6c1311cb 100644 --- a/packages/callback-hook/hook.js +++ b/packages/callback-hook/hook.js @@ -99,7 +99,7 @@ export class Hook { // Invoking bindEnvironment'd callbacks outside of a Fiber in Node doesn't // run them to completion (and exceptions thrown from onException are not // propagated), so we need to be in a Fiber. - if (Meteor._isFibersEnabled()) { + if (Meteor._isFibersEnabled) { Meteor._nodeCodeMustBeInFiber(); } diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 02ecdec18b..ea872e102f 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -330,7 +330,7 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - ASL.run({}, function () { + ASL.run(Meteor._getAslStore, function () { self.startUniversalSubs(); }); @@ -557,7 +557,7 @@ Object.assign(Session.prototype, { // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. if (self.heartbeat) { - ASL.run({}, function () { + ASL.run(Meteor._getAslStore, function () { self.heartbeat.messageReceived(); }); } @@ -584,7 +584,7 @@ Object.assign(Session.prototype, { return; } - ASL.run({}, function () { + ASL.run(Meteor._getAslStore, function () { var blocked = true; var unblock = function () { @@ -1475,7 +1475,7 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - ASL.run({}, function () { + ASL.run(Meteor._getAslStore, function () { self._handleConnect(socket, msg); }); return; @@ -1494,7 +1494,7 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - ASL.run({}, function () { + ASL.run(Meteor._getAslStore, function () { socket._meteorSession.close(); }); } @@ -1674,7 +1674,7 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - ASL.run({}, function() { + ASL.run(Meteor._getAslStore, function() { session._startSubscription(handler); }); } diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js new file mode 100644 index 0000000000..77214cfc32 --- /dev/null +++ b/packages/meteor/asl-helpers.js @@ -0,0 +1,8 @@ +const getAslStore = () => (Meteor.isServer && global?.asyncLocalStorage?.getStore()) || {}; +const getValueFromAslStore = key => getAslStore()[key]; +const updateAslStore = (key, value) => getAslStore()[key] = value; + +Meteor._isFibersEnabled = !process.env.DISABLE_FIBERS && Meteor.isServer; +Meteor._getAslStore = getAslStore; +Meteor._getValueFromAslStore = getValueFromAslStore; +Meteor._updateAslStore = updateAslStore; diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 4d10d268e9..b204bb5765 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -1,15 +1,14 @@ -// Fiber-aware implementation of dynamic scoping, for use on the server +/** + * Fiber-aware implementation of dynamic scoping, for use on the server + * + * If we are using Fiber, we store/update/fetch the context from the current Fiber. + * Else, we fetch from the AsyncLocalStorage. + */ var Fiber = Npm.require('fibers'); var nextSlot = 0; -const getStore = () => global.asyncLocalStorage.getStore(); -const getValueFromStore = key => getStore()[key]; -const updateStore = (key, value) => getStore()[key] = value; - -const IS_FIBERS_ENABLED = !!!process.env.DISABLE_FIBERS; - Meteor._nodeCodeMustBeInFiber = function () { if (!Fiber.current) { throw new Error("Meteor code must always run within a Fiber. " + @@ -37,16 +36,14 @@ var EVp = Meteor.EnvironmentVariable.prototype; * @memberof Meteor.EnvironmentVariable */ EVp.get = function () { - if (IS_FIBERS_ENABLED) { + if (Meteor._isFibersEnabled) { Meteor._nodeCodeMustBeInFiber(); return Fiber.current._meteor_dynamics && Fiber.current._meteor_dynamics[this.slot]; } - // TODO -> Meteor tools also use this, so the asyncLocalStorage there will be null. - // When handling with tools, we should always end up with the isFibersEnabled condition. - return global?.asyncLocalStorage?.getStore?.() || null; + return Meteor._getValueFromAslStore("_meteor_dynamics")?.[this.slot] || null; }; // Most Meteor code ought to run inside a fiber, and the @@ -63,7 +60,7 @@ EVp.get = function () { // returns null rather than throwing when called from outside a Fiber. (On the // client, it is identical to get().) EVp.getOrNullIfOutsideFiber = function () { - if (!Fiber.current) + if (!Fiber.current || !Meteor._isFibersEnabled) return null; return this.get(); }; @@ -93,22 +90,22 @@ function withValuesWithFiber(value, func) { * @return {Any} Return value of function */ EVp.withValue = function (value, func) { - if (IS_FIBERS_ENABLED) { + if (Meteor._isFibersEnabled) { return withValuesWithFiber.call(this, value, func); } - let meteorDynamics = getValueFromStore('_meteor_dynamics'); + let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); if (!meteorDynamics) { meteorDynamics = []; - updateStore('_meteor_dynamics', []); + Meteor._updateAslStore('_meteor_dynamics', []); } const saved = meteorDynamics[this.slot]; try { - updateStore('_meteor_dynamics', value); + Meteor._updateAslStore('_meteor_dynamics', value); return func(); } finally { - updateStore('_meteor_dynamics', saved); + Meteor._updateAslStore('_meteor_dynamics', saved); } }; @@ -143,12 +140,12 @@ EVp.withValue = function (value, func) { * @return {Function} The wrapped function */ Meteor.bindEnvironment = function (func, onException, _this) { - if (IS_FIBERS_ENABLED) { + if (Meteor._isFibersEnabled) { return bindEnvironmentWithFibers({ func, onException, _this }); } - const savedValues = getValueFromStore('_meteor_dynamics'); - const boundValues = savedValues ? savedValues.slice() : []; + const savedValues = Meteor._getValueFromAslStore('_meteor_dynamics'); + const boundValues = Array.isArray(savedValues) ? savedValues.slice() : []; return function(/* arguments */) { const args = Array.prototype.slice.call(arguments); @@ -161,7 +158,7 @@ Meteor.bindEnvironment = function (func, onException, _this) { savedValues, boundValues, handleUpdate(values) { - updateStore('_meteor_dynamics', values); + Meteor._updateAslStore('_meteor_dynamics', values); }, }); diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 40bb18d361..613af48e76 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -98,7 +98,7 @@ const runTask = ({ task, self }) => { SQp.runTask = function(task) { const self = this; - if (global._isFibersEnabled()) { + if (Meteor._isFibersEnabled) { runTaskWithFibers({ task, self }); return; } @@ -146,13 +146,16 @@ SQp._scheduleRun = function () { self._runningOrRunScheduled = true; setImmediate(function() { - if (global._isFibersEnabled()) { + if (Meteor._isFibersEnabled) { Fiber(function() { self._run(); }).run(); return; } - self._run(); + + global.asyncLocalStorage.run(Meteor._getAslStore(), () => { + self._run(); + }); }); }; @@ -186,7 +189,7 @@ SQp._run = function () { self._currentTaskFiber = undefined; } // Run the task. - if (Meteor._isFibersEnabled()) { + if (Meteor._isFibersEnabled) { runFiber(); } else { try { @@ -200,7 +203,7 @@ SQp._run = function () { self._runningOrRunScheduled = false; self._scheduleRun(); - if (Meteor._isFibersEnabled()) { + if (Meteor._isFibersEnabled) { // If this was queued with runTask, let the runTask call return (throwing if // the task threw). if (taskHandle.future) { diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index ad28064003..e64921a5ef 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -171,5 +171,3 @@ function logErr(err) { ); } } - -Meteor._isFibersEnabled = global._isFibersEnabled; diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 828e4d18e1..c2ac5fe0e2 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -33,6 +33,7 @@ Package.onUse(function (api) { api.addFiles('setimmediate.js', ['client', 'server']); api.addFiles('timers.js', ['client', 'server']); api.addFiles('errors.js', ['client', 'server']); + api.addFiles('asl-helpers.js', 'server'); api.addFiles('fiber_helpers.js', 'server'); api.addFiles('fiber_stubs_client.js', 'client'); api.addFiles('startup_client.js', ['client']); diff --git a/packages/webapp/connect.js b/packages/webapp/connect.js index 8338364688..9b0b254dfc 100644 --- a/packages/webapp/connect.js +++ b/packages/webapp/connect.js @@ -1,7 +1,7 @@ import npmConnect from "connect"; export function connect(...connectArgs) { - if (!Meteor._isFibersEnabled()) { + if (!Meteor._isFibersEnabled) { return npmConnect.apply(this, connectArgs); } diff --git a/tools/static-assets/server/boot.js b/tools/static-assets/server/boot.js index b0122d9273..68ae408045 100644 --- a/tools/static-assets/server/boot.js +++ b/tools/static-assets/server/boot.js @@ -14,13 +14,7 @@ var MIN_NODE_VERSION = 'v14.0.0'; var hasOwn = Object.prototype.hasOwnProperty; -// For now it's a function to ensure we don't get a falsy value. -// Once we figure out the best place to create this EV (maybe it's here), -// it won't need to be a function anymore. - -global._isFibersEnabled = function () { - return !process.env.DISABLE_FIBERS; -}; +const IS_FIBERS_ENABLED = !!!process.env.DISABLE_FIBERS; if (require('semver').lt(process.version, MIN_NODE_VERSION)) { process.stderr.write( @@ -491,7 +485,7 @@ function startServerProcess() { Profile.run('Server startup', function() { // TODO the if around loadServerBundles should be enough - if (global._isFibersEnabled()) { + if (IS_FIBERS_ENABLED) { loadServerBundles(); callStartupHooks(); runMain(); @@ -505,7 +499,7 @@ function startServerProcess() { }); } -if (global._isFibersEnabled()) { +if (IS_FIBERS_ENABLED) { Fiber(function() { startServerProcess(); }).run(); From 81c38dc4eb18aa33dd1b34c34c98ad54abb38774 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 20 Aug 2022 00:18:51 -0300 Subject: [PATCH 0003/1965] For now, don't defer and then yield when waiting for the task to run. At least for autoupdate, this doesn't seem to affect. Still need to check for the other places. --- packages/autoupdate/autoupdate_server.js | 60 ++++++++++++++---------- packages/meteor/fiber_helpers.js | 22 +++++---- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index 5a85f62bfe..14f85ad84b 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -26,7 +26,6 @@ // the document are the versions described above. import { ClientVersions } from "./client_versions.js"; -var Future = Npm.require("fibers/future"); export const Autoupdate = __meteor_runtime_config__.autoupdate = { // Map from client architectures (web.browser, web.browser.legacy, @@ -145,33 +144,46 @@ Meteor.startup(function () { }); }); -var fut = new Future(); - -// We only want 'refresh' to trigger 'updateVersions' AFTER onListen, -// so we add a queued task that waits for onListen before 'refresh' can queue -// tasks. Note that the `onListening` callbacks do not fire until after -// Meteor.startup, so there is no concern that the 'updateVersions' calls from -// 'refresh' will overlap with the `updateVersions` call from Meteor.startup. - -syncQueue.queueTask(function () { - fut.wait(); -}); - -WebApp.onListening(function () { - fut.return(); -}); - function enqueueVersionsRefresh() { syncQueue.queueTask(function () { updateVersions(true); }); } -// Listen for messages pertaining to the client-refresh topic. -import { onMessage } from "meteor/inter-process-messaging"; -onMessage("client-refresh", enqueueVersionsRefresh); +const setupListeners = () => { + // Listen for messages pertaining to the client-refresh topic. + import { onMessage } from "meteor/inter-process-messaging"; + onMessage("client-refresh", enqueueVersionsRefresh); -// Another way to tell the process to refresh: send SIGHUP signal -process.on('SIGHUP', Meteor.bindEnvironment(function () { - enqueueVersionsRefresh(); -}, "handling SIGHUP signal for refresh")); + // Another way to tell the process to refresh: send SIGHUP signal + process.on('SIGHUP', Meteor.bindEnvironment(function () { + enqueueVersionsRefresh(); + }, "handling SIGHUP signal for refresh")); +}; + +if (Meteor._isFibersEnabled) { + var Future = Npm.require("fibers/future"); + + var fut = new Future(); + + // We only want 'refresh' to trigger 'updateVersions' AFTER onListen, + // so we add a queued task that waits for onListen before 'refresh' can queue + // tasks. Note that the `onListening` callbacks do not fire until after + // Meteor.startup, so there is no concern that the 'updateVersions' calls from + // 'refresh' will overlap with the `updateVersions` call from Meteor.startup. + + syncQueue.queueTask(function () { + fut.wait(); + }); + + WebApp.onListening(function () { + fut.return(); + }); + + setupListeners(); + +} else { + WebApp.onListening(function () { + Promise.resolve(setupListeners()); + }); +} diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 613af48e76..9eb0510e10 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -91,9 +91,6 @@ const runTask = ({ task, self }) => { self._taskHandles.push(handle); self._scheduleRun(); - // TODO Check this comment: - // Yield. We'll get back here after the task is run (and will throw if the - // task throws). }; SQp.runTask = function(task) { @@ -145,18 +142,25 @@ SQp._scheduleRun = function () { return; self._runningOrRunScheduled = true; - setImmediate(function() { - if (Meteor._isFibersEnabled) { + + /** + * FIXME: + * Here seems like we should defer and also yield while the handler is not + * finished... + * For autoupdate, for example, swapping to just running it sync won't make a difference, + * but maybe there is another place that would? (DDP/Web)-Server? + */ + if (Meteor._isFibersEnabled) { + setImmediate(function() { Fiber(function() { self._run(); }).run(); - return; - } - + }); + } else { global.asyncLocalStorage.run(Meteor._getAslStore(), () => { self._run(); }); - }); + } }; SQp._run = function () { From d752c8dab8ec6982a3267287af84b82a3d75a568 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 23 Aug 2022 16:52:00 -0300 Subject: [PATCH 0004/1965] Create AsynchronousCursor wrapper to use instead of SynchronousCursor. This is kinda of a "light-wrapper", just to keep our current API. Also, run _startTailing on the callback of the database command. --- packages/mongo/mongo_driver.js | 201 ++++++++++++++++++++- packages/mongo/oplog_tailing.js | 67 ++++++- packages/mongo/remote_collection_driver.js | 4 +- 3 files changed, 253 insertions(+), 19 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index f327c1caad..70781e652f 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -762,7 +762,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, _.each(["insert", "update", "remove", "dropCollection", "dropDatabase"], function (method) { MongoConnection.prototype[method] = function (/* arguments */) { var self = this; - return Meteor.wrapAsync(self["_" + method]).apply(self, arguments); + return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : self[`_${method}`].apply(self, arguments); }; }); @@ -1019,9 +1019,154 @@ MongoConnection.prototype._createSynchronousCursor = function( dbCursor = dbCursor.hint(cursorOptions.hint); } - return new SynchronousCursor(dbCursor, cursorDescription, options); + return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options) : new AsynchronousCursor(dbCursor, cursorDescription, options); }; +/** + * This is just a light wrapper for the cursor. The goal here is to ensure compatibility even if + * there are breaking changes on the MongoDB driver. + * + * @constructor + */ +class AsynchronousCursor { + constructor(dbCursor, cursorDescription, options) { + this._cursor = dbCursor; + this._cursorDescription = cursorDescription + + this._selfForIteration = options.selfForIteration || this; + if (options.useTransform && cursorDescription.options.transform) { + this._transform = LocalCollection.wrapTransform( + cursorDescription.options.transform); + } else { + this._transform = null; + } + + this._visitedIds = new LocalCollection._IdMap; + } + + [Symbol.iterator]() { + return this._cursor[Symbol.iterator](); + } + + // Returns a Promise for the next object from the underlying cursor (before + // the Mongo->Meteor type replacement). + async _rawNextObjectPromise() { + try { + return this._cursor.next(); + } catch (e) { + console.error(e); + } + } + + // Returns a Promise for the next object from the cursor, skipping those whose + // IDs we've already seen and replacing Mongo atoms with Meteor atoms. + async _nextObjectPromise () { + while (true) { + var doc = await this._rawNextObjectPromise(); + + if (!doc) return null; + doc = replaceTypes(doc, replaceMongoAtomWithMeteor); + + if (!this._cursorDescription.options.tailable && _.has(doc, '_id')) { + // Did Mongo give us duplicate documents in the same cursor? If so, + // ignore this one. (Do this before the transform, since transform might + // return some unrelated value.) We don't do this for tailable cursors, + // because we want to maintain O(1) memory usage. And if there isn't _id + // for some reason (maybe it's the oplog), then we don't do this either. + // (Be careful to do this for falsey but existing _id, though.) + if (this._visitedIds.has(doc._id)) continue; + this._visitedIds.set(doc._id, true); + } + + if (this._transform) + doc = this._transform(doc); + + return doc; + } + } + + // Returns a promise which is resolved with the next object (like with + // _nextObjectPromise) or rejected if the cursor doesn't return within + // timeoutMS ms. + _nextObjectPromiseWithTimeout(timeoutMS) { + if (!timeoutMS) { + return this._nextObjectPromise(); + } + const nextObjectPromise = this._nextObjectPromise(); + const timeoutErr = new Error('Client-side timeout waiting for next object'); + const timeoutPromise = new Promise((resolve, reject) => { + setTimeout(() => { + reject(timeoutErr); + }, timeoutMS); + }); + return Promise.race([nextObjectPromise, timeoutPromise]) + .catch((err) => { + if (err === timeoutErr) { + this.close(); + } + throw err; + }); + } + + forEach(callback, thisArg) { + // Get back to the beginning. + this._rewind(); + + return this._cursor.forEach((doc, index) => { + callback.call(thisArg, doc, index) + }, this._selfForIteration); + } + + async map(callback, thisArg) { + const results = []; + + await this.forEach(async (doc, index) => { + results.push(await callback.call(thisArg, doc, index, this._selfForIteration)); + }); + + return results; + } + + _rewind() { + // known to be synchronous + this._cursor.rewind(); + + this._visitedIds = new LocalCollection._IdMap; + } + + // Mostly usable for tailable cursors. + close() { + this._cursor.close(); + } + + fetch() { + return this._cursor.toArray(); + } + + /** + * FIXME: (node:34680) [MONGODB DRIVER] Warning: cursor.count is deprecated and will be + * removed in the next major version, please use `collection.estimatedDocumentCount` or + * `collection.countDocuments` instead. + */ + count() { + return this._cursor.count(); + } + + // This method is NOT wrapped in Cursor. + getRawObjects(ordered) { + var self = this; + if (ordered) { + return self.fetch(); + } else { + var results = new LocalCollection._IdMap; + self.forEach(function (doc) { + results.set(doc._id, doc); + }); + return results; + } + } +} + var SynchronousCursor = function (dbCursor, cursorDescription, options) { var self = this; options = _.pick(options || {}, 'selfForIteration', 'useTransform'); @@ -1226,7 +1371,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo var stopped = false; var lastTS; - var loop = function () { + const loopWithFibers = function () { var doc = null; while (true) { if (stopped) @@ -1257,9 +1402,9 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo newSelector.ts = {$gt: lastTS}; } cursor = self._createSynchronousCursor(new CursorDescription( - cursorDescription.collectionName, - newSelector, - cursorDescription.options)); + cursorDescription.collectionName, + newSelector, + cursorDescription.options)); // Mongo failover takes many seconds. Retry in a bit. (Without this // setTimeout, we peg the CPU at 100% and never notice the actual // failover. @@ -1268,8 +1413,50 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo } } }; + const loop = async function () { + var doc = null; + while (true) { + if (stopped) + return; + try { + doc = await cursor._nextObjectPromiseWithTimeout(timeoutMS); + } catch (err) { + // There's no good way to figure out if this was actually an error from + // Mongo, or just client-side (including our own timeout error). Ah + // well. But either way, we need to retry the cursor (unless the failure + // was because the observe got stopped). + doc = null; + } + // Since we awaited a promise above, we need to check again to see if + // we've been stopped before calling the callback. + if (stopped) + return; + if (doc) { + // If a tailable cursor contains a "ts" field, use it to recreate the + // cursor on error. ("ts" is a standard that Mongo uses internally for + // the oplog, and there's a special flag that lets you do binary search + // on it instead of needing to use an index.) + lastTS = doc.ts; + docCallback(doc); + } else { + var newSelector = _.clone(cursorDescription.selector); + if (lastTS) { + newSelector.ts = {$gt: lastTS}; + } + cursor = self._createSynchronousCursor(new CursorDescription( + cursorDescription.collectionName, + newSelector, + cursorDescription.options)); + // Mongo failover takes many seconds. Retry in a bit. (Without this + // setTimeout, we peg the CPU at 100% and never notice the actual + // failover. + setTimeout(loop, 100); + break; + } + } + }; - Meteor.defer(loop); + Meteor.defer(Meteor._isFibersEnabled ? loopWithFibers : loop); return { stop: function () { diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index fc702318db..64bb9b0982 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -213,23 +213,70 @@ Object.assign(OplogHandle.prototype, { self._oplogLastEntryConnection = new MongoConnection( self._oplogUrl, {maxPoolSize: 1}); + if (Meteor._isFibersEnabled) { + return this._startTailingFibers(); + } + + self._oplogLastEntryConnection.db.admin().command( + { ismaster: 1 }, function(_, isMasterDoc) { + if (!(isMasterDoc && isMasterDoc.setName)) { + throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + + "a Mongo replica set"); + } + + // Find the last oplog entry. + var lastOplogEntry = self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); + + var oplogSelector = Object.assign({}, self._baseOplogSelector); + if (lastOplogEntry) { + // Start after the last entry that currently exists. + oplogSelector.ts = {$gt: lastOplogEntry.ts}; + // If there are any calls to callWhenProcessedLatest before any other + // oplog entries show up, allow callWhenProcessedLatest to call its + // callback immediately. + self._lastProcessedTS = lastOplogEntry.ts; + } + + var cursorDescription = new CursorDescription( + OPLOG_COLLECTION, oplogSelector, {tailable: true}); + + // Start tailing the oplog. + // + // We restart the low-level oplog query every 30 seconds if we didn't get a + // doc. This is a workaround for #8598: the Node Mongo driver has at least + // one bug that can lead to query callbacks never getting called (even with + // an error) when leadership failover occur. + self._tailHandle = self._oplogTailConnection.tail( + cursorDescription, + function (doc) { + self._entryQueue.push(doc); + self._maybeStartWorker(); + }, + TAIL_TIMEOUT + ); + }); + }, + + _startTailingFibers: function() { + const self = this; // Now, make sure that there actually is a repl set here. If not, oplog // tailing won't ever find anything! // More on the isMasterDoc // https://docs.mongodb.com/manual/reference/command/isMaster/ var f = new Future; self._oplogLastEntryConnection.db.admin().command( - { ismaster: 1 }, f.resolver()); + { ismaster: 1 }, f.resolver()); var isMasterDoc = f.wait(); if (!(isMasterDoc && isMasterDoc.setName)) { throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + - "a Mongo replica set"); + "a Mongo replica set"); } // Find the last oplog entry. var lastOplogEntry = self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); + OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); var oplogSelector = _.clone(self._baseOplogSelector); if (lastOplogEntry) { @@ -242,7 +289,7 @@ Object.assign(OplogHandle.prototype, { } var cursorDescription = new CursorDescription( - OPLOG_COLLECTION, oplogSelector, {tailable: true}); + OPLOG_COLLECTION, oplogSelector, {tailable: true}); // Start tailing the oplog. // @@ -251,12 +298,12 @@ Object.assign(OplogHandle.prototype, { // one bug that can lead to query callbacks never getting called (even with // an error) when leadership failover occur. self._tailHandle = self._oplogTailConnection.tail( - cursorDescription, - function (doc) { - self._entryQueue.push(doc); - self._maybeStartWorker(); - }, - TAIL_TIMEOUT + cursorDescription, + function (doc) { + self._entryQueue.push(doc); + self._maybeStartWorker(); + }, + TAIL_TIMEOUT ); self._readyFuture.return(); }, diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo/remote_collection_driver.js index f237879de0..a7b654135c 100644 --- a/packages/mongo/remote_collection_driver.js +++ b/packages/mongo/remote_collection_driver.js @@ -40,8 +40,8 @@ MongoInternals.defaultRemoteCollectionDriver = _.once(function () { // to know about a database connection problem before the app starts. Doing so // in a `Meteor.startup` is fine, as the `WebApp` handles requests only after // all are finished. - Meteor.startup(() => { - Promise.await(driver.mongo.client.connect()); + Meteor.startup(async () => { + await driver.mongo.client.connect(); }); return driver; From 8ed48bce295eb6743fdd4d1c23d17447077beddc Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 24 Aug 2022 10:51:22 -0300 Subject: [PATCH 0005/1965] Livedata Server should work with fibers too. --- packages/ddp-server/livedata_server.js | 57 ++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index ea872e102f..2b4308aa5e 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -330,9 +330,15 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - ASL.run(Meteor._getAslStore, function () { - self.startUniversalSubs(); - }); + if (Meteor._isFibersEnabled) { + Fiber(function() { + self.startUniversalSubs(); + }).run(); + } else { + ASL.run(Meteor._getAslStore, function () { + self.startUniversalSubs(); + }); + } if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -556,7 +562,11 @@ Object.assign(Session.prototype, { // // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. - if (self.heartbeat) { + if (Meteor._isFibersEnabled && self.heartbeat) { + Fiber(function() { + self.heartbeat.messageReceived(); + }).run(); + } else if (self.heartbeat) { ASL.run(Meteor._getAslStore, function () { self.heartbeat.messageReceived(); }); @@ -584,7 +594,7 @@ Object.assign(Session.prototype, { return; } - ASL.run(Meteor._getAslStore, function () { + function runHandlers() { var blocked = true; var unblock = function () { @@ -604,7 +614,14 @@ Object.assign(Session.prototype, { else self.sendError('Bad request', msg); unblock(); // in case the handler didn't already do it - }); + } + + if (Meteor._isFibersEnabled) { + Fiber(runHandlers).run(); + return; + } + + ASL.run(Meteor._getAslStore, runHandlers); }; processNext(); @@ -1475,9 +1492,17 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - ASL.run(Meteor._getAslStore, function () { - self._handleConnect(socket, msg); - }); + + if (Meteor._isFibersEnabled) { + Fiber(function() { + self._handleConnect(socket, msg); + }).run(); + } else { + ASL.run(Meteor._getAslStore, function () { + self._handleConnect(socket, msg); + }); + } + return; } @@ -1494,6 +1519,13 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { + if (Meteor._isFibersEnabled) { + Fiber(function() { + socket._meteorSession.close() + }).run(); + return; + } + ASL.run(Meteor._getAslStore, function () { socket._meteorSession.close(); }); @@ -1674,6 +1706,13 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { + if (Meteor._isFibersEnabled) { + Fiber(function() { + session._startSubscription(handler); + }).run(); + return; + } + ASL.run(Meteor._getAslStore, function() { session._startSubscription(handler); }); From c2c7fbaf03a42862f8fe30213980ee2fff184b3b Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 24 Aug 2022 13:42:30 -0300 Subject: [PATCH 0006/1965] Correctly support async callbacks on cursor.map. --- packages/mongo/mongo_driver.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 70781e652f..e85fa6df4c 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1120,9 +1120,11 @@ class AsynchronousCursor { async map(callback, thisArg) { const results = []; - await this.forEach(async (doc, index) => { - results.push(await callback.call(thisArg, doc, index, this._selfForIteration)); - }); + let idx = 0; + for await (const doc of this._cursor) { + results.push(await callback.call(thisArg, doc, idx, this._selfForIteration)) + idx++; + } return results; } From b41f68d9c946a24025a1e0e603af5860e173b0e1 Mon Sep 17 00:00:00 2001 From: aquinoit Date: Wed, 24 Aug 2022 19:27:33 +0000 Subject: [PATCH 0007/1965] feature-linux-arm --- meteor | 4 ++-- .../eslint-plugin-meteor/scripts/admin/launch-meteor | 4 +++- .../scripts/admin/publish-meteor-tool-on-all-platforms.sh | 2 +- .../scripts/admin/publish-meteor-tool-on-arch.sh | 4 ++-- packages/promise/.npm/package/npm-shrinkwrap.json | 6 +++--- scripts/admin/launch-meteor | 2 ++ scripts/admin/publish-meteor-tool-on-all-platforms.sh | 2 +- scripts/admin/publish-meteor-tool-on-arch.sh | 4 ++-- scripts/build-dev-bundle-common.sh | 7 +++++-- scripts/generate-dev-bundle.sh | 4 +++- tools/cli/commands-packages.js | 2 +- tools/utils/archinfo.ts | 3 +++ 12 files changed, 28 insertions(+), 16 deletions(-) diff --git a/meteor b/meteor index e6a5cbd0d2..70dd6d91be 100755 --- a/meteor +++ b/meteor @@ -30,9 +30,9 @@ if [ "$UNAME" = "Darwin" ] ; then fi elif [ "$UNAME" = "Linux" ] ; then : "${ARCH:=$(uname -m)}" - if [ "$ARCH" != "x86_64" ] ; then + if [[ "$ARCH" != "x86_64" && "$ARCH" != "aarch64" ]] ; then echo "Unsupported architecture: $ARCH" - echo "Meteor only supports x86_64" + echo "Meteor only supports x86_64 and aarch64" exit 1 fi fi diff --git a/npm-packages/eslint-plugin-meteor/scripts/admin/launch-meteor b/npm-packages/eslint-plugin-meteor/scripts/admin/launch-meteor index 1ec76b4280..b749cc20ea 100755 --- a/npm-packages/eslint-plugin-meteor/scripts/admin/launch-meteor +++ b/npm-packages/eslint-plugin-meteor/scripts/admin/launch-meteor @@ -60,9 +60,11 @@ if [ ! -x "$METEOR_WAREHOUSE_DIR/meteor" ]; then PLATFORM="os.linux.x86_32" elif [ "${LINUX_ARCH}" = "x86_64" ] ; then PLATFORM="os.linux.x86_64" + elif [ "${LINUX_ARCH}" = "aarch64" ] ; then + PLATFORM="os.linux.aarch64" else echo "Unusable architecture: ${LINUX_ARCH}" - echo "Meteor only supports i686 and x86_64 for now." + echo "Meteor only supports i686, x86_64 and aarch64 for now." exit 1 fi fi diff --git a/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-all-platforms.sh b/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-all-platforms.sh index 2e96214eae..8d1cd11ba2 100755 --- a/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-all-platforms.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-all-platforms.sh @@ -42,7 +42,7 @@ main () { echo # XXX there is no os.windows.x86_64 as we don't build for it at the moment - PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.windows.x86_32 ) + PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.windows.x86_32 os.linux.aarch64 ) for PLATFORM in ${PLATFORMS[@]}; do COMMAND="`dirname $0`/publish-meteor-tool-on-arch.sh $GITSHA $PLATFORM $SESSION_FILE" echo $COMMAND diff --git a/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-arch.sh b/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-arch.sh index 9041ac4035..795f43a140 100755 --- a/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-arch.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/admin/publish-meteor-tool-on-arch.sh @@ -18,7 +18,7 @@ main () { echo "usage: $0 " 1>&2 echo "The passed sha1 is checked out and published from the machines." 1>&2 echo "Options for platform:" 1>&2 - echo " os.osx.x86_64 os.linux.x86_64 os.linux.x86_32" 1>&2 + echo " os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.linux.aarch64" 1>&2 echo " os.windows.x86_32 os.windows.x86_64" 1>&2 exit 1 fi @@ -33,7 +33,7 @@ main () { METEOR="$CHECKOUT_DIR/meteor" - UNIX_PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 ) + UNIX_PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.linux.aarch64 ) WINDOWS_PLATFORMS=( os.windows.x86_32 os.windows.x86_64 ) if [[ $PLATFORM =~ ^(os\.linux|os\.osx) ]] ; then diff --git a/packages/promise/.npm/package/npm-shrinkwrap.json b/packages/promise/.npm/package/npm-shrinkwrap.json index 1fc0ce2f37..28c929e19b 100644 --- a/packages/promise/.npm/package/npm-shrinkwrap.json +++ b/packages/promise/.npm/package/npm-shrinkwrap.json @@ -7,9 +7,9 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "meteor-promise": { - "version": "1.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/meteor-promise/-/meteor-promise-1.0.0-alpha.0.tgz", - "integrity": "sha512-f0WbzHSkAqzaQW+LSVhj/XES9dnxNqiKj/qd18Dj0Mt6znt0+f+PYFEsO9PkLdHnIJzvX1iHDjfHvLzpTNPymw==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/meteor-promise/-/meteor-promise-0.9.0.tgz", + "integrity": "sha512-O1Fj1Oa5FfyIkAkDtZVnoYYEIC3miy7lvEeIQZVYunGSbOuivSbfAiPPsD+P45WNlcBALhUo94UzlHeIKBYNuQ==" }, "promise": { "version": "8.1.0", diff --git a/scripts/admin/launch-meteor b/scripts/admin/launch-meteor index 1ec76b4280..69e3afb265 100755 --- a/scripts/admin/launch-meteor +++ b/scripts/admin/launch-meteor @@ -60,6 +60,8 @@ if [ ! -x "$METEOR_WAREHOUSE_DIR/meteor" ]; then PLATFORM="os.linux.x86_32" elif [ "${LINUX_ARCH}" = "x86_64" ] ; then PLATFORM="os.linux.x86_64" + elif [ "${LINUX_ARCH}" = "aarch64" ] ; then + PLATFORM="os.linux.aarch64" else echo "Unusable architecture: ${LINUX_ARCH}" echo "Meteor only supports i686 and x86_64 for now." diff --git a/scripts/admin/publish-meteor-tool-on-all-platforms.sh b/scripts/admin/publish-meteor-tool-on-all-platforms.sh index 2e96214eae..8d1cd11ba2 100755 --- a/scripts/admin/publish-meteor-tool-on-all-platforms.sh +++ b/scripts/admin/publish-meteor-tool-on-all-platforms.sh @@ -42,7 +42,7 @@ main () { echo # XXX there is no os.windows.x86_64 as we don't build for it at the moment - PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.windows.x86_32 ) + PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.windows.x86_32 os.linux.aarch64 ) for PLATFORM in ${PLATFORMS[@]}; do COMMAND="`dirname $0`/publish-meteor-tool-on-arch.sh $GITSHA $PLATFORM $SESSION_FILE" echo $COMMAND diff --git a/scripts/admin/publish-meteor-tool-on-arch.sh b/scripts/admin/publish-meteor-tool-on-arch.sh index 9041ac4035..795f43a140 100755 --- a/scripts/admin/publish-meteor-tool-on-arch.sh +++ b/scripts/admin/publish-meteor-tool-on-arch.sh @@ -18,7 +18,7 @@ main () { echo "usage: $0 " 1>&2 echo "The passed sha1 is checked out and published from the machines." 1>&2 echo "Options for platform:" 1>&2 - echo " os.osx.x86_64 os.linux.x86_64 os.linux.x86_32" 1>&2 + echo " os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.linux.aarch64" 1>&2 echo " os.windows.x86_32 os.windows.x86_64" 1>&2 exit 1 fi @@ -33,7 +33,7 @@ main () { METEOR="$CHECKOUT_DIR/meteor" - UNIX_PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 ) + UNIX_PLATFORMS=( os.osx.x86_64 os.linux.x86_64 os.linux.x86_32 os.linux.aarch64 ) WINDOWS_PLATFORMS=( os.windows.x86_32 os.windows.x86_64 ) if [[ $PLATFORM =~ ^(os\.linux|os\.osx) ]] ; then diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index d297b745f0..ea55f615ff 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -13,9 +13,9 @@ NPM_VERSION=6.14.17 if [ "$UNAME" == "Linux" ] ; then NODE_BUILD_NUMBER= - if [ "$ARCH" != "i686" -a "$ARCH" != "x86_64" ] ; then + if [[ "$ARCH" != "i686" && "$ARCH" != "x86_64" && "$ARCH" != "aarch64" ]] ; then echo "Unsupported architecture: $ARCH" - echo "Meteor only supports i686 and x86_64 for now." + echo "Meteor only supports i686, x86_64 and aarch64 for now." exit 1 fi @@ -65,6 +65,9 @@ then elif [ "$ARCH" == "x86_64" ] then NODE_TGZ="node-v${NODE_VERSION}-linux-x64.tar.gz" + elif [ "$ARCH" == "aarch64" ] + then + NODE_TGZ="node-v${NODE_VERSION}-linux-arm64.tar.gz" else echo "Unknown architecture: $UNAME $ARCH" exit 1 diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 5a6d10eeb6..d8d9e99483 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -64,7 +64,7 @@ fi case $OS in macos) MONGO_BASE_URL="https://fastdl.mongodb.org/osx" ;; linux) - [ $ARCH = "i686" ] && + [ $ARCH = "i686" -o $ARCH = "aarch64" ] && MONGO_BASE_URL="https://fastdl.mongodb.org/linux" || MONGO_BASE_URL="https://github.com/meteor/mongodb-builder/releases/download/v${MONGO_VERSION}" ;; @@ -73,6 +73,8 @@ esac if [ $OS = "macos" ] && [ "$(uname -m)" = "arm64" ] ; then MONGO_NAME="mongodb-${OS}-x86_64-${MONGO_VERSION}" +elif [ $OS = "linux" ] && [ "$ARCH" = "aarch64" ] ; then + MONGO_NAME="mongodb-linux-aarch64-ubuntu2004-${MONGO_VERSION}" else MONGO_NAME="mongodb-${OS}-${ARCH}-${MONGO_VERSION}" fi diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index a68c0db0ac..abf743b438 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -2627,7 +2627,7 @@ main.registerCommand({ // check if the passed arch is in the list var arch = options['target-arch']; if (! osArches.includes(arch)) { - throw new Error( + throw new Error( arch + ": the arch is not available for the release. Available arches: " + osArches.join(', ')); } diff --git a/tools/utils/archinfo.ts b/tools/utils/archinfo.ts index d454535355..0ba7102657 100644 --- a/tools/utils/archinfo.ts +++ b/tools/utils/archinfo.ts @@ -132,6 +132,7 @@ export const VALID_ARCHITECTURES: Record = { "os.osx.arm64": true, "os.linux.x86_64": true, "os.windows.x86_64": true, + "os.linux.aarch64": true, }; // Returns the fully qualified arch of this host -- something like @@ -173,6 +174,8 @@ export function host() { const machine = run('uname', '-m'); if (["x86_64", "amd64", "ia64"].includes(machine)) { _host = "os.linux.x86_64"; + } else if(machine === "aarch64") { + _host = "os.linux.aarch64"; } else { throw new Error(`Unsupported architecture: ${machine}`); } From 40cdbefe71cdba77f240253027861019b4e7c71a Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 25 Aug 2022 22:20:30 -0300 Subject: [PATCH 0008/1965] Adapting oplog and observer to run without Fibers. The publication "works" right now, but I still need to check if we need to wait and why. --- packages/meteor/fiber_helpers.js | 21 +++- packages/mongo/collection.js | 8 +- packages/mongo/observe_multiplex.js | 21 +++- packages/mongo/oplog_observe_driver.js | 137 +++++++++++++++++++---- packages/mongo/oplog_tailing.js | 84 ++++++++++++-- packages/mongo/polling_observe_driver.js | 9 +- 6 files changed, 236 insertions(+), 44 deletions(-) diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 9eb0510e10..2b240efa26 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -117,7 +117,13 @@ SQp.flush = function () { self.runTask(function () {}); }; + +// TODO -> Check if we are already running a task. SQp.safeToRunTask = function () { + if (!Meteor._isFibersEnabled) { + return true; + } + var self = this; return Fiber.current && self._currentTaskFiber !== Fiber.current; }; @@ -223,9 +229,14 @@ SQp._run = function () { // methods). // Meteor._sleepForMs = function (ms) { - var fiber = Fiber.current; - setTimeout(function() { - fiber.run(); - }, ms); - Fiber.yield(); + if (Meteor._isFibersEnabled) { + var fiber = Fiber.current; + setTimeout(function() { + fiber.run(); + }, ms); + Fiber.yield(); + return; + } + + return new Promise(resolve => setTimeout(() => resolve(), ms)); }; diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 3dcc12dc96..0e4ede1375 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -434,9 +434,13 @@ Object.assign(Mongo.Collection, { // possibly calling _publishCursor on multiple returned cursors. // register stop callback (expects lambda w/ no args). - sub.onStop(function() { + const onStopFibers = () => { observeHandle.stop(); - }); + }; + const onStopNoFibers = async () => { + await (observeHandle).stop(); + }; + sub.onStop(Meteor._isFibersEnabled ? onStopFibers : onStopNoFibers); // return the observeHandle in case it needs to be stopped early return observeHandle; diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index 6e8f9349f6..ead4570ebc 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -14,6 +14,7 @@ ObserveMultiplexer = function (options) { self._queue = new Meteor._SynchronousQueue(); self._handles = {}; self._readyFuture = new Future; + self._isReady = false; self._cache = new LocalCollection._CachingChangeObserver({ ordered: options.ordered}); // Number of addHandleAndSendInitialAdds tasks scheduled but not yet @@ -45,13 +46,16 @@ _.extend(ObserveMultiplexer.prototype, { self._queue.runTask(function () { self._handles[handle._id] = handle; - // Send out whatever adds we have so far (whether or not we the + // Send out whatever adds we have so far (whether the // multiplexer is ready). self._sendAdds(handle); --self._addHandleTasksScheduledButNotPerformed; }); - // *outside* the task, since otherwise we'd deadlock - self._readyFuture.wait(); + + if (Meteor._isFibersEnabled) { + // *outside* the task, since otherwise we'd deadlock + self._readyFuture.wait(); + } }, // Remove an observe handle. If it was the last observe handle, call the @@ -106,7 +110,12 @@ _.extend(ObserveMultiplexer.prototype, { self._queue.queueTask(function () { if (self._ready()) throw Error("can't make ObserveMultiplex ready twice!"); - self._readyFuture.return(); + if (Meteor._isFibersEnabled) { + self._readyFuture.return(); + return; + } + + self._isReady = true; }); }, @@ -145,7 +154,7 @@ _.extend(ObserveMultiplexer.prototype, { return ["added", "changed", "removed"]; }, _ready: function () { - return this._readyFuture.isResolved(); + return Meteor._isFibersEnabled ? this._readyFuture.isResolved() : !!this._isReady; }, _applyCallback: function (callbackName, args) { var self = this; @@ -187,7 +196,7 @@ _.extend(ObserveMultiplexer.prototype, { // flush the queue afterwards to ensure that the callbacks get out. _sendAdds: function (handle) { var self = this; - if (self._queue.safeToRunTask()) + if (self._queue.safeToRunTask() && Meteor._isFibersEnabled) throw Error("_sendAdds may only be called from within a task!"); var add = self._ordered ? handle._addedBefore : handle._added; if (!add) diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 773e7e3feb..529301a5b1 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -191,9 +191,13 @@ OplogObserveDriver = function (options) { // Give _observeChanges a chance to add the new ObserveHandle to our // multiplexer, so that the added calls get streamed. - Meteor.defer(finishIfNeedToPollQuery(function () { + function deferFibers() { self._runInitialQuery(); - })); + } + const deferNoFibers = async () => { + await self._runInitialQuery(); + }; + Meteor.defer(finishIfNeedToPollQuery(Meteor._isFibersEnabled ? deferFibers : deferNoFibers)); }; _.extend(OplogObserveDriver.prototype, { @@ -658,8 +662,8 @@ _.extend(OplogObserveDriver.prototype, { } }); }, - // Yields! - _runInitialQuery: function () { + + _runInitialQueryFibers() { var self = this; if (self._stopped) throw new Error("oplog stopped surprisingly early"); @@ -676,6 +680,28 @@ _.extend(OplogObserveDriver.prototype, { self._doneQuerying(); // yields }, + async _runInitialQueryNoFibers() { + var self = this; + if (self._stopped) + throw new Error("oplog stopped surprisingly early"); + + await self._runQuery({initial: true}); // yields + + if (self._stopped) + return; // can happen on queryError + + // Allow observeChanges calls to return. (After this, it's possible for + // stop() to be called.) + self._multiplexer.ready(); + + await self._doneQuerying(); // yields + }, + + // Yields! + _runInitialQuery: function () { + return Meteor._isFibersEnabled ? this._runInitialQueryFibers() : this._runInitialQueryNoFibers(); + }, + // In various circumstances, we may just want to stop processing the oplog and // re-run the initial query, just as if we were a PollingObserveDriver. // @@ -712,7 +738,63 @@ _.extend(OplogObserveDriver.prototype, { }, // Yields! - _runQuery: function (options) { + async _runQueryNoFibers(options) { + var self = this; + options = options || {}; + var newResults, newBuffer; + + // This while loop is just to retry failures. + while (true) { + // If we've been stopped, we don't have to run anything any more. + if (self._stopped) + return; + + newResults = new LocalCollection._IdMap; + newBuffer = new LocalCollection._IdMap; + + // Query 2x documents as the half excluded from the original query will go + // into unpublished buffer to reduce additional Mongo lookups in cases + // when documents are removed from the published set and need a + // replacement. + // XXX needs more thought on non-zero skip + // XXX 2 is a "magic number" meaning there is an extra chunk of docs for + // buffer if such is needed. + var cursor = self._cursorForQuery({ limit: self._limit * 2 }); + try { + await cursor.forEach(function (doc, i) { // yields + if (!self._limit || i < self._limit) { + newResults.set(doc._id, doc); + } else { + newBuffer.set(doc._id, doc); + } + }); + break; + } catch (e) { + if (options.initial && typeof(e.code) === 'number') { + // This is an error document sent to us by mongod, not a connection + // error generated by the client. And we've never seen this query work + // successfully. Probably it's a bad selector or something, so we + // should NOT retry. Instead, we should halt the observe (which ends + // up calling `stop` on us). + self._multiplexer.queryError(e); + return; + } + + // During failover (eg) if we get an exception we should log and retry + // instead of crashing. + Meteor._debug("Got exception while polling query", e); + await Meteor._sleepForMs(100); + } + } + + if (self._stopped) + return; + + self._publishNewResults(newResults, newBuffer); + }, + + // Yields! + _runQueryWithFibers: function (options) { var self = this; options = options || {}; var newResults, newBuffer; @@ -767,6 +849,11 @@ _.extend(OplogObserveDriver.prototype, { self._publishNewResults(newResults, newBuffer); }, + // Yields! + _runQuery: function (options) { + return Meteor._isFibersEnabled ? this._runQueryWithFibers(options) : this._runQueryNoFibers(options); + }, + // Transitions to QUERYING and runs another query, or (if already in QUERYING) // ensures that we will query again later. // @@ -804,22 +891,32 @@ _.extend(OplogObserveDriver.prototype, { if (self._stopped) return; - self._mongoHandle._oplogHandle.waitUntilCaughtUp(); // yields - if (self._stopped) - return; - if (self._phase !== PHASE.QUERYING) - throw Error("Phase unexpectedly " + self._phase); - Meteor._noYieldsAllowed(function () { - if (self._requeryWhenDoneThisQuery) { - self._requeryWhenDoneThisQuery = false; - self._pollQuery(); - } else if (self._needToFetch.empty()) { - self._beSteady(); - } else { - self._fetchModifiedDocuments(); - } - }); + const afterCaughtUp = () => { + if (self._stopped) + return; + if (self._phase !== PHASE.QUERYING) + throw Error("Phase unexpectedly " + self._phase); + + Meteor._noYieldsAllowed(function () { + if (self._requeryWhenDoneThisQuery) { + self._requeryWhenDoneThisQuery = false; + self._pollQuery(); + } else if (self._needToFetch.empty()) { + self._beSteady(); + } else { + self._fetchModifiedDocuments(); + } + }); + }; + + if (Meteor._isFibersEnabled) { + self._mongoHandle._oplogHandle.waitUntilCaughtUp(); // yields + return afterCaughtUp(); + } + + // TODO -> Should we wait? This doesn't make much sense without Fibers. + return self._mongoHandle._oplogHandle.waitUntilCaughtUp().then(afterCaughtUp); }, _cursorForQuery: function (optionsOverwrite) { diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index 64bb9b0982..eb978441ac 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -36,6 +36,7 @@ OplogHandle = function (oplogUrl, dbName) { self._stopped = false; self._tailHandle = null; self._readyFuture = new Future(); + self._isTailingReady = false; self._crossbar = new DDPServer._Crossbar({ factPackage: "mongo-livedata", factName: "oplog-watchers" }); @@ -100,8 +101,13 @@ Object.assign(OplogHandle.prototype, { if (self._stopped) throw new Error("Called onOplogEntry on stopped handle!"); - // Calling onOplogEntry requires us to wait for the tailing to be ready. - self._readyFuture.wait(); + // TODO -> How to wait here? Actually, should we wait? + if (!self._isTailingReady && !Meteor._isFibersEnabled) { + return; + } else if (Meteor._isFibersEnabled) { + // Calling onOplogEntry requires us to wait for the tailing to be ready. + self._readyFuture.wait(); + } var originalCallback = callback; callback = Meteor.bindEnvironment(function (notification) { @@ -124,12 +130,8 @@ Object.assign(OplogHandle.prototype, { throw new Error("Called onSkippedEntries on stopped handle!"); return self._onSkippedEntriesHook.register(callback); }, - // Calls `callback` once the oplog has been processed up to a point that is - // roughly "now": specifically, once we've processed all ops that are - // currently visible. - // XXX become convinced that this is actually safe even if oplogConnection - // is some kind of pool - waitUntilCaughtUp: function () { + + _waitUntilCaughtUpFibers() { var self = this; if (self._stopped) throw new Error("Called waitUntilCaughtUp on stopped handle!"); @@ -145,8 +147,8 @@ Object.assign(OplogHandle.prototype, { // find a TS that won't show up in the actual tail stream. try { lastEntry = self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, self._baseOplogSelector, - {fields: {ts: 1}, sort: {$natural: -1}}); + OPLOG_COLLECTION, self._baseOplogSelector, + {fields: {ts: 1}, sort: {$natural: -1}}); break; } catch (e) { // During failover (eg) if we get an exception we should log and retry @@ -185,6 +187,66 @@ Object.assign(OplogHandle.prototype, { self._catchingUpFutures.splice(insertAfter, 0, {ts: ts, future: f}); f.wait(); }, + + async _waitUntilCaughtUpNoFibers() { + var self = this; + if (self._stopped) + throw new Error("Called waitUntilCaughtUp on stopped handle!"); + + // TODO -> Should we wait? Is waiting needed? + // Calling waitUntilCaughtUp requries us to wait for the oplog connection to + // be ready. + if (!self._isReady) { + return; + } + + var lastEntry; + + while (!self._stopped) { + // We need to make the selector at least as restrictive as the actual + // tailing selector (ie, we need to specify the DB name) or else we might + // find a TS that won't show up in the actual tail stream. + try { + lastEntry = self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, self._baseOplogSelector, + {fields: {ts: 1}, sort: {$natural: -1}}); + break; + } catch (e) { + // During failover (eg) if we get an exception we should log and retry + // instead of crashing. + Meteor._debug("Got exception while reading last entry", e); + await Meteor._sleepForMs(100); + } + } + + if (self._stopped) + return; + + if (!lastEntry) { + // Really, nothing in the oplog? Well, we've processed everything. + return; + } + + var ts = lastEntry.ts; + if (!ts) + throw Error("oplog entry without ts: " + EJSON.stringify(lastEntry)); + + if (self._lastProcessedTS && ts.lessThanOrEqual(self._lastProcessedTS)) { + // We've already caught up to here. + return; + } + }, + + + // Calls `callback` once the oplog has been processed up to a point that is + // roughly "now": specifically, once we've processed all ops that are + // currently visible. + // XXX become convinced that this is actually safe even if oplogConnection + // is some kind of pool + waitUntilCaughtUp: function () { + return Meteor._isFibersEnabled ? this._waitUntilCaughtUpFibers() : this._waitUntilCaughtUpNoFibers() + }, + _startTailing: function () { var self = this; // First, make sure that we're talking to the local database. @@ -255,6 +317,8 @@ Object.assign(OplogHandle.prototype, { }, TAIL_TIMEOUT ); + + self._isTailingReady = true; }); }, diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js index f378d28c43..fb3f692fef 100644 --- a/packages/mongo/polling_observe_driver.js +++ b/packages/mongo/polling_observe_driver.js @@ -211,7 +211,14 @@ _.extend(PollingObserveDriver.prototype, { stop: function () { var self = this; self._stopped = true; - _.each(self._stopCallbacks, function (c) { c(); }); + const stopCallbacksCallerFibers = function (c) { + c(); + }; + const stopCallbacksCallerNoFibers = async function(c) { + await c(); + }; + + _.each(self._stopCallbacks, Meteor._isFibersEnabled ? stopCallbacksCallerFibers : stopCallbacksCallerNoFibers); // Release any write fences that are waiting on us. _.each(self._pendingWrites, function (w) { w.committed(); From 2f5e8e4432517317f32b68d7b951844b96a05759 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 26 Aug 2022 19:29:25 -0300 Subject: [PATCH 0009/1965] Correctly implement the usage of promises for the publication initial and stop handles. Seems that waiting with the while loop is enough (?), but it's something we will need to confirm. --- packages/ddp-server/livedata_server.js | 44 ++-- packages/mongo/collection.js | 72 ++++-- packages/mongo/mongo_driver.js | 309 +++++++++++++++++-------- packages/mongo/observe_multiplex.js | 37 ++- packages/mongo/oplog_observe_driver.js | 53 +++-- packages/mongo/oplog_tailing.js | 42 +++- 6 files changed, 388 insertions(+), 169 deletions(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 2b4308aa5e..8d8f0ed451 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1177,15 +1177,21 @@ Object.assign(Subscription.prototype, { return c && c._publishCursor; }; if (isCursor(res)) { - try { - res._publishCursor(self); - } catch (e) { - self.error(e); - return; + if (Meteor._isFibersEnabled) { + try { + res._publishCursor(self); + } catch (e) { + self.error(e); + return; + } + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else { + res._publishCursor(self).then(() => { + self.ready(); + }).catch((e) => self.error(e)); } - // _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)) { @@ -1207,15 +1213,21 @@ Object.assign(Subscription.prototype, { collectionNames[collectionName] = true; }; - try { - _.each(res, function (cur) { - cur._publishCursor(self); - }); - } catch (e) { - self.error(e); - return; + if (Meteor._isFibersEnabled) { + try { + _.each(res, function (cur) { + cur._publishCursor(self); + }); + } catch (e) { + self.error(e); + return; + } + self.ready(); + } else { + Promise.all(res.map((c) => c._publishCursor(self))).then(() => { + self.ready(); + }).catch((e) => self.error(e)); } - self.ready(); } else if (res) { // Truthy values other than cursors or arrays are probably a // user mistake (possible returning a Mongo document via, say, diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 0e4ede1375..9cc1067d6e 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -412,40 +412,72 @@ Object.assign(Mongo.Collection.prototype, { }); Object.assign(Mongo.Collection, { - _publishCursor(cursor, sub, collection) { + _publishCursorFibers(cursor, sub, collection) { var observeHandle = cursor.observeChanges( - { - added: function(id, fields) { - sub.added(collection, id, fields); + { + added: function(id, fields) { + sub.added(collection, id, fields); + }, + changed: function(id, fields) { + sub.changed(collection, id, fields); + }, + removed: function(id) { + sub.removed(collection, id); + }, }, - changed: function(id, fields) { - sub.changed(collection, id, fields); - }, - removed: function(id) { - sub.removed(collection, id); - }, - }, - // Publications don't mutate the documents - // This is tested by the `livedata - publish callbacks clone` test - { nonMutatingCallbacks: true } + // Publications don't mutate the documents + // This is tested by the `livedata - publish callbacks clone` test + { nonMutatingCallbacks: true } ); // 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). - const onStopFibers = () => { + sub.onStop(function() { observeHandle.stop(); - }; - const onStopNoFibers = async () => { - await (observeHandle).stop(); - }; - sub.onStop(Meteor._isFibersEnabled ? onStopFibers : onStopNoFibers); + }); // return the observeHandle in case it needs to be stopped early return observeHandle; }, + async _publishCursorNoFibers(cursor, sub, collection) { + var observeHandle = await cursor.observeChanges( + { + added: function(id, fields) { + sub.added(collection, id, fields); + }, + changed: function(id, fields) { + sub.changed(collection, id, fields); + }, + removed: function(id) { + sub.removed(collection, id); + }, + }, + // Publications don't mutate the documents + // This is tested by the `livedata - publish callbacks clone` test + { nonMutatingCallbacks: true } + ); + + // 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(); + }); + + // return the observeHandle in case it needs to be stopped early + return observeHandle; + }, + + _publishCursor(cursor, sub, collection) { + return Meteor._isFibersEnabled + ? this._publishCursorFibers(cursor, sub, collection) + : this._publishCursorNoFibers(cursor, sub, collection); + }, + // protect against dangerous selectors. falsey and {_id: falsey} are both // likely programmer error, and not what you want, particularly for destructive // operations. If a falsey _id is sent in, a new string _id will be diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index e85fa6df4c..a1a463238a 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1468,112 +1468,229 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo }; }; -MongoConnection.prototype._observeChanges = function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - var self = this; +Object.assign(MongoConnection.prototype, { + _observeChanges: function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + return Meteor._isFibersEnabled + ? this._observeChangesFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks) + : this._observeChangesNoFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks); + }, - if (cursorDescription.options.tailable) { - return self._observeChangesTailable(cursorDescription, ordered, callbacks); - } + _observeChangesNoFibers: async function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + var self = this; - // You may not filter out _id when observing changes, because the id is a core - // part of the observeChanges API. - const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; - if (fieldsOptions && - (fieldsOptions._id === 0 || - fieldsOptions._id === false)) { - throw Error("You may not observe a cursor with {fields: {_id: 0}}"); - } - - var observeKey = EJSON.stringify( - _.extend({ordered: ordered}, cursorDescription)); - - var multiplexer, observeDriver; - var firstHandle = false; - - // Find a matching ObserveMultiplexer, or create a new one. This next block is - // guaranteed to not yield (and it doesn't call anything that can observe a - // new query), so no other calls to this function can interleave with it. - Meteor._noYieldsAllowed(function () { - if (_.has(self._observeMultiplexers, observeKey)) { - multiplexer = self._observeMultiplexers[observeKey]; - } else { - firstHandle = true; - // Create a new ObserveMultiplexer. - multiplexer = new ObserveMultiplexer({ - ordered: ordered, - onStop: function () { - delete self._observeMultiplexers[observeKey]; - observeDriver.stop(); - } - }); - self._observeMultiplexers[observeKey] = multiplexer; + if (cursorDescription.options.tailable) { + return self._observeChangesTailable(cursorDescription, ordered, callbacks); } - }); - var observeHandle = new ObserveHandle(multiplexer, - callbacks, - nonMutatingCallbacks, - ); + // You may not filter out _id when observing changes, because the id is a core + // part of the observeChanges API. + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { + throw Error("You may not observe a cursor with {fields: {_id: 0}}"); + } - if (firstHandle) { - var matcher, sorter; - var canUseOplog = _.all([ - function () { - // At a bare minimum, using the oplog requires us to have an oplog, to - // want unordered callbacks, and to not want a callback on the polls - // that won't happen. - return self._oplogHandle && !ordered && - !callbacks._testOnlyPollCallback; - }, function () { - // We need to be able to compile the selector. Fall back to polling for - // some newfangled $selector that minimongo doesn't support yet. - try { - matcher = new Minimongo.Matcher(cursorDescription.selector); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }, function () { - // ... and the selector itself needs to support oplog. - return OplogObserveDriver.cursorSupported(cursorDescription, matcher); - }, function () { - // And we need to be able to compile the sort, if any. eg, can't be - // {$natural: 1}. - if (!cursorDescription.options.sort) - return true; - try { - sorter = new Minimongo.Sorter(cursorDescription.options.sort); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }], function (f) { return f(); }); // invoke each function + var observeKey = EJSON.stringify( + _.extend({ordered: ordered}, cursorDescription)); - var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; - observeDriver = new driverClass({ - cursorDescription: cursorDescription, - mongoHandle: self, - multiplexer: multiplexer, - ordered: ordered, - matcher: matcher, // ignored by polling - sorter: sorter, // ignored by polling - _testOnlyPollCallback: callbacks._testOnlyPollCallback + var multiplexer, observeDriver; + var firstHandle = false; + + // Find a matching ObserveMultiplexer, or create a new one. This next block is + // guaranteed to not yield (and it doesn't call anything that can observe a + // new query), so no other calls to this function can interleave with it. + Meteor._noYieldsAllowed(function () { + if (_.has(self._observeMultiplexers, observeKey)) { + multiplexer = self._observeMultiplexers[observeKey]; + } else { + firstHandle = true; + // Create a new ObserveMultiplexer. + multiplexer = new ObserveMultiplexer({ + ordered: ordered, + onStop: function () { + delete self._observeMultiplexers[observeKey]; + observeDriver.stop(); + } + }); + self._observeMultiplexers[observeKey] = multiplexer; + } }); - // This field is only set for use in tests. - multiplexer._observeDriver = observeDriver; - } + var observeHandle = new ObserveHandle(multiplexer, + callbacks, + nonMutatingCallbacks, + ); - // Blocks until the initial adds have been sent. - multiplexer.addHandleAndSendInitialAdds(observeHandle); + if (firstHandle) { + var matcher, sorter; + var canUseOplog = _.all([ + function () { + // At a bare minimum, using the oplog requires us to have an oplog, to + // want unordered callbacks, and to not want a callback on the polls + // that won't happen. + return self._oplogHandle && !ordered && + !callbacks._testOnlyPollCallback; + }, function () { + // We need to be able to compile the selector. Fall back to polling for + // some newfangled $selector that minimongo doesn't support yet. + try { + matcher = new Minimongo.Matcher(cursorDescription.selector); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }, function () { + // ... and the selector itself needs to support oplog. + return OplogObserveDriver.cursorSupported(cursorDescription, matcher); + }, function () { + // And we need to be able to compile the sort, if any. eg, can't be + // {$natural: 1}. + if (!cursorDescription.options.sort) + return true; + try { + sorter = new Minimongo.Sorter(cursorDescription.options.sort); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }], function (f) { return f(); }); // invoke each function + + var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; + observeDriver = new driverClass({ + cursorDescription: cursorDescription, + mongoHandle: self, + multiplexer: multiplexer, + ordered: ordered, + matcher: matcher, // ignored by polling + sorter: sorter, // ignored by polling + _testOnlyPollCallback: callbacks._testOnlyPollCallback + }); + + // This field is only set for use in tests. + multiplexer._observeDriver = observeDriver; + } + + // Blocks until the initial adds have been sent. + await multiplexer.addHandleAndSendInitialAdds(observeHandle); + + return observeHandle; + }, + + _observeChangesFibers: function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + var self = this; + + if (cursorDescription.options.tailable) { + return self._observeChangesTailable(cursorDescription, ordered, callbacks); + } + + // You may not filter out _id when observing changes, because the id is a core + // part of the observeChanges API. + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { + throw Error("You may not observe a cursor with {fields: {_id: 0}}"); + } + + var observeKey = EJSON.stringify( + _.extend({ordered: ordered}, cursorDescription)); + + var multiplexer, observeDriver; + var firstHandle = false; + + // Find a matching ObserveMultiplexer, or create a new one. This next block is + // guaranteed to not yield (and it doesn't call anything that can observe a + // new query), so no other calls to this function can interleave with it. + Meteor._noYieldsAllowed(function () { + if (_.has(self._observeMultiplexers, observeKey)) { + multiplexer = self._observeMultiplexers[observeKey]; + } else { + firstHandle = true; + // Create a new ObserveMultiplexer. + multiplexer = new ObserveMultiplexer({ + ordered: ordered, + onStop: function () { + delete self._observeMultiplexers[observeKey]; + observeDriver.stop(); + } + }); + self._observeMultiplexers[observeKey] = multiplexer; + } + }); + + var observeHandle = new ObserveHandle(multiplexer, + callbacks, + nonMutatingCallbacks, + ); + + if (firstHandle) { + var matcher, sorter; + var canUseOplog = _.all([ + function () { + // At a bare minimum, using the oplog requires us to have an oplog, to + // want unordered callbacks, and to not want a callback on the polls + // that won't happen. + return self._oplogHandle && !ordered && + !callbacks._testOnlyPollCallback; + }, function () { + // We need to be able to compile the selector. Fall back to polling for + // some newfangled $selector that minimongo doesn't support yet. + try { + matcher = new Minimongo.Matcher(cursorDescription.selector); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }, function () { + // ... and the selector itself needs to support oplog. + return OplogObserveDriver.cursorSupported(cursorDescription, matcher); + }, function () { + // And we need to be able to compile the sort, if any. eg, can't be + // {$natural: 1}. + if (!cursorDescription.options.sort) + return true; + try { + sorter = new Minimongo.Sorter(cursorDescription.options.sort); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }], function (f) { return f(); }); // invoke each function + + var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; + observeDriver = new driverClass({ + cursorDescription: cursorDescription, + mongoHandle: self, + multiplexer: multiplexer, + ordered: ordered, + matcher: matcher, // ignored by polling + sorter: sorter, // ignored by polling + _testOnlyPollCallback: callbacks._testOnlyPollCallback + }); + + // This field is only set for use in tests. + multiplexer._observeDriver = observeDriver; + } + + // Blocks until the initial adds have been sent. + multiplexer.addHandleAndSendInitialAdds(observeHandle); + + return observeHandle; + }, +}); - return observeHandle; -}; // Listen for the invalidation messages that will trigger us to poll the // database for changes. If this selector specifies specific IDs, specify them diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index ead4570ebc..c442aa269e 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -30,7 +30,7 @@ ObserveMultiplexer = function (options) { }; _.extend(ObserveMultiplexer.prototype, { - addHandleAndSendInitialAdds: function (handle) { + _addHandleAndSendInitialAddsFibers: function (handle) { var self = this; // Check this before calling runTask (even though runTask does the same @@ -42,7 +42,7 @@ _.extend(ObserveMultiplexer.prototype, { ++self._addHandleTasksScheduledButNotPerformed; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-handles", 1); + "mongo-livedata", "observe-handles", 1); self._queue.runTask(function () { self._handles[handle._id] = handle; @@ -52,11 +52,38 @@ _.extend(ObserveMultiplexer.prototype, { --self._addHandleTasksScheduledButNotPerformed; }); - if (Meteor._isFibersEnabled) { - // *outside* the task, since otherwise we'd deadlock - self._readyFuture.wait(); + // *outside* the task, since otherwise we'd deadlock + self._readyFuture.wait(); + }, + _addHandleAndSendInitialAddsNoFibers: async function (handle) { + var self = this; + + // Check this before calling runTask (even though runTask does the same + // check) so that we don't leak an ObserveMultiplexer on error by + // incrementing _addHandleTasksScheduledButNotPerformed and never + // decrementing it. + if (!self._queue.safeToRunTask()) + throw new Error("Can't call observeChanges from an observe callback on the same query"); + ++self._addHandleTasksScheduledButNotPerformed; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-handles", 1); + + self._queue.runTask(function () { + self._handles[handle._id] = handle; + // Send out whatever adds we have so far (whether the + // multiplexer is ready). + self._sendAdds(handle); + --self._addHandleTasksScheduledButNotPerformed; + }); + + while (!self._isReady) { + await Meteor._sleepForMs(100); } }, + addHandleAndSendInitialAdds: function (handle) { + return Meteor._isFibersEnabled ? this._addHandleAndSendInitialAddsFibers(handle) : this._addHandleAndSendInitialAddsNoFibers(handle); + }, // Remove an observe handle. If it was the last observe handle, call the // onStop callback; you cannot add any more observe handles after this. diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 529301a5b1..9efc7d5791 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -1018,30 +1018,41 @@ _.extend(OplogObserveDriver.prototype, { if (self._stopped) return; self._stopped = true; - _.each(self._stopHandles, function (handle) { - handle.stop(); - }); - // Note: we *don't* use multiplexer.onFlush here because this stop - // callback is actually invoked by the multiplexer itself when it has - // determined that there are no handles left. So nothing is actually going - // to get flushed (and it's probably not valid to call methods on the - // dying multiplexer). - _.each(self._writesToCommitWhenWeReachSteady, function (w) { - w.committed(); // maybe yields? - }); - self._writesToCommitWhenWeReachSteady = null; + const afterStopHandles = () => { + // Note: we *don't* use multiplexer.onFlush here because this stop + // callback is actually invoked by the multiplexer itself when it has + // determined that there are no handles left. So nothing is actually going + // to get flushed (and it's probably not valid to call methods on the + // dying multiplexer). + _.each(self._writesToCommitWhenWeReachSteady, function (w) { + w.committed(); // maybe yields? + }); + self._writesToCommitWhenWeReachSteady = null; - // Proactively drop references to potentially big things. - self._published = null; - self._unpublishedBuffer = null; - self._needToFetch = null; - self._currentlyFetching = null; - self._oplogEntryHandle = null; - self._listenersHandle = null; + // Proactively drop references to potentially big things. + self._published = null; + self._unpublishedBuffer = null; + self._needToFetch = null; + self._currentlyFetching = null; + self._oplogEntryHandle = null; + self._listenersHandle = null; - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-oplog", -1); + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-oplog", -1); + }; + + if (Meteor._isFibersEnabled) { + self._stopHandles.forEach(handle => { + handle.stop(); + }); + return afterStopHandles(); + } + + Promise.all(self._stopHandles.map(async handle => { + const _h = await handle; + _h.stop(); + })).then(() => afterStopHandles()); }, _registerPhaseChange: function (phase) { diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index eb978441ac..9b21debc85 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -36,7 +36,7 @@ OplogHandle = function (oplogUrl, dbName) { self._stopped = false; self._tailHandle = null; self._readyFuture = new Future(); - self._isTailingReady = false; + self._isReady = false; self._crossbar = new DDPServer._Crossbar({ factPackage: "mongo-livedata", factName: "oplog-watchers" }); @@ -96,17 +96,34 @@ Object.assign(OplogHandle.prototype, { self._tailHandle.stop(); // XXX should close connections too }, - onOplogEntry: function (trigger, callback) { + _onOplogEntryFibers: function(trigger, callback) { var self = this; if (self._stopped) throw new Error("Called onOplogEntry on stopped handle!"); - // TODO -> How to wait here? Actually, should we wait? - if (!self._isTailingReady && !Meteor._isFibersEnabled) { - return; - } else if (Meteor._isFibersEnabled) { - // Calling onOplogEntry requires us to wait for the tailing to be ready. - self._readyFuture.wait(); + // Calling onOplogEntry requires us to wait for the tailing to be ready. + self._readyFuture.wait(); + + var originalCallback = callback; + callback = Meteor.bindEnvironment(function (notification) { + originalCallback(notification); + }, function (err) { + Meteor._debug("Error in oplog callback", err); + }); + var listenHandle = self._crossbar.listen(trigger, callback); + return { + stop: function () { + listenHandle.stop(); + } + }; + }, + async _onOplogEntryNoFibers(trigger, callback) { + var self = this; + if (self._stopped) + throw new Error("Called onOplogEntry on stopped handle!"); + + while (!self._isReady) { + await Meteor._sleepForMs(100); } var originalCallback = callback; @@ -122,6 +139,9 @@ Object.assign(OplogHandle.prototype, { } }; }, + onOplogEntry: function (trigger, callback) { + return Meteor._isFibersEnabled ? this._onOplogEntryFibers(trigger, callback) : this._onOplogEntryNoFibers(trigger, callback); + }, // Register a callback to be invoked any time we skip oplog entries (eg, // because we are too far behind). onSkippedEntries: function (callback) { @@ -196,8 +216,8 @@ Object.assign(OplogHandle.prototype, { // TODO -> Should we wait? Is waiting needed? // Calling waitUntilCaughtUp requries us to wait for the oplog connection to // be ready. - if (!self._isReady) { - return; + while (!this._isReady) { + await Meteor._sleepForMs(100); } var lastEntry; @@ -318,7 +338,7 @@ Object.assign(OplogHandle.prototype, { TAIL_TIMEOUT ); - self._isTailingReady = true; + self._isReady = true; }); }, From bd61ad58ba05c2afac8979a2f8a44ed5d30a3547 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 26 Aug 2022 23:02:03 -0300 Subject: [PATCH 0010/1965] Remove Fibers from TinyTest package. --- packages/tinytest/tinytest.js | 46 ++++++++++++---------------- packages/tinytest/tinytest_server.js | 2 +- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 045548c6de..e516feabdf 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -1,5 +1,3 @@ -const Future = Meteor.isServer && require('fibers/future'); - /******************************************************************************/ /* TestCaseResults */ /******************************************************************************/ @@ -548,35 +546,31 @@ export class TestRun { if (Meteor.isServer) { // On the server, ensure that only one test runs at a time, even // with multiple clients. - this.manager.testQueue.queueTask(() => { - // The future resolves when the test completes or times out. - var future = new Future(); - Meteor.setTimeout( - () => { - if (future.isResolved()) - // If the future has resolved the test has completed. - return; - test.timedOut = true; - this._report(test, { - type: "exception", - details: { - message: "test timed out" - } - }); - future['return'](); - }, - 3 * 60 * 1000 // 3 minutes - ); + let hasRan = false; + const timeoutPromise = new Promise((resolve, reject) => { + Meteor.setTimeout(() => { + if (hasRan) { + resolve(); + } + + reject(); + }, 3 * 60 * 1000); + }); + const runnerPromise = new Promise((resolve) => { this._runTest(test, () => { // The test can complete after it has timed out (it might // just be slow), so only resolve the future if the test // hasn't timed out. - if (! future.isResolved()) - future['return'](); + if (!hasRan) { + hasRan = true; + resolve(); + } }, stop_at_offset); - // Wait for the test to complete or time out. - future.wait(); - onComplete && onComplete(); + }); + this.manager.testQueue.queueTask(() => { + Promise.race([runnerPromise, timeoutPromise]).finally(() => { + onComplete && onComplete(); + }); }); } else { // client diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index c43fb12b34..8336498734 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -58,7 +58,7 @@ Meteor.methods({ } function onReport(report) { - if (! Fiber.current) { + if (!Fiber.current && Meteor._isFibersEnabled) { Meteor._debug("Trying to report a test not in a fiber! "+ "You probably forgot to wrap a callback in bindEnvironment."); console.trace(); From 8d5983d2bcc5283fd702ccf5d9a2e480d8f90f57 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 29 Aug 2022 20:30:46 -0300 Subject: [PATCH 0011/1965] Create Meteor._runAsync helper. --- packages/ddp-server/livedata_server.js | 61 +++++--------------------- packages/meteor/asl-helpers.js | 14 ++++++ 2 files changed, 26 insertions(+), 49 deletions(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 8d8f0ed451..ba95cc1492 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1,7 +1,5 @@ DDPServer = {}; -var Fiber = Npm.require('fibers'); -const ASL = global.asyncLocalStorage; // Publication strategies define how we handle data from published cursors at the collection level // This allows someone to: // - Choose a trade-off between client-server bandwidth and server memory usage @@ -330,15 +328,9 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - if (Meteor._isFibersEnabled) { - Fiber(function() { - self.startUniversalSubs(); - }).run(); - } else { - ASL.run(Meteor._getAslStore, function () { - self.startUniversalSubs(); - }); - } + Meteor._runAsync(function() { + self.startUniversalSubs(); + }); if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -562,15 +554,11 @@ Object.assign(Session.prototype, { // // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. - if (Meteor._isFibersEnabled && self.heartbeat) { - Fiber(function() { - self.heartbeat.messageReceived(); - }).run(); - } else if (self.heartbeat) { - ASL.run(Meteor._getAslStore, function () { + if (self.heartbeat) { + Meteor._runAsync(function() { self.heartbeat.messageReceived(); }); - } + }; if (self.version !== 'pre1' && msg_in.msg === 'ping') { if (self._respondToPings) @@ -616,12 +604,7 @@ Object.assign(Session.prototype, { unblock(); // in case the handler didn't already do it } - if (Meteor._isFibersEnabled) { - Fiber(runHandlers).run(); - return; - } - - ASL.run(Meteor._getAslStore, runHandlers); + Meteor._runAsync(runHandlers); }; processNext(); @@ -1505,15 +1488,9 @@ Server = function (options = {}) { return; } - if (Meteor._isFibersEnabled) { - Fiber(function() { - self._handleConnect(socket, msg); - }).run(); - } else { - ASL.run(Meteor._getAslStore, function () { - self._handleConnect(socket, msg); - }); - } + Meteor._runAsync(function() { + self._handleConnect(socket, msg); + }) return; } @@ -1531,14 +1508,7 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - if (Meteor._isFibersEnabled) { - Fiber(function() { - socket._meteorSession.close() - }).run(); - return; - } - - ASL.run(Meteor._getAslStore, function () { + Meteor._runAsync(function() { socket._meteorSession.close(); }); } @@ -1718,14 +1688,7 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - if (Meteor._isFibersEnabled) { - Fiber(function() { - session._startSubscription(handler); - }).run(); - return; - } - - ASL.run(Meteor._getAslStore, function() { + Meteor._runAsync(function() { session._startSubscription(handler); }); } diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js index 77214cfc32..27d9b227cb 100644 --- a/packages/meteor/asl-helpers.js +++ b/packages/meteor/asl-helpers.js @@ -6,3 +6,17 @@ Meteor._isFibersEnabled = !process.env.DISABLE_FIBERS && Meteor.isServer; Meteor._getAslStore = getAslStore; Meteor._getValueFromAslStore = getValueFromAslStore; Meteor._updateAslStore = updateAslStore; + +Meteor._runAsync = (fn, ctx) => { + if (Meteor._isFibersEnabled) { + const Fiber = Npm.require('fibers'); + + return Fiber(() => { + fn.call(ctx); + }).run(); + } + + global.asyncLocalStorage.run(Meteor._getAslStore(), () => { + fn.call(ctx); + }); +}; From 38635c680132e33698fb211078af0e1ceb95b23e Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 29 Aug 2022 20:57:00 -0300 Subject: [PATCH 0012/1965] Add .jshintrc to remove some warnings from WebStorm. --- .jshintrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000000..711f4c4f56 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "esversion": 11 +} From 6aecc664a72b1c5975de81e9d2725e77fba77e30 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 29 Aug 2022 20:59:21 -0300 Subject: [PATCH 0013/1965] Fix ecmascript tests and ensure that we don't have a Fiber when running with the DISABLE_FIBERS flag enabled. --- packages/ecmascript/runtime-tests.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/ecmascript/runtime-tests.js b/packages/ecmascript/runtime-tests.js index f0cb308a97..92ce9db8d3 100644 --- a/packages/ecmascript/runtime-tests.js +++ b/packages/ecmascript/runtime-tests.js @@ -380,7 +380,11 @@ Tinytest.addAsync('ecmascript - runtime - misc support', (test, done) => { if (Meteor.isServer) { const Fiber = Npm.require('fibers'); // Make sure the Promise polyfill runs callbacks in a Fiber. - test.instanceOf(Fiber.current, Fiber); + if (Meteor._isFibersEnabled) { + test.instanceOf(Fiber.current, Fiber); + } else { + test.isUndefined(Fiber.current); + } } }) .then(done, error => test.exception(error)); @@ -401,8 +405,15 @@ Tinytest.addAsync('ecmascript - runtime - async fibers', (test, done) => { const fiberBeforeAwait = Fiber.current; await wait(); const fiberAfterAwait = Fiber.current; - test.isTrue(fiberBeforeAwait instanceof Fiber); - test.isTrue(fiberBeforeAwait === fiberAfterAwait); + if (Meteor._isFibersEnabled) { + test.isTrue(fiberBeforeAwait instanceof Fiber); + test.isTrue(fiberBeforeAwait === fiberAfterAwait); + return; + } + + test.isUndefined(fiberBeforeAwait); + test.isUndefined(fiberAfterAwait) + test.isTrue(fiberBeforeAwait === fiberAfterAwait) } check().then(done); From f9289262442d5355f5347fd31f8070170c0b4ce0 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 31 Aug 2022 16:50:56 -0300 Subject: [PATCH 0014/1965] Create findOneNoFibers and make async_multi correctly handle async functions. --- packages/mongo/mongo_driver.js | 21 +++++++++++++++++++-- packages/mongo/oplog_tailing.js | 2 +- packages/test-helpers/async_multi.js | 6 +++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index a1a463238a..9ad15f5da3 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -794,8 +794,7 @@ MongoConnection.prototype.find = function (collectionName, selector, options) { self, new CursorDescription(collectionName, selector, options)); }; -MongoConnection.prototype.findOne = function (collection_name, selector, - options) { +MongoConnection.prototype._findOneFibers = function (collection_name, selector, options) { var self = this; if (arguments.length === 1) selector = {}; @@ -805,6 +804,24 @@ MongoConnection.prototype.findOne = function (collection_name, selector, return self.find(collection_name, selector, options).fetch()[0]; }; +MongoConnection.prototype._findOneNoFibers = async function (collection_name, selector, options) { + var self = this; + if (arguments.length === 1) { + selector = {}; + } + + options = options || {}; + options.limit = 1; + + const results = await self.find(collection_name, selector, options).fetch(); + + return results[0]; +}; + +MongoConnection.prototype.findOne = function (...args) { + return Meteor._isFibersEnabled ? this._findOneFibers(...args) : this._findOneNoFibers(...args); +}; + // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. MongoConnection.prototype.createIndex = function (collectionName, index, diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index 9b21debc85..0728b121a0 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -227,7 +227,7 @@ Object.assign(OplogHandle.prototype, { // tailing selector (ie, we need to specify the DB name) or else we might // find a TS that won't show up in the actual tail stream. try { - lastEntry = self._oplogLastEntryConnection.findOne( + lastEntry = await self._oplogLastEntryConnection.findOne( OPLOG_COLLECTION, self._baseOplogSelector, {fields: {ts: 1}, sort: {$natural: -1}}); break; diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index e5ec3cb43c..028be0dbb1 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -142,7 +142,11 @@ testAsyncMulti = function (name, funcs, { isOnly = false } = {}) { test.extraDetails.asyncBlock = i++; new Promise(resolve => { - resolve(func.apply(context, [test, _.bind(em.expect, em)])); + if (func.constructor.name === "Function") { + return resolve(func.apply(context, [test, _.bind(em.expect, em)])); + } + + return func.apply(context, [test, _.bind(em.expect, em)]).then((r) => resolve(r)); }).then(result => { em.done(); }, exception => { From a3e82c88842cd09f385f13d909ec5473d735f7aa Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 2 Sep 2022 21:31:41 -0300 Subject: [PATCH 0015/1965] Create helper to promisify functions that have callbacks with errors first parameter. Apply this to create real async insert method for mongo. --- packages/meteor/fiber_helpers.js | 4 ++ packages/meteor/helpers.js | 32 +++++++++ packages/mongo/collection.js | 119 ++++++++++++++++++++++++++----- packages/mongo/mongo_driver.js | 2 +- 4 files changed, 139 insertions(+), 18 deletions(-) diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 2b240efa26..d16cb9d787 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -2,6 +2,10 @@ var Fiber = Npm.require('fibers'); var Future = Npm.require('fibers/future'); Meteor._noYieldsAllowed = function (f) { + if (!Meteor._isFibersEnabled) { + return f(); + } + var savedYield = Fiber.yield; Fiber.yield = function () { throw new Error("Can't call yield in a noYieldsAllowed block!"); diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index e64921a5ef..242921945c 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -71,6 +71,38 @@ Meteor._delete = function (obj /*, arguments */) { } }; + +/** + * Takes a function that has a callback argument as the last one and promissify it. + * One option would be to use node utils.promisify, but it won't work on the browser. + * @param fn + * @param context + * @param errorFirst - If the callback follows the errorFirst style + * @returns {function(...[*]): Promise} + */ +Meteor.promisify = function (fn, context, errorFirst = true) { + return function (...fnArgs) { + return new Promise((resolve, reject) => { + const callback = Meteor.bindEnvironment((error, result) => { + let _error = error, _result = result; + if (!errorFirst) { + _error = result; + _result = error; + } + + if (_error) { + return reject(_error); + } + + resolve(_result); + }); + + const filteredArgs = [...fnArgs, callback].filter(i => i !== undefined); + return fn.apply(context || this, filteredArgs); + }); + }; +}; + // wrapAsync can wrap any function that takes some number of arguments that // can't be undefined, followed by some optional arguments, where the callback // is the last optional argument. diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 9cc1067d6e..b5cdda952f 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -533,16 +533,7 @@ Object.assign(Mongo.Collection.prototype, { // them. In the future maybe we should provide a flag to turn this // off. - /** - * @summary Insert a document in the collection. Returns its unique _id. - * @locus Anywhere - * @method insert - * @memberof Mongo.Collection - * @instance - * @param {Object} doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. - * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. - */ - insert(doc, callback) { + _insertSync(doc, callback) { // Make sure we were passed a document to insert if (!doc) { throw new Error('insert requires an argument'); @@ -550,17 +541,17 @@ Object.assign(Mongo.Collection.prototype, { // Make a shallow clone of the document, preserving its prototype. doc = Object.create( - Object.getPrototypeOf(doc), - Object.getOwnPropertyDescriptors(doc) + Object.getPrototypeOf(doc), + Object.getOwnPropertyDescriptors(doc) ); if ('_id' in doc) { if ( - !doc._id || - !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) + !doc._id || + !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) ) { throw new Error( - 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' + 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' ); } } else { @@ -597,8 +588,8 @@ Object.assign(Mongo.Collection.prototype, { }; const wrappedCallback = wrapCallback( - callback, - chooseReturnValueFromCollectionResult + callback, + chooseReturnValueFromCollectionResult ); if (this._isRemoteCollection()) { @@ -623,6 +614,100 @@ Object.assign(Mongo.Collection.prototype, { } }, + async _insertAsync(doc, callback) { + // Make sure we were passed a document to insert + if (!doc) { + throw new Error('insert requires an argument'); + } + + // Make a shallow clone of the document, preserving its prototype. + doc = Object.create( + Object.getPrototypeOf(doc), + Object.getOwnPropertyDescriptors(doc) + ); + + if ('_id' in doc) { + if ( + !doc._id || + !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) + ) { + throw new Error( + 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' + ); + } + } else { + let generateId = true; + + // Don't generate the id if we're the client and the 'outermost' call + // This optimization saves us passing both the randomSeed and the id + // Passing both is redundant. + if (this._isRemoteCollection()) { + const enclosing = DDP._CurrentMethodInvocation.get(); + if (!enclosing) { + generateId = false; + } + } + + if (generateId) { + doc._id = this._makeNewID(); + } + } + + // On inserts, always return the id that we generated; on all other + // operations, just return the result from the collection. + var chooseReturnValueFromCollectionResult = function(result) { + if (doc._id) { + return doc._id; + } + + // XXX what is this for?? + // It's some iteraction between the callback to _callMutatorMethod and + // the return value conversion + doc._id = result; + + return result; + }; + + const wrappedCallback = wrapCallback( + callback, + chooseReturnValueFromCollectionResult + ); + + if (this._isRemoteCollection()) { + const result = this._callMutatorMethod('insert', [doc], wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } + + // it's my collection. descend into the collection object + // and propagate any exception. + try { + // If the user provided a callback and the collection implements this + // operation asynchronously, then queryRet will be undefined, and the + // result will be returned through the callback instead. + const result = await this._collection.insert(doc, wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } catch (e) { + if (callback) { + callback(e); + return null; + } + throw e; + } + }, + + /** + * @summary Insert a document in the collection. Returns its unique _id. + * @locus Anywhere + * @method insert + * @memberof Mongo.Collection + * @instance + * @param {Object} doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. + * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. + */ + insert(doc, callback) { + return Meteor._isFibersEnabled ? this._insertSync(doc, callback) : this._insertAsync(doc, callback); + }, + /** * @summary Modify one or more documents in the collection. Returns the number of matched documents. * @locus Anywhere diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 9ad15f5da3..0f6396b505 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -762,7 +762,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, _.each(["insert", "update", "remove", "dropCollection", "dropDatabase"], function (method) { MongoConnection.prototype[method] = function (/* arguments */) { var self = this; - return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : self[`_${method}`].apply(self, arguments); + return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : Meteor.promisify(self[`_${method}`]).apply(self, arguments); }; }); From 24b01a4cb8b6bffb4031bb040e32771ce0ff2e8f Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 6 Sep 2022 13:48:48 -0300 Subject: [PATCH 0016/1965] Correctly implement forEach (make it synchronously run the callbacks) and apply that to map. Also, correctly implement the withValue from the Meteor.EVp. Seems that the values were being set the wrong way. --- packages/ddp-server/writefence.js | 26 +++- packages/meteor/dynamics_nodejs.js | 6 +- packages/meteor/fiber_helpers.js | 6 +- packages/minimongo/local_collection.js | 153 ++++++++++++++++++++++- packages/mongo/mongo_driver.js | 34 +++-- packages/mongo/polling_observe_driver.js | 114 +++++++++++++++-- 6 files changed, 306 insertions(+), 33 deletions(-) diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index e9310c9f7f..f911ae71b7 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -79,8 +79,7 @@ _.extend(DDPServer._WriteFence.prototype, { self.completion_callbacks.push(func); }, - // Convenience function. Arms the fence, then blocks until it fires. - armAndWait: function () { + _armAndWaitFibers: function () { var self = this; var future = new Future; self.onAllCommitted(function () { @@ -89,6 +88,29 @@ _.extend(DDPServer._WriteFence.prototype, { self.arm(); future.wait(); }, + _armAndWaitNoFibers: function () { + var self = this; + + let _resolver; + self.onAllCommitted(function () { + if (!_resolver) { + console.warn("oops, no resolver"); + return; + } + + _resolver(); + }); + + return new Promise((r) => { + _resolver = r; + self.arm(); + } ); + }, + + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait: function () { + return Meteor._isFibersEnabled ? this._armAndWaitFibers() : this._armAndWaitNoFibers(); + }, _maybeFire: function () { var self = this; diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index b204bb5765..6fe1200df8 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -97,15 +97,15 @@ EVp.withValue = function (value, func) { let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); if (!meteorDynamics) { meteorDynamics = []; - Meteor._updateAslStore('_meteor_dynamics', []); } const saved = meteorDynamics[this.slot]; try { - Meteor._updateAslStore('_meteor_dynamics', value); + meteorDynamics[this.slot] = value; return func(); } finally { - Meteor._updateAslStore('_meteor_dynamics', saved); + meteorDynamics[this.slot] = saved; + Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); } }; diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index d16cb9d787..a976d59616 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -162,12 +162,12 @@ SQp._scheduleRun = function () { */ if (Meteor._isFibersEnabled) { setImmediate(function() { - Fiber(function() { + Meteor._runAsync(function() { self._run(); - }).run(); + }); }); } else { - global.asyncLocalStorage.run(Meteor._getAslStore(), () => { + Meteor._runAsync(() => { self._run(); }); } diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index e3668eeb03..1466e0a783 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -689,8 +689,6 @@ LocalCollection._CachingChangeObserver = class _CachingChangeObserver { this.docs.putBefore(id, doc, before || null); }, movedBefore: (id, before) => { - const doc = this.docs.get(id); - if (callbacks.movedBefore) { callbacks.movedBefore.call(this, id, before); } @@ -1201,7 +1199,7 @@ LocalCollection._modify = (doc, modifier, options = {}) => { }); }; -LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => { +LocalCollection._observeFromObserveChangesFibers = (cursor, observeCallbacks) => { const transform = cursor.getTransform() || (doc => doc); let suppressed = !!observeCallbacks._suppress_initial; @@ -1344,6 +1342,155 @@ LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => { return handle; }; +LocalCollection._observeFromObserveChangesNoFibers = async (cursor, observeCallbacks) => { + const transform = cursor.getTransform() || (doc => doc); + let suppressed = !!observeCallbacks._suppress_initial; + + let observeChangesCallbacks; + if (LocalCollection._observeCallbacksAreOrdered(observeCallbacks)) { + // The "_no_indices" option sets all index arguments to -1 and skips the + // linear scans required to generate them. This lets observers that don't + // need absolute indices benefit from the other features of this API -- + // relative order, transforms, and applyChanges -- without the speed hit. + const indices = !observeCallbacks._no_indices; + + observeChangesCallbacks = { + addedBefore(id, fields, before) { + if (suppressed || !(observeCallbacks.addedAt || observeCallbacks.added)) { + return; + } + + const doc = transform(Object.assign(fields, {_id: id})); + + if (observeCallbacks.addedAt) { + observeCallbacks.addedAt( + doc, + indices + ? before + ? this.docs.indexOf(before) + : this.docs.size() + : -1, + before + ); + } else { + observeCallbacks.added(doc); + } + }, + changed(id, fields) { + if (!(observeCallbacks.changedAt || observeCallbacks.changed)) { + return; + } + + let doc = EJSON.clone(this.docs.get(id)); + if (!doc) { + throw new Error(`Unknown id for changed: ${id}`); + } + + const oldDoc = transform(EJSON.clone(doc)); + + DiffSequence.applyChanges(doc, fields); + + if (observeCallbacks.changedAt) { + observeCallbacks.changedAt( + transform(doc), + oldDoc, + indices ? this.docs.indexOf(id) : -1 + ); + } else { + observeCallbacks.changed(transform(doc), oldDoc); + } + }, + movedBefore(id, before) { + if (!observeCallbacks.movedTo) { + return; + } + + const from = indices ? this.docs.indexOf(id) : -1; + let to = indices + ? before + ? this.docs.indexOf(before) + : this.docs.size() + : -1; + + // When not moving backwards, adjust for the fact that removing the + // document slides everything back one slot. + if (to > from) { + --to; + } + + observeCallbacks.movedTo( + transform(EJSON.clone(this.docs.get(id))), + from, + to, + before || null + ); + }, + removed(id) { + if (!(observeCallbacks.removedAt || observeCallbacks.removed)) { + return; + } + + // technically maybe there should be an EJSON.clone here, but it's about + // to be removed from this.docs! + const doc = transform(this.docs.get(id)); + + if (observeCallbacks.removedAt) { + observeCallbacks.removedAt(doc, indices ? this.docs.indexOf(id) : -1); + } else { + observeCallbacks.removed(doc); + } + }, + }; + } else { + observeChangesCallbacks = { + added(id, fields) { + if (!suppressed && observeCallbacks.added) { + observeCallbacks.added(transform(Object.assign(fields, {_id: id}))); + } + }, + changed(id, fields) { + if (observeCallbacks.changed) { + const oldDoc = this.docs.get(id); + const doc = EJSON.clone(oldDoc); + + DiffSequence.applyChanges(doc, fields); + + observeCallbacks.changed( + transform(doc), + transform(EJSON.clone(oldDoc)) + ); + } + }, + removed(id) { + if (observeCallbacks.removed) { + observeCallbacks.removed(transform(this.docs.get(id))); + } + }, + }; + } + + const changeObserver = new LocalCollection._CachingChangeObserver({ + callbacks: observeChangesCallbacks + }); + + // CachingChangeObserver clones all received input on its callbacks + // So we can mark it as safe to reduce the ejson clones. + // This is tested by the `mongo-livedata - (extended) scribbling` tests + changeObserver.applyChange._fromObserve = true; + const handle = await cursor.observeChanges(changeObserver.applyChange, + { nonMutatingCallbacks: true }); + + suppressed = false; + + return handle; +}; + +LocalCollection._observeFromObserveChanges = (cursor, observeCallbacks) => { + return Meteor._isFibersEnabled + ? LocalCollection._observeFromObserveChangesFibers(cursor, observeCallbacks) + : LocalCollection._observeFromObserveChangesNoFibers(cursor, observeCallbacks); +}; + LocalCollection._observeCallbacksAreOrdered = callbacks => { if (callbacks.added && callbacks.addedAt) { throw new Error('Please specify only one of added() and addedAt()'); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 0f6396b505..a6ff3afe79 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1125,23 +1125,23 @@ class AsynchronousCursor { }); } - forEach(callback, thisArg) { + async forEach(callback, thisArg) { // Get back to the beginning. this._rewind(); - return this._cursor.forEach((doc, index) => { - callback.call(thisArg, doc, index) - }, this._selfForIteration); + let idx = 0; + while (true) { + const doc = await this._nextObjectPromise(); + if (!doc) return; + await callback.call(thisArg, doc, idx++, this._selfForIteration); + } } async map(callback, thisArg) { const results = []; - - let idx = 0; - for await (const doc of this._cursor) { - results.push(await callback.call(thisArg, doc, idx, this._selfForIteration)) - idx++; - } + await this.forEach(async (doc, index) => { + results.push(await callback.call(thisArg, doc, index, this._selfForIteration)); + }); return results; } @@ -1159,7 +1159,7 @@ class AsynchronousCursor { } fetch() { - return this._cursor.toArray(); + return this.map(_.identity); } /** @@ -1172,13 +1172,13 @@ class AsynchronousCursor { } // This method is NOT wrapped in Cursor. - getRawObjects(ordered) { + async getRawObjects(ordered) { var self = this; if (ordered) { return self.fetch(); } else { var results = new LocalCollection._IdMap; - self.forEach(function (doc) { + await self.forEach(function (doc) { results.set(doc._id, doc); }); return results; @@ -1590,6 +1590,10 @@ Object.assign(MongoConnection.prototype, { _testOnlyPollCallback: callbacks._testOnlyPollCallback }); + if (observeDriver._init) { + await observeDriver._init(); + } + // This field is only set for use in tests. multiplexer._observeDriver = observeDriver; } @@ -1697,6 +1701,10 @@ Object.assign(MongoConnection.prototype, { _testOnlyPollCallback: callbacks._testOnlyPollCallback }); + if (observeDriver._init) { + observeDriver._init(); + } + // This field is only set for use in tests. multiplexer._observeDriver = observeDriver; } diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js index fb3f692fef..32744abeba 100644 --- a/packages/mongo/polling_observe_driver.js +++ b/packages/mongo/polling_observe_driver.js @@ -11,7 +11,7 @@ PollingObserveDriver = function (options) { self._stopCallbacks = []; self._stopped = false; - self._synchronousCursor = self._mongoHandle._createSynchronousCursor( + self._cursor = self._mongoHandle._createSynchronousCursor( self._cursorDescription); // previous results snapshot. on each poll cycle, diffs against @@ -74,15 +74,28 @@ PollingObserveDriver = function (options) { Meteor.clearInterval(intervalHandle); }); } - - // Make sure we actually poll soon! - self._unthrottledEnsurePollIsScheduled(); - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-polling", 1); }; _.extend(PollingObserveDriver.prototype, { + _initAsync: async function () { + // Make sure we actually poll soon! + await this._unthrottledEnsurePollIsScheduled(); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", 1); + }, + _init() { + if (!Meteor._isFibersEnabled) { + return this._initAsync(); + } + + var self = this; + // Make sure we actually poll soon! + self._unthrottledEnsurePollIsScheduled(); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", 1); + }, // This is always called through _.throttle (except once at startup). _unthrottledEnsurePollIsScheduled: function () { var self = this; @@ -129,7 +142,7 @@ _.extend(PollingObserveDriver.prototype, { }); }, - _pollMongo: function () { + _pollMongoFibers: function () { var self = this; --self._pollsScheduledButNotStarted; @@ -153,7 +166,7 @@ _.extend(PollingObserveDriver.prototype, { // Get the new query results. (This yields.) try { - newResults = self._synchronousCursor.getRawObjects(self._ordered); + newResults = self._cursor.getRawObjects(self._ordered); } catch (e) { if (first && typeof(e.code) === 'number') { // This is an error document sent to us by mongod, not a connection @@ -208,6 +221,89 @@ _.extend(PollingObserveDriver.prototype, { }); }, + async _pollMongoNoFibers() { + var self = this; + --self._pollsScheduledButNotStarted; + + if (self._stopped) + return; + + var first = false; + var newResults; + var oldResults = self._results; + if (!oldResults) { + first = true; + // XXX maybe use OrderedDict instead? + oldResults = self._ordered ? [] : new LocalCollection._IdMap; + } + + self._testOnlyPollCallback && self._testOnlyPollCallback(); + + // Save the list of pending writes which this round will commit. + var writesForCycle = self._pendingWrites; + self._pendingWrites = []; + + // Get the new query results. (This yields.) + try { + newResults = await self._cursor.getRawObjects(self._ordered); + } catch (e) { + if (first && typeof(e.code) === 'number') { + // This is an error document sent to us by mongod, not a connection + // error generated by the client. And we've never seen this query work + // successfully. Probably it's a bad selector or something, so we should + // NOT retry. Instead, we should halt the observe (which ends up calling + // `stop` on us). + self._multiplexer.queryError( + new Error( + "Exception while polling query " + + JSON.stringify(self._cursorDescription) + ": " + e.message)); + return; + } + + // getRawObjects can throw if we're having trouble talking to the + // database. That's fine --- we will repoll later anyway. But we should + // make sure not to lose track of this cycle's writes. + // (It also can throw if there's just something invalid about this query; + // unfortunately the ObserveDriver API doesn't provide a good way to + // "cancel" the observe from the inside in this case. + Array.prototype.push.apply(self._pendingWrites, writesForCycle); + Meteor._debug("Exception while polling query " + + JSON.stringify(self._cursorDescription), e); + return; + } + + // Run diffs. + if (!self._stopped) { + LocalCollection._diffQueryChanges( + self._ordered, oldResults, newResults, self._multiplexer); + } + + // Signals the multiplexer to allow all observeChanges calls that share this + // multiplexer to return. (This happens asynchronously, via the + // multiplexer's queue.) + if (first) + self._multiplexer.ready(); + + // Replace self._results atomically. (This assignment is what makes `first` + // stay through on the next cycle, so we've waited until after we've + // committed to ready-ing the multiplexer.) + self._results = newResults; + + // Once the ObserveMultiplexer has processed everything we've done in this + // round, mark all the writes which existed before this call as + // commmitted. (If new writes have shown up in the meantime, there'll + // already be another _pollMongo task scheduled.) + self._multiplexer.onFlush(function () { + _.each(writesForCycle, function (w) { + w.committed(); + }); + }); + }, + + _pollMongo: function () { + return Meteor._isFibersEnabled ? this._pollMongoFibers() : this._pollMongoNoFibers(); + }, + stop: function () { var self = this; self._stopped = true; From 9a8164f70af496c90c2c2554259186a84f82893d Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 13 Sep 2022 15:56:38 -0300 Subject: [PATCH 0017/1965] Update the store when setting the meteor_dynamics value. --- packages/meteor/dynamics_nodejs.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 6fe1200df8..75a1fb43ad 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -89,7 +89,7 @@ function withValuesWithFiber(value, func) { * @param {Function} func The function to run * @return {Any} Return value of function */ -EVp.withValue = function (value, func) { +EVp.withValue = async function (value, func) { if (Meteor._isFibersEnabled) { return withValuesWithFiber.call(this, value, func); } @@ -102,7 +102,9 @@ EVp.withValue = function (value, func) { const saved = meteorDynamics[this.slot]; try { meteorDynamics[this.slot] = value; - return func(); + Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); + const result = await func(); + return result; } finally { meteorDynamics[this.slot] = saved; Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); From 63bc15b5b7a1cc47b72f5d104f78f58c3b716f07 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 30 Sep 2022 17:01:29 -0300 Subject: [PATCH 0018/1965] Working on mongo-livedata tests. --- packages/meteor/fiber_helpers.js | 38 +++++++++++++++++------ packages/mongo/mongo_driver.js | 21 ++++++++++--- packages/mongo/observe_multiplex.js | 10 +++--- packages/test-helpers/async_multi.js | 13 ++++++++ packages/test-helpers/package.js | 2 +- packages/tinytest/tinytest.js | 46 ++++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 19 deletions(-) diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index a976d59616..2bffec369e 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -160,17 +160,35 @@ SQp._scheduleRun = function () { * For autoupdate, for example, swapping to just running it sync won't make a difference, * but maybe there is another place that would? (DDP/Web)-Server? */ - if (Meteor._isFibersEnabled) { - setImmediate(function() { - Meteor._runAsync(function() { - self._run(); - }); - }); - } else { - Meteor._runAsync(() => { - self._run(); - }); + setImmediate(function() { + Meteor._runAsync(Meteor._isFibersEnabled ? self._run : self._runAsync, self); + }); +}; + +SQp._runAsync = async function() { + var self = this; + + if (!self._runningOrRunScheduled) + throw new Error("expected to be _runningOrRunScheduled"); + + if (self._taskHandles.isEmpty()) { + // Done running tasks! Don't immediately schedule another run, but + // allow future tasks to do so. + self._runningOrRunScheduled = false; + return; } + var taskHandle = self._taskHandles.shift(); + + // Run the task. + try { + await taskHandle.task(); + } catch (err) { + Meteor._debug("Exception in queued task", err); + } + + // Soon, run the next task, if there is any. + self._runningOrRunScheduled = false; + self._scheduleRun(); }; SQp._run = function () { diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index a6ff3afe79..105a05a281 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -479,14 +479,27 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, // non-object modifier in that they don't crash, they are not // meaningful operations and do not do anything. Defensively throw an // error here. - if (!mod || typeof mod !== 'object') - throw new Error("Invalid modifier. Modifier must be an object."); + if (!mod || typeof mod !== 'object') { + const error = new Error("Invalid modifier. Modifier must be an object."); + + if (callback) { + return callback(error); + } else { + throw error; + } + } if (!(LocalCollection._isPlainObject(mod) && !EJSON._isCustomType(mod))) { - throw new Error( - "Only plain objects may be used as replacement" + + const error = new Error( + "Only plain objects may be used as replacement" + " documents in MongoDB"); + + if (callback) { + return callback(error); + } else { + throw error; + } } if (!options) options = {}; diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index c442aa269e..9e0acf3c20 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -185,7 +185,7 @@ _.extend(ObserveMultiplexer.prototype, { }, _applyCallback: function (callbackName, args) { var self = this; - self._queue.queueTask(function () { + self._queue.queueTask(async function () { // If we stopped in the meantime, do nothing. if (!self._handles) return; @@ -205,15 +205,17 @@ _.extend(ObserveMultiplexer.prototype, { // can continue until these are done. (But we do have to be careful to not // use a handle that got removed, because removeHandle does not use the // queue; thus, we iterate over an array of keys that we control.) - _.each(_.keys(self._handles), function (handleId) { + const toAwait = Object.keys(self._handles).map(async (handleId) => { var handle = self._handles && self._handles[handleId]; if (!handle) return; var callback = handle['_' + callbackName]; // clone arguments so that callbacks can mutate their arguments - callback && callback.apply(null, - handle.nonMutatingCallbacks ? args : EJSON.clone(args)); + callback && await callback.apply(null, + handle.nonMutatingCallbacks ? args : EJSON.clone(args)); }); + + await Promise.all(toAwait); }); }, diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index 028be0dbb1..9a76c20f21 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -195,3 +195,16 @@ pollUntil = function (expect, f, timeout, step, noFail) { step ); }; + +runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => { + let err, result; + try { + result = await fn(); + } catch (e) { + err = e; + } + + test[shouldErrorOut ? "isTrue" : "isFalse"](err); + + return result; +}; diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 17b6e0f37a..561f207e4e 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -28,7 +28,7 @@ Package.onUse(function (api) { 'SeededRandom', 'clickElement', 'blurElement', 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', 'renderToDiv', 'clickIt', - 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', + 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', 'runAndThrowIfNeeded', 'makeTestConnection', 'DomUtils']); api.addFiles('try_all_permutations.js'); diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index e516feabdf..62cf133894 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -240,6 +240,52 @@ export class TestCaseResults { }); } + /** + * Same as throw, but accepts an async function as a parameter. + * @param f + * @param expected + * @param message + * @returns {Promise} + */ + async throwsAsync(f, expected, message) { + var actual, predicate; + + if (expected === undefined) { + predicate = function (actual) { + return true; + }; + } else if (typeof expected === "string") { + predicate = function (actual) { + return typeof actual.message === "string" && + actual.message.indexOf(expected) !== -1; + }; + } else if (expected instanceof RegExp) { + predicate = function (actual) { + return expected.test(actual.message); + }; + } else if (typeof expected === 'function') { + predicate = expected; + } else { + throw new Error('expected should be a string, regexp, or predicate function'); + } + + try { + await f(); + } catch (exception) { + actual = exception; + } + + if (actual && predicate(actual)) + this.ok(); + else + this.fail({ + type: "throws", + message: (actual ? + "wrong error thrown: " + actual.message : + "did not throw an error as expected") + (message ? ": " + message : ""), + }); + } + isTrue(v, msg) { if (v) this.ok(); From 1c590919b85ef3635a0cb15ae1567289454e7bf7 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 17 Oct 2022 17:27:41 -0300 Subject: [PATCH 0019/1965] # Conflicts: # packages/mongo/mongo_driver.js --- .circleci/config.yml | 63 +- .mailmap | 164 ---- .travis.yml | 13 +- CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 4 +- docs/_config.yml | 1 + docs/history.md | 52 +- docs/source/api/methods.md | 12 +- docs/source/api/pubsub.md | 2 +- docs/source/commandline.md | 76 +- docs/source/packages/fetch.md | 2 +- docs/source/roadmap.md | 10 +- guide/_config.yml | 3 +- guide/package.json | 2 +- guide/source/2.8-migration.md | 132 +++- guide/source/images/live-data-error.png | Bin 0 -> 32315 bytes meteor | 2 +- npm-packages/meteor-installer/package.json | 2 +- .../.npm/package/npm-shrinkwrap.json | 5 + .../ddp-client/client/client_convenience.js | 2 + .../ddp-client/common/livedata_connection.js | 305 ++++++-- packages/ddp-client/common/namespace.js | 5 + packages/ddp-client/package.js | 2 +- packages/ddp-common/method_invocation.js | 3 + packages/ddp-server/livedata_server.js | 55 +- packages/ddp-server/package.js | 2 +- packages/ddp-server/server_convenience.js | 19 +- packages/meteor-tool/package.js | 2 +- packages/meteor/dynamics_browser.js | 23 + packages/meteor/dynamics_nodejs.js | 25 + packages/meteor/package.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 712 ++++++++++++------ .../.npm/package/npm-shrinkwrap.json | 57 +- packages/minimongo/matcher.js | 4 +- packages/minimongo/minimongo_tests_client.js | 8 + packages/minimongo/package.js | 2 +- packages/modules/package.js | 2 +- packages/mongo/mongo_driver.js | 12 +- packages/mongo/mongo_livedata_tests.js | 1 + packages/mongo/oplog_tests.js | 37 +- packages/mongo/package.js | 2 +- .../.npm/package/npm-shrinkwrap.json | 18 +- packages/npm-mongo/package.js | 4 +- .../plugin/minifyStdCSS/npm-shrinkwrap.json | 18 +- .../plugin/minifyStdJS/npm-shrinkwrap.json | 6 +- packages/test-in-console/driver.js | 14 +- packages/test-in-console/package.js | 2 +- packages/test-in-console/puppeteerRunner.js | 47 +- .../admin/meteor-release-experimental.json | 2 +- scripts/build-dev-bundle-common.sh | 2 +- scripts/make-release-tarballs.sh | 27 + tools/cli/commands.js | 8 +- tools/cli/help.txt | 4 +- tools/static-assets/README.md | 8 + tools/static-assets/skel-chakra-ui/.gitignore | 1 + .../skel-chakra-ui/.meteor/.gitignore | 1 + .../skel-chakra-ui/.meteor/packages | 23 + .../skel-chakra-ui/.meteor/platforms | 2 + .../skel-chakra-ui/client/main.css | 4 + .../skel-chakra-ui/client/main.html | 7 + .../skel-chakra-ui/client/main.jsx | 8 + .../skel-chakra-ui/imports/api/links.js | 3 + .../skel-chakra-ui/imports/ui/App.jsx | 24 + .../skel-chakra-ui/imports/ui/Hello.jsx | 17 + .../skel-chakra-ui/imports/ui/Info.jsx | 22 + .../static-assets/skel-chakra-ui/package.json | 29 + .../skel-chakra-ui/server/main.js | 31 + .../skel-chakra-ui/tests/main.js | 20 + tools/static-assets/skel-solid/.gitignore | 1 + .../skel-solid/.meteor/.gitignore | 1 + .../static-assets/skel-solid/.meteor/packages | 23 + .../skel-solid/.meteor/platforms | 2 + .../static-assets/skel-solid/client/main.css | 4 + .../static-assets/skel-solid/client/main.html | 9 + .../static-assets/skel-solid/client/main.jsx | 1 + .../skel-solid/imports/api/links.js | 4 + .../skel-solid/imports/ui/App.jsx | 12 + .../skel-solid/imports/ui/Hello.jsx | 16 + .../skel-solid/imports/ui/Info.jsx | 26 + .../skel-solid/imports/ui/main.jsx | 8 + tools/static-assets/skel-solid/package.json | 28 + tools/static-assets/skel-solid/server/main.js | 31 + tools/static-assets/skel-solid/tests/main.js | 20 + tools/static-assets/skel-solid/vite.config.js | 12 + 84 files changed, 1707 insertions(+), 674 deletions(-) delete mode 100644 .mailmap create mode 100644 guide/source/images/live-data-error.png create mode 100755 scripts/make-release-tarballs.sh create mode 100644 tools/static-assets/skel-chakra-ui/.gitignore create mode 100644 tools/static-assets/skel-chakra-ui/.meteor/.gitignore create mode 100644 tools/static-assets/skel-chakra-ui/.meteor/packages create mode 100644 tools/static-assets/skel-chakra-ui/.meteor/platforms create mode 100644 tools/static-assets/skel-chakra-ui/client/main.css create mode 100644 tools/static-assets/skel-chakra-ui/client/main.html create mode 100644 tools/static-assets/skel-chakra-ui/client/main.jsx create mode 100644 tools/static-assets/skel-chakra-ui/imports/api/links.js create mode 100644 tools/static-assets/skel-chakra-ui/imports/ui/App.jsx create mode 100644 tools/static-assets/skel-chakra-ui/imports/ui/Hello.jsx create mode 100644 tools/static-assets/skel-chakra-ui/imports/ui/Info.jsx create mode 100644 tools/static-assets/skel-chakra-ui/package.json create mode 100644 tools/static-assets/skel-chakra-ui/server/main.js create mode 100644 tools/static-assets/skel-chakra-ui/tests/main.js create mode 100644 tools/static-assets/skel-solid/.gitignore create mode 100644 tools/static-assets/skel-solid/.meteor/.gitignore create mode 100644 tools/static-assets/skel-solid/.meteor/packages create mode 100644 tools/static-assets/skel-solid/.meteor/platforms create mode 100644 tools/static-assets/skel-solid/client/main.css create mode 100644 tools/static-assets/skel-solid/client/main.html create mode 100644 tools/static-assets/skel-solid/client/main.jsx create mode 100644 tools/static-assets/skel-solid/imports/api/links.js create mode 100644 tools/static-assets/skel-solid/imports/ui/App.jsx create mode 100644 tools/static-assets/skel-solid/imports/ui/Hello.jsx create mode 100644 tools/static-assets/skel-solid/imports/ui/Info.jsx create mode 100644 tools/static-assets/skel-solid/imports/ui/main.jsx create mode 100644 tools/static-assets/skel-solid/package.json create mode 100644 tools/static-assets/skel-solid/server/main.js create mode 100644 tools/static-assets/skel-solid/tests/main.js create mode 100644 tools/static-assets/skel-solid/vite.config.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 8b275071cd..91e4525564 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,4 @@ -version: 2 +version: 2.1 # A reusable "run" snippet which is ran before each test to setup the # environment for user-limits, core-dumps, etc. @@ -96,6 +96,29 @@ build_machine_environment: &build_machine_environment NUM_GROUPS: 12 RUNNING_AVG_LENGTH: 6 +can_disable_fibers: &can_disable_fibers + parameters: + fibers: + type: boolean + default: true + +set_fibers_env: &set_fibers_env + name: "Disable Fibers" + command: | + if [ "<< parameters.fibers >>" == "false" ]; then + echo "Disabling Fibers" + echo 'export DISABLE_FIBERS=1' >> "$BASH_ENV" + source "$BASH_ENV" + fi + + +# Run tests with Fibers and then without. +matrix_for_fibers: &matrix_for_fibers + matrix: + parameters: + fibers: [true, false] + + jobs: Get Ready: <<: *build_machine_environment @@ -167,6 +190,7 @@ jobs: path: /tmp/memuse.txt Isolated Tests: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -175,6 +199,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -209,6 +234,7 @@ jobs: path: /tmp/memuse.txt Test Group 0: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -217,6 +243,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -249,6 +276,7 @@ jobs: path: /tmp/memuse.txt Test Group 1: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -257,6 +285,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -289,6 +318,7 @@ jobs: path: /tmp/memuse.txt Test Group 2: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -297,6 +327,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -329,6 +360,7 @@ jobs: path: /tmp/memuse.txt Test Group 3: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -337,6 +369,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -369,6 +402,7 @@ jobs: path: /tmp/memuse.txt Test Group 4: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -377,6 +411,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -409,6 +444,7 @@ jobs: path: /tmp/memuse.txt Test Group 5: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -417,6 +453,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -449,6 +486,7 @@ jobs: path: /tmp/memuse.txt Test Group 6: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -457,6 +495,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -489,6 +528,7 @@ jobs: path: /tmp/memuse.txt Test Group 7: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -497,6 +537,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -529,6 +570,7 @@ jobs: path: /tmp/memuse.txt Test Group 8: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -537,6 +579,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -569,6 +612,7 @@ jobs: path: /tmp/memuse.txt Test Group 9: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -577,6 +621,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -609,6 +654,7 @@ jobs: path: /tmp/memuse.txt Test Group 10: + <<: *can_disable_fibers <<: *build_machine_environment steps: - run: @@ -617,6 +663,7 @@ jobs: <<: *run_env_change - attach_workspace: at: . + - run: *set_fibers_env - run: name: "Print environment" command: printenv @@ -687,6 +734,7 @@ jobs: npm test Clean Up: + <<: *can_disable_fibers <<: *build_machine_environment steps: - attach_workspace: @@ -769,42 +817,55 @@ workflows: - Docs - Get Ready - Isolated Tests: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 0: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 1: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 2: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 3: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 4: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 5: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 6: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 7: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 8: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 9: + <<: *matrix_for_fibers requires: - Get Ready - Test Group 10: + <<: *matrix_for_fibers requires: - Get Ready - Clean Up: + <<: *matrix_for_fibers requires: - Isolated Tests - Test Group 0 diff --git a/.mailmap b/.mailmap deleted file mode 100644 index afea9abd97..0000000000 --- a/.mailmap +++ /dev/null @@ -1,164 +0,0 @@ -# The presence of this file makes it easier to find GitHub usernames for -# History.md. -# -# This is a git dotfile that affects the output of 'git shortlog'. eg, run: -# git shortlog -s release/METEOR@1.0.1..HEAD -# to get a sorted list of all committers to revisions in HEAD but not -# in 1.0.1. To get the list including email addresses (useful for input -# to the script below) include --email as well. -# -# For any emails that show up in the shortlog that aren't in one of -# these lists, figure out their GitHub username and add them. -# -# A command-line way to get the GitHub username for an author: -# scripts/admin/find-author-github.sh 'User Name ' -# (Note that this script always outputs GITHUB so you should manually -# check to see if they are an Meteor Software employee!) - -GITHUB: 0a- -GITHUB: adnissen -GITHUB: aldeed -GITHUB: AlexeyMK -GITHUB: andylash -GITHUB: ansman -GITHUB: anstarovoyt -GITHUB: apendua -GITHUB: awatson1978 -GITHUB: awwx -GITHUB: babenzele -GITHUB: benweissmann -GITHUB: bwhitty -GITHUB: Cangit -GITHUB: chrisbridgett -GITHUB: christianbundy -GITHUB: cmather -GITHUB: codeinthehole -GITHUB: colllin -GITHUB: cryptoquick -GITHUB: d4nyll -GITHUB: dandv -GITHUB: DanielDent -GITHUB: DanielDent -GITHUB: DanielDornhardt -GITHUB: davegonzalez -GITHUB: DenisGorbachev -GITHUB: DenisGorbachev -GITHUB: ducdigital -GITHUB: duckspeaker -GITHUB: ecwyne -GITHUB: emgee3 -GITHUB: EOT -GITHUB: fay-jai -GITHUB: felixrabe -GITHUB: FooBarWidget -GITHUB: FredericoC -GITHUB: Gaelan -GITHUB: graemian -GITHUB: gsuess -GITHUB: hwillson -GITHUB: icellan -GITHUB: ImtiazMajeed -GITHUB: jacott -GITHUB: jakozaur -GITHUB: JamesLefrere -GITHUB: jbruni -GITHUB: jfhamlin -GITHUB: jperl -GITHUB: kentonv -GITHUB: kevinchiu -GITHUB: knownasilya -GITHUB: LyuGGang -GITHUB: marcandre -GITHUB: mart-jansink -GITHUB: matteodem -GITHUB: Maxhodges -GITHUB: MaximDubrovin -GITHUB: meawoppl -GITHUB: meonkeys -GITHUB: michaelbishop -GITHUB: mitar -GITHUB: mitar -GITHUB: mizzao -GITHUB: mquandalle -GITHUB: mquandalle -GITHUB: murillo128 -GITHUB: musically-ut -GITHUB: nathan-muir -GITHUB: Neftedollar -GITHUB: netanelgilad -GITHUB: ogourment -GITHUB: ograycode -GITHUB: omeid -GITHUB: OyoKooN -GITHUB: paulswartz -GITHUB: pcjpcj2 -GITHUB: Pent -GITHUB: physiocoder -GITHUB: PooMaster -GITHUB: prapicault -GITHUB: prapicault -GITHUB: Primigenus -GITHUB: pscanf -GITHUB: queso -GITHUB: rbabayoff -GITHUB: rcy -GITHUB: rdickert -GITHUB: restebanez -GITHUB: rgoomar -GITHUB: rgould -GITHUB: RichardLitt -GITHUB: richguan -GITHUB: rick-golden-healthagen -GITHUB: rissem -GITHUB: rjakobsson -GITHUB: RobertLowe -GITHUB: romanzolotarev -GITHUB: rosh93 -GITHUB: ryw -GITHUB: rzymek -GITHUB: sdarnell -GITHUB: Siilwyn -GITHUB: smallhelm -GITHUB: subhog -GITHUB: svda -GITHUB: Tarang -GITHUB: tbjers -GITHUB: thatneat -GITHUB: timhaines -GITHUB: timoabend -GITHUB: tmeasday -GITHUB: TomFreudenberg -GITHUB: trusktr -GITHUB: twhy -GITHUB: Urigo -GITHUB: waitingkuo -GITHUB: wulfmeister -GITHUB: yauh -GITHUB: yeputons -GITHUB: zol - -METEOR: arbesfeld -METEOR: avital -METEOR: benjamn -METEOR: benjamn -METEOR: debergalis -METEOR: dgreensp -METEOR: ekatek -METEOR: estark37 -METEOR: estark37 -METEOR: glasser -METEOR: glasser -METEOR: gschmidt -METEOR: justinsb -METEOR: karayu -METEOR: mariapacana -METEOR: multilinear -METEOR: n1mmy -METEOR: sixolet -METEOR: Slava -METEOR: Slava -METEOR: stubailo -METEOR: stubailo -METEOR: stubailo -METEOR: stubailo -METEOR: yyx990803 diff --git a/.travis.yml b/.travis.yml index 2d4a4a74d6..d3f1d88c1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,13 +8,16 @@ cache: - ".meteor" - ".babel-cache" script: - - export phantom=false - # to skip Downloading Chromium on every run - # https://github.com/dfernandez79/puppeteer/blob/main/README.md#q-chromium-gets-downloaded-on-every-npm-ci-run-how-can-i-cache-the-download - - export PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium - travis_retry ./packages/test-in-console/run.sh env: - - CXX=g++-4.8 + global: + - CXX=g++-4.8 + - phantom=false + - PUPPETEER_DOWNLOAD_PATH=~/.npm/chromium + jobs: + - DISABLE_FIBERS=1 + # Use a different flag, since node would use false as a string. + - FIBERS_ENABLED=1 addons: apt: sources: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 748f29a311..5f6acfdae1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,9 +61,9 @@ If you believe someone is violating the code of conduct, please report it by ema Sections of this Code of Conduct were inspired in by the following Codes from other open source projects and resources we admire: -- [The Contributor Covenant](http://contributor-covenant.org/version/1/4/) +- [The Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) - [Python](https://www.python.org/psf/codeofconduct/) -- [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) +- [Ubuntu](https://ubuntu.com/community/code-of-conduct) - [Django](https://www.djangoproject.com/conduct/) *This Meteor Code of Conduct is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) license. This Code was last updated on August 28, 2017.* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38f04ee3e1..8e4f734767 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,8 +49,7 @@ Current Reviewers: - [@aquinoit](https://github.com/aquinoit) - [@Grubba27](https://github.com/Grubba27) - [@filipenevola](https://github.com/filipenevola) -- [@Grubba27](https://github.com/Grubba27) - - [@StorytellerCZ](https://github.com/StorytellerCZ) +- [@StorytellerCZ](https://github.com/StorytellerCZ) - [@zodern](https://github.com/zodern) - [@CaptainN](https://github.com/CaptainN) - [@radekmie](https://github.com/radekmie) @@ -73,7 +72,6 @@ Current Core Committers: - [@edimarlnx](https://github.com/edimarlnx) - [@matheusccastroo](https://github.com/matheusccastroo) - [@eduwr](https://github.com/eduwr) -- [@Grubba27](https://github.com/Grubba27) ### Tracking project work diff --git a/docs/_config.yml b/docs/_config.yml index 8c927446c7..78db90b0a2 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,6 +1,7 @@ title: Meteor API Docs subtitle: API Docs versions: + - '2.8' - '2.7' - '2.6' - '2.5' diff --git a/docs/history.md b/docs/history.md index 4e96f7d2fa..f2c66e6665 100644 --- a/docs/history.md +++ b/docs/history.md @@ -2,32 +2,53 @@ #### Highlights * New MongoDB Package Async API. [PR](https://github.com/meteor/meteor/pull/12028) -* Node update to [v14.20.0](https://nodejs.org/en/blog/release/v14.20.0/) as part of the [July 7th security release](https://nodejs.org/en/blog/vulnerability/july-2022-security-releases/) -* Update MongoDB driver to 4.8. [PR](https://github.com/meteor/meteor/pull/12097) +* Node update to [v14.20.1](https://nodejs.org/en/blog/release/v14.20.1/) as part of the [September 22nd security release](https://nodejs.org/en/blog/vulnerability/september-2022-security-releases/) +* Update MongoDB driver to 4.9. [PR](https://github.com/meteor/meteor/pull/12097) +* Meteor.callAsync method. [PR](https://github.com/meteor/meteor/pull/12196) #### Breaking Changes N/A #### Migration Steps -Read our [Migration Guide](https://deploy-preview-12057--meteor-guide.netlify.app/2.8-migration.html) for this version. +Read our [Migration Guide](https://guide.meteor.com/2.8-migration.html) for this version. #### Meteor Version Release * `modules@0.19.0`: - Updating reify version. [PR](https://github.com/meteor/meteor/pull/12055). * `minimongo@1.9.0`: - - New methods to work with the Async API. [PR](https://github.com/meteor/meteor/pull/12028/files). + - New methods to work with the Async API. [PR](https://github.com/meteor/meteor/pull/12028). + - Solved invalid dates in Minimongo Matcher [PR](https://github.com/meteor/meteor/pull/12165). * `mongo@1.16.0`: - Adding async counterparts that allows gradual migration from Fibers. [PR](https://github.com/meteor/meteor/pull/12028). + - Improved oplogV2V1Converter implementation. [PR](https://github.com/meteor/meteor/pull/12116). + - Exit on MongoDB connection error. [PR](https://github.com/meteor/meteor/pull/12115). + - Fixed MongoConnection._onFailover hook. [PR](https://github.com/meteor/meteor/pull/12125). + - Fixed handling objects in oplogV2V1Converter. [PR](https://github.com/meteor/meteor/pull/12107). * `meteor@1.10.1`: - Create method to check if Fibers is enabled by flag DISABLE_FIBERS. [PR](https://github.com/meteor/meteor/pull/12100). -* `npm-mongo@4.8.0`: - - Updated MongoDB driver to 4.8. [PR](https://github.com/meteor/meteor/pull/12097). + - Fix bugs for linter build plugins. [PR](https://github.com/meteor/meteor/pull/12120). + - Document meteor show METEOR. [PR](https://github.com/meteor/meteor/pull/12124). + - Update Cordova Android to 10.1.2. [PR](https://github.com/meteor/meteor/pull/12131). + - Fixed flaky test. [PR](https://github.com/meteor/meteor/pull/12129). + - Refactoring/Remove unused imports from tools folder. [PR](https://github.com/meteor/meteor/pull/12084). + - Fix problem when publishing async methods. [PR](https://github.com/meteor/meteor/pull/12152). + - Update skeletons Apollo[PR](https://github.com/meteor/meteor/pull/12091) and other skeletons [PR](https://github.com/meteor/meteor/pull/12099) + - Added callAsync method for calling async methods [PR](https://github.com/meteor/meteor/pull/12196). +* `meteor-installer@2.7.5`: + - Validates required Node.js version. [PR](https://github.com/meteor/meteor/pull/12066). +* `npm-mongo@4.9.0`: + - Updated MongoDB driver to 4.9. [PR](https://github.com/meteor/meteor/pull/12163). + #### Independent Releases * `accounts-passwordless@2.1.3`: - - Fixing bug where tokes where never expiring. [PR](https://github.com/meteor/meteor/pull/12088). + - Fixing bug where tokens where never expiring. [PR](https://github.com/meteor/meteor/pull/12088). * `accounts-base@2.2.4`: - Adding new options to the `Accounts.config()` method: `loginTokenExpirationHours` and `tokenSequenceLength`. [PR](https://github.com/meteor/meteor/pull/12088). +* `Meteor Repo`: + - Included githubactions in the dependabot config. [PR](https://github.com/meteor/meteor/pull/12061). + - Visual rework in meteor readme. [PR](https://github.com/meteor/meteor/pull/12133). + - Remove useraccounts from Guide. [PR](https://github.com/meteor/meteor/pull/12090). * `minifier-css@1.6.1`: - Update postcss package to avoid issues with `Browserslist` and `caniuse-lite`. [PR](https://github.com/meteor/meteor/pull/12136). * `minifier-js@2.7.5`: @@ -36,6 +57,23 @@ Read our [Migration Guide](https://deploy-preview-12057--meteor-guide.netlify.ap - Update dependencies to avoid issues with `Browserslist` and `caniuse-lite`. [PR](https://github.com/meteor/meteor/pull/12141). * `standard-minifier-js@2.8.1`: - Update dependencies to avoid issues with `Browserslist` and `caniuse-lite`. [PR](https://github.com/meteor/meteor/pull/12142). +* `ddp-server@2.5.1`: + - Rename setPublicationStrategy and getPublicationStrategy arguments. [PR](https://github.com/meteor/meteor/pull/12166). + +#### Special thanks to +- [@fredmaiaarantes](https://github.com/fredmaiaarantes) +- [@radekmie](https://github.com/radekmie) +- [@naveensrinivasan](https://github.com/naveensrinivasan) +- [@zodern](https://github.com/zodern) +- [@brucejo75](https://github.com/brucejo75) +- [@matheusccastroo](https://github.com/matheusccastroo) +- [@victoriaquasar](https://github.com/victoriaquasar) +- [@StorytellerCZ](https://github.com/StorytellerCZ) +- [@Grubba27](https://github.com/Grubba27) +- [@denihs](https://github.com/denihs) +- [@edimarlnx](https://github.com/edimarlnx) + +For making this great framework even better! ## v2.7.3, 2022-05-31 diff --git a/docs/source/api/methods.md b/docs/source/api/methods.md index 22fe6e85cb..58ae24f15f 100644 --- a/docs/source/api/methods.md +++ b/docs/source/api/methods.md @@ -130,7 +130,7 @@ sanitized version is available, the client gets {% apibox "Meteor.call" %} -This is how to invoke a method. It will run the method on the server. If a +This is how to invoke a method with a sync stub. It will run the method on the server. If a stub is available, it will also run the stub on the client. (See also [`Meteor.apply`](#meteor_apply), which is identical to `Meteor.call` except that you specify the parameters as an array instead of as separate arguments and you @@ -186,12 +186,22 @@ you want to process the method's result as soon as it arrives from the server, even if the method's writes are not available yet, you can specify an `onResultReceived` callback to [`Meteor.apply`](#meteor_apply). +{% apibox "Meteor.callAsync" %} + +`Meteor.callAsync` is just like `Meteor.call`, except that it'll return a promise that you need to solve to get the result. + +> Be aware that you should never call a method (async or not) after calling `Meteor.callAsync` without waiting for the promise to solve. To understand why, please read this part of our [migration guide](https://guide.meteor.com/2.8-migration.html#the-limitations). + {% apibox "Meteor.apply" %} `Meteor.apply` is just like `Meteor.call`, except that the method arguments are passed as an array rather than directly as arguments, and you can specify options about how the client executes the method. +{% apibox "Meteor.applyAsync" %} + +`Meteor.applyAsync` is just like `Meteor.apply`, except it is an async function, and it will consider that the stub is async. +

DDPRateLimiter

Customize rate limiting for methods and subscriptions to avoid a high load of WebSocket messages in your app. diff --git a/docs/source/api/pubsub.md b/docs/source/api/pubsub.md index 414a0bc0dc..76f1343952 100644 --- a/docs/source/api/pubsub.md +++ b/docs/source/api/pubsub.md @@ -303,7 +303,7 @@ You can use the following methods to set or get the publication strategy for pub {% apibox "setPublicationStrategy" %} -For publication `foo`, you can set `NO_MERGE` strategy as shown: +For the `foo` collection, you can set the `NO_MERGE` strategy as shown: ```js import { DDPServer } from "meteor/ddp-server"; diff --git a/docs/source/commandline.md b/docs/source/commandline.md index a06221eeaf..906e1d2588 100644 --- a/docs/source/commandline.md +++ b/docs/source/commandline.md @@ -140,41 +140,49 @@ Create a basic [Svelte](https://svelte.dev/) app. Create a basic [React](https://reactjs.org) + [Tailwind CSS](https://tailwindcss.com) app. +`--chakra-ui` + +Create a basic [chakra-ui](https://chakra-ui.com/) app. + +`--solid` + +Create a basic [solid](https://www.solidjs.com/) app. + **Packages** -| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze`| `--apollo` | `--vue` | `--svelte` | `--tailwind` | -|------------------------------------------------------------------------------------------------------|:-------------------:|:---------:|:---------:|:-----------:|:--------:|:----------:|:-------:|:----------:|:------------:| -| [autopublish](https://atmospherejs.com/meteor/autopublish) |X| | | |X| | |X|X| -| [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) | | | | | | |X| | | -| [apollo](https://atmospherejs.com/meteor/apollo) | | | | | |X| | | | -| [blaze-html-templates](https://atmospherejs.com/meteor/blaze-html-templates) | | |X| |X| | | | | -| [ecmascript](https://atmospherejs.com/meteor/ecmascript) |X|X|X|X|X|X|X|X|X| -| [es5-shim](https://atmospherejs.com/meteor/es5-shim) |X|X|X|X|X|X|X|X|X| -| [hot-module-replacement](https://atmospherejs.com/meteor/hot-module-replacement) |X| | | |X|X| | |X| -| [insecure](https://atmospherejs.com/meteor/insecure) |X| | | |X| | |X|X| -| [johanbrook:publication-collector](https://atmospherejs.com/meteor/johanbrook/publication-collector) | | |X| | |X| | | -| [jquery](https://atmospherejs.com/meteor/jquery) | | |X| |X| | | | -| [ostrio:flow-router-extra](https://atmospherejs.com/meteor/ostrio/flow-router-extra) | | |X|| | | | | | -| [less](https://atmospherejs.com/meteor/less) | | |X| | | | | | | -| [meteor](https://atmospherejs.com/meteor/meteor) | | | |X| | | | | | -| [meteor-base](https://atmospherejs.com/meteor/meteor-base) |X|X|X| |X|X|X|X|X| -| [mobile-experience](https://atmospherejs.com/meteor/mobile-experience) |X|X|X| |X|X|X|X|X| -| [mongo](https://atmospherejs.com/meteor/mongo) |X|X|X| |X|X|X|X|X| -| [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) | | |X| | | |X| | -| [reactive-var](https://atmospherejs.com/meteor/reactive-var) |X|X|X| |X|X|X|X|X| -| [rdb:svelte-meteor-data](https://atmospherejs.com/rdb/svelte-meteor-data) | | | | | | | |X| | -| [server-render](https://atmospherejs.com/meteor/server-render) | | | |X| |X|X| | | -| [shell-server](https://atmospherejs.com/meteor/shell-server) | |X| |X|X|X|X|X | -| [standard-minifier-css](https://atmospherejs.com/meteor/standard-minifier-css) |X|X|X|X|X|X|X|X|X| -| [standard-minifier-js](https://atmospherejs.com/meteor/standard-minifier-js) |X|X|X|X|X|X|X|X|X| -| [static-html](https://atmospherejs.com/meteor/static-html) | |X| |X| |X|X|X| | -| [svelte:compiler](https://atmospherejs.com/svelte/compiler) | | | | | | | |X| | -| [swydo:graphql](https://atmospherejs.com/swydo/graphql) | | | | | |X| | | | -| [tailwindcss](https://tailwindcss.com) | |X|X| |X| |X| |X| -| [tracker](https://atmospherejs.com/meteor/tracker) | |X|X| |X| |X| | | -| [typescript](https://atmospherejs.com/meteor/typescript) |X|X|X|X|X|X|X|X|X| -| [webapp](https://atmospherejs.com/meteor/webapp) | | | |X| | | | | | -| [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data) |X| | | | | | | |X| +| | Default (`--react`) | `--bare` | `--full` | `--minimal` | `--blaze` | `--apollo` | `--vue` | `--svelte` | `--tailwind` | `--chakra-ui` | `--solid` | +|------------------------------------------------------------------------------------------------------|:-------------------:|:--------:|:--------:|:-----------:|:---------:|:----------:|:-------:|:----------:|:------------:|:-------------:|:---------:| +| [autopublish](https://atmospherejs.com/meteor/autopublish) | X | | | | X | | | X | X | X | X | +| [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) | | | | | | | X | | | | | +| [apollo](https://atmospherejs.com/meteor/apollo) | | | | | | X | | | | | | +| [blaze-html-templates](https://atmospherejs.com/meteor/blaze-html-templates) | | | X | | X | | | | | | | +| [ecmascript](https://atmospherejs.com/meteor/ecmascript) | X | X | X | X | X | X | X | X | X | X | X | +| [es5-shim](https://atmospherejs.com/meteor/es5-shim) | X | X | X | X | X | X | X | X | X | X | X | +| [hot-module-replacement](https://atmospherejs.com/meteor/hot-module-replacement) | X | | | | X | X | | | X | X | X | +| [insecure](https://atmospherejs.com/meteor/insecure) | X | | | | X | | | X | X | X | X | +| [johanbrook:publication-collector](https://atmospherejs.com/meteor/johanbrook/publication-collector) | | | X | | | X | | | | | | +| [jquery](https://atmospherejs.com/meteor/jquery) | | | X | | X | | | | | | | +| [ostrio:flow-router-extra](https://atmospherejs.com/meteor/ostrio/flow-router-extra) | | | X | | | | | | | | | +| [less](https://atmospherejs.com/meteor/less) | | | X | | | | | | | | | +| [meteor](https://atmospherejs.com/meteor/meteor) | | | | X | | | | | | | | +| [meteor-base](https://atmospherejs.com/meteor/meteor-base) | X | X | X | | X | X | X | X | X | X | X | +| [mobile-experience](https://atmospherejs.com/meteor/mobile-experience) | X | X | X | | X | X | X | X | X | X | X | +| [mongo](https://atmospherejs.com/meteor/mongo) | X | X | X | | X | X | X | X | X | X | X | +| [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) | | | X | | | | X | | | | | +| [reactive-var](https://atmospherejs.com/meteor/reactive-var) | X | X | X | | X | X | X | X | X | X | X | +| [rdb:svelte-meteor-data](https://atmospherejs.com/rdb/svelte-meteor-data) | | | | | | | | X | | | | +| [server-render](https://atmospherejs.com/meteor/server-render) | | | | X | | X | X | | | | | +| [shell-server](https://atmospherejs.com/meteor/shell-server) | | X | | X | X | X | X | X | X | X | X | +| [standard-minifier-css](https://atmospherejs.com/meteor/standard-minifier-css) | X | X | X | X | X | X | X | X | X | X | X | +| [standard-minifier-js](https://atmospherejs.com/meteor/standard-minifier-js) | X | X | X | X | X | X | X | X | X | X | X | +| [static-html](https://atmospherejs.com/meteor/static-html) | | X | | X | | X | X | X | | | | +| [svelte:compiler](https://atmospherejs.com/svelte/compiler) | | | | | | | | X | | | | +| [swydo:graphql](https://atmospherejs.com/swydo/graphql) | | | | | | X | | | | | | +| [tailwindcss](https://tailwindcss.com) | | X | X | | X | | X | | X | | | +| [tracker](https://atmospherejs.com/meteor/tracker) | | X | X | | X | | X | | | | | +| [typescript](https://atmospherejs.com/meteor/typescript) | X | X | X | X | X | X | X | X | X | X | X | +| [webapp](https://atmospherejs.com/meteor/webapp) | | | | X | | | | | | | | +| [react-meteor-data](https://atmospherejs.com/meteor/react-meteor-data) | X | | | | | | | | X | X | |

meteor login / logout

@@ -258,7 +266,7 @@ Your project should be a git repository as the commit hash is going to be used t The `cache-build` option is available since Meteor 1.11. {% endpullquote %} -With the argument `--container-size` you can change your app's container size using the deploy command. The valid arguments are: `tiny`, `compact`, `standard`, `double`, `quad`, `octa`, and `dozen`. To see more about the difference and prices of each one you can check it [here](https://www.meteor.com/cloud#pricing-section). +With the argument `--container-size` you can change your app's container size using the deploy command. The valid arguments are: `tiny`, `compact`, `standard`, `double`, `quad`, `octa`, and `dozen`. One more thing to note here is that the `--container-size` flag can only be used when the `--plan` option is already specified, else the use of `--container-size` option will throw an error with the message : `Error deploying application: Internal error`. To see more about the difference and prices of each one you can check it [here](https://www.meteor.com/cloud#pricing-section). {% pullquote warning %} The `--container-size` option is available since Meteor 2.4.1. diff --git a/docs/source/packages/fetch.md b/docs/source/packages/fetch.md index 4f301a3d32..4f5176e374 100644 --- a/docs/source/packages/fetch.md +++ b/docs/source/packages/fetch.md @@ -57,7 +57,7 @@ async function postData (url, data) { const data = await response.json(); return response(null, data); } catch (err) { - return response(error, null); + return response(err, null); } } diff --git a/docs/source/roadmap.md b/docs/source/roadmap.md index 7bbabc2561..ed910b7f56 100644 --- a/docs/source/roadmap.md +++ b/docs/source/roadmap.md @@ -5,7 +5,7 @@ description: Describes the high-level features and actions for the Meteor projec ## Introduction -Last update: August 4, 2022. +Last update: Setember 14, 2022. This document describes the high-level features and actions for the Meteor project in the near-to-medium term future. The description of many items include sentences and ideas from Meteor community members. @@ -45,6 +45,11 @@ Contributors are encouraged to focus their efforts on work that aligns with the - Remove Fibers dependency from Meteor Public APIs; - Remove Fibers entirely; +### Community items +- Release Blaze 2.6.2; ([Milestone](https://github.com/meteor/blaze/milestone/9)) +- Vue 3 integration; ([Forums](https://forums.meteor.com/t/status-of-vue-3-meteor/57915/25) / [Discussion](https://github.com/meteor/meteor/discussions/11521)) +- SolidJS starter template; + ### Next educational items - Create a new Meteor Guide; ([Current Guide](https://guide.meteor.com/)) - Login and Accounts course; ([Meteor University](https://university.meteor.com/)) @@ -52,9 +57,8 @@ Contributors are encouraged to focus their efforts on work that aligns with the ### Future items We plan to implement these items in the near future. Community help would be greatly appreciated. -- ES Modules Support; ([Discussion](https://github.com/meteor/meteor/discussions/11727)) +- Support package.json exports fields; ([Discussion](https://github.com/meteor/meteor/discussions/11727)) - MongoDB 6.0 Support; ([Discussion](https://github.com/meteor/meteor/discussions/12092)) -- Vue 3 integration; ([Forums](https://forums.meteor.com/t/status-of-vue-3-meteor/57915/25) / [Discussion](https://github.com/meteor/meteor/discussions/11521)) - Improve Meteor build time; ([Discussion](https://github.com/meteor/meteor/discussions/11587)) - HTTP/3 Support; - Tree-shaking; ([PR](https://github.com/meteor/meteor/pull/11164)) diff --git a/guide/_config.yml b/guide/_config.yml index 53c0233fe5..7a99eb70f2 100644 --- a/guide/_config.yml +++ b/guide/_config.yml @@ -5,6 +5,7 @@ edit_branch: 'devel' edit_path: 'guide' content_root: 'content' versions: + - '2.8' - '2.7' - '2.6' - '2.5' @@ -36,7 +37,7 @@ sidebar_categories: - index - code-style - structure - - 2.7-migration + - 2.8-migration Data: - collections - data-loading diff --git a/guide/package.json b/guide/package.json index 13142b4947..35e610d9b6 100644 --- a/guide/package.json +++ b/guide/package.json @@ -23,4 +23,4 @@ "test": "npm run clean; npm run build", "start": "npm run build && chexo meteor-hexo-config -- server" } -} \ No newline at end of file +} diff --git a/guide/source/2.8-migration.md b/guide/source/2.8-migration.md index a788a04eed..bc9c96f09e 100644 --- a/guide/source/2.8-migration.md +++ b/guide/source/2.8-migration.md @@ -17,9 +17,7 @@ If you want to know more about the plan, you can check this [discussion](https:/

Why doing this now?

-This will be a considerable change for older Meteor applications, and some parts of the code of any Meteor app will have to be adjusted eventually. - -But we're going to do this in a way that has the most negligible impact possible on the applications and over time. +This will be a considerable change for older Meteor applications, and some parts of the code of any Meteor app will have to be adjusted eventually. So it's important to start the migration process now. With this version, you'll be able to start preparing your app for the future by replacing your current MongoDB methods with the new async ones. @@ -36,33 +34,115 @@ Here are the newly added methods (you can see this description and the code [her ```js for await (const document of collection.find(query, options)) /* ... */ ``` -**A few internal methods are now async.** - - - These are: `MongoConnection` (constructor), `MongoInternals.RemoteCollectionDriver` (constructor), `MongoInternals.defaultRemoteCollectionDriver`, `OplogHandle` (constructor). -Adding these async counterparts allows gradual migration, as both versions will coexist for the time being. At the moment, all async methods are calling sync one directly, so all hooks and wrappers should work as expected. +There is also a new Meteor method called `callAsync`. It should be used to call async methods. + +

How can I start using these new features?

+ +We got a few examples for making it easy to use these new feature, you can look the code snippet bellow + +```js +// SERVER + +// Before 2.8, we would use something like this +export const removeByID = ({ id }) => { + SomeCollection.remove({ _id: id }); +}; + +// Now we can also do like this +export const removeByIDAsync = async ({ id }) => { + await SomeCollection.removeAsync({ _id: id }); +}; + +Meteor.methods({ + //... + removeByID, + removeByIDAsync, +}); + +// CLIENT + +const result = Meteor.call('removeByID', { id }); + +// For the async, you call it like this: + +const result = await Meteor.callAsync('removeByIDAsync', { id }); + +// or even like this: + +Meteor.callAsync('removeByIDAsync', { id }).then(result => { + console.log(result); +}); +``` + +More examples can be retrieved from [this commit](https://github.com/fredmaiaarantes/simpletasks/compare/main...mongodb-async-api). + + +

The callAsync limitations

+ +You should never call a method if another method is still running, you need to be sure that only one method is running each time. So, for example: + +```js +// This is ok: + +Meteor.call('removeByID', { id }, (error, result) => { + // do something +}); +Meteor.callAsync('removeByIDAsync', { id }).then(result => { + // do something +}); + +// This is NOT ok: + +Meteor.callAsync('removeByIDAsync', { id }).then(result => { + // do something +}); +Meteor.call('removeByID', { id }, (error, result) => { + // do something +}); + +// instead, do it like this: + +await Meteor.callAsync('removeByIDAsync', { id }); +Meteor.call('removeByID', { id }, (error, result) => { + // do something +}); + +// or this + +Meteor.callAsync('removeByIDAsync', { id }).then(result => { + // do something + Meteor.call('removeByID', { id }, (error, result) => { + // do something + }); +}); +``` + +As `callAsync` returns a promise, it'll be solved in the future. So you need to wait until it finishes before calling another method (async or not). + +> If you wish to understand why this limitation exist, you can read [this comment](https://github.com/meteor/meteor/pull/12196#issue-1386273927) in the PR that created the `callAsync`. + +It's also important to understand what will happen if you call an async method with `Meteor.call`, and vice versa. + +If you can an async method with `Meteor.call` in the client, and you don't have the package `insecure` on your project, an error like this will be thrown: + + + +This error is thrown because when `Meteor.call` execute your async method, the method will return a promise. Then, when your method run in the future, it won't have the [global contexts](https://github.com/meteor/meteor/blob/662eee3bf9635b135e81b672d1415f1ae673053b/packages/meteor/dynamics_browser.js#L24-L33) anymore. Meaning that if you have some MongoDB method, for example, inside your async method, it will run outside a stub. + +It would the equivalent of running something like this directly on the client: `SomeCollection.remove({ _id: id })`. Hence, the error. If you have the `insecure` package on your project, this error won't show up because `insecure` allows your app to run write methods from the client. + +In the server it's fine to call an async method using `Meteor.call()`. + +About `Meteor.callAsync()`, is fine to call it with a sync method either from the client or server. + +

Our recommendation for the future

+ +We recommend that you start to write new methods and publications using async from this version forward, forcing internal APIs to also be async with time. And, of course, also updating your current ones. As soon you start, the better.

Can I update to this version without changing my app?

-Yes and no. - -If you're not using any constructor, like `MongoInternals.RemoteCollectionDriver`, then yes. Your app will run normally. - -But if you're using a construction like that one, then an error will be thrown because these are async now. In this case, you have two options: - - You can wrap the call in an async function, like so: - ```js - async function Connection() { - return await MongoInternals.RemoteCollectionDriver(/.../) - } - ``` - - Use `.await()`, like so: - ```js - const Connection = new MongoInternals.RemoteCollectionDriver(/.../).await(); - ``` - -The last option is easier for a quick fix. Just bear in mind that the function `.await()` is working with Fibers, meaning that it'll be deprecated in the future. - -Also, remember to add the `ecmascript` package, otherwise the async won't work. The `ecmascript` package has the build plugin that compiles await and async to run within fibers. +Yes. You can update to this version without changing your app.

Migrating from a version older than 2.8?

diff --git a/guide/source/images/live-data-error.png b/guide/source/images/live-data-error.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbec0a670fee74732c0810a2dac6fbcc99f81f3 GIT binary patch literal 32315 zcmbTeV{|6ZA3d0v%*3{hCz{xJV%xTDV`4j*iEZ1qIk9b<&*uC4@1C=7ckhets_H)7 zr>d(zb^G3`4wsh|gNMO^0Rsbrmk<|L1OxjH{guvug814#sB#d0?S423N+?5p!5hjr z>?@D$ETZnLWM}H^X5eT7W@c+=V?yU-tt@{d7kvp z@HxCSx(ydeTL%Rof;ufRPtlBvmr^}#RIH{;+n8(kXU$0SkfvE`VG-H7Pj^w2DrKQI zK|V>&3{U`Qi2fA8(iepY!Zy6NDO{mgvUBCCQtm5^qtb+GmPjH`He-9OyREau1Jteorc|nTM&+)Vc#au-o1{ zP8_$CLh$wD+446(&Sch+*N_9f#+`)?2Rt2Md=1x>>Xo=Ca2+$-3c6l+a<-b!aB7^M z$zlvnSF0^ki5$$Ahr@}Au$v2v(9JJ^7q{!9ZMQ=+A;x>)pBxIH6j#j`Weun?AvIrg zeV>Hg(ezBSdsj`Ye}-+?oJs7ytEgK2{JWL(yPaVf?oiDG(%8|IfyGS<84iipJTXaN zGxMFf#{=tlFzqG*VPxH%X;PrC;RT_Jt>|M%YwR~S8%h>u=CO9f>qp%^Kgu0Y_Ncn7 zQ%I)FmFQi1J`fx7bKSTRfNi1vp48&LUICWu?#F-{n~t#=P5Nd>xjik~YzC2k zRDjHT2Hr)u2raTGhez$)zT4{>^N-m!h}Va?y&Jb52}Fb4d1Nbzl6$mDNsK&9nFk+WNdMJb`$X^4O2>S`MDP( z!ia*w%@IIwD9DsBCBHVWGLt1Bf;kk*y}AB_U7$`3AyJFWH*4ZK;N-W@!tTsbK?cIdz~88ueiduXem#ukN6`zKt1t!7_G zBQVZWLUtLv`g(SkF-WB`L+o(IE;R!a;;5dEE!rDgn0|9q46()E029@)EBDQ6Lww^I zoJs^@U94iiXC1&$SnD$TUG+kOl#now^4DShiGs4rL#Y&jafB`0o(-^ujw;k!%kA*| zIchi~w&u7p8_L^;Qi|Y8{|S#VVAzu4#gV$Sje+1;`$)m@vHrPISls#jDQQzUDr$>#?OvVK7PlXPxpA$MrVxpJGiPU=Fdzbssr>SqS z7KaCQa?glH%Ywr!zV@Kk+mu2@D0i0L*j=ZUa{vY_WcOxY7w{jNmWg{qcRF-+j3`xT z2RLpx>!Z=odZ@grYZEV|*Afi(RRG4{p4^Am-J-pJD4~Ea}rhUv$D726fzB`G9wZ=(UGg_I?&Z;OJ zqSyc4DO-gQ(8y{{lxW4|Fax{*fx|NT&ce~!zg7pwW2*u%n{Q^r+FG|5PO|a!=epsw z|0M~mkqY~OI~FFuXSyY!vUl^a1tj}f!SnmBQ*HV_;XzKT-yAQ*vjdNZYj6qBsEE(DG1xAph|7O`_vD>$ znqHUUU$FWx;-$Z^H#M&o+?6v{(*8b1d35JZXa;x*M%0ZY)mJ9c5K|hRPq%NqbZ~9;)EuqWx&}vujYkzhUi4${An%6w`Hc*`%7uZMZ!Kq zl?V^E1d4mE*$+GPgV83V?%`54u6Muz_EaAsnGBsn-LsWTUC=8CEn1v)y8A zn4r+)ieze9E0i%Gibv2sy*cyObddIkK*Zjtj{{DcPHMv`BeI3d!JLl0+ zMPHm^kkD56k7~UTHL>5)|DJ@6&bg6SPK2~&7fG_)!mH|O=HK2(6tmPrHOd9pPLP_r zF;$oqqpVCFcLFbxFaU9}+Tz6vs1B`EKO3;aFvxs1+-KdMzt=tmXh0d2Juf{27%el~ zR(ZWHgz}84LkDoVnSwN+h$k%LeM;vTZ*&~wmNpIlaRbAw>yZSHMVdtEgJvQ-=@F2g zI$C53aDy4LU$mnx9)7F;(L^uPwe~1#KW5S|d%jK5g(42p$j5k5CB9_`(zLDD^S>L~ zlFT~54)7v+6Dd|S(x=M}M$r3BAbHj!k$`Ga-1OXT-VwZ>cNHt#yS9W3H3Ut(Pla#B zQzhARF+ez|_b=$^zuGiG6WyG&`>;iopcFC}TyYN2()wTFdI1DlbJ^m@;x{_YM7 zjPZ6Df=Q_Z!AeKMT}XnPE8?m83NPSV;8 z&vE_Zj;eEWS@*qm0|NLA)rs^n<-tLXokeWHta~@iXvIEic&=YeBT^ zb%5!VI^`#ze23E3{}`8ze?rQfEFGsmjplH@Rn0qX<@eg?4+p#FX2S{Y6_Rnj z>R=Pii6m(I+idL{jiH^AOXIIYsoiOe6t8RqUYTV#pb4|TZg^yR-Q&n%G;ZCfEDbt!ecZX68lwPN5*<`L za!l5}Z>%JsaHMz^i*^7NdLFH79tV|D*!-I0#m)do>3!BAZiK4<`CySGHyfWEWyrb? zD0O1%u(&2f){ReTEf{v2t%ojwUliV_`5_%Y6LXDZp|>O(=`ctXKVy~#5T{4qbxa$< zKBY`h%divmv_qh4rpPtN99KKITW-JeU{FW+wc&1TkA+=okxW2aP;`jIVTfTDeQw;W zet-{Efv3oxAwk+bpGceO0`zwTWB>lKPGo~m;}>l_#cc%T^v&bwo3P7FZk1-FDYuwr zNQOW2=0WLLwP33rq_;7KEP>QOCFnz#-GHE6rS!PJ4OP`FN`!zhpculfPX^&x7oqe-VO;xd2=5-$@v2CC67{HQQz4sx#84OMSw(Yo$-0~*27Si0Mv zqjL0ZISaq{+3b>H85_^%ZI-emuHqb}NAgeG?`{A5uv>_@on;ujOsgQh0t(n}&uM{U zp$jmryjZRFJ=o7=?7$5BQaYQq*o=#W;d}Db9=HDo{jt|exPvz*=i(MBP z*C6g`AXY>@c*|28KDlNcjFo#t+VstRf5$@cO@(D#21$YgX@`&xrBIuHq`?kt8oGEK zUJh&5XQhCe@EExpNf}0ld4za#%RK3io{__+>HHMD!N}RF5tGVRBVlgu`ABg)Yx&{tbGJMu{cPSh4Ji;^Rh8xa_)ApY;Hoc_YGf;?<1Wz4vp=9jIF31345N! zNN*f|*bfb_VgD0b_ANq<1$wZ5bF{bK3S^Mu<;q;}HER7+;f>8p=#c;^bYapnJCFN> zqoid}eNM`J08U{2SZs~mP5T}lIP1nlKNKH@hI-NFxhl3L9h|{YnKO-FDZv*v7_)V6 ztjAz@<3?Olmi2VmA73FR@mj?#7o~c;F28g(wV1~8M@(kvZ+X&L!gtAv-;HoI+sF73 zOfy-rZTYnW&GX+ue$CdThZL7$h~hEGSGgL{D?(X zcw~^J73yS9=y|B#JuC)88o?hbYIUt}vzkt%{$2eLNd=5}V{=jvzMYdjrTo+vYIF`c);Fkpx48 zI@zJpkaoi<0RCZ4e%Es`yVIabu4oC)lmKwSm+7&3j1N?0`sog^L_AniPMH(3S6CF6 z>RsBW{nhtWpX?q?)uR&r6lR-}sN9&|icv6FgiCw##$i3Ov+p!(UD>8?+v{e$K;(sh zeY7t;9bsuNPD8FDv)x&L>auuhbYmyOCJ%4bDP`he%Z!Mz}n=>N}7t={ntS<&SYK~Hag$Ox5Y4~n3Ft$w=JeW(AP=9c(% zRD-rM?K95BXl@Nv<9-M3Qp<+e`dWF@_f4X$oh2R)jEm8tUjm}aBdPl$cKAA07G73P z-Kp`|yZs;5uZH=%otpvcwQGb=d48(P`KP<7CL&IJ^ES z_`%fpq^Pcev9{JxRvxb0jy2@5#{Gj&HW?zcAwg591;igCoypcwukkVXVuJ5d^P)~4UHqriX8N+>n z+kJ!F&=2Cs()Or0vcH%$>E0sp!WyNE6 z*1z%`L+KbH&Z#3CE07$lZAP|sIIw}g;cmERo>dfemBotWY{vbDCeMoS{0Lh({06+f z@!vkR$imI7<*}^m(yr8g+Z^A^hgs=zVWj_9%dGJtIC>Fl+m?ndV`2Tqo0WSCub5Xn zFbj{#=_<62(>GH_Q6nD>0n9tYIb=t@BW58a+vVAFS~r&8xtO*|lb-kuJBUUoK47t5 z1n~wo?=Q4`nJS@m^=cc^=~aV~>_g$ih%LKE!!%F=I6#9pgaTsN zjbsU>cOxX-w5ZUDnH}N;JRC_(ko(P*EaEg&B-Hzd7j(zvWku9y6^oXA>^Pma_jv_)Qi4FM}bkjb*=tbg#p|#^Iq- zL#GR&RV$swpv;$K3S%&%jQ)bzqQLD~_9=uU*?;igKhHZnMdtl`ECp`e4Yzs^(@JhT zzpg~qUdI`q{vgQMnNH7*0NjmXI@5U$1~NK>^JT;pE~W^HWW5AF1*I2&CH3(m^VuwR z#X2yAl{Eb+9BsK5vO?c>fR|n?Q0nciiUmf+U5v@ok}SXSC*FrMqt@e=>;yA!&$pV@ z8db1pTFHjPlGzsslz-xtrG&-F2?>e;q0@Vn2Jrum6RhjteD6y&kIj`6)po_Cqhq~v zVjzYQp4d8N`#WJf1=l&)ouYd$IeOvw?q90o`Rrd6Q$LA))s zs8$sh45atcclYn*%80o87-ui$L%|kyMVpX)NdMfCThQ}Q%QWc2o9k+umjvp9?cmca zF(&1@L7OhrnnP?1;ArL1`&Y%@Tt>Cy>Oxx1CaCSpHABD#_|rzCgL-`*wC`UP@iH^3 zf>M(M%1DBS%L#so$z+)k^|DWjGB_RsJqqw9NgdyAX#QruA$s$rCGQ;k29GGCKYXWk zeJ|2KzK0kq8{~J@ZT0zg6=Fdfn^r~t`s}AusLhSa_AIf9^-e~9vZnDWKr8mvJruY} zeoP=8fR7o%LW-^TZIVbb(vIurnZ7G3`xu&8*+6*F@` zZL!9W&q`Jv_mHAx6PA*oGGbL>0#+ifYH+d)FA^=Z-i@ArImrfEb86n z+{f+$1aVqjCDE<2Ab{e6a>x2#;Fx&0XY+H>-hQTiBL?mmwuTzJ3Z^gMM@$$fhqXcD)gDFKF8*)zt|w=l za%;}N1|qAR@gJt6{n>J^|Is%9F^yWRpZtaBh$nG)A|{;rH%YkTUqbw$)HH3d1_;F~ z?Qd|N<;e-}%y`j2J*&0c#)2Q^gO-GI)~XpXUo=YS$qNr#6pZF|9c_1>+Gb0e6^Fh1 zR77om@rn!v=Aps0VqVkYi~CT9R|KF`ETTC34b&YpGkHGNYW-=mru*SiTwG164jZ=B z5eoOzLvsVRZ$9hsve0$7E%&=ST*-mzc>J3cv_~;2M`7j8@SlO(SwyBwJqv9b%SP$S>PrkIY|}`1Sp89s$zd&-pr?`s=MUbKbNQYuAJI3ozr7=)otBEy ztlz%`w1>onb7`y3BB>`LAWxkiCEwC#UZseoY-Wi92Fr?h#s`$G`Tjs4ZokLT*rKX)zJg|_h?*``fy~D(~ zFiSwsRwNr^d=;D=j$jtijEG(f_M&>7>~ei*(=GSFb?NyAcxL- zS*&YlT7sl>Ln0Ld2ceJxA{G5l+VPXpzU5=|vwJb9;F!xwk{^V+$yfZeHkCxY%6Sgh zkjYgx+?Kh)#pEG=;&kOmI$U?fMgCbYf9kYXR3Nu;y|I!4a+i1GZI~Md+sz1 zt%oDqU3N?0AO@#nwybb$wv2OO{f;!w3-QxZx-1yc6W&RaEYD0&M%DC|86%}Hgwo|Z zOr#klM2y~48nlYuCkB(E6V%yeW$#Y;6x zW3v!tiHbz8r#dgm505c0WZ#fiMVXCe_W*Pl;(PUsh#h&6rr#G+pVICqhM@hu#$>OY`o8NyQFpyi1eC9GD#Y#+sa5evyCKmgXzr zf6HoE8*8>YK@Z)C2WfN@8qcVoHl5Js2T?|EGnzZ6|5nsLLKjcs*0Fe8Sv1brneKuW z?VbB50SU8mPo2Bu?C~v1oGri=e&P-o9|E`mn<+VA-z}IYf1l@_4Sp|=2`ZA6!?A4$ z+OBKlOze&`Hv%lzECM%ok|$*(0@W4n_8+&e?U)`98-(&x0I^L~0pX|w5h2ST_NTai zsyX^d6_h+$TA|_n!l1#s^!MJX>jq(BY~JM&+%w9G%E`2%r<(0LOKE%foOLe6sv(LB z*56`H?^7SVZ%-p1lb^t|jD`YLDr?YXnjo@?K%${;@WsC5mjM|iXX~x;)zrI&OZ=Y7 ztlxk2cCbiI!rEO3aIVD@2qk+r<)C))Ka%r80y!tePMK(b!FUQo*4>tihcs3q=TL=5 zU-^c4u7Y>i(?MI1#HN+~r;C>RN9F6?jQ;hk7`cWzoeZ;xT@iz^-;WU!O{{SxWiDQ} zzrPTiXt9VwaQRVT?g$HYtjwBWq*soLq+k>_`A5|7o%8nXmE~5!(MFw}KEH=8rbdF5 zOUTX-Z$`f8Cl6YKj~9MODYL{wc9?w7_WOWf-_UX=SB#$MaDiDX(#|L+8porLe~jMR zhIUbPUZ*7vHZd&KUyp4h(`?1-?=uvN40fDHgmmB4{4zph3+^Hm9&b$-9bY0bM^ZAv z<2=4JRUDArpPkvL%$9XB~Pd+6)S8}c(Fw5qC9?~R@&&T{!H_a0SeC!e39}`C+JMB z`19P)eXJceAh}J^FOU*@+$Kd(!G`^rq5e^UIp0}p;#INUupmizHxvd!H&-0HHSF?- zgH!XOBn#$7b7G5~4dkMAwx0=IQqX{2kQI)^^Lr$Gbeqcso-C+6AIIfy3&DCA!4ugU zMs)F3HR_9}(#JdoIoXVF|4}hSjdQv&(Ymws(Q+GS_VA_-6*d%#pG1`f+0HWkO*9Tn zy{=bqmCk7vKN<1=a05!V(rj4D^t1e4S9XcvYptrPt3QMA7>6miVb-g!`%)Qen5X+gLsI&go#|leol|UF|}*w>H>u ze+ow-;~S5-dp3r-@oJ9f(>n|_W(PrN@!f3s_Rb*!4Nf99$o?)k|GT^E1{w^nq}L5s ze7hg8dk(TG)}rSkZqon3LW zHQqfv)qAFOi=R_eiZ;5py&^6Q+e8 zJ=@Hbfv5(h&Q4y}W^I3j`p-joQ{TRVo9A3uCuERKG|dK46wmHWhVbn+pvgiVG_&Q; zF1R=T>5P;Yc}<)dN*pk7qN?KREq+GAyu6ah>!$4H^hj`@uGf|>-B4=l#20(=m~DtS z*3r8j!I%ynG5=Sb{?@}D<_%_t&oS}E$(G&rwd-xec`ZyfkZCl2MrWI(sL1!Wk*XTX zeWs=(f!ei~IH5nTV)8&63KO0e=pCKaJ1xH|VInJWf8keCE}j)=OKpn$J#?G;@9T5( z$D7`UmC&>B`Z)dt?((`y0mb;K+_35SaA#pjOVDM)_iooo-nHHku686)&6GQfdYd*> ztByqEisWDV-16(b>dg~H6gq(uGgvBBfWtqN%>wW+TX;#8RJEq&jUO2y(gWqg@PJ!w zqcx5AioS{{Yvms!_dCLld;T?>94eO$mCruNW)$b5T^uo+X+qB_YVn<-Djs4`HkfvK z?UF)zG9=^G{W<^7T!3C=2jlL}z}vPRiqbeS;m;+6&(g?2ePV_hkF^xUA~x;cSMTY} zu%4TXwB4Xc?WdzfQqmCie*PK96a03RzYDBsu5#}B(6L<}b!pK#++D$q+%btNy8(_f z7wYh=vQJjhFVhJFugy)@ViHuI5?YIK>n<>I8^0i>$*i^IJonLt9^(wS+tUxk zi0V4SrCo>zD+90|m+eWYxfgdE0tg;O8zQ*ic3AYSfuXD}m0lQ5C>ofcTuJm_Xa(ydO za({AB89OC8UeY11cB-$3kbqT18Ouq6*(&Khf2s|`r~etb*puW;?@hQrt1mN&Y0 zd{8$n?NQ6kmOHW-t@8Xme_}J}07Jm{+PfQd{rO_~;K~b4_uvy(5bT zB#Sz1-bqSpx+^Q9y0zlq_0a0n+!m_Ef{|h1g~-cJs_|uX(S|tuDIWP$-B?3cb3z2A z>x_drR3+H-=cl;}HMRWQ80nAZF>l)LIEm3I8XDSLM$ay?>PVtAK;w7bE6hRE2 z4hgiPzncvl<5NB~rrq~hVZ2r-eHnCym@=54=cDG`02vmk7ka-dFT*cCR?>7*0T<1T zJu(hYVX@+|R8$4bY!zf7N( z2z5twO6g(M2r8PGNG1pLgMlKn*C5$*o%=TRI(Ds;FKep+*`kc5?r$r!*g&{+CjeD2 zW$FU$uBeN$EPyq06=2#`tNNa#L@{tMZ`cpa#ssN53Zuc!)e=np>3f2-l{9i@X}e**YGNRPjqBs=4OE74#bxhrDh1!m>=; zX7MXzk~!6ZaHevk!(|JS(mfUhb?lD=cGAI7O12er@Jf-s7ND#$vG_7$90!M_XIATqk?DrFN!(KfyZuFkAkbO0_8FNH(}R3RC!vJ#5^9L z+6MS`jlN~_j8zcqy$u$+)clP{2>K>r z#|Qq55{KkVvl9~r2H;CEsQf@)7{;}dfc~|q%b_d7SqmM^HwKS}TPbfasENSwmV54L z@=295-MKzH3oq8g*&-EfRQ@GOB!+=%hA&8Ts&tOgcZ0mVfa?|pB2qY|`9nhcRpJ9K zr^4E3Ssd=>HL97K)_g&)Ht4qHOFLV`eBZb2-vFzwKLJJ87beQmx?;yzZz~U#?|Iia z=Ac)WsuZbOM`DBdeETGS?_+?r#DFrkYpN=ElA-~GF+mGlD+$t4!L1&uIvag4;97-C zR&kf;uE)ikB_>(yfvu9wd?=%$&*W6lo9jEkybug>Y1M2}D^X>+&udRPe})~~mU(ST zw6?S*11~^IIMeARO2Sx9J@oy<<@vdkft*Hu-FEsZqJTrX&U`hf1hDzeDnbokh>e5C z9ilvZa<7T!rWtwM<%wgXml0{VagrECCaII$!otyZ<$pF}(6Wx_FnLIPHVNLp{ycp* zJ21iNIB8`%HaDKqx;0cNh0b2o^a{%OgZnHOK5QP9_ra_3vlQyg#4m$H(_XPldy??v z7r-jROUsb9my!r+*pv!{o>1sOMpRaHEXn*2!oNcsp1|MtAvT?E>8qT0yZ5!fBf5-r z6Rhby60P}Wu-LITr*fO*XV%5J&^7ws88m9Bb0yAZc?aZ4a?Fmk(cE30)K(_JS8YzQ zriOn}&DHcww-VfXVHdZIs+Byh@*a#yRc5G0taK45e|*4ocg#NMMvwsDY7$UrBbf?qd z00CT`K+xv=??UsK{AV*io z2;Czpqbj~ia~)Ur1$jt#)c|dF69y^Ey9;-iJQfEDIXs? zdoH`5A^~S*h#x1b78Yv9jYA*~5cXP$yw)^ZIh#GTepc>okTQocd46&ZjW{pbo0vCB zYM(1AYo0YrEPcv5ipJwsuOdIoW5*A~tJRfPI^X&W2FY9eiwl)uIW@saO+nULId}T< zbkhbf?S=~@B2l6D`g0BS%0v_`QwZ)06;;WEmMi%^%A=rGoq? zV<%SHEkKMzsI^T5oep;ZPwp~;S6N%$eyCoteHEf=)vzZvBF~VTM-XSxl)9wO;T#^@mQY>8S_pVz=|nX1OFBOiN($w>}!sF`GNbk;2@ouUJy|;1Ae_QpA*r4$n*S2?I$)#yn z`rK6-76+8A%Px<>f|@9pl%|a;z^pM~m{bau@aC#cuYIh__Wtp^A!jCHezJDk+q{@Z zbY9!3t&sdd&+9yo>84kUonQE=7D=E(^&5I&mdr7u!&1&AV`Feku8X*~BsEp7jCg!k z?zh=NgOHaWUZv$Pv{%$~Ki2>8B%-U>A51XkPsv!YR85}9=T}h2qhoaBIa*}jruY?L zv|Qg2F2FAw*Q++&xsC6{%D#=kXS}lgQNjQQe3(0kjEB@%>x$=kknFV0S-Nyz-w>!T z;DBjTB?Eq)7hMPW*y+5S@S=TcwoXswMN9myyxppE*x83BN6hm|RRXmD<}iTr|9AL5+>9Mq=m;&mGil%Nl^r zUIfr8MQ!usPHf*ldKk6Cg@%ZKWwI${)do&*wmYQV$gQ@eF;-{{3>r~a3=XRMZS}rT zjRSBw{xsoRiRKI@<@J`v)EU&b3~^a>)brWNjV`3d*-on-^N^gRG6TW6LsyFh!$I z`$E&VOsDwh-#c>~>j`_aW@rz9)dcq^c*B1(UV)uj&o52Z?qDQ|qA;!_@#oZpx38{^&DQ#^J`ZVKTC>vIeaa z=B>|=rt{@2JHn3ayEmLkPh8_f2UH?Wl0VVl`;xEb>(!qm5m;kHVL&!j@kLhH=nX|* z1iI~rI5W||HkICMyT_AwaChjWAs74ULAb0wUOpGfi&HRefA4d5c`9DeqFr9*mWnc*UC9NDQbI;JPg&^$tf?f5@(8Jw zwaPg#ahp;!eq||<2a()XiG9|?6Dnu$j|bGG*LX_~>-?Bj_}PewvyYeI1v8noMT)Xy zM!Hvw5PVf*Oc4bhu@Gi(HF>m6v7^1;Tyzrejj{bwvW`p>+#D14m!Wv~7?`ZaZ-t$t zf`jdbpvXY{%(m*e0YM<^o!3xz?1ih+!Yr$>YKC+Bovdk5Q{hJm_WGJ81 z={)xe%O!<#Tr-phNMmBIFI1hac9@R2fyNBP^0rc0|-yb{Y|)aoyg5eCP?% z=}|9LnIURsKe?e3gF83)v}9*l>-~a`lkg%_H9 z&CegkJ(8BOG_b&u(gBs>d~0MUrd}Nc+TaIpOqVJGJt4S+1ir8NQIT6ZVvh31mBdO* zY)_|I;dpc?XzOu{UoBN#ZKmsr(TSyinmO@`xDi249`$fPz=BEd&-Q1mNptlmL%W0g z!J5y-1A>Bt(H9#oXtkeU+Z@I4GCNNmw%vz@S!yCh1qFB8Mrq2n!^$%Dj>b6isj9O) zOY$1w+Mc;*suRJ%lMsHmfBrngiHrIH{U5?DCsgg7OS(66I=)vNyqkz3mHj`5pu-_v ztfKz1pr&tzmCk6py;TSOhXK6=tu^rxx$W}b&FXwQ50uX9q9~NKW+q$eQ)(inN;f)3 z&FZHnptpA&n?&Mu&t>t|N8~fvl&T#9-qH3h{g=?}8s|aT1glIE#gS>yXAr)b*0GIp zhS_igJ%@9xNx?uwt<5s|&wQu7H- zHlxrsU-vV%5WNtKxQ@1_f$Wx54c+dI3C7$-*2WMw_V3u*@|GH&wm zfNLBKsH9DAq(RM5LF&)KkEvrt!`m7v+6%^#!AK(2(^rm$t1CR$KY_-4ZNp3`oj0;Z zANVi|K~?bBE#N%*QTv7}PTxS}gh=AKY=3z|RPFqhjK4)0XOXb8!HQN4V}3ch@wJ`^ z1%+Q=bf&3-tu~<~Utva35V4-)@Q+>Ra=a^9$S%>vi!OfLkEH)sQ@8-cNs?k#|E>_At9zdYiaf+VH8A0lY={`uHDJ z$2~t(-ETmamjO(*Y$Gld*yabpK_Mmqd65n;zBPB7V8$zO%qfwQVS+MUX!!1YiJr24 zZ;kBu17O$j_-c zAr_Ub&OfC%8!CsKdogfl57(n-D_9Ik?WkblU)PL+h~47amYJo7 zz)bn^&jXDY0%<`Pq4GeL~RZ z+8*r9@M-+`&cDOhl`64%om44@-WpJYqYlHt>PG3n-I#K(_NyJ&uEtII(P%pg51~SK z_qv4)p}hs={gC66Z?rm?S=B%9Az80Zm90csg5_`fwo;|wQH0e4h$4ytc5r1)LstlH z##M`Z!erfD!u&!WKd2)41Bk27qF$pDWwtREMB|_-pkXRkEpsWEUWSYQtL1YLfja)b zAeG$O81;jl5)DB9M5`1BasY0F03;ik{qqLdy-H{PM67MB zyO1tdc>3HZcRBghk#WqlT)|A?m>llDE`L$PUN*VCr50B(>tPGJzkkEkc@ULAU}@VD zYeGABxRM@aq8D-vx`VA&!pCOW-cLdAYsYW%Q`wvy#NkTZ%#7cRDQm2533i(c6Vt7R zRBgc_H8Q8J@v?bY?R@z-TK7)W!?>0pmS@VCsLZEYQz}>U;hN%oxu$KE(TWmX zYq`LoW1$K;Zm>qV-dNN55LGk@4u|^Mp2v4>-Bwd|d`QTy-j#&d_4#FM$;;BTSxS9XC|98F>QdVZ6|+&oYO=Mj+= z%f68^!=*2^8D*P0B?tfR)n!LN!cI_Sl>yrX0u$Eg!B@pj@5$2F(c4%3LdW~N5#^s8 zl$c}&b8B&-&N)YYSX@hZA|;wF$4>Axx5l{x_j0E5Q`71PJHg$4S{-&)Q_+q|PrSc9 zj}R+f*S`hVW7>2WD@XeQxPUzzdjhV0^&#p?oJ_?{KOWEJ#1jp^qpzH6bmJytPf);~ zzeF9pnLLXD$CfwMu#?SEdrLxP1_(aW4%%s#@wBfIqKw_m_%Q}@xjmGGiuq*fbNhf19e2I$D-E1SnaAjMHhC5dR_tzhx|3H}qQsQ5D zcZrV*!YIy2S}4shKT~{jw4|n|WwGB{72^OYvGB@f86QFfZcI)lD%3+>_tWPqWMcJo z)I>_BJEgq?fj9pfsr(FV=`J9C_et*_UGD57{!B5hYVhL-A%=aOoBElP@*E00ETInC z+}l|nlD=m{^t5viC;lLQ%8Y%nBzN{D{xLsg?09F~kp6*~m30|P$LZhc6zj;=3nhSm z{J7c=hkao)4W@*$1u){=Vo&hBwETRo{q0x{6+(b3(mdA1d-D5pU+E)0*MPFXQU0mUX#+V?KPh$J6?x#kzvphPuBygx^WSt2xGh_?vt! zY!#xbc@y{lJUGGwj5Eim3{MkWF~1~ao)6#4ngS=sNrJ9;ue%okT-2A8gw-bu)s7-W+!uqFF-U9576CRE53vra(0H5sf*O2rb@;{usx+c?H)V5I4PwTZl6k@}eF7h7~?UE9K4i7!)Pi|Y;> z*p>R$Z{*Ju)`-L*mT#ZUK-tgDIB|`Kml{6D4w0R7{|itc8{0}-O8QhKU|ReJdsMCx zsXS5us{!3jd*Y}jl3;P!zcFs4j}^e{PcY#{XkpRg00%ga=ZFLSHMgYt)EV1eqo|Ba z_)#XlPWmin(5Uf<&W@U`%A?5uWuW;Ie-)hf6<+Xbc@{!|5W}1C7q}gT5XBSa!P`(=pbUJ!Tip=MRKVwY+R;wA>MP$0Pk!2-xc1>O*p+M z`+>ugW5{1ia@CS<2f70sz$Y5<@Z-jN4XKNVD|X1f4d4&&&P90kjoa{IvysG#fo`}3 z-rgLVjj8_Y>f-Mb>zbkgYPj0?Jp=W;tx7#Ze4*zDi<@YVh+U)`R zjw=zTkx%)7vVO6p2>s`O?|R8(Wn()rzeKZ_@v^Th*^NB{rV6L=MdZ=-U-l-DoU0sJ zX5jX2=og!t^8F&t{(?Nmx8maU{o>vr1H++8iUn7fZm^&Svd8zY8HB5#gw4Alx;Dxi ziCNI_=Etyz#$hDL51M_dCdx7PHvwtlaVNA1kn$z zY3c4}`(~!qPiKjD$!F{8Y1|=$+FJL>6{QYRFE4U4|Bhi4|Q5C6|zyA}pBVDI| zrOnQwq|9LWteB&09_7SMXa)0!08$VxQ;jA{T=-^E5VK3bFTt1iS0~m+qUhzJ?Ff0?tAZ^ zyVm=!R`2d!ySl%w?}x9dmc_!{koEhE^*xCb-uEp!Z>E&eW}Emp-tBO{MATVy&>df7 zFJuk_*enzmyRH&h@9v{gnts$q#QTP+uC8?bxMZRO{>>(Tk5K)X`bep8;d8~3_unZa zu&GKhp<5NoELMGwnT@za!8x^NN-9}BL*2qB4_u00ynHuHTMg}YH5?WL8Z1_1Y6=GA z5-fmS*EZW)SGFGCD>S>lFJ@zv#u1-L?YYA=x_Shwj^#LfJmUyP?f{YZvnbMw4cmD5 zWO@fJ0X#pxUE5-{u?7vqM3nL}Y=6EnJnh$lBSqE3Au zl7FX2=ofX&cK_n{`a1+8Tha``6$8UCW(MXKDZo&4lShcUFhMT-#b7Yy&D#kPg7JMy zI8Va=rk{S}!;>jZ{~b<&ulU#NlsY2Kw(QSZC#Na|D(1K+y1FndP)eY3mev1pZf12# z)^1FadNV>I8sr8bn^4fWokh6ky%*vZKx#*);DGRiTzW{E=1UP1nc@vuY0|gEw%|&T zU#_!UlK+m4mgH||%uPZu>R^H|LAW~7#HLnHImi`v3ZqO*;>>b|1mZCD^hbK^l*OV< zY4>_dIB-*YwA#NBa3)pgaDw`=yyw4OysZiwboL`~CZ5~|G zaO6%`@OUK_m43h-Amqs{VGQrlRh0h`Mcdi%XUm)!`%_1Ca$3;zk0vq4Q5pV02m2C){-bF}Y*c4;&H>EL+aL@Is_~l(;)>D*L;_)qNVB%S| ztopohr|Fe*`hfbIeuTM$UoC(S5qx0>q6cpDXC))EnO(87W2Q0tZ-t_nxFKCLn#Y3* z(Um0GXFKi92O6>MPjOfrxmR}w8fIeXz^ixc?{qMS5mYZ@0e22yELqW_a?s@j75A6Wr916W4oPSK{OV(NQLz!&0aF1hlHL_edSaCd zBxE*rd~!UP#7NkV?d!lA#6o}4VZ1%0BbQIxxsI_JJy@g5NLlStfwpQlBW?M>PVt|d zj{rxFE%cZ;i~dHbAv3F88I%5KGKHMg6ZFMv4fIiy2CI=jwiWCb;nA@Nq74tTgSLUe z9w|1+^RbC4Qz|Rx0Wl$?0b$3lYZfpE@DO_KpNrb|DjFY`9>J~!orOn7^_?bpYd5`}G+)!X%@|Hyz_#7*%v zCZR4vS!SkBQRQ`R_=^0(tS{_%=K&76%|8%+6XiajJYXn3m|ah7{EnXNIAi5LmaGtu z;C~pq#L!5S1|AK*0C`#w8J(PRQfZP7FX-s$NwzQWOU%bx{2*$G-&Xb!% zCfSgYMukjtwR()o{qG(tiEP^EMEWPTe(ZP{P~%)-Sne)>6=~XV@B|#Vr(sDp@J$X>D97I8%4!bnF7|VU_bPS}f=$qZ`;@ul1 zTwODvQ_C*kNG?DGP9u4R9XNoGLl}K%E(6AcLgBe{I&aD6-pUVO`As*GX5N7f1@_+j z+#lY&yr#*2(cxY$3xv)1ASnN(Ip_z2s?z0!%-NWKWw}nhk~R`aN5xz=C}6GxD{uhn z*PZMp)~wQMGVc@sN^Y)faKn7f*k9RWp9Lus;}N@bt=k<>VZY7TRiSb4Jfs$AWI*6?#j*M#9C=o?E3c_?xYBGHa{ zr$Z-%u{g}3f+nY5PD*bVlkzGRmXmL5+VQN)(`-w+yxHY*N&|9LJA$f7x-K~Oxfza| znW4MOZO+?d+o*C65Wzvo7*>shYN;oHkgqP&=}T_@n9|0ZvTk0uC164`7%0pXLgD;p z=HWTA3HvoNEy7u3XwiyEUM}}QG~U{}?U9Ud*Da#)bU+yM4^?=(dtGqcq5o}DgP)yGCzT1(rtqUUIej?NYRS>w;^=2 z0D{|0`4_YG2oeYW;}CKy*}ocW20tJQH{^{gc2_Wg4q-Ia-xI;-LGYU`4jqheT7ta} zvb+*%IG%AMSE4R&KHzNaKdxcT)uohIT_&3@Q<^eo106NVUq7{e)O{7VedG9`I)>D@ zr~P}Ib*!V9o)5k74)pf&p7?&sR-2 z>2Z_KK@1#x6un$f*{5@c6lTr9+kU)>`XBru zes8P+N_v9%nFIWL)q}>={|SOgJ5WY69qy5ghl$cNI~K`{SXeRN5pE>&gKqIclh6B8G(>#pN&F&R6zb6a^RCj$1eN;5#_ZB$yt?X!N;r<)g+D6?7I(J?c zuq^ESJxg{)vUX!*-^{fU(j-t*?%O1qA9IQ8msT+ds&WQkHUzS^V39%ESv~HzEoWz$ ztef7#`0UfR1HKpW7fa|SOlFC_woF;N7Z%~1&olHjuKcziZ-+l83a|VKcajY|&YJ_= zRc@Ae0APfKo(UwMd?7DQh&LBCo#e8AvwbF{j!YmL!9mp`dd4mHhE`LPK3?!=1oXxl08< z-i9#a#ScqEPP^SCv$tn5*Ml~cUA0otf5ma`np7r?MBLFw4QHU~8)sb{5%!fCT8PqkM0nc5-xF17_=^1kcObSo(8#IT5vC?Kx%lSC0@xihsg zPh|KyN%6I2Z6H+^oU4nT{89r_+$XiWw#&6oX&Cm70i?@d>+_F>!ga&2jV`M`>bp zDPHL_r$Z4YIexH3@TJ>!5RRBbI3cIvbq9WzyPxZtWJl0%8d^>Tc*tX%0x7w3DfkDCr~Fh52J6;Dfg!WJ}RK^M3hKu=3S$Z?qI74AJE4$gfnOyubdktl3c}S3h)6h zfjQ&euoP0qmwk68RY)dsc95vLKx5DDC6Kp{ZniPuD0fk0P?FzxW0xZSAdJEaeLo(EZc@)%eLT|23BWZ!T@b`YsfxEH`b>pL--7 z+7g^+nypv>YcNvaU<3%6QuauBUp?nUhp8%&EaJypR&P^dd1&e~NNPIegDHLp)G#6C z&pfavXu0~D2UknEVn<|-`&5NsDEqTv(0G7e&a5)7|05FUtHQ^1L4HE*yztW1-znlb zvXnocMDRL2wEC_iGf`#4I_F_MXNAqKj&q{3ZPX~q`VkZO!Mr7tF>cTCM}Jahx{E`g z;N<4=xg4ebg;p7*uTL)`;HHQ$YSj$+aXxMJAa~wdMr}DvZ>JfsAMb;y?S)PxAN$L{ z(=Rq|*=L}ApU)VR_-|nZ5O2tD^z3`8Y?u@LJWqWy7|<9g%D&=WK*TIwS24sK+mHN> z@l^x=PDfz80GpSq{;x0ve*pZ152 ziIt<}oNN@SkUw1W4-H8FoxYGk!{`o6bb2&+Q|9FYI*sy8U&gTHppEbM>N-u!=pC0( ze5cR{1PhhI^LMDnlNP{7b1>+^zM+Ak~*;CgkMS5a?f7j>mft7#+Y5amY{Vd zy+Y`uyPdn+HRb}|8(aaq<(FRD_)@!f)GRmhEKEg@(|dG#cL$9c{v%58`q@$@VTn@c zP%xdV{?Er1?|#}vK=6hD48k7F>?<<|?$7t9OHbz;b|>YgYqu-z4Fx*Kbu@I`V!h>w z*Vt1JwkNfXXHSg5(Uo!?MCv9$pPzWe?)=wN4;Jdo%*?bf+%DGY+guGP-0N?aFRUN8 z%fctCN%NQEJ6*+v*Jf7{?Km&gmjZTMF1D{BheO^sj|E?3I=EpbA#+50{S{Yn~Dhg$^0y6#bUaWZ8`LNFYf_+6CihP$!oVy!cR+>x4kbf?2&7r=9m4EorP?!1<#PFbi^y{5RL$30sLiWMWM&#ar zG@o+YquAH1(14I9d0%B0Uw$p3CbCC%Bi^saqfx~djRHaKu%zTde|=IeTXxDB20kh8 z%8%rW+~G00Oq~T~lz3Kp_mm__V}`uXLE&)*fEV>qE!(+jUfSgWHl*IZ9L>qj7xp0x z_vi0g=2A}7&$iVh%|;b&0sO2DnfiI@Ju8K>Y{`DM+eYxy7xX0xQ)JTy6yp^c#lUEx z^FiL4#5#NbO;8#pb(UCd4CVp`xi@J7^a%E;)}`)_`9bUo7Q?FJl#{mYLea4=N|E&S zD8>_guSe1&jVU~h0!BttWgU?Wtjyfh5hHTv0V$E2gwd$FDIo`G>JX@jQo~DN3n-fN zeH~nImYUgPEhHf9RvLU_QTEiRWfh|QP!HgX*z{f-ZIK<7aor4z`pHJcKvybX4XLv;kO#K;P))-`>Y36BNZ#_K_1Q2n=@^a zO8{3aQ1{1yFQ%O%9a|eE1$ixYZbTrx#6A}L5dbzFGjKh`vR#5R>G)PBKd(R)h|Wd! zMt7e3ks0d${mtyiFDSU6ux0$RQEzxj9mlW^RH+2n%5>b`A{p98U#Egl#GahN)e|60-_}lmS6qZADgS zMcP5jT27sYRVZ$@R2hHe;JQB9h`a$~Bi^)b{=MrXJsY-bct7D7Tq{ z+?(6dAy9wv@UIa@mL~}sMh8r9b|9(5OkQa+;==e?67#Ewym)MvU8lwNO$j<=19d>s=cGL`U?w31FKTLplX5Ej z{T^dx(eht7azRZ+ACA|BP$fB=#9W2AWjJ`x%M$CJaQJOE4}_E|sJVe@t_Z`)W7!Q+ z24&+GC8cY6KF`7MmBPfHI`_e*n0-z0i@VRX**xKdg7BD{t^3xBq z>hi)DdK&QH6i&ZyBHoaJu=VPVjWsgQe69 zmVzO=nvx7b7YT~3y=*9J5n~>dOJM%w^GPe(uxgiCe8&r@@oAZX*mk7c8p+o3QOg1A z7*}7=((P|@12;DkF17fanhv@P0!M?RdjL_@n5hssheGH$tJ47fyr6xx%!Pz>1nmT( zNK%@6j{>i2Z-P=OR9=BI8J(2IZr7>wG|u7RqE4L^Y)`V8_9O-bSFMm{zoshw8|byb zs=IL}lR%Ug}Iw #31E)MGNqa-0QxgJLHbW;(}pv;+t9N^%Y!B!;~ys%_0P>oiFzqomm=ww;B@Ge~E!jppXIA z5bwlUMBOdsR7DgjrJ31b*^q)!W`7_rw5maEf>p`M9PCuS_3DM6FwxlgGRLj=Bu|oquO}kKA+4~eRoW@t=F-HuD!O^H#@o* zZxQa~GR$EIAZvuk{Su3v5cTMPEx99R#Z0##hIP*=oRb|rdOAgF<~7f&f!yn?U-&Cl z1O+UvJMbxkd=>Op>3Ih4$NPwi)neGP;rGTck9|~vNlZjc!1%I8rh!)Q zeMtM}YfCyDm(NIaofv2V4RIXA*6!#Kr9eR~tksGHt8{0imt;r;IsE8DY#VLFZf0wr z#Q5~C90ElXy3>Hkwy+S4SUN%m_vZ73uQd>R@0}GY$#2tJqds!5Ir_yXRuAUzzYGi3b+-W-T5*thhn;Qkr?Ql)vs<_Ny4(0UGs0FP^F`ee2JOf z%*}Hjib*?HnMiD>vo)(amKOY0IowT`m$XM8DYsLQrb^FVdaGR3(+O>vt;&l`4J!Do z0OccYsKV5x8+)Phsdbu^DVNk&hPIEqO_^@n6G(E^j!8xs);biWeY?WiZb&vr>P?=X zhxxZx5NXacrE9v9Dp65M&Yr3jQpHS+;lv&FGk;$Oth=HN*b~El!}?c{S2H=K0%MB| zXfxlda$sRhdRC|+)j&(GXLNS#(;pn4@&Z|9&E{qxcE~|RneIJVmeUch28W-Y-bkUN zI9S1mm`;|L`;vTwKlFi%6X#QS7$KOVSI(02{px;`L*c@W82*%F^02;&USKg3Ef#G; z(z$Cx7tnH6%(nGE#)j{>fmbJ)3=f^xWTm_wa<3u+r2n{?pM83fqWJ-qhy9=xQob)1 z{>{Uj+dR2y&)(OJ-r5D|w}n5_68)7^%PIYQaRx|flG(UxD8uOHUGniL(+X*0D7Vr^=iM`3Y!jDM)#sLD zx01)_u*pzG$rdgnUKl#UYj>b`Z9I6hMj7AEI&!C3)T~-W!WJsy2X0PG!r)XwDL5IX)5v{4Dh5tBA3&NXV)M9AXuAJUrGO788}7vm2|pzdKv@-zx3}O0slVu zUyaQQl2SYv{jhV4j@%NqS1MQn+5BU1_Z%!rgs}XbqY5b?O6@@kgGT9;h+-IYG;t-a zXpvzW(0|Rb2Jl5!@d{IocVwc9uP?9NNkP!Xm|xMY<_DJrDM~xzX><|-iC>onka$&( zE!E0-K(HH5Vjz{B;^4=8u9aL+6#k`wb|c+zC^&G-*l*ve$bE$`;*Z;bO-rRV&JF0l zespc-Bgx{m)m3@>?JaF|azleQ&!nTrbz3`XOSFc>@7?j?%UW~qLHHk0`Z!C^Q{@Fw z95FKmwCTwQP3Lx@5FHT^sf$q~`SLs2XIn5w=Ql;r2JMf&ERJzt&9h4OY(D*n`DEU^ zjv|Nzn|ZWM8rx11EN<{J>xXrdkV3F8-Mr6);5JXrtCyhDDY zPkNp-E+$b?nhZ~8%r*{pFE460+C$U4 z1_%xxW}!(k7fWXQiHV)`YLC5^Ymdy)9c}v_B>sgTbL`DCA}qehGrr(|U}LrZ%+Sf$%UF#|-7Gs8;Qk2@Gf zq4B(Vq3?PUPZEiO@zb5jgR57G-2Twxz>%I7#vzkPYCJ{kT2w&wPp}+$IQ{n;k-_p* zaLhQ(+UiYBm+X|Sw9Hr_=y%aG>P<54HnyVH>*Nj*r|U~ICbs}U8|0gXg5J-PzYVq# z>=48sGX1t49mvFHp zWrx$H4MnjupgQE3Wb775R>u01OkFDcMxHUR!aC*%d5Cz-eX+S>7Awe{q?V>_{!?=BM9hj)0ejW z8@1yT%2=IP2h{zSu3J_H)O7`6iS2CR8%l%m^cpjJ3(RNu&le)%r#d>#YiX!z2hXX4y z-?UPlJMIjVD{EY<0>y@=l9=_jR&;Q&>)tGo1z6bD<_>Z5u8by;Lm)-9zTyKj$Xh0E zvrrFPNX|7VDc>x|fXmz!DH5v}X9qjnE+*`UjM?p0FY`o~S&g}~gh2g&#@D@}Ik7n( zzY!>MoV02(p#ZEFov*TBQ(gv`_iScbX5Pwh{@?|)Hc4o2;4YHBTeM9ei;%DczM%CO zX_mwfWr9ND=QOwZY*5%u1+O5z#!j$?*#-Va`P+pM@|D8|Pn~EQ5mE-cLViCfpdBtW z!;gnmtq#q17DuISu4bK{OQIfNgtZu!E&A^j-EL=@I2lt7W>+=71aSF*`N2>h+qHJPczHh)JnHj)CzHRkCpx~;9#5oTMh_8&oxy)T8f`T=_n)XPX zI53@F67bCClY+Bd_2OeuY@Y2;Cwu4T$7j&v4S{nl*^qf(Z2$#B3*UhwmS%RRU z1w-&(7Q5YQ;Id>fvhGX9z#;uF@a&cee)dmyALL)qI1%YE z3!Z-2{=x&8Km=6(M{N4v#AVQxi;p-QhHHLI# zjn&(dyEyCy8+Mi?H~rNM_0?{%Lk{`oWvPNhUSN3(t2H-*c)|`gyNHZsV?fg2!FG)S zjj^@5lUq`B9h=qK;H1eO)UD3xMn~Y4E#ioV!tNEK~1RbZI3}N>b(k+$#)|TbS4SRrW2@cETza5*T*)mcRMH||8L@DTN z9bd{5n?%9f-dU8H(h+RGH2Q!Y!K^&+nJaIq)5Z`D483QJkQoUv+} zF#=cd;2x3+I;apsl>K(rOFfdE9l|s`Vj6`wSxN;l30Ckuu#vXFMH5f#)O4*^L~%5) z?XL^XV^jvL8U$jXR@Xz9f0j&MWugfE1I|IO|Nk|>uI|0{ua9rK0Gq?eka$q@F;-lS z{WGs+K6BO7fh6TOb^s^|WiFIX6m9)YxQnI!Vd5;bAPIvNn+u#6|GuECui%M)wwlVR^xLMfY&M2V3NBmD0_-QtHh3`V zBpmx`NwBDvu&_Pa2MCTa%r1+u~rV3cTE@THT zT$Bd{URhatp@T}bE!Cs-+hr8YDJj_@2W1)!TpSAd1{)929CjPmHaaQA{db0+aP4(I z+>d`dI;eEB?T*^LUQ)Cvmv7*>kZ8G9_0W-UYS%2c*)8gfZJZ5#yuDo{)&`*SeYs>b z(XTn8&c=jR*C!7eNr#r;Z6Vf?77S7Xq>^)&S{ngkA&8~cc3-+Vja4z|n$o@0m&M@n*HWkxer zq?FLNw+>T;rHX+qbTP&uLlQTDpEk^BN+bvICRS4zFua?MtBM%7tId@G7N|>ndyXkk zZm=R3dr^c9{6=}{^22_;E`cO=Sch}5E2DnzqR`O=rL{$i-jwi6YqvVR`D%I zRG3OydeC>Dcg14q2~DE60XQRoae$$))#n1TU-rB#wetK;n5z|lDVN#g%@>#RF7aHPJAh+4x#&P?@9euKzCRAmaEBDc(; zsY1&QHk#aUqxSb*mp!FxX>|T_^KY(*^5>8zPA*UM*`d-E1I|LQy+Z|+bWbXBkzy>| zAns8(98w6X+q9P=wpv-CpT>w~9N3i=u@7z^ok;s_lrlfvbY*;V@qBnQLmF+^ZKd`k zJy_AX*w4!Im_kC0WF_-!7Bji4fi!yL4IyY^ako-^mx&2b1)>R{=|SFGM*rL5V(n?+;n-Iv9quNp0E6#|l@6Tx^eNc|-W2g0w7 zd`YLcW23fn^;}<#Jt&_=>UG<2&)O(q!t?WH+nMNiP^dge@fRa`IlQ8{EuM70N!;5$ z<^jE%OFQjKEXoMjeohX2b4%{xyIibZ63THqI4o~UKOdz>7Yl(&`A1+BE5`W*rp3>% z-K+4uwCrBpJ%6njuhbq{%07XSX~y+8+~$w`EcV8FRaxwJZMkQ@PrQd1j51n#g8@Oa z73gVtum@$tm3MxbRq=SUV9fs@Mw6o3u69E}Igz&JYTbqIbbof7+?|GZ!gVp#^ttDv zp5U13qRKD<)?ki0bl}|oRt}-}UpVxN4nwKuK`M7MN zO1~3;+o_l{1HAAmxHL17R&Io;pMahdskjQWz&Oe{&A+Tlx#DJn?AF1|?iY9gvIKij&gxc3!aJ5 zgs_(0s-T?QYOU1ggog^YF{Z9Iq*kql(+wHR3ci_9nOr1+eGcH6UcPK?i-a*Tv@2WsLHFgew(_UM%xXkHOKPU-T>-#cr(QB{x# zc|DI%B*q76KmCuw#V4=%t6M^vO+8sV%<#Jbf3*jQ6(UwR>X(T|uI zg~jF^Gp8*^iY6y~&1?Xx(sK(l>d~|4Wnph!O}7V@FpqrX3Y^ zPLUZb_btxYa#Jfkt8zTSn8T_;=7m`{?rAl<`|@3f?5@NxGE>oX<|})9)}un5O$R+Y z)Jd{b(PV&pcVGDhR2oEx9Ggm4@jIw}zTKn{uQ^%Hj#;G=RD}~SiCcQ}qSbyD$K4VJ zgX<<8vreb%Eyv~#pY$jn zT%Hnwt6bV1A+}&QgvRk5PNm_VkI<7u>&GQ|%XN1bpXidpMUP)z^jD^UDKG4{eH4(x zPVR`o7~W<-P11LI*ahuq<%3|;S8bdMC)*rFQ3`}P%P_M^gT-W8+C+cxlNab^X~5Q)YG3Qc z1tvqTiBsfy%gVgZv%OY(9%0nqFIx&>}|s< zVxzePyONAuvQEeHR)`LN@4DY>Z6~bl*ASJ44>_k$ye+WdYUm-GHCW9k$fQ`;8}bIA zb^^%>GwRFxo>sf6B~B_>+M{VyB&Gx$x(MV94n^5=|FXwFbd0c`MuY@L|Bs!6&k_Gm z28i4)7{im3)(~ij1MYh6nP?I+q2R!_9uvJIQHyO}MaTTldhF_?*m#p)CVR#4O8aIr zTOj`=pVM;6KeS>#^HLY({d`D9W~nv{DI8KiR?_`D!qn#^5m8x;kE}>IY zW%v(2M&)E#+oL{rZ>OFfuAB4YVN$dt@1csUlh>!7+oCSA28Rao6fHrbvbJw`E%+VZ z$Olz970;~JSl5#WWxU}T-Kjndt)`By^^<2@JKWaY#W8E2Q0 zQ|;_!o=>|#7r_Z0;%fjZvyxH|Qqr{wJNTri9Y_3-){u|QnYVHA#(poju7wTt)c7%d zN%p!ix}g>M7#`C96>D|2j8(`34|eP8f3%_e(f$)N ze!R(~#?U$gEvY1x2wg$KS@}5p4O~vbfo`y8mixvI>RI{qv%cB0@pBcG6sn?QI^g1N+sy{9z3@U*7bMZc=!`rSAJ~7%rmLzUP{0 z7S#8W7yM?+O1mhf{Ipi&@n~r2Ccs3q9hA79ocOB?3c7+#f1?e!F2z=R?5#WQJXXo~ zhUgXWbCqa~X`EDtcpfzpOFYi=O3TeQrzx8{X3m0LFNfRsSR0JW49HTIrhvZU;QB6;tfI%W}K!(?AbR?#1~M*gckxEDA*RM4hb;R&MB zVJHAzZVI}NZw{AVseKr?0)q&jy_lXgkbvmW;ltqz*)l~br%MtOQj?l(74Kpj|JEo! zP2qtk+aIJzGY=Dxq%Jkbi!E%6^gU%d?;ANe5;|$P3)_GmATHDe3;t|6A>>SP^WXKO zC%1e4p+UH_5I+Zx2;ck%%FTWJPz!QYJ!3waW;DIL0zPhkH7(J;w$`FCm1@>g88U}+ zD=uK*^JkCD0@HWh{jzBtmDM@o=YYC3FdcMC$$wj0OY2JW78o{Lm0xhX)g?`p!4BEU zn9m9+L3qiFksqXh>yP2+NpJ0?!G~PR9D9DVrvhqTP0Zm1fIS->+{i?jYgR-D}^^J~#-Slqg4(?@{PjD8=}Xf=1YT)xgJ*^(1D{)3am{ zyE>F{xgXJDhObJ*G;!k!+l(4Ur= zD)ji`O%#rJO%Tvt)}B4U>jbHX=I>sbJ<^F0D=aE^nbJhLS@1PQlx3>Hr+!^LWN7^a z$CR7fPnm8vJ@_ahU)0~$;*Bvgq=A~dej00Vbd=+MzyX1ZYOAwQ1u+2-MhwY zuSp21v#3|JV+8|X2EC0erw8e{h2Mo`=$$|J8@qNIJVtJz8wkwrNCi9jA)2EzlBS7TQyjDVFgPak>MWi4o!!iBvR} z59ruu%;MJ#MUL|1jGPMXb<}ZE{OGq#>#`KDc%Nn)L<+|bPR_<8)1;_^_X@(rZ(gcf z$UkL7xOREpSeuOp*fKY8U{k{D#s|nxbffJIFEW)tAwC0EzNuaaxHDklD6UEh=c4=g(KTCCT2FTTv-fUjK6qy~R z$p(s>w=~Y4Q2GKVrafqN_R6lGSfagH4kM*WRb-4Dk=c?kq5}<>A_rPgnNIc~m6mCU zjwHbR}=&v$jxkNN$sH_{K;t*Zny1?*2PMa;HWhV zlT`y=Y8yt|y4u8=xh>I|6jUP8$iDvBa zT7~OHwaAf<*)QZ=8g_vDvqvqaaHmRsw`3vyZOOIL5ji~ICPguFd$#)R^L$N!GRP5c z@Hz(Tr;|%a{2LW)1m!+PVZ?WMb79CzTF6gz$Qe3mX^aruy~&<$rg7(x6jao<-HX1*yXwE)VuLI!{Upol@c zH=&zd`Mt{`eP6Y2>HV99B|$(){Uz4vYDYXgxddg3)(bsX4ic}Ie zxJ%A@Hob9(K|w&U!rKPKKtPZQp@F-$cO(Df&^J=x9_=F6TpoAzg<+js&& { + * Meteor.callAsync("m2") + * }) + * + * The call the method m2 will act as a simulation and won't reach the server. That's why we reset the context here + * before calling everything else. + * + * */ + DDP._CurrentMethodInvocation._set(); + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); + return new Promise((resolve, reject) => { + this.applyAsync(name, args, { isFromCallAsync: true }, (err, result) => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + if (err) { + reject(err); + return; + } + resolve(result); + }); + }); + } /** * @memberOf Meteor @@ -568,6 +634,79 @@ export class Connection { * @param {Function} [asyncCallback] Optional callback; same semantics as in [`Meteor.call`](#meteor_call). */ apply(name, args, options, callback) { + const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args)); + + if (stubOptions.hasStub) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { + this._saveOriginals(); + } + try { + stubOptions.stubReturnValue = DDP._CurrentMethodInvocation + .withValue(invocation, stubInvocation); + } catch (e) { + stubOptions.exception = e; + } + } + return this._apply(name, stubOptions, args, options, callback); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.applyAsync + * @summary Invoke a method passing an array of arguments. + * @locus Anywhere + * @param {String} name Name of method to invoke + * @param {EJSONable[]} args Method arguments + * @param {Object} [options] + * @param {Boolean} options.wait (Client only) If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed. + * @param {Function} options.onResultReceived (Client only) This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method. + * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. + * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. + * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. + * @param {Function} [asyncCallback] Optional callback. + */ + async applyAsync(name, args, options, callback) { + const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); + if (stubOptions.hasStub) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { + this._saveOriginals(); + } + try { + /* + * The code below follows the same logic as the function withValues(). + * + * But as the Meteor package is not compiled by ecmascript, it is unable to use newer syntax in the browser, + * such as, the async/await. + * + * So, to keep supporting old browsers, like IE 11, we're creating the logic one level above. + */ + const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( + invocation + ); + try { + stubOptions.stubReturnValue = await stubInvocation(); + } finally { + DDP._CurrentMethodInvocation._set(currentContext); + } + } catch (e) { + stubOptions.exception = e; + } + } + return this._apply(name, stubOptions, args, options, callback); + } + + _apply(name, stubCallValue, args, options, callback) { const self = this; // We were passed 3 arguments. They may be either (name, args, options) @@ -592,85 +731,17 @@ export class Connection { // while because of a wait method). args = EJSON.clone(args); - const enclosing = DDP._CurrentMethodInvocation.get(); - const alreadyInSimulation = enclosing && enclosing.isSimulation; - - // Lazily generate a randomSeed, only if it is requested by the stub. - // The random streams only have utility if they're used on both the client - // and the server; if the client doesn't generate any 'random' values - // then we don't expect the server to generate any either. - // Less commonly, the server may perform different actions from the client, - // and may in fact generate values where the client did not, but we don't - // have any client-side values to match, so even here we may as well just - // use a random seed on the server. In that case, we don't pass the - // randomSeed to save bandwidth, and we don't even generate it to save a - // bit of CPU and to avoid consuming entropy. - let randomSeed = null; - const randomSeedGenerator = () => { - if (randomSeed === null) { - randomSeed = DDPCommon.makeRpcSeed(enclosing, name); - } - return randomSeed; - }; - - // Run the stub, if we have one. The stub is supposed to make some - // temporary writes to the database to give the user a smooth experience - // until the actual result of executing the method comes back from the - // server (whereupon the temporary writes to the database will be reversed - // during the beginUpdate/endUpdate process.) - // - // Normally, we ignore the return value of the stub (even if it is an - // exception), in favor of the real return value from the server. The - // exception is if the *caller* is a stub. In that case, we're not going - // to do a RPC, so we use the return value of the stub as our return - // value. - - let stubReturnValue; - let exception; - const stub = self._methodHandlers[name]; - if (stub) { - const setUserId = userId => { - self.setUserId(userId); - }; - - const invocation = new DDPCommon.MethodInvocation({ - isSimulation: true, - userId: self.userId(), - setUserId: setUserId, - randomSeed() { - return randomSeedGenerator(); - } - }); - - if (!alreadyInSimulation) self._saveOriginals(); - - try { - // Note that unlike in the corresponding server code, we never audit - // that stubs check() their arguments. - stubReturnValue = DDP._CurrentMethodInvocation.withValue( - invocation, - () => { - if (Meteor.isServer) { - // Because saveOriginals and retrieveOriginals aren't reentrant, - // don't allow stubs to yield. - return Meteor._noYieldsAllowed(() => { - // re-clone, so that the stub can't affect our caller's values - return stub.apply(invocation, EJSON.clone(args)); - }); - } else { - return stub.apply(invocation, EJSON.clone(args)); - } - } - ); - } catch (e) { - exception = e; - } - } + const { hasStub, exception, stubReturnValue, alreadyInSimulation, randomSeed } = stubCallValue; // If we're in a simulation, stop and return the result we have, // rather than going on to do an RPC. If there was no stub, // we'll end up returning undefined. - if (alreadyInSimulation) { + if ( + this._getIsSimulation({ + alreadyInSimulation, + isFromCallAsync: stubCallValue.isFromCallAsync, + }) + ) { if (callback) { callback(exception, stubReturnValue); return undefined; @@ -682,7 +753,7 @@ export class Connection { // We only create the methodId here because we don't actually need one if // we're already in a simulation const methodId = '' + self._nextMethodId++; - if (stub) { + if (hasStub) { self._retrieveAndStoreOriginals(methodId); } @@ -738,8 +809,8 @@ export class Connection { } // Send the randomSeed only if we used it - if (randomSeed !== null) { - message.randomSeed = randomSeed; + if (randomSeed.value !== null) { + message.randomSeed = randomSeed.value; } const methodInvoker = new MethodInvoker({ @@ -783,6 +854,82 @@ export class Connection { return options.returnStubValue ? stubReturnValue : undefined; } + + _stubCall(name, args, options) { + // Run the stub, if we have one. The stub is supposed to make some + // temporary writes to the database to give the user a smooth experience + // until the actual result of executing the method comes back from the + // server (whereupon the temporary writes to the database will be reversed + // during the beginUpdate/endUpdate process.) + // + // Normally, we ignore the return value of the stub (even if it is an + // exception), in favor of the real return value from the server. The + // exception is if the *caller* is a stub. In that case, we're not going + // to do a RPC, so we use the return value of the stub as our return + // value. + const self = this; + const enclosing = DDP._CurrentMethodInvocation.get(); + const stub = self._methodHandlers[name]; + const alreadyInSimulation = enclosing?.isSimulation; + const isFromCallAsync = enclosing?._isFromCallAsync; + const randomSeed = { value: null}; + + const defaultReturn = { + alreadyInSimulation, randomSeed, isFromCallAsync + }; + if (!stub) { + return { ...defaultReturn, hasStub: false }; + } + + // Lazily generate a randomSeed, only if it is requested by the stub. + // The random streams only have utility if they're used on both the client + // and the server; if the client doesn't generate any 'random' values + // then we don't expect the server to generate any either. + // Less commonly, the server may perform different actions from the client, + // and may in fact generate values where the client did not, but we don't + // have any client-side values to match, so even here we may as well just + // use a random seed on the server. In that case, we don't pass the + // randomSeed to save bandwidth, and we don't even generate it to save a + // bit of CPU and to avoid consuming entropy. + + const randomSeedGenerator = () => { + if (randomSeed.value === null) { + randomSeed.value = DDPCommon.makeRpcSeed(enclosing, name); + } + return randomSeed.value; + }; + + const setUserId = userId => { + self.setUserId(userId); + }; + + const invocation = new DDPCommon.MethodInvocation({ + isSimulation: true, + userId: self.userId(), + isFromCallAsync: options?.isFromCallAsync, + setUserId: setUserId, + randomSeed() { + return randomSeedGenerator(); + } + }); + + // Note that unlike in the corresponding server code, we never audit + // that stubs check() their arguments. + const stubInvocation = () => { + if (Meteor.isServer) { + // Because saveOriginals and retrieveOriginals aren't reentrant, + // don't allow stubs to yield. + return Meteor._noYieldsAllowed(() => { + // re-clone, so that the stub can't affect our caller's values + return stub.apply(invocation, EJSON.clone(args)); + }); + } else { + return stub.apply(invocation, EJSON.clone(args)); + } + }; + return { ...defaultReturn, hasStub: true, stubInvocation, invocation }; + } + // Before calling a method stub, prepare all stores to track changes and allow // _retrieveAndStoreOriginals to get the original versions of changed // documents. diff --git a/packages/ddp-client/common/namespace.js b/packages/ddp-client/common/namespace.js index 3fc0685eb6..117ea27d6b 100644 --- a/packages/ddp-client/common/namespace.js +++ b/packages/ddp-client/common/namespace.js @@ -58,6 +58,11 @@ DDP.randomStream = name => { * @summary Connect to the server of a different Meteor application to subscribe to its document sets and invoke its remote methods. * @locus Anywhere * @param {String} url The URL of another Meteor application. + * @param {Object} [options] + * @param {Boolean} options.reloadWithOutstanding is it OK to reload if there are outstanding methods? + * @param {Object} options.headers extra headers to send on the websockets connection, for server-to-server DDP only + * @param {Object} options._sockjsOptions Specifies options to pass through to the sockjs client + * @param {Function} options.onDDPNegotiationVersionFailure callback when version negotiation fails. */ DDP.connect = (url, options) => { const ret = new Connection(url, options); diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index c15af7e866..93f5de6052 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.5.0', + version: '2.6.0-rc280.0', documentation: null }); diff --git a/packages/ddp-common/method_invocation.js b/packages/ddp-common/method_invocation.js index 578e855de0..acf66120d5 100644 --- a/packages/ddp-common/method_invocation.js +++ b/packages/ddp-common/method_invocation.js @@ -33,6 +33,9 @@ DDPCommon.MethodInvocation = class MethodInvocation { this._unblock = options.unblock || function () {}; this._calledUnblock = false; + // used to know when the function apply was called by callAsync + this._isFromCallAsync = options.isFromCallAsync; + // current user id /** diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index ba95cc1492..fcdbc29003 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -762,16 +762,33 @@ Object.assign(Session.prototype, { } } - resolve(DDPServer._CurrentWriteFence.withValue( - fence, - () => DDP._CurrentMethodInvocation.withValue( - invocation, - () => maybeAuditArgumentChecks( - handler, invocation, msg.params, + const getCurrentMethodInvocationResult = () => { + const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( + invocation + ); + + try { + let result; + const resultOrThenable = maybeAuditArgumentChecks( + handler, + invocation, + msg.params, "call to '" + msg.method + "'" - ) - ) - )); + ); + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + result = Promise.await(resultOrThenable); + } else { + result = resultOrThenable; + } + return result; + } finally { + DDP._CurrentMethodInvocation._set(currentContext); + } + }; + + resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult)); }); function finish() { @@ -784,7 +801,7 @@ Object.assign(Session.prototype, { id: msg.id }; - promise.then((result) => { + promise.then(result => { finish(); if (result !== undefined) { payload.result = result; @@ -1531,33 +1548,33 @@ Object.assign(Server.prototype, { }, /** - * @summary Set publication strategy for the given publication. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()` + * @summary Set publication strategy for the given collection. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()` * @locus Server * @alias setPublicationStrategy - * @param publicationName {String} + * @param collectionName {String} * @param strategy {{useCollectionView: boolean, doAccountingForCollection: boolean}} * @memberOf Meteor.server * @importFromPackage meteor */ - setPublicationStrategy(publicationName, strategy) { + setPublicationStrategy(collectionName, strategy) { if (!Object.values(publicationStrategies).includes(strategy)) { throw new Error(`Invalid merge strategy: ${strategy} - for collection ${publicationName}`); + for collection ${collectionName}`); } - this._publicationStrategies[publicationName] = strategy; + this._publicationStrategies[collectionName] = strategy; }, /** - * @summary Gets the publication strategy for the requested publication. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()` + * @summary Gets the publication strategy for the requested collection. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()` * @locus Server * @alias getPublicationStrategy - * @param publicationName {String} + * @param collectionName {String} * @memberOf Meteor.server * @importFromPackage meteor * @return {{useCollectionView: boolean, doAccountingForCollection: boolean}} */ - getPublicationStrategy(publicationName) { - return this._publicationStrategies[publicationName] + getPublicationStrategy(collectionName) { + return this._publicationStrategies[collectionName] || this.options.defaultPublicationStrategy; }, diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js index 9830339be8..4a70ae18a7 100644 --- a/packages/ddp-server/package.js +++ b/packages/ddp-server/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data server", - version: '2.5.0', + version: '2.6.0-rc280.0', documentation: null }); diff --git a/packages/ddp-server/server_convenience.js b/packages/ddp-server/server_convenience.js index 5d34986053..063bbe6385 100755 --- a/packages/ddp-server/server_convenience.js +++ b/packages/ddp-server/server_convenience.js @@ -11,7 +11,18 @@ Meteor.refresh = function (notification) { // Proxy the public methods of Meteor.server so they can // be called directly on Meteor. -_.each(['publish', 'methods', 'call', 'apply', 'onConnection', 'onMessage'], - function (name) { - Meteor[name] = _.bind(Meteor.server[name], Meteor.server); - }); +_.each( + [ + 'publish', + 'methods', + 'call', + 'callAsync', + 'apply', + 'applyAsync', + 'onConnection', + 'onMessage', + ], + function(name) { + Meteor[name] = _.bind(Meteor.server[name], Meteor.server); + } +); diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index f8e369a66d..2607911613 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'The Meteor command-line tool', - version: '2.8.0-beta.1', + version: '2.8.0-rc.0', }); Package.includeTool(); diff --git a/packages/meteor/dynamics_browser.js b/packages/meteor/dynamics_browser.js index 9057a9eb7e..6a05f0bd94 100644 --- a/packages/meteor/dynamics_browser.js +++ b/packages/meteor/dynamics_browser.js @@ -2,6 +2,7 @@ var nextSlot = 0; var currentValues = []; +var callAsyncMethodRunning = false; Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; @@ -9,6 +10,9 @@ Meteor.EnvironmentVariable = function () { var EVp = Meteor.EnvironmentVariable.prototype; +EVp.getCurrentValues = function () { + return currentValues; +}; EVp.get = function () { return currentValues[this.slot]; }; @@ -28,6 +32,25 @@ EVp.withValue = function (value, func) { return ret; }; +EVp._set = function (context) { + currentValues[this.slot] = context; +}; + +EVp._setNewContextAndGetCurrent = function (value) { + const saved = currentValues[this.slot]; + this._set(value); + return saved; +}; + +EVp._isCallAsyncMethodRunning = function () { + return callAsyncMethodRunning; +}; + +EVp._setCallAsyncMethodRunning = function (value) { + callAsyncMethodRunning = value; +}; + + Meteor.bindEnvironment = function (func, onException, _this) { // needed in order to be able to create closures inside func and // have the closed variables not change back to their original diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 75a1fb43ad..b53f7c7c11 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -111,6 +111,31 @@ EVp.withValue = async function (value, func) { } }; +/** + * @summary Set the environment variable to the given value while a function is run + * @locus Anywhere + * @method withValueAsync + * @memberof Meteor.EnvironmentVariable + * @param {Any} value Value the environment variable should be set to + * @param {Function} func The function to run + * @return {Any} Return value of function + */ + +EVp._set = function (context) { + Meteor._nodeCodeMustBeInFiber(); + Fiber.current._meteor_dynamics[this.slot] = context; +}; + +EVp._setNewContextAndGetCurrent = function (value) { + Meteor._nodeCodeMustBeInFiber(); + if (!Fiber.current._meteor_dynamics) { + Fiber.current._meteor_dynamics = []; + } + const saved = Fiber.current._meteor_dynamics[this.slot]; + this._set(value); + return saved; +}; + // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from // inside a fiber and ensures it sees the values of Meteor environment diff --git a/packages/meteor/package.js b/packages/meteor/package.js index c2ac5fe0e2..504a2231e7 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Core Meteor environment", - version: '1.10.1-beta280.1' + version: '1.10.1-beta280.2' }); Package.registerBuildPlugin({ diff --git a/packages/minifier-css/.npm/package/npm-shrinkwrap.json b/packages/minifier-css/.npm/package/npm-shrinkwrap.json index 2e7b3bdfbb..f267ad83cb 100644 --- a/packages/minifier-css/.npm/package/npm-shrinkwrap.json +++ b/packages/minifier-css/.npm/package/npm-shrinkwrap.json @@ -9,7 +9,7 @@ "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==" }, "ansi-styles": { "version": "3.2.1", @@ -21,15 +21,20 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" }, + "array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==" + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==" + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" }, "call-bind": { "version": "1.0.2", @@ -39,17 +44,17 @@ "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=" + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==" }, "caller-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=" + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==" }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==" }, "caniuse-api": { "version": "3.0.0", @@ -57,21 +62,14 @@ "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==" }, "caniuse-lite": { - "version": "1.0.30001243", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz", - "integrity": "sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==" + "version": "1.0.30001409", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001409.tgz", + "integrity": "sha512-V0mnJ5dwarmhYv8/MzhJ//aW68UpvnQBXv8lJ2QUsvn2pHcmAuNtu8hQEDz37XnA1iE+lRR9CIfGWWpgJ5QedQ==" }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" - } - } + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" }, "coa": { "version": "2.0.2", @@ -79,9 +77,9 @@ "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==" }, "color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", - "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==" }, "color-convert": { "version": "1.9.3", @@ -91,17 +89,12 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==" - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==" }, "cosmiconfig": { "version": "5.2.1", @@ -111,17 +104,22 @@ "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==" }, "css-declaration-sorter": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -155,10 +153,15 @@ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -167,32 +170,42 @@ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, "cssnano-util-get-arguments": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==" }, "cssnano-util-get-match": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==" }, "cssnano-util-raw-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -219,9 +232,9 @@ } }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==" }, "dom-serializer": { "version": "0.2.2", @@ -229,9 +242,9 @@ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "dependencies": { "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" } } }, @@ -251,9 +264,9 @@ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==" }, "electron-to-chromium": { - "version": "1.3.772", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz", - "integrity": "sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA==" + "version": "1.4.257", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.257.tgz", + "integrity": "sha512-C65sIwHqNnPC2ADMfse/jWTtmhZMII+x6ADI9gENzrOiI7BpxmfKFE84WkIEl5wEg+7+SfIkwChDlsd1Erju2A==" }, "entities": { "version": "2.2.0", @@ -266,9 +279,14 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==" }, "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==" + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz", + "integrity": "sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==" + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" }, "es-to-primitive": { "version": "1.2.1", @@ -283,7 +301,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "esprima": { "version": "4.0.1", @@ -295,10 +313,25 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==" + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==" }, "has": { "version": "1.0.3", @@ -306,19 +339,29 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==" }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" }, "hex-color-regex": { "version": "1.1.0", @@ -328,72 +371,77 @@ "hsl-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==" }, "hsla-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==" }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=" + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==" }, "indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==" + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==" }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==" }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==" }, "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==" }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.6.tgz", + "integrity": "sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==" }, "is-color-stop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=" + "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==" }, "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" }, "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==" }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==" }, "is-obj": { "version": "2.0.0", @@ -401,25 +449,35 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==" + }, "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==" }, "is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==" }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==" + }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -433,12 +491,12 @@ "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, "mdn-data": { "version": "2.0.4", @@ -446,24 +504,24 @@ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==" + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==" }, "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" }, "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "normalize-url": { "version": "3.3.0", @@ -476,9 +534,9 @@ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==" }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", @@ -486,39 +544,49 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==" }, "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==" }, "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==" }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=" + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "postcss": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", - "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==" + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==" }, "postcss-calc": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -527,10 +595,15 @@ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -544,10 +617,15 @@ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -561,10 +639,15 @@ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -573,10 +656,15 @@ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -585,10 +673,15 @@ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -597,10 +690,15 @@ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -609,10 +707,15 @@ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -626,10 +729,15 @@ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-selector-parser": { "version": "3.1.2", @@ -643,10 +751,15 @@ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -660,10 +773,15 @@ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -677,10 +795,15 @@ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -694,10 +817,15 @@ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-selector-parser": { "version": "3.1.2", @@ -711,10 +839,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -723,10 +856,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -740,10 +878,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -757,10 +900,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -774,10 +922,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -791,10 +944,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -808,10 +966,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -825,10 +988,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -842,10 +1010,15 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -859,10 +1032,15 @@ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -876,10 +1054,15 @@ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, @@ -888,10 +1071,15 @@ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -901,19 +1089,24 @@ } }, "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==" + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==" }, "postcss-svgo": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-value-parser": { "version": "3.3.1", @@ -927,47 +1120,62 @@ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" } } }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==" }, "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" }, "rgb-regex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==" }, "rgba-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==" }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==" + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "dependencies": { "is-arrayish": { "version": "0.3.2", @@ -982,14 +1190,14 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "stable": { "version": "0.1.8", @@ -997,24 +1205,29 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==" }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==" }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==" + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==" }, "postcss-selector-parser": { "version": "3.1.2", @@ -1024,9 +1237,9 @@ } }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" }, "svgo": { "version": "1.3.2", @@ -1036,32 +1249,37 @@ "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==" }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==" }, "uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" }, "uniqs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==" }, "unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "update-browserslist-db": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", + "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "util.promisify": { "version": "1.0.1", diff --git a/packages/minifier-js/.npm/package/npm-shrinkwrap.json b/packages/minifier-js/.npm/package/npm-shrinkwrap.json index a44b80076e..1b657072a1 100644 --- a/packages/minifier-js/.npm/package/npm-shrinkwrap.json +++ b/packages/minifier-js/.npm/package/npm-shrinkwrap.json @@ -1,10 +1,40 @@ { "lockfileVersion": 1, "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==" + }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" }, "buffer-from": { "version": "1.1.2", @@ -17,26 +47,19 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==" }, "terser": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", - "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==" + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==" } } } diff --git a/packages/minimongo/matcher.js b/packages/minimongo/matcher.js index 15003628b3..a55a9d879b 100644 --- a/packages/minimongo/matcher.js +++ b/packages/minimongo/matcher.js @@ -262,8 +262,8 @@ LocalCollection._f = { if (ta === 9) { // Date // Convert to millis. ta = tb = 1; - a = a.getTime(); - b = b.getTime(); + a = isNaN(a) ? 0 : a.getTime(); + b = isNaN(b) ? 0 : b.getTime(); } if (ta === 1) { // double diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 0d99c9d91c..f1d35b8fba 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -439,8 +439,16 @@ Tinytest.add('minimongo - selector_compiler', test => { // dates const date1 = new Date; const date2 = new Date(date1.getTime() + 1000); + const date3 = new Date(''); match({a: date1}, {a: date1}); nomatch({a: date1}, {a: date2}); + match({a: date3}, {a: date3}); + nomatch({a: date1}, {a: date3}); + nomatch({a: date3}, {a: date1}); + match({a: {$gt: date3}}, {a: date1}); + match({a: {$gte: date3}}, {a: date1}); + nomatch({a: {$lt: date3}}, {a: date1}); + nomatch({a: {$lte: date3}}, {a: date1}); // arrays diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 98ccabd808..61bd3b9b05 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.9.0-beta280.1' + version: '1.9.0-rc280.0' }); Package.onUse(api => { diff --git a/packages/modules/package.js b/packages/modules/package.js index 5511cc0cb6..ddebe4e805 100644 --- a/packages/modules/package.js +++ b/packages/modules/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "modules", - version: "0.19.0-beta280.1", + version: "0.19.0-rc280.0", summary: "CommonJS module system", documentation: "README.md" }); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 105a05a281..f6562720f5 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1049,7 +1049,7 @@ MongoConnection.prototype._createSynchronousCursor = function( dbCursor = dbCursor.hint(cursorOptions.hint); } - return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options) : new AsynchronousCursor(dbCursor, cursorDescription, options); + return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options, collection) : new AsynchronousCursor(dbCursor, cursorDescription, options, collection); }; /** @@ -1199,7 +1199,7 @@ class AsynchronousCursor { } } -var SynchronousCursor = function (dbCursor, cursorDescription, options) { +var SynchronousCursor = function (dbCursor, cursorDescription, options, collection) { var self = this; options = _.pick(options || {}, 'selfForIteration', 'useTransform'); @@ -1215,7 +1215,13 @@ var SynchronousCursor = function (dbCursor, cursorDescription, options) { self._transform = null; } - self._synchronousCount = Future.wrap(dbCursor.count.bind(dbCursor)); + self._synchronousCount = Future.wrap( + collection.countDocuments.bind( + collection, + replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), + replaceTypes(cursorDescription.options, replaceMeteorAtomWithMongo), + ) + ); self._visitedIds = new LocalCollection._IdMap; }; diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js index 394226dad1..c6a2484728 100644 --- a/packages/mongo/mongo_livedata_tests.js +++ b/packages/mongo/mongo_livedata_tests.js @@ -3232,6 +3232,7 @@ Meteor.isServer && testAsyncMulti("mongo-livedata - update with replace forbidde Meteor.isServer && Tinytest.add( "mongo-livedata - connection failure throws", function (test) { + // Exception happens in 30s test.throws(function () { const connection = new MongoInternals.Connection('mongodb://this-does-not-exist.test/asdf'); diff --git a/packages/mongo/oplog_tests.js b/packages/mongo/oplog_tests.js index 6ef4ef681d..bb3374f8fb 100644 --- a/packages/mongo/oplog_tests.js +++ b/packages/mongo/oplog_tests.js @@ -164,18 +164,27 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( ] ); -Tinytest.addAsync("mongo-livedata - oplog - _onFailover", async () => { - const driver = MongoInternals.defaultRemoteCollectionDriver(); - const failoverPromise = new Promise(resolve => { - driver.mongo._onFailover(() => { - resolve(); - }); - }); - await driver.mongo.db.admin().command({ - replSetStepDown: 1, - force: true - }); - - return failoverPromise; -}); +// Meteor.isServer && Tinytest.addAsync( +// "mongo-livedata - oplog - _onFailover", +// async function (test) { +// const driver = MongoInternals.defaultRemoteCollectionDriver(); +// const failoverPromise = new Promise(resolve => { +// driver.mongo._onFailover(() => { +// resolve(true); +// }); +// }); +// +// +// await driver.mongo.db.admin().command({ +// replSetStepDown: 1, +// force: true +// }); +// +// try { +// const result = await failoverPromise; +// test.isTrue(result); +// } catch (e) { +// test.fail({ message: "Error waiting on Promise", value: JSON.stringify(e) }); +// } +// }); diff --git a/packages/mongo/package.js b/packages/mongo/package.js index dad9ea74be..05f7d833f8 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.16.0-beta280.1' + version: '1.16.0-rc280.0' }); Npm.depends({ diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index 2de006917d..bdf03af949 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@types/node": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", - "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" + "version": "18.7.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", + "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==" }, "@types/webidl-conversions": { "version": "6.1.1", @@ -22,9 +22,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bson": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz", - "integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==" + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", + "integrity": "sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA==" }, "buffer": { "version": "5.7.1", @@ -52,9 +52,9 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.0.tgz", - "integrity": "sha512-a0eVzm1e1kxwnzJV1wZXIS54KegM2y6wXTXOGTSAxr/E2YOUkl/zGBHNSI4z+6z+YQtVdzDqy1nJ4n5MxYJRnQ==" + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.0.tgz", + "integrity": "sha512-tJJEFJz7OQTQPZeVHZJIeSOjMRqc5eSyXTt86vSQENEErpkiG7279tM/GT5AVZ7TgXNh9HQxoa2ZkbrANz5GQw==" }, "mongodb-connection-string-url": { "version": "2.5.3", diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index 30f3e77627..a63486965d 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.8.0-beta280.1", + version: "4.9.0-rc280.0", documentation: null }); Npm.depends({ - mongodb: "4.8.0" + mongodb: "4.9.0" }); Package.onUse(function (api) { diff --git a/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json b/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json index f4ccfc2319..f2e91b0c9a 100644 --- a/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json +++ b/packages/standard-minifier-css/.npm/plugin/minifyStdCSS/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@babel/runtime": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", - "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==" }, "braces": { "version": "3.0.2", @@ -27,9 +27,9 @@ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==" + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==" }, "picomatch": { "version": "2.3.1", @@ -42,9 +42,9 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "to-regex-range": { "version": "5.0.1", diff --git a/packages/standard-minifier-js/.npm/plugin/minifyStdJS/npm-shrinkwrap.json b/packages/standard-minifier-js/.npm/plugin/minifyStdJS/npm-shrinkwrap.json index 56735d318b..e0ab114049 100644 --- a/packages/standard-minifier-js/.npm/plugin/minifyStdJS/npm-shrinkwrap.json +++ b/packages/standard-minifier-js/.npm/plugin/minifyStdJS/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 1, "dependencies": { "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==" }, "regenerator-runtime": { "version": "0.13.9", diff --git a/packages/test-in-console/driver.js b/packages/test-in-console/driver.js index 018680e56f..7b8711ad75 100644 --- a/packages/test-in-console/driver.js +++ b/packages/test-in-console/driver.js @@ -2,10 +2,16 @@ DONE = false; // Failure count for phantomjs exit code FAILURES = null; +// Where are the failures +WHERE_FAILED = []; +// Passed count for phantomjs exit code +PASSED = null; TEST_STATUS = { DONE: false, - FAILURES: null + FAILURES: null, + PASSED: null, + WHERE_FAILED: [] }; // xUnit format uses XML output @@ -55,6 +61,7 @@ var xunit = function (s) { var passed = 0; var failed = 0; +var whereFailed = []; var expected = 0; var resultSet = {}; var toReport = []; @@ -163,6 +170,7 @@ runTests = function () { report(name, true); log(name, ":", "!!!!!!!!! FAIL !!!!!!!!!!!"); log(JSON.stringify(resultSet[name].info)); + whereFailed.push({ name: name, info: JSON.stringify(resultSet[name].info) }); break; default: log(name, ": unknown state for the test to be in"); @@ -187,10 +195,14 @@ runTests = function () { log("Waiting 3s for any last reports to get sent out"); setTimeout(function () { TEST_STATUS.FAILURES = FAILURES = failed; + TEST_STATUS.WHERE_FAILED = WHERE_FAILED = whereFailed; + TEST_STATUS.PASSED = PASSED = passed; TEST_STATUS.DONE = DONE = true; }, 3000); } else { TEST_STATUS.FAILURES = FAILURES = failed; + TEST_STATUS.WHERE_FAILED = WHERE_FAILED = whereFailed; + TEST_STATUS.PASSED = PASSED = passed; TEST_STATUS.DONE = DONE = true; } }); diff --git a/packages/test-in-console/package.js b/packages/test-in-console/package.js index 439e9e2b0e..c4759ae33f 100644 --- a/packages/test-in-console/package.js +++ b/packages/test-in-console/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'Run tests noninteractively, with results going to the console.', - version: '1.2.4-beta280.1' + version: '1.2.4-rc280.0' }); Package.onUse(function(api) { diff --git a/packages/test-in-console/puppeteerRunner.js b/packages/test-in-console/puppeteerRunner.js index 9a09024a59..a2d07f633f 100644 --- a/packages/test-in-console/puppeteerRunner.js +++ b/packages/test-in-console/puppeteerRunner.js @@ -4,7 +4,9 @@ async function runNextUrl(browser) { const page = await browser.newPage(); page.on('console', msg => { - console.log(msg._text); + if (msg._text !== undefined) { + console.log(msg._text); + } }); if (!process.env.URL) { @@ -17,7 +19,11 @@ async function runNextUrl(browser) { async function poll() { if (await isDone(page)) { let failCount = await getFailCount(page); + console.log(`Tests complete with ${failCount} failures`); + console.log(`Tests complete with ${await getPassCount(page)} passes`); if (failCount > 0) { + const failed = await getFailed(page); + failed.map( (f) => console.log(`${f.name} failed: ${f.info}`)); await page.close(); await browser.close(); process.exit(1); @@ -34,6 +40,11 @@ async function runNextUrl(browser) { poll(); } +/** + * + * @param page + * @return {Promise} + */ async function isDone(page) { return await page.evaluate(function() { if (typeof TEST_STATUS !== 'undefined') { @@ -44,6 +55,26 @@ async function isDone(page) { }); } +/** + * + * @param page + * @return {Promise} + */ +async function getPassCount(page) { + return await page.evaluate(function() { + if (typeof TEST_STATUS !== 'undefined') { + return TEST_STATUS.PASSED; + } + + return typeof PASSED !== 'undefined' && PASSED; + }); +} + +/** + * + * @param page + * @return {Promise} + */ async function getFailCount(page) { return await page.evaluate(function() { if (typeof TEST_STATUS !== 'undefined') { @@ -58,6 +89,20 @@ async function getFailCount(page) { }); } +/** + * + * @param page + * @return {Promise<[{name: string, info: string}]>} + */ +async function getFailed(page) { + return await page.evaluate(function() { + if (typeof TEST_STATUS !== 'undefined') { + return TEST_STATUS.WHERE_FAILED; + } + return typeof WHERE_FAILED !== 'undefined' && WHERE_FAILED; + }); +} + async function runTests() { console.log(`Running test with Puppeteer at ${process.env.URL}`); diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index c16c51dc6b..73b31948b6 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "2.8-beta.1", + "version": "2.8-rc.0", "recommended": false, "official": false, "description": "Meteor experimental release" diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index f124d3c95c..d7b4cfaa10 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,7 +5,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=14.20.0 +NODE_VERSION=14.20.1 MONGO_VERSION_64BIT=5.0.5 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.17 diff --git a/scripts/make-release-tarballs.sh b/scripts/make-release-tarballs.sh new file mode 100755 index 0000000000..6b52e9c5e7 --- /dev/null +++ b/scripts/make-release-tarballs.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +for ARGUMENT in "$@"; do + KEY=$(echo $ARGUMENT | cut -f1 -d=) + + KEY_LENGTH=${#KEY} + VALUE="${ARGUMENT:$KEY_LENGTH+1}" + + export "$KEY"="$VALUE" +done + +echo "BRANCH_NAME = $BRANCH_NAME" +echo "VERSION = $VERSION" + +git fetch origin && git checkout release/METEOR@$VERSION && + git reset --hard origin/$BRANCH_NAME && + git clean -df && + ./meteor admin make-bootstrap-tarballs --target-arch os.windows.x86_64 $VERSION win64 && + ./meteor admin make-bootstrap-tarballs --target-arch os.linux.x86_64 $VERSION linux64 && + ./meteor admin make-bootstrap-tarballs --target-arch os.osx.x86_64 $VERSION osx && + ./meteor admin make-bootstrap-tarballs --target-arch os.osx.arm64 $VERSION osx && + aws s3 mb s3://com.meteor.static/packages-bootstrap/$VERSION/ && + aws s3 cp --acl public-read win64/meteor-bootstrap-os.windows.x86_64.tar.gz s3://com.meteor.static/packages-bootstrap/$VERSION/ && + aws s3 cp --acl public-read linux64/meteor-bootstrap-os.linux.x86_64.tar.gz s3://com.meteor.static/packages-bootstrap/$VERSION/ && + aws s3 cp --acl public-read osx/meteor-bootstrap-os.osx.x86_64.tar.gz s3://com.meteor.static/packages-bootstrap/$VERSION/ && + aws s3 cp --acl public-read osx/meteor-bootstrap-os.osx.arm64.tar.gz s3://com.meteor.static/packages-bootstrap/$VERSION/ && + aws s3 ls s3://com.meteor.static/packages-bootstrap/$VERSION diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 1454f3abf3..4a726cb3f3 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -516,6 +516,8 @@ export const AVAILABLE_SKELETONS = [ "vue", "svelte", "tailwind", + "chakra-ui", + "solid", ]; main.registerCommand({ @@ -535,6 +537,8 @@ main.registerCommand({ apollo: { type: Boolean }, svelte: { type: Boolean }, tailwind: { type: Boolean }, + 'chakra-ui': { type: Boolean }, + solid: { type: Boolean }, }, catalogRefresh: new catalog.Refresh.Never() }, function (options) { @@ -907,6 +911,8 @@ main.registerCommand({ cmd("meteor create --typescript # to create an app using TypeScript and React"); cmd("meteor create --blaze # to create an app using Blaze"); cmd("meteor create --tailwind # to create an app using React and Tailwind"); + cmd("meteor create --chakra-ui # to create an app Chakra UI and React"); + cmd("meteor create --solid # to create a basic Solid app"); } Console.info(""); @@ -1258,7 +1264,7 @@ main.registerCommand({ // This option has never done anything, but we are keeping it for // backwards compatibility since it existed for 7 years before adding - // the correctly named option + // the correctly named option 'allow-incompatible-updates': { type: Boolean } }, catalogRefresh: new catalog.Refresh.Never() diff --git a/tools/cli/help.txt b/tools/cli/help.txt index 787fa97ff4..13c612e9fd 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -150,7 +150,7 @@ Options: >>> create Create a new project. -Usage: meteor create [--release ] [--bare|--minimal|--full|--react|--vue|--apollo|--svelte|--blaze|--tailwind] +Usage: meteor create [--release ] [--bare|--minimal|--full|--react|--vue|--apollo|--svelte|--blaze|--tailwind|--chakra-ui|--solid] meteor create [--release ] --example [] meteor create --list meteor create --package [] @@ -189,6 +189,8 @@ Options: --typescript Create a basic Typescript React-based app. --blaze Create a basic blaze-based app. --tailwind Create a basic react-based app, with tailwind configured. + --chakra-ui Create a basic react-based app, with chakra-ui configured. + --solid Create a basic solid-based app. >>> update diff --git a/tools/static-assets/README.md b/tools/static-assets/README.md index b2efb024ea..db232e18af 100644 --- a/tools/static-assets/README.md +++ b/tools/static-assets/README.md @@ -32,6 +32,14 @@ Similar to `skel`, `skel-react` is copied on `meteor create --react` command. Similar to `skel`, `skel-tailwind` is copied on `meteor create --tailwind` command. +## skel-chakra-ui - Package Skeleton + +Similar to `skel`, `skel-chakra-ui` is copied on `meteor create --chakra-ui` command. + +## skel-solid - Package Skeleton + +Similar to `skel`, `skel-solid` is copied on `meteor create --solid` command. + ## server - Bundled App's Bootstrap The `server` folder is copied by Isobuild when the app is bundled (on diff --git a/tools/static-assets/skel-chakra-ui/.gitignore b/tools/static-assets/skel-chakra-ui/.gitignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/tools/static-assets/skel-chakra-ui/.meteor/.gitignore b/tools/static-assets/skel-chakra-ui/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tools/static-assets/skel-chakra-ui/.meteor/packages b/tools/static-assets/skel-chakra-ui/.meteor/packages new file mode 100644 index 0000000000..72de92e77b --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/.meteor/packages @@ -0,0 +1,23 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-base # Packages every Meteor app needs to have +mobile-experience # Packages for a great mobile UX +mongo # The database Meteor supports right now +reactive-var # Reactive variable for tracker + +standard-minifier-css # CSS minifier run for production mode +standard-minifier-js # JS minifier run for production mode +es5-shim # ECMAScript 5 compatibility for older browsers +ecmascript # Enable ECMAScript2015+ syntax in app code +typescript # Enable TypeScript syntax in .ts and .tsx modules +shell-server # Server-side component of the `meteor shell` command +hot-module-replacement # Update client in development without reloading the page + +autopublish # Publish all data to the clients (for prototyping) +insecure # Allow all DB writes from clients (for prototyping) +static-html # Define static page content in .html files +react-meteor-data # React higher-order component for reactively tracking Meteor data diff --git a/tools/static-assets/skel-chakra-ui/.meteor/platforms b/tools/static-assets/skel-chakra-ui/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/tools/static-assets/skel-chakra-ui/client/main.css b/tools/static-assets/skel-chakra-ui/client/main.css new file mode 100644 index 0000000000..7f354f0fa7 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/client/main.css @@ -0,0 +1,4 @@ +body { + padding: 10px; + font-family: sans-serif; +} diff --git a/tools/static-assets/skel-chakra-ui/client/main.html b/tools/static-assets/skel-chakra-ui/client/main.html new file mode 100644 index 0000000000..27c3712e54 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/client/main.html @@ -0,0 +1,7 @@ + + ~name~ + + + +
+ diff --git a/tools/static-assets/skel-chakra-ui/client/main.jsx b/tools/static-assets/skel-chakra-ui/client/main.jsx new file mode 100644 index 0000000000..a42cee8ff3 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/client/main.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Meteor } from 'meteor/meteor'; +import { render } from 'react-dom'; +import { App } from '/imports/ui/App'; + +Meteor.startup(() => { + render(, document.getElementById('react-target')); +}); diff --git a/tools/static-assets/skel-chakra-ui/imports/api/links.js b/tools/static-assets/skel-chakra-ui/imports/api/links.js new file mode 100644 index 0000000000..050c508eae --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/imports/api/links.js @@ -0,0 +1,3 @@ +import { Mongo } from 'meteor/mongo'; + +export const LinksCollection = new Mongo.Collection('links'); diff --git a/tools/static-assets/skel-chakra-ui/imports/ui/App.jsx b/tools/static-assets/skel-chakra-ui/imports/ui/App.jsx new file mode 100644 index 0000000000..4abd60f5e7 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/imports/ui/App.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import {Hello} from './Hello.jsx'; +import {Info} from './Info.jsx'; +import {ChakraProvider, ColorModeScript, extendTheme, Heading} from '@chakra-ui/react'; + +const theme = extendTheme({ + config: { + initialColorMode: 'dark', + useSystemColorMode: false, + }, +}); + +export const App = () => { + return ( + <> + + + Welcome to Meteor! + + + + + ); +}; diff --git a/tools/static-assets/skel-chakra-ui/imports/ui/Hello.jsx b/tools/static-assets/skel-chakra-ui/imports/ui/Hello.jsx new file mode 100644 index 0000000000..4103e284c0 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/imports/ui/Hello.jsx @@ -0,0 +1,17 @@ +import React, { useState } from 'react'; +import {Box, Button, Text} from "@chakra-ui/react"; + +export const Hello = () => { + const [counter, setCounter] = useState(0); + + const increment = () => { + setCounter(counter + 1); + }; + + return ( + + + You've pressed the button {counter} times. + + ); +}; diff --git a/tools/static-assets/skel-chakra-ui/imports/ui/Info.jsx b/tools/static-assets/skel-chakra-ui/imports/ui/Info.jsx new file mode 100644 index 0000000000..5b483a097b --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/imports/ui/Info.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useTracker } from 'meteor/react-meteor-data'; +import { LinksCollection } from '../api/links'; +import {Box, Heading, Link, ListItem, UnorderedList} from "@chakra-ui/react"; +import {ExternalLinkIcon} from "@chakra-ui/icons"; + +export const Info = () => { + const links = useTracker(() => { + return LinksCollection.find().fetch(); + }); + + return ( + + Learn Meteor! + {links.map( + link => + {link.title} + + )} + + ); +}; diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json new file mode 100644 index 0000000000..c02b9d2b53 --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -0,0 +1,29 @@ +{ + "name": "~name~", + "private": true, + "scripts": { + "start": "meteor run", + "test": "meteor test --once --driver-package meteortesting:mocha", + "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha", + "visualize": "meteor --production --extra-packages bundle-visualizer" + }, + "dependencies": { + "@babel/runtime": "^7.18.6", + "@chakra-ui/icons": "^1.1.7", + "@chakra-ui/react": "^1.8.8", + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", + "@react-icons/all-files": "^4.1.0", + "framer-motion": "^6.4.2", + "meteor-node-stubs": "^1.2.3", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "meteor": { + "mainModule": { + "client": "client/main.jsx", + "server": "server/main.js" + }, + "testModule": "tests/main.js" + } +} diff --git a/tools/static-assets/skel-chakra-ui/server/main.js b/tools/static-assets/skel-chakra-ui/server/main.js new file mode 100644 index 0000000000..9c774d214c --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/server/main.js @@ -0,0 +1,31 @@ +import { Meteor } from 'meteor/meteor'; +import { LinksCollection } from '/imports/api/links'; + +async function insertLink({ title, url }) { + await LinksCollection.insertAsync({ title, url, createdAt: new Date() }); +} + +Meteor.startup(async () => { + // If the Links collection is empty, add some data. + if (await LinksCollection.find().countAsync() === 0) { + await insertLink({ + title: 'Do the Tutorial', + url: 'https://www.meteor.com/tutorials/react/creating-an-app', + }); + + await insertLink({ + title: 'Follow the Guide', + url: 'https://guide.meteor.com', + }); + + await insertLink({ + title: 'Read the Docs', + url: 'https://docs.meteor.com', + }); + + await insertLink({ + title: 'Discussions', + url: 'https://forums.meteor.com', + }); + } +}); diff --git a/tools/static-assets/skel-chakra-ui/tests/main.js b/tools/static-assets/skel-chakra-ui/tests/main.js new file mode 100644 index 0000000000..c48096a6fb --- /dev/null +++ b/tools/static-assets/skel-chakra-ui/tests/main.js @@ -0,0 +1,20 @@ +import assert from "assert"; + +describe("chakra-template", function () { + it("package.json has correct name", async function () { + const { name } = await import("../package.json"); + assert.strictEqual(name, "chakra-template"); + }); + + if (Meteor.isClient) { + it("client is not server", function () { + assert.strictEqual(Meteor.isServer, false); + }); + } + + if (Meteor.isServer) { + it("server is not client", function () { + assert.strictEqual(Meteor.isClient, false); + }); + } +}); diff --git a/tools/static-assets/skel-solid/.gitignore b/tools/static-assets/skel-solid/.gitignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/tools/static-assets/skel-solid/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/tools/static-assets/skel-solid/.meteor/.gitignore b/tools/static-assets/skel-solid/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/tools/static-assets/skel-solid/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tools/static-assets/skel-solid/.meteor/packages b/tools/static-assets/skel-solid/.meteor/packages new file mode 100644 index 0000000000..d6c05d244b --- /dev/null +++ b/tools/static-assets/skel-solid/.meteor/packages @@ -0,0 +1,23 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-base # Packages every Meteor app needs to have +mobile-experience # Packages for a great mobile UX +mongo # The database Meteor supports right now +reactive-var # Reactive variable for tracker + +standard-minifier-css # CSS minifier run for production mode +standard-minifier-js # JS minifier run for production mode +es5-shim # ECMAScript 5 compatibility for older browsers +ecmascript # Enable ECMAScript2015+ syntax in app code +typescript # Enable TypeScript syntax in .ts and .tsx modules +shell-server # Server-side component of the `meteor shell` command +hot-module-replacement # Update client in development without reloading the page + +autopublish # Publish all data to the clients (for prototyping) +insecure # Allow all DB writes from clients (for prototyping) +static-html # Define static page content in .html files +vite:bundler diff --git a/tools/static-assets/skel-solid/.meteor/platforms b/tools/static-assets/skel-solid/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/tools/static-assets/skel-solid/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/tools/static-assets/skel-solid/client/main.css b/tools/static-assets/skel-solid/client/main.css new file mode 100644 index 0000000000..7f354f0fa7 --- /dev/null +++ b/tools/static-assets/skel-solid/client/main.css @@ -0,0 +1,4 @@ +body { + padding: 10px; + font-family: sans-serif; +} diff --git a/tools/static-assets/skel-solid/client/main.html b/tools/static-assets/skel-solid/client/main.html new file mode 100644 index 0000000000..f6b88dd21d --- /dev/null +++ b/tools/static-assets/skel-solid/client/main.html @@ -0,0 +1,9 @@ + + ~name~ + + + + +
+ + diff --git a/tools/static-assets/skel-solid/client/main.jsx b/tools/static-assets/skel-solid/client/main.jsx new file mode 100644 index 0000000000..97d382a9bd --- /dev/null +++ b/tools/static-assets/skel-solid/client/main.jsx @@ -0,0 +1 @@ +// main entry point is in imports/ui/main.jsx diff --git a/tools/static-assets/skel-solid/imports/api/links.js b/tools/static-assets/skel-solid/imports/api/links.js new file mode 100644 index 0000000000..ffe1b58fbe --- /dev/null +++ b/tools/static-assets/skel-solid/imports/api/links.js @@ -0,0 +1,4 @@ +import { Mongo } from 'meteor/mongo'; + +export const LinksCollection = new Mongo.Collection('links'); + diff --git a/tools/static-assets/skel-solid/imports/ui/App.jsx b/tools/static-assets/skel-solid/imports/ui/App.jsx new file mode 100644 index 0000000000..53c80e4498 --- /dev/null +++ b/tools/static-assets/skel-solid/imports/ui/App.jsx @@ -0,0 +1,12 @@ +import { Hello } from "./Hello"; +import { Info } from "./Info"; + +export const App = () => ( +
+

Welcome to Meteor!

+ + +
+); + + diff --git a/tools/static-assets/skel-solid/imports/ui/Hello.jsx b/tools/static-assets/skel-solid/imports/ui/Hello.jsx new file mode 100644 index 0000000000..c23767f44d --- /dev/null +++ b/tools/static-assets/skel-solid/imports/ui/Hello.jsx @@ -0,0 +1,16 @@ +import { createSignal } from "solid-js"; + +export const Hello = () => { + const [counter, setCounter] = createSignal(0); + + const increment = () => { + setCounter(counter() + 1); + }; + + return ( +
+ +

You've pressed the button {counter()} times.

+
+ ); +} diff --git a/tools/static-assets/skel-solid/imports/ui/Info.jsx b/tools/static-assets/skel-solid/imports/ui/Info.jsx new file mode 100644 index 0000000000..6f4a441e79 --- /dev/null +++ b/tools/static-assets/skel-solid/imports/ui/Info.jsx @@ -0,0 +1,26 @@ +import { LinksCollection } from "../api/links"; +import { createSignal, For } from "solid-js"; +import { Tracker } from "meteor/tracker"; + +export const Info = () => { + const [links, setLinks] = createSignal([]); + + Tracker.autorun(() => { + setLinks(LinksCollection.find().fetch()); + }); + + return ( +
+

Learn Meteor!

+ +
+ ) + +} diff --git a/tools/static-assets/skel-solid/imports/ui/main.jsx b/tools/static-assets/skel-solid/imports/ui/main.jsx new file mode 100644 index 0000000000..99eb6c43d7 --- /dev/null +++ b/tools/static-assets/skel-solid/imports/ui/main.jsx @@ -0,0 +1,8 @@ +/* @refresh reload */ +import { render } from 'solid-js/web'; +import { App } from './App'; +import { Meteor } from "meteor/meteor"; + +Meteor.startup(() => { + render(() => , document.getElementById('root')); +}) diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json new file mode 100644 index 0000000000..e3cbb0efb5 --- /dev/null +++ b/tools/static-assets/skel-solid/package.json @@ -0,0 +1,28 @@ +{ + "name": "~name~", + "private": true, + "scripts": { + "start": "meteor run", + "test": "meteor test --once --driver-package meteortesting:mocha", + "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha", + "visualize": "meteor --production --extra-packages bundle-visualizer" + }, + "dependencies": { + "@babel/runtime": "^7.17.9", + "meteor-node-stubs": "^1.2.1", + "solid-js": "^1.5.4" + }, + "meteor": { + "mainModule": { + "client": "client/main.jsx", + "server": "server/main.js" + }, + "testModule": "tests/main.js" + }, + "devDependencies": { + "babel-preset-solid": "^1.5.4", + "vite": "^3.0.9", + "vite-plugin-solid": "^2.3.0", + "vite-plugin-solid-svg": "^0.4.1" + } +} diff --git a/tools/static-assets/skel-solid/server/main.js b/tools/static-assets/skel-solid/server/main.js new file mode 100644 index 0000000000..99eab74e29 --- /dev/null +++ b/tools/static-assets/skel-solid/server/main.js @@ -0,0 +1,31 @@ +import { Meteor } from 'meteor/meteor'; +import { LinksCollection } from '/imports/api/links'; + +async function insertLink({ title, url }) { + await LinksCollection.insertAsync({ title, url, createdAt: new Date() }); +} + +Meteor.startup(async () => { + // If the Links collection is empty, add some data. + if (await LinksCollection.find().countAsync() === 0) { + await insertLink({ + title: 'Do the Tutorial', + url: 'https://www.solidjs.com/tutorial/introduction_basics', + }); + + await insertLink({ + title: 'Follow the Guide', + url: 'https://guide.meteor.com', + }); + + await insertLink({ + title: 'Read the Docs', + url: 'https://docs.meteor.com', + }); + + await insertLink({ + title: 'Discussions', + url: 'https://forums.meteor.com', + }); + } +}); diff --git a/tools/static-assets/skel-solid/tests/main.js b/tools/static-assets/skel-solid/tests/main.js new file mode 100644 index 0000000000..6486533db3 --- /dev/null +++ b/tools/static-assets/skel-solid/tests/main.js @@ -0,0 +1,20 @@ +import assert from "assert"; + +describe("solid-template", function () { + it("package.json has correct name", async function () { + const { name } = await import("../package.json"); + assert.strictEqual(name, "solid-template"); + }); + + if (Meteor.isClient) { + it("client is not server", function () { + assert.strictEqual(Meteor.isServer, false); + }); + } + + if (Meteor.isServer) { + it("server is not client", function () { + assert.strictEqual(Meteor.isClient, false); + }); + } +}); diff --git a/tools/static-assets/skel-solid/vite.config.js b/tools/static-assets/skel-solid/vite.config.js new file mode 100644 index 0000000000..c49fa70b16 --- /dev/null +++ b/tools/static-assets/skel-solid/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import solidSvg from "vite-plugin-solid-svg"; + +export default defineConfig({ + plugins: [solidPlugin(), solidSvg({ + defaultExport: 'component', + })], + meteor: { + clientEntry: 'imports/ui/main.jsx', + }, +}); From 88a0bf88db2f2fd5c7c1adbf3b318bd5adc820ba Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 17 Oct 2022 17:30:44 -0300 Subject: [PATCH 0020/1965] # Conflicts: # packages/mongo/mongo_driver.js # packages/test-helpers/async_multi.js # packages/test-helpers/package.js # packages/tinytest/tinytest.js # packages/tinytest/tinytest_server.js --- packages/test-helpers/async_multi.js | 17 ++- packages/test-helpers/package.js | 5 +- packages/tinytest/tinytest.js | 153 +++++++++++++-------------- packages/tinytest/tinytest_server.js | 4 +- 4 files changed, 89 insertions(+), 90 deletions(-) diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index 9a76c20f21..04be6aedfe 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -142,12 +142,13 @@ testAsyncMulti = function (name, funcs, { isOnly = false } = {}) { test.extraDetails.asyncBlock = i++; new Promise(resolve => { - if (func.constructor.name === "Function") { - return resolve(func.apply(context, [test, _.bind(em.expect, em)])); + const result = func.apply(context, [test, _.bind(em.expect, em)]); + if (result && typeof result.then === "function") { + return result.then((r) => resolve(r)) } - return func.apply(context, [test, _.bind(em.expect, em)]).then((r) => resolve(r)); - }).then(result => { + return resolve(result); + }).then(() => { em.done(); }, exception => { if (em.cancel()) { @@ -196,6 +197,14 @@ pollUntil = function (expect, f, timeout, step, noFail) { ); }; +/** + * Helper that is used on the async tests. + * Just run the function and assert if we have an error or not. + * @param fn + * @param test + * @param shouldErrorOut + * @returns {Promise<*>} + */ runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => { let err, result; try { diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 561f207e4e..399e768cbe 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Utility functions for tests", - version: '1.3.0' + version: '1.3.1' }); Package.onUse(function (api) { @@ -28,7 +28,8 @@ Package.onUse(function (api) { 'SeededRandom', 'clickElement', 'blurElement', 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', 'renderToDiv', 'clickIt', - 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', 'runAndThrowIfNeeded', + 'withCallbackLogger', 'testAsyncMulti', + 'simplePoll', 'runAndThrowIfNeeded', 'makeTestConnection', 'DomUtils']); api.addFiles('try_all_permutations.js'); diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 62cf133894..f5cd025b98 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -184,6 +184,43 @@ export class TestCaseResults { this.ok(); } + _assertActual(actual, predicate, message) { + if (actual && predicate(actual)) + this.ok(); + else + this.fail({ + type: "throws", + message: (actual ? + "wrong error thrown: " + actual.message : + "did not throw an error as expected") + (message ? ": " + message : ""), + }); + } + + _guessPredicate(expected) { + let predicate; + + if (expected === undefined) { + predicate = function () { + return true; + }; + } else if (typeof expected === "string") { + predicate = function (actual) { + return typeof actual.message === "string" && + actual.message.indexOf(expected) !== -1; + }; + } else if (expected instanceof RegExp) { + predicate = function (actual) { + return expected.test(actual.message); + }; + } else if (typeof expected === 'function') { + predicate = expected; + } else { + throw new Error('expected should be a string, regexp, or predicate function'); + } + + return predicate; + } + // expected can be: // undefined: accept any exception. // string: pass if the string is a substring of the exception message. @@ -202,26 +239,8 @@ export class TestCaseResults { // particular class, use a predicate function. // throws(f, expected, message) { - var actual, predicate; - - if (expected === undefined) { - predicate = function (actual) { - return true; - }; - } else if (typeof expected === "string") { - predicate = function (actual) { - return typeof actual.message === "string" && - actual.message.indexOf(expected) !== -1; - }; - } else if (expected instanceof RegExp) { - predicate = function (actual) { - return expected.test(actual.message); - }; - } else if (typeof expected === 'function') { - predicate = expected; - } else { - throw new Error('expected should be a string, regexp, or predicate function'); - } + let actual; + const predicate = this._guessPredicate(expected); try { f(); @@ -229,15 +248,7 @@ export class TestCaseResults { actual = exception; } - if (actual && predicate(actual)) - this.ok(); - else - this.fail({ - type: "throws", - message: (actual ? - "wrong error thrown: " + actual.message : - "did not throw an error as expected") + (message ? ": " + message : ""), - }); + this._assertActual(actual, predicate, message); } /** @@ -248,26 +259,8 @@ export class TestCaseResults { * @returns {Promise} */ async throwsAsync(f, expected, message) { - var actual, predicate; - - if (expected === undefined) { - predicate = function (actual) { - return true; - }; - } else if (typeof expected === "string") { - predicate = function (actual) { - return typeof actual.message === "string" && - actual.message.indexOf(expected) !== -1; - }; - } else if (expected instanceof RegExp) { - predicate = function (actual) { - return expected.test(actual.message); - }; - } else if (typeof expected === 'function') { - predicate = expected; - } else { - throw new Error('expected should be a string, regexp, or predicate function'); - } + let actual; + const predicate = this._guessPredicate(expected); try { await f(); @@ -275,15 +268,7 @@ export class TestCaseResults { actual = exception; } - if (actual && predicate(actual)) - this.ok(); - else - this.fail({ - type: "throws", - message: (actual ? - "wrong error thrown: " + actual.message : - "did not throw an error as expected") + (message ? ": " + message : ""), - }); + this._assertActual(actual, predicate, message); } isTrue(v, msg) { @@ -353,7 +338,7 @@ export class TestCaseResults { pass = true; } } else { - /* fail -- not something that contains other things */; + /* fail -- not something that contains other things */ } if (pass === ! not) { @@ -590,30 +575,34 @@ export class TestRun { } if (Meteor.isServer) { - // On the server, ensure that only one test runs at a time, even - // with multiple clients. - let hasRan = false; - const timeoutPromise = new Promise((resolve, reject) => { - Meteor.setTimeout(() => { - if (hasRan) { - resolve(); - } - - reject(); - }, 3 * 60 * 1000); - }); - const runnerPromise = new Promise((resolve) => { - this._runTest(test, () => { - // The test can complete after it has timed out (it might - // just be slow), so only resolve the future if the test - // hasn't timed out. - if (!hasRan) { - hasRan = true; - resolve(); - } - }, stop_at_offset); - }); this.manager.testQueue.queueTask(() => { + // On the server, ensure that only one test runs at a time, even + // with multiple clients. + let hasRan = false; + const timeoutPromise = new Promise((resolve) => { + Meteor.setTimeout(() => { + if (!hasRan) { + test.timedOut = true; + this._report(test, { + type: "exception", + details: { + message: "test timed out" + } + }); + } + + resolve(); + }, 3 * 60 * 1000); + }); + const runnerPromise = new Promise((resolve) => { + this._runTest(test, () => { + if (!hasRan) { + hasRan = true; + } + resolve(); + }, stop_at_offset); + }); + Promise.race([runnerPromise, timeoutPromise]).finally(() => { onComplete && onComplete(); }); diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index 8336498734..331a7007e7 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -9,7 +9,7 @@ import { export { Tinytest }; -const Fiber = require('fibers'); +const Fiber = Meteor._isFibersEnabled && require('fibers'); const handlesForRun = new Map; const reportsForRun = new Map; @@ -58,7 +58,7 @@ Meteor.methods({ } function onReport(report) { - if (!Fiber.current && Meteor._isFibersEnabled) { + if (Fiber && !Fiber.current) { Meteor._debug("Trying to report a test not in a fiber! "+ "You probably forgot to wrap a callback in bindEnvironment."); console.trace(); From e6efc44c2e6dcc7ee80217ef9b0d85e75931aba3 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 19 Oct 2022 17:20:52 -0300 Subject: [PATCH 0021/1965] Create async queue and add helpers to OrderedDict and IdMap --- packages/callback-hook/hook.js | 19 ++ packages/id-map/id-map.js | 13 ++ packages/meteor/asl-helpers.js | 4 + packages/meteor/fiber_helpers.js | 288 ++++++++++---------------- packages/ordered-dict/ordered_dict.js | 11 + packages/ordered-dict/package.js | 2 +- 6 files changed, 161 insertions(+), 176 deletions(-) diff --git a/packages/callback-hook/hook.js b/packages/callback-hook/hook.js index ca6c1311cb..69fa487bb0 100644 --- a/packages/callback-hook/hook.js +++ b/packages/callback-hook/hook.js @@ -84,6 +84,11 @@ export class Hook { }; } + clear() { + this.nextCallbackId = 0; + this.callbacks = []; + } + /** * For each registered callback, call the passed iterator function with the callback. * @@ -116,6 +121,20 @@ export class Hook { } } + async forEachAsync(iterator) { + const ids = Object.keys(this.callbacks); + for (let i = 0; i < ids.length; ++i) { + const id = ids[i]; + // check to see if the callback was removed during iteration + if (hasOwn.call(this.callbacks, id)) { + const callback = this.callbacks[id]; + if (!await iterator(callback)) { + break; + } + } + } + } + /** * @deprecated use forEach * @param iterator diff --git a/packages/id-map/id-map.js b/packages/id-map/id-map.js index 7824848898..c0c58ff057 100644 --- a/packages/id-map/id-map.js +++ b/packages/id-map/id-map.js @@ -54,6 +54,19 @@ export class IdMap { } } + async forEachAsync(iterator) { + for (let [key, value] of this._map){ + const breakIfFalse = await iterator.call( + null, + value, + this._idParse(key) + ); + if (breakIfFalse === false) { + return; + } + } + } + size() { return this._map.size; } diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js index 27d9b227cb..3701538c04 100644 --- a/packages/meteor/asl-helpers.js +++ b/packages/meteor/asl-helpers.js @@ -20,3 +20,7 @@ Meteor._runAsync = (fn, ctx) => { fn.call(ctx); }); }; + +Meteor._isPromise = (r) => { + return r && typeof r.then === 'function'; +}; diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 2bffec369e..d4bb486551 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -36,215 +36,153 @@ Meteor._DoubleEndedQueue = Npm.require('double-ended-queue'); // XXX could maybe use the npm 'schlock' module instead, which would // also support multiple concurrent "read" tasks // +class AsynchronousQueue { + constructor() { + this._taskHandles = new Meteor._DoubleEndedQueue(); + this._runningOrRunScheduled = false; + // This is true if we're currently draining. While we're draining, a further + // drain is a noop, to prevent infinite loops. "drain" is a heuristic type + // operation, that has a meaning like unto "what a naive person would expect + // when modifying a table from an observe" + this._draining = false; + } + + queueTask(task) { + this._taskHandles.push({ + task: task, + name: task.name + }); + return this._scheduleRun(); + } + + _scheduleRun() { + // Already running or scheduled? Do nothing. + if (this._runningOrRunScheduled) + return; + + this._runningOrRunScheduled = true; + + let resolver; + const returnValue = new Promise(r => resolver = r); + setImmediate(async () => { + await this._run(); + + if (!resolver) { + throw new Error("Resolver not found for task"); + } + + resolver(); + }); + + return returnValue; + } + + async _run() { + if (!this._runningOrRunScheduled) + throw new Error("expected to be _runningOrRunScheduled"); + + if (this._taskHandles.isEmpty()) { + // Done running tasks! Don't immediately schedule another run, but + // allow future tasks to do so. + this._runningOrRunScheduled = false; + return; + } + const taskHandle = this._taskHandles.shift(); + + // Run the task. + try { + await taskHandle.task(); + } catch (err) { + Meteor._debug("Exception in queued task", err); + } + + // Soon, run the next task, if there is any. + this._runningOrRunScheduled = false; + this._scheduleRun(); + } + + runTask(task) { + const handle = { + task: Meteor.bindEnvironment(task, function(e) { + Meteor._debug('Exception from task', e); + throw e; + }), + name: task.name + }; + this._taskHandles.push(handle); + return this._scheduleRun(); + } + + async flush() { + await this.runTask(() => {}); + } + + drain() { + if (this._draining) + return; + if (this._taskHandles.isEmpty()) + return; + + this._draining = true; + + return Promise.all(this._taskHandles).finally(() => { + this._draining = false; + }); + } +} + +Meteor._AsynchronousQueue = AsynchronousQueue; + +const runWithFibers = (fn) => { + if (!Meteor._isFibersEnabled) return fn(); + + Promise.await(fn()); +}; + Meteor._SynchronousQueue = function () { - var self = this; - // List of tasks to run (not including a currently-running task if any). Each - // is an object with field 'task' (the task function to run) and 'future' (the - // Future associated with the blocking runTask call that queued it, or null if - // called from queueTask). - self._taskHandles = new Meteor._DoubleEndedQueue(); - // This is true if self._run() is either currently executing or scheduled to - // do so soon. - self._runningOrRunScheduled = false; + const self = this; // During the execution of a task, this is set to the fiber used to execute // that task. We use this to throw an error rather than deadlocking if the // user calls runTask from within a task on the same fiber. self._currentTaskFiber = undefined; - // This is true if we're currently draining. While we're draining, a further - // drain is a noop, to prevent infinite loops. "drain" is a heuristic type - // operation, that has a meaning like unto "what a naive person would expect - // when modifying a table from an observe" - self._draining = false; + self._asyncQueue = new AsynchronousQueue(); }; var SQp = Meteor._SynchronousQueue.prototype; -const runTaskWithFibers = ({ task, self }) => { - 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'); - } - } - - const fut = new Future(); - const handle = { - task: Meteor.bindEnvironment(task, function(e) { - Meteor._debug('Exception from task', e); - throw e; - }), - future: fut, - name: task.name - }; - self._taskHandles.push(handle); - self._scheduleRun(); - // Yield. We'll get back here after the task is run (and will throw if the - // task throws). - fut.wait(); -}; - -const runTask = ({ task, self }) => { - const handle = { - task: Meteor.bindEnvironment(task, function(e) { - Meteor._debug('Exception from task', e); - throw e; - }), - name: task.name, - }; - - self._taskHandles.push(handle); - self._scheduleRun(); -}; - -SQp.runTask = function(task) { - const self = this; - if (Meteor._isFibersEnabled) { - runTaskWithFibers({ task, self }); - return; - } - runTask({ task, self }); +SQp.runTask = function (task) { + var self = this; + runWithFibers(() => self._asyncQueue.runTask(task)); }; SQp.queueTask = function (task) { var self = this; - self._taskHandles.push({ - task: task, - name: task.name - }); - self._scheduleRun(); - // No need to block. + self._asyncQueue.queueTask(task); }; SQp.flush = function () { var self = this; - self.runTask(function () {}); + runWithFibers(self._asyncQueue.flush); }; - -// TODO -> Check if we are already running a task. SQp.safeToRunTask = function () { - if (!Meteor._isFibersEnabled) { - return true; - } - var self = this; return Fiber.current && self._currentTaskFiber !== Fiber.current; }; SQp.drain = function () { var self = this; - if (self._draining) - return; - if (!self.safeToRunTask()) - return; - self._draining = true; - while (! self._taskHandles.isEmpty()) { - self.flush(); - } - self._draining = false; + runWithFibers(self._asyncQueue.drain()); }; SQp._scheduleRun = function () { var self = this; - // Already running or scheduled? Do nothing. - if (self._runningOrRunScheduled) - return; - - self._runningOrRunScheduled = true; - - /** - * FIXME: - * Here seems like we should defer and also yield while the handler is not - * finished... - * For autoupdate, for example, swapping to just running it sync won't make a difference, - * but maybe there is another place that would? (DDP/Web)-Server? - */ - setImmediate(function() { - Meteor._runAsync(Meteor._isFibersEnabled ? self._run : self._runAsync, self); - }); -}; - -SQp._runAsync = async function() { - var self = this; - - if (!self._runningOrRunScheduled) - throw new Error("expected to be _runningOrRunScheduled"); - - if (self._taskHandles.isEmpty()) { - // Done running tasks! Don't immediately schedule another run, but - // allow future tasks to do so. - self._runningOrRunScheduled = false; - return; - } - var taskHandle = self._taskHandles.shift(); - - // Run the task. - try { - await taskHandle.task(); - } catch (err) { - Meteor._debug("Exception in queued task", err); - } - - // Soon, run the next task, if there is any. - self._runningOrRunScheduled = false; - self._scheduleRun(); + self._asyncQueue._scheduleRun(); }; SQp._run = function () { var self = this; - - if (!self._runningOrRunScheduled) - throw new Error("expected to be _runningOrRunScheduled"); - - if (self._taskHandles.isEmpty()) { - // Done running tasks! Don't immediately schedule another run, but - // allow future tasks to do so. - self._runningOrRunScheduled = false; - return; - } - var taskHandle = self._taskHandles.shift(); - - var exception = undefined; - function runFiber() { - self._currentTaskFiber = Fiber.current; - try { - taskHandle.task(); - } catch (err) { - if (taskHandle.future) { - // We'll throw this exception through runTask. - exception = err; - } else { - Meteor._debug("Exception in queued task", err); - } - } - self._currentTaskFiber = undefined; - } - // Run the task. - if (Meteor._isFibersEnabled) { - runFiber(); - } else { - try { - taskHandle.task(); - } catch (err) { - Meteor._debug("Exception in queued task", err); - } - } - - // Soon, run the next task, if there is any. - self._runningOrRunScheduled = false; - self._scheduleRun(); - - if (Meteor._isFibersEnabled) { - // If this was queued with runTask, let the runTask call return (throwing if - // the task threw). - if (taskHandle.future) { - if (exception) - taskHandle.future['throw'](exception); - else - taskHandle.future['return'](); - } - } + runWithFibers(self._asyncQueue._run); }; // Sleep. Mostly used for debugging (eg, inserting latency into server diff --git a/packages/ordered-dict/ordered_dict.js b/packages/ordered-dict/ordered_dict.js index dc759d7b9e..471d334f89 100644 --- a/packages/ordered-dict/ordered_dict.js +++ b/packages/ordered-dict/ordered_dict.js @@ -128,6 +128,17 @@ export class OrderedDict { } } + async forEachAsync(asyncIter, context = null) { + let i = 0; + let elt = this._first; + while (elt !== null) { + const b = await asyncIter.call(context, elt.value, elt.key, i); + if (b === OrderedDict.BREAK) return; + elt = elt.next; + i++; + } + } + first() { if (this.empty()) { return; diff --git a/packages/ordered-dict/package.js b/packages/ordered-dict/package.js index d235512a69..9042f64bda 100644 --- a/packages/ordered-dict/package.js +++ b/packages/ordered-dict/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Ordered traversable dictionary with a mutable ordering", - version: '1.1.0', + version: '1.1.1', documentation: null }); From 6b4345b6a37e724f683dcb2a248506e297928577 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 19 Oct 2022 17:21:52 -0300 Subject: [PATCH 0022/1965] Refactor and modernize observe_multiplex.js --- packages/mongo/observe_multiplex.js | 268 ++++++++++++---------------- 1 file changed, 110 insertions(+), 158 deletions(-) diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index 9e0acf3c20..3b40d58ac6 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -1,89 +1,53 @@ -var Future = Npm.require('fibers/future'); +let nextObserveHandleId = 1; -ObserveMultiplexer = function (options) { - var self = this; +ObserveMultiplexer = class { + constructor({ ordered, onStop = () => {} } = {}) { + if (ordered === undefined) throw Error("must specify ordered"); - if (!options || !_.has(options, 'ordered')) - throw Error("must specified ordered"); + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-multiplexers", 1); - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-multiplexers", 1); + this._ordered = ordered; + this._onStop = onStop; + this._queue = new Meteor._AsynchronousQueue(); + this._handles = {}; + this._resolver = null; + this._readyPromise = new Promise(r => this._resolver = r).then(() => this._isReady = true); + this._cache = new LocalCollection._CachingChangeObserver({ + ordered}); + // Number of addHandleAndSendInitialAdds tasks scheduled but not yet + // running. removeHandle uses this to know if it's time to call the onStop + // callback. + this._addHandleTasksScheduledButNotPerformed = 0; - self._ordered = options.ordered; - self._onStop = options.onStop || function () {}; - self._queue = new Meteor._SynchronousQueue(); - self._handles = {}; - self._readyFuture = new Future; - self._isReady = false; - self._cache = new LocalCollection._CachingChangeObserver({ - ordered: options.ordered}); - // Number of addHandleAndSendInitialAdds tasks scheduled but not yet - // running. removeHandle uses this to know if it's time to call the onStop - // callback. - self._addHandleTasksScheduledButNotPerformed = 0; + const self = this; + this.callbackNames().forEach(callbackName => { + this[callbackName] = function(/* ... */) { + self._applyCallback(callbackName, _.toArray(arguments)); + }; + }); + } - _.each(self.callbackNames(), function (callbackName) { - self[callbackName] = function (/* ... */) { - self._applyCallback(callbackName, _.toArray(arguments)); - }; - }); -}; + addHandleAndSendInitialAdds(handle) { + return Meteor._isFibersEnabled ? Promise.await(this._addHandleAndSendInitialAdds(handle)) : this._addHandleAndSendInitialAdds(handle); + } -_.extend(ObserveMultiplexer.prototype, { - _addHandleAndSendInitialAddsFibers: function (handle) { - var self = this; - - // Check this before calling runTask (even though runTask does the same - // check) so that we don't leak an ObserveMultiplexer on error by - // incrementing _addHandleTasksScheduledButNotPerformed and never - // decrementing it. - if (!self._queue.safeToRunTask()) - throw new Error("Can't call observeChanges from an observe callback on the same query"); - ++self._addHandleTasksScheduledButNotPerformed; + async _addHandleAndSendInitialAdds(handle) { + ++this._addHandleTasksScheduledButNotPerformed; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( "mongo-livedata", "observe-handles", 1); - self._queue.runTask(function () { + const self = this; + await this._queue.runTask(function () { self._handles[handle._id] = handle; // Send out whatever adds we have so far (whether the // multiplexer is ready). self._sendAdds(handle); --self._addHandleTasksScheduledButNotPerformed; }); - - // *outside* the task, since otherwise we'd deadlock - self._readyFuture.wait(); - }, - _addHandleAndSendInitialAddsNoFibers: async function (handle) { - var self = this; - - // Check this before calling runTask (even though runTask does the same - // check) so that we don't leak an ObserveMultiplexer on error by - // incrementing _addHandleTasksScheduledButNotPerformed and never - // decrementing it. - if (!self._queue.safeToRunTask()) - throw new Error("Can't call observeChanges from an observe callback on the same query"); - ++self._addHandleTasksScheduledButNotPerformed; - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-handles", 1); - - self._queue.runTask(function () { - self._handles[handle._id] = handle; - // Send out whatever adds we have so far (whether the - // multiplexer is ready). - self._sendAdds(handle); - --self._addHandleTasksScheduledButNotPerformed; - }); - - while (!self._isReady) { - await Meteor._sleepForMs(100); - } - }, - addHandleAndSendInitialAdds: function (handle) { - return Meteor._isFibersEnabled ? this._addHandleAndSendInitialAddsFibers(handle) : this._addHandleAndSendInitialAddsNoFibers(handle); - }, + await this._readyPromise; + } // Remove an observe handle. If it was the last observe handle, call the // onStop callback; you cannot add any more observe handles after this. @@ -91,60 +55,58 @@ _.extend(ObserveMultiplexer.prototype, { // This is not synchronized with polls and handle additions: this means that // you can safely call it from within an observe callback, but it also means // that we have to be careful when we iterate over _handles. - removeHandle: function (id) { - var self = this; - + async removeHandle(id) { // This should not be possible: you can only call removeHandle by having // access to the ObserveHandle, which isn't returned to user code until the // multiplex is ready. - if (!self._ready()) + if (!this._ready()) throw new Error("Can't remove handles until the multiplex is ready"); - delete self._handles[id]; + delete this._handles[id]; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-handles", -1); + "mongo-livedata", "observe-handles", -1); - if (_.isEmpty(self._handles) && - self._addHandleTasksScheduledButNotPerformed === 0) { - self._stop(); + if (_.isEmpty(this._handles) && + this._addHandleTasksScheduledButNotPerformed === 0) { + await this._stop(); } - }, - _stop: function (options) { - var self = this; + } + async _stop(options) { options = options || {}; // It shouldn't be possible for us to stop when all our handles still // haven't been returned from observeChanges! - if (! self._ready() && ! options.fromQueryError) + if (! this._ready() && ! options.fromQueryError) throw Error("surprising _stop: not ready"); // Call stop callback (which kills the underlying process which sends us // callbacks and removes us from the connection's dictionary). - self._onStop(); + await this._onStop(); Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-multiplexers", -1); + "mongo-livedata", "observe-multiplexers", -1); // Cause future addHandleAndSendInitialAdds calls to throw (but the onStop // callback should make our connection forget about us). - self._handles = null; - }, + this._handles = null; + } // Allows all addHandleAndSendInitialAdds calls to return, once all preceding // adds have been processed. Does not block. - ready: function () { - var self = this; - self._queue.queueTask(function () { + ready() { + const self = this; + this._queue.queueTask(function () { if (self._ready()) throw Error("can't make ObserveMultiplex ready twice!"); - if (Meteor._isFibersEnabled) { - self._readyFuture.return(); - return; + + if (!self._resolver) { + throw new Error("Missing resolver"); } + self._resolver(); self._isReady = true; }); - }, + } // If trying to execute the query results in an error, call this. This is // intended for permanent errors, not transient network errors that could be @@ -152,47 +114,45 @@ _.extend(ObserveMultiplexer.prototype, { // that meant that you managed to run the query once. It will stop this // ObserveMultiplex and cause addHandleAndSendInitialAdds calls (and thus // observeChanges calls) to throw the error. - queryError: function (err) { + async queryError(err) { var self = this; - self._queue.runTask(function () { + await this._queue.runTask(function () { if (self._ready()) throw Error("can't claim query has an error after it worked!"); self._stop({fromQueryError: true}); - self._readyFuture.throw(err); + throw err; }); - }, + } // Calls "cb" once the effects of all "ready", "addHandleAndSendInitialAdds" // and observe callbacks which came before this call have been propagated to // all handles. "ready" must have already been called on this multiplexer. - onFlush: function (cb) { + onFlush(cb) { var self = this; - self._queue.queueTask(function () { + this._queue.queueTask(async function () { if (!self._ready()) throw Error("only call onFlush on a multiplexer that will be ready"); - cb(); + await cb(); }); - }, - callbackNames: function () { - var self = this; - if (self._ordered) + } + callbackNames() { + if (this._ordered) return ["addedBefore", "changed", "movedBefore", "removed"]; else return ["added", "changed", "removed"]; - }, - _ready: function () { - return Meteor._isFibersEnabled ? this._readyFuture.isResolved() : !!this._isReady; - }, - _applyCallback: function (callbackName, args) { - var self = this; - self._queue.queueTask(async function () { + } + _ready() { + return !!this._isReady; + } + _applyCallback(callbackName, args) { + const self = this; + this._queue.queueTask(async function () { // If we stopped in the meantime, do nothing. if (!self._handles) return; // First, apply the change to the cache. - self._cache.applyChange[callbackName].apply(null, args); - + await self._cache.applyChange[callbackName].apply(null, args); // If we haven't finished the initial adds, then we should only be getting // adds. if (!self._ready() && @@ -217,63 +177,55 @@ _.extend(ObserveMultiplexer.prototype, { await Promise.all(toAwait); }); - }, + } // Sends initial adds to a handle. It should only be called from within a task // (the task that is processing the addHandleAndSendInitialAdds call). It // synchronously invokes the handle's added or addedBefore; there's no need to // flush the queue afterwards to ensure that the callbacks get out. - _sendAdds: function (handle) { - var self = this; - if (self._queue.safeToRunTask() && Meteor._isFibersEnabled) - throw Error("_sendAdds may only be called from within a task!"); - var add = self._ordered ? handle._addedBefore : handle._added; + async _sendAdds(handle) { + var add = this._ordered ? handle._addedBefore : handle._added; if (!add) return; // note: docs may be an _IdMap or an OrderedDict - self._cache.docs.forEach(function (doc, id) { - if (!_.has(self._handles, handle._id)) + await this._cache.docs.forEachAsync(async (doc, id) => { + if (!_.has(this._handles, handle._id)) throw Error("handle got removed before sending initial adds!"); const { _id, ...fields } = handle.nonMutatingCallbacks ? doc - : EJSON.clone(doc); - if (self._ordered) - add(id, fields, null); // we're going in order, so add at end + : EJSON.clone(doc); + if (this._ordered) + await add(id, fields, null); // we're going in order, so add at end else - add(id, fields); + await add(id, fields); }); } -}); - - -var nextObserveHandleId = 1; +}; // When the callbacks do not mutate the arguments, we can skip a lot of data clones -ObserveHandle = function (multiplexer, callbacks, nonMutatingCallbacks = false) { - var self = this; - // The end user is only supposed to call stop(). The other fields are - // accessible to the multiplexer, though. - self._multiplexer = multiplexer; - _.each(multiplexer.callbackNames(), function (name) { - if (callbacks[name]) { - self['_' + name] = callbacks[name]; - } else if (name === "addedBefore" && callbacks.added) { - // Special case: if you specify "added" and "movedBefore", you get an - // ordered observe where for some reason you don't get ordering data on - // the adds. I dunno, we wrote tests for it, there must have been a - // reason. - self._addedBefore = function (id, fields, before) { - callbacks.added(id, fields); - }; - } - }); - self._stopped = false; - self._id = nextObserveHandleId++; - self.nonMutatingCallbacks = nonMutatingCallbacks; -}; -ObserveHandle.prototype.stop = function () { - var self = this; - if (self._stopped) - return; - self._stopped = true; - self._multiplexer.removeHandle(self._id); +ObserveHandle = class { + constructor(multiplexer, callbacks, nonMutatingCallbacks = false) { + this._multiplexer = multiplexer; + multiplexer.callbackNames().forEach((name) => { + if (callbacks[name]) { + this['_' + name] = callbacks[name]; + } else if (name === "addedBefore" && callbacks.added) { + // Special case: if you specify "added" and "movedBefore", you get an + // ordered observe where for some reason you don't get ordering data on + // the adds. I dunno, we wrote tests for it, there must have been a + // reason. + this._addedBefore = function (id, fields, before) { + callbacks.added(id, fields); + }; + } + }); + this._stopped = false; + this._id = nextObserveHandleId++; + this.nonMutatingCallbacks = nonMutatingCallbacks; + } + + async stop() { + if (this._stopped) return; + this._stopped = true; + await this._multiplexer.removeHandle(this._id); + } }; From e52001e7d340e8d2264ee8f8e2db642b3822c936 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 19 Oct 2022 17:22:22 -0300 Subject: [PATCH 0023/1965] Fix conflicts with merge on latest 2.8. --- packages/meteor/dynamics_nodejs.js | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index b53f7c7c11..db02f9bcc7 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -99,12 +99,11 @@ EVp.withValue = async function (value, func) { meteorDynamics = []; } - const saved = meteorDynamics[this.slot]; + const saved = meteorDynamics[this.slot] || {}; try { meteorDynamics[this.slot] = value; Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); - const result = await func(); - return result; + return await func(); } finally { meteorDynamics[this.slot] = saved; Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); @@ -120,18 +119,33 @@ EVp.withValue = async function (value, func) { * @param {Function} func The function to run * @return {Any} Return value of function */ - EVp._set = function (context) { - Meteor._nodeCodeMustBeInFiber(); - Fiber.current._meteor_dynamics[this.slot] = context; + if (Meteor._isFibersEnabled) { + Meteor._nodeCodeMustBeInFiber(); + Fiber.current._meteor_dynamics[this.slot] = context; + return; + } + + Meteor._updateAslStore("_meteor_dynamics", context); }; EVp._setNewContextAndGetCurrent = function (value) { - Meteor._nodeCodeMustBeInFiber(); - if (!Fiber.current._meteor_dynamics) { - Fiber.current._meteor_dynamics = []; + if (Meteor._isFibersEnabled) { + Meteor._nodeCodeMustBeInFiber(); + if (!Fiber.current._meteor_dynamics) { + Fiber.current._meteor_dynamics = []; + } + const saved = Fiber.current._meteor_dynamics[this.slot]; + this._set(value); + return saved; } - const saved = Fiber.current._meteor_dynamics[this.slot]; + + let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); + if (!meteorDynamics) { + meteorDynamics = []; + } + + const saved = meteorDynamics[this.slot]; this._set(value); return saved; }; From fb5e207e431e1132f361fb2e8f2b03a2f5347c21 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 20 Oct 2022 23:24:38 -0300 Subject: [PATCH 0024/1965] Improving refactor of synchronous queue. --- packages/meteor/fiber_helpers.js | 116 +++++++++++++------------------ 1 file changed, 48 insertions(+), 68 deletions(-) diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index d4bb486551..431f3543d7 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -64,14 +64,16 @@ class AsynchronousQueue { let resolver; const returnValue = new Promise(r => resolver = r); - setImmediate(async () => { - await this._run(); + setImmediate(() => { + Meteor._runAsync(async () => { + await this._run(); - if (!resolver) { - throw new Error("Resolver not found for task"); - } + if (!resolver) { + throw new Error("Resolver not found for task"); + } - resolver(); + resolver(); + }); }); return returnValue; @@ -98,7 +100,7 @@ class AsynchronousQueue { // Soon, run the next task, if there is any. this._runningOrRunScheduled = false; - this._scheduleRun(); + await this._scheduleRun(); } runTask(task) { @@ -113,90 +115,68 @@ class AsynchronousQueue { return this._scheduleRun(); } - async flush() { - await this.runTask(() => {}); + flush() { + return this.runTask(() => {}); } - drain() { + async drain() { if (this._draining) return; - if (this._taskHandles.isEmpty()) - return; this._draining = true; - - return Promise.all(this._taskHandles).finally(() => { - this._draining = false; - }); + while (!this._taskHandles.isEmpty()) { + await this.flush(); + } + this._draining = false; } } Meteor._AsynchronousQueue = AsynchronousQueue; -const runWithFibers = (fn) => { - if (!Meteor._isFibersEnabled) return fn(); +Meteor._SynchronousQueue = class extends AsynchronousQueue { + constructor() { + super(); + // During the execution of a task, this is set to the fiber used to execute + // that task. We use this to throw an error rather than deadlocking if the + // user calls runTask from within a task on the same fiber. + this._currentTaskFiber = undefined; + } - Promise.await(fn()); -}; + runWithFibers(fn, args) { + if (!Meteor._isFibersEnabled) { + return fn.apply(this, args); + } -Meteor._SynchronousQueue = function () { - const self = this; - // During the execution of a task, this is set to the fiber used to execute - // that task. We use this to throw an error rather than deadlocking if the - // user calls runTask from within a task on the same fiber. - self._currentTaskFiber = undefined; - self._asyncQueue = new AsynchronousQueue(); -}; + return Promise.await(fn.apply(this, args)); + } -var SQp = Meteor._SynchronousQueue.prototype; + runTask(task) { + this.runWithFibers(super.runTask, [task]); + } -SQp.runTask = function (task) { - var self = this; - runWithFibers(() => self._asyncQueue.runTask(task)); -}; + flush() { + this.runWithFibers(super.flush); + } -SQp.queueTask = function (task) { - var self = this; - self._asyncQueue.queueTask(task); -}; + safeToRunTask() { + return Fiber.current && this._currentTaskFiber !== Fiber.current; + } -SQp.flush = function () { - var self = this; - runWithFibers(self._asyncQueue.flush); -}; + drain() { + this.runWithFibers(super.drain); + } -SQp.safeToRunTask = function () { - var self = this; - return Fiber.current && self._currentTaskFiber !== Fiber.current; -}; - -SQp.drain = function () { - var self = this; - runWithFibers(self._asyncQueue.drain()); -}; - -SQp._scheduleRun = function () { - var self = this; - self._asyncQueue._scheduleRun(); -}; - -SQp._run = function () { - var self = this; - runWithFibers(self._asyncQueue._run); + _run() { + this.runWithFibers(super._run); + } }; // Sleep. Mostly used for debugging (eg, inserting latency into server // methods). // +const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); Meteor._sleepForMs = function (ms) { - if (Meteor._isFibersEnabled) { - var fiber = Fiber.current; - setTimeout(function() { - fiber.run(); - }, ms); - Fiber.yield(); - return; - } + if (!Meteor._isFibersEnabled) return _sleep(ms); - return new Promise(resolve => setTimeout(() => resolve(), ms)); + Promise.await(_sleep(ms)); }; From 53ff57b492d6ec9c5faa77af0175fcdc55b43056 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 20 Oct 2022 23:55:58 -0300 Subject: [PATCH 0025/1965] Improving write fence. --- packages/ddp-server/writefence.js | 254 +++++++++++++----------------- 1 file changed, 113 insertions(+), 141 deletions(-) diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index f911ae71b7..d85f028ff8 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -1,18 +1,121 @@ -var Future = Npm.require('fibers/future'); - // A write fence collects a group of writes, and provides a callback // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) // -DDPServer._WriteFence = function () { - var self = this; +DDPServer._WriteFence = class { + constructor() { + this.armed = false; + this.fired = false; + this.retired = false; + this.outstanding_writes = 0; + this.before_fire_callbacks = []; + this.completion_callbacks = []; + } - self.armed = false; - self.fired = false; - self.retired = false; - self.outstanding_writes = 0; - self.before_fire_callbacks = []; - self.completion_callbacks = []; + // Start tracking a write, and return an object to represent it. The + // object has a single method, committed(). This method should be + // called when the write is fully committed and propagated. You can + // continue to add writes to the WriteFence up until it is triggered + // (calls its callbacks because all writes have committed.) + beginWrite() { + if (this.retired) + return { committed: function () {} }; + + if (this.fired) + throw new Error("fence has already activated -- too late to add writes"); + + this.outstanding_writes++; + let committed = false; + const _committedFn = async () => { + if (committed) + throw new Error("committed called twice on the same write"); + committed = true; + this.outstanding_writes--; + await this._maybeFire(); + }; + + const self = this; + return { + committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, + }; + } + + // Arm the fence. Once the fence is armed, and there are no more + // uncommitted writes, it will activate. + arm() { + if (this === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); + this.armed = true; + return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); + } + + // Register a function to be called once before firing the fence. + // Callback function can add new writes to the fence, in which case + // it won't fire until those writes are done as well. + onBeforeFire(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.before_fire_callbacks.push(func); + } + + // Register a function to be called when the fence fires. + onAllCommitted(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.completion_callbacks.push(func); + } + + _armAndWait() { + let resolver; + const returnValue = new Promise(r => resolver = r); + this.onAllCommitted(resolver); + this.arm(); + + return returnValue; + } + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait() { + return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait(); + } + + async _maybeFire() { + if (this.fired) + throw new Error("write fence already activated?"); + if (this.armed && !this.outstanding_writes) { + const invokeCallback = async (func) => { + try { + await func(this); + } catch (err) { + Meteor._debug("exception in write fence callback:", err); + } + }; + + this.outstanding_writes++; + while (this.before_fire_callbacks.length > 0) { + const cb = this.before_fire_callbacks.shift(); + await invokeCallback(cb); + } + this.outstanding_writes--; + + if (!this.outstanding_writes) { + this.fired = true; + while (this.completion_callbacks.length > 0) { + const cb = this.completion_callbacks.shift(); + await invokeCallback(cb); + } + } + } + } + + // Deactivate this fence so that adding more writes has no effect. + // The fence must have already fired. + retire() { + if (!this.fired) + throw new Error("Can't retire a fence that hasn't fired."); + this.retired = true; + } }; // The current write fence. When there is a current write fence, code @@ -20,134 +123,3 @@ DDPServer._WriteFence = function () { // beginWrite(). // DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; - -_.extend(DDPServer._WriteFence.prototype, { - // Start tracking a write, and return an object to represent it. The - // object has a single method, committed(). This method should be - // called when the write is fully committed and propagated. You can - // continue to add writes to the WriteFence up until it is triggered - // (calls its callbacks because all writes have committed.) - beginWrite: function () { - var self = this; - - if (self.retired) - return { committed: function () {} }; - - if (self.fired) - throw new Error("fence has already activated -- too late to add writes"); - - self.outstanding_writes++; - var committed = false; - return { - committed: function () { - if (committed) - throw new Error("committed called twice on the same write"); - committed = true; - self.outstanding_writes--; - self._maybeFire(); - } - }; - }, - - // Arm the fence. Once the fence is armed, and there are no more - // uncommitted writes, it will activate. - arm: function () { - var self = this; - if (self === DDPServer._CurrentWriteFence.get()) - throw Error("Can't arm the current fence"); - self.armed = true; - self._maybeFire(); - }, - - // Register a function to be called once before firing the fence. - // Callback function can add new writes to the fence, in which case - // it won't fire until those writes are done as well. - onBeforeFire: function (func) { - var self = this; - if (self.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - self.before_fire_callbacks.push(func); - }, - - // Register a function to be called when the fence fires. - onAllCommitted: function (func) { - var self = this; - if (self.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - self.completion_callbacks.push(func); - }, - - _armAndWaitFibers: function () { - var self = this; - var future = new Future; - self.onAllCommitted(function () { - future['return'](); - }); - self.arm(); - future.wait(); - }, - _armAndWaitNoFibers: function () { - var self = this; - - let _resolver; - self.onAllCommitted(function () { - if (!_resolver) { - console.warn("oops, no resolver"); - return; - } - - _resolver(); - }); - - return new Promise((r) => { - _resolver = r; - self.arm(); - } ); - }, - - // Convenience function. Arms the fence, then blocks until it fires. - armAndWait: function () { - return Meteor._isFibersEnabled ? this._armAndWaitFibers() : this._armAndWaitNoFibers(); - }, - - _maybeFire: function () { - var self = this; - if (self.fired) - throw new Error("write fence already activated?"); - if (self.armed && !self.outstanding_writes) { - function invokeCallback (func) { - try { - func(self); - } catch (err) { - Meteor._debug("exception in write fence callback", err); - } - } - - self.outstanding_writes++; - while (self.before_fire_callbacks.length > 0) { - var callbacks = self.before_fire_callbacks; - self.before_fire_callbacks = []; - _.each(callbacks, invokeCallback); - } - self.outstanding_writes--; - - if (!self.outstanding_writes) { - self.fired = true; - var callbacks = self.completion_callbacks; - self.completion_callbacks = []; - _.each(callbacks, invokeCallback); - } - } - }, - - // Deactivate this fence so that adding more writes has no effect. - // The fence must have already fired. - retire: function () { - var self = this; - if (! self.fired) - throw new Error("Can't retire a fence that hasn't fired."); - self.retired = true; - } -}); From 61a0dbbb08e9de2fc75ee2211913c882a078a9a2 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 21 Oct 2022 00:09:55 -0300 Subject: [PATCH 0026/1965] Organizing code. --- packages/meteor/dynamics_nodejs.js | 367 +++++++++++++---------------- 1 file changed, 166 insertions(+), 201 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index db02f9bcc7..e0c13b496d 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -1,136 +1,57 @@ -/** - * Fiber-aware implementation of dynamic scoping, for use on the server - * - * If we are using Fiber, we store/update/fetch the context from the current Fiber. - * Else, we fetch from the AsyncLocalStorage. - */ +// Implementation of dynamic scoping, for use on the server - with Fibers or AsyncLocalStorage -var Fiber = Npm.require('fibers'); +var Fiber = Meteor._isFibersEnabled && Npm.require('fibers'); -var nextSlot = 0; +let nextSlot = 0; Meteor._nodeCodeMustBeInFiber = function () { if (!Fiber.current) { throw new Error("Meteor code must always run within a Fiber. " + - "Try wrapping callbacks that you pass to non-Meteor " + - "libraries with Meteor.bindEnvironment."); + "Try wrapping callbacks that you pass to non-Meteor " + + "libraries with Meteor.bindEnvironment."); } }; -/** - * @memberOf Meteor - * @summary Constructor for EnvironmentVariable - * @locus Anywhere - * @class - */ -Meteor.EnvironmentVariable = function () { - this.slot = nextSlot++; -}; +class EnvironmentVariableFibers { + constructor() { + this.slot = nextSlot++; + } -var EVp = Meteor.EnvironmentVariable.prototype; - -/** - * @summary Return value of environment variable if available - * @locus Anywhere - * @method get - * @memberof Meteor.EnvironmentVariable - */ -EVp.get = function () { - if (Meteor._isFibersEnabled) { + get() { Meteor._nodeCodeMustBeInFiber(); return Fiber.current._meteor_dynamics && Fiber.current._meteor_dynamics[this.slot]; } - return Meteor._getValueFromAslStore("_meteor_dynamics")?.[this.slot] || null; -}; - -// Most Meteor code ought to run inside a fiber, and the -// _nodeCodeMustBeInFiber assertion helps you remember to include appropriate -// bindEnvironment calls (which will get you the *right value* for your -// environment variables, on the server). -// -// In some very special cases, it's more important to run Meteor code on the -// server in non-Fiber contexts rather than to strongly enforce the safeguard -// against forgetting to use bindEnvironment. For example, using `check` in -// some top-level constructs like connect handlers without needing unnecessary -// Fibers on every request is more important that possibly failing to find the -// correct argumentChecker. So this function is just like get(), but it -// returns null rather than throwing when called from outside a Fiber. (On the -// client, it is identical to get().) -EVp.getOrNullIfOutsideFiber = function () { - if (!Fiber.current || !Meteor._isFibersEnabled) - return null; - return this.get(); -}; - -function withValuesWithFiber(value, func) { - Meteor._nodeCodeMustBeInFiber(); - - if (!Fiber.current._meteor_dynamics) - Fiber.current._meteor_dynamics = []; - var currentValues = Fiber.current._meteor_dynamics; - - var saved = currentValues[this.slot]; - try { - currentValues[this.slot] = value; - return func(); - } finally { - currentValues[this.slot] = saved; - } -} -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValue - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ -EVp.withValue = async function (value, func) { - if (Meteor._isFibersEnabled) { - return withValuesWithFiber.call(this, value, func); + getOrNullIfOutsideFiber() { + if (!Fiber.current) + return null; + return this.get(); } - let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); - if (!meteorDynamics) { - meteorDynamics = []; + withValue(value, func) { + Meteor._nodeCodeMustBeInFiber(); + + if (!Fiber.current._meteor_dynamics) + Fiber.current._meteor_dynamics = []; + var currentValues = Fiber.current._meteor_dynamics; + + var saved = currentValues[this.slot]; + try { + currentValues[this.slot] = value; + return func(); + } finally { + currentValues[this.slot] = saved; + } } - const saved = meteorDynamics[this.slot] || {}; - try { - meteorDynamics[this.slot] = value; - Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); - return await func(); - } finally { - meteorDynamics[this.slot] = saved; - Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); - } -}; - -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValueAsync - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ -EVp._set = function (context) { - if (Meteor._isFibersEnabled) { + _set(context) { Meteor._nodeCodeMustBeInFiber(); Fiber.current._meteor_dynamics[this.slot] = context; - return; } - Meteor._updateAslStore("_meteor_dynamics", context); -}; - -EVp._setNewContextAndGetCurrent = function (value) { - if (Meteor._isFibersEnabled) { + _setNewContextAndGetCurrent(value) { Meteor._nodeCodeMustBeInFiber(); if (!Fiber.current._meteor_dynamics) { Fiber.current._meteor_dynamics = []; @@ -139,16 +60,64 @@ EVp._setNewContextAndGetCurrent = function (value) { this._set(value); return saved; } +} - let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); - if (!meteorDynamics) { - meteorDynamics = []; +class EnvironmentVariableAsync { + constructor() { + this.slot = nextSlot++; } - const saved = meteorDynamics[this.slot]; - this._set(value); - return saved; -}; + get() { + const currentValue = Meteor._getValueFromAslStore("_meteor_dynamics"); + return currentValue && currentValue[this.slot]; + } + + getOrNullIfOutsideFiber() { + return this.get(); + } + + async withValue(value, func) { + let currentValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + if (!currentValues) { + currentValues = []; + } + + const saved = currentValues[this.slot]; + let ret; + try { + currentValues[this.slot] = value; + ret = await func(); + } finally { + currentValues[this.slot] = saved; + } + + return ret; + } + + _set(context) { + const _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics") || []; + _meteor_dynamics[this.slot] = context; + } + + _setNewContextAndGetCurrent(value) { + let _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + if (!_meteor_dynamics) { + _meteor_dynamics = []; + } + + const saved = _meteor_dynamics[this.slot]; + this._set(value); + return saved; + } +} + +/** + * @memberOf Meteor + * @summary Constructor for EnvironmentVariable + * @locus Anywhere + * @class + */ +Meteor.EnvironmentVariable = Meteor._isFibersEnabled ? EnvironmentVariableFibers : EnvironmentVariableAsync; // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from @@ -181,97 +150,93 @@ EVp._setNewContextAndGetCurrent = function (value) { * @return {Function} The wrapped function */ Meteor.bindEnvironment = function (func, onException, _this) { - if (Meteor._isFibersEnabled) { - return bindEnvironmentWithFibers({ func, onException, _this }); - } - - const savedValues = Meteor._getValueFromAslStore('_meteor_dynamics'); - const boundValues = Array.isArray(savedValues) ? savedValues.slice() : []; - - return function(/* arguments */) { - const args = Array.prototype.slice.call(arguments); - - const runWithEnvironment = handleRunWithEnvironment({ - args, - _this, - func, - onException, - savedValues, - boundValues, - handleUpdate(values) { - Meteor._updateAslStore('_meteor_dynamics', values); - }, - }); - - runWithEnvironment(); - }; + return Meteor._isFibersEnabled ? bindEnvironmentFibers(func, onException, _this) : bindEnvironmentAsync(func, onException, _this); }; -const handleRunWithEnvironment = ({ - args, - _this, - savedValues, - boundValues, - handleUpdate, - onException, - func, -}) => () => { - let handleException = onException; - - if (!onException || typeof handleException === 'string') { - const description = handleException || 'callback of async function'; - handleException = function(error) { - Meteor._debug('Exception in ' + description + ':', error); - }; - } else if (typeof handleException !== 'function') { - throw new Error( - 'onException argument must be a function, string or undefined for Meteor.bindEnvironment().' - ); - } - - let ret; - try { - // Need to clone boundValues in case two fibers invoke this - // function at the same time - handleUpdate(boundValues.slice()); - - ret = func.apply(_this, args); - } catch (e) { - // TODO check the scenario from this comment when we don't have a Fiber - // note: callback-hook currently relies on the fact that if onException - // throws and you were originally calling the wrapped callback from - // within a Fiber, the wrapped call throws. - handleException(e); - } finally { - handleUpdate(savedValues); - } - return ret; -}; - -function bindEnvironmentWithFibers({ func, onException, _this }) { +const bindEnvironmentFibers = (func, onException, _this) => { Meteor._nodeCodeMustBeInFiber(); - // to keep the function scope and don't lose the Fiber.current reference - const fibersCurrent = Fiber.current; + var dynamics = Fiber.current._meteor_dynamics; + var boundValues = dynamics ? dynamics.slice() : []; - const savedValues = fibersCurrent._meteor_dynamics; - const boundValues = savedValues ? savedValues.slice() : []; + if (!onException || typeof(onException) === 'string') { + var description = onException || "callback of async function"; + onException = function (error) { + Meteor._debug( + "Exception in " + description + ":", + error + ); + }; + } else if (typeof(onException) !== 'function') { + throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } - return function(/* arguments */) { - const args = Array.prototype.slice.call(arguments); - const runWithEnvironment = handleRunWithEnvironment({ - args, - _this, - func, - onException, - savedValues, - boundValues, - handleUpdate(values) { - fibersCurrent._meteor_dynamics = values; - }, - }); + return function (/* arguments */) { + var args = Array.prototype.slice.call(arguments); - if (Fiber.current) return runWithEnvironment(); + var runWithEnvironment = function () { + var savedValues = Fiber.current._meteor_dynamics; + try { + // Need to clone boundValues in case two fibers invoke this + // function at the same time + Fiber.current._meteor_dynamics = boundValues.slice(); + var ret = func.apply(_this, args); + } catch (e) { + // note: callback-hook currently relies on the fact that if onException + // throws and you were originally calling the wrapped callback from + // within a Fiber, the wrapped call throws. + onException(e); + } finally { + Fiber.current._meteor_dynamics = savedValues; + } + return ret; + }; + + if (Fiber.current) + return runWithEnvironment(); Fiber(runWithEnvironment).run(); }; -} +}; + +const bindEnvironmentAsync = (func, onException, _this) => { + var dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + var boundValues = Array.isArray(dynamics) ? dynamics.slice() : []; + + if (!onException || typeof(onException) === 'string') { + var description = onException || "callback of async function"; + onException = function (error) { + Meteor._debug( + "Exception in " + description + ":", + error + ); + }; + } else if (typeof(onException) !== 'function') { + throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } + + return function (/* arguments */) { + var args = Array.prototype.slice.call(arguments); + + var runWithEnvironment = async function () { + const savedValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + let ret; + try { + // Need to clone boundValues in case two fibers invoke this + // function at the same time + // TODO -> Probably not needed + Meteor._updateAslStore("_meteor_dynamics", boundValues.slice()); + ret = await func.apply(_this, args); + } catch (e) { + onException(e); + } finally { + Meteor._updateAslStore("_meteor_dynamics", savedValues); + } + return ret; + }; + + if (Meteor._getAslStore()) { + return runWithEnvironment(); + } + global.asyncLocalStorage.run({}, runWithEnvironment); + }; +}; From 104324afe569b59722062d2617da679af6d4f091 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 23 Jul 2022 15:48:03 -0300 Subject: [PATCH 0027/1965] Move babel-preset-meteor up one dir. It's still in the npm-packages folder, but for some reason it was nested inside other directory. --- .../babel-preset-meteor/{babel-presets-meteor => }/.gitignore | 0 .../babel-preset-meteor/{babel-presets-meteor => }/LICENSE.txt | 0 .../babel-preset-meteor/{babel-presets-meteor => }/README.md | 0 .../babel-preset-meteor/{babel-presets-meteor => }/index.js | 0 .../babel-preset-meteor/{babel-presets-meteor => }/modern.js | 0 .../{babel-presets-meteor => }/package-lock.json | 0 .../babel-preset-meteor/{babel-presets-meteor => }/package.json | 0 .../babel-preset-meteor/{babel-presets-meteor => }/proposals.js | 0 .../{babel-presets-meteor => }/scripts/update-versions | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/.gitignore (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/LICENSE.txt (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/README.md (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/index.js (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/modern.js (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/package-lock.json (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/package.json (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/proposals.js (100%) rename npm-packages/babel-preset-meteor/{babel-presets-meteor => }/scripts/update-versions (100%) diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/.gitignore b/npm-packages/babel-preset-meteor/.gitignore similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/.gitignore rename to npm-packages/babel-preset-meteor/.gitignore diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/LICENSE.txt b/npm-packages/babel-preset-meteor/LICENSE.txt similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/LICENSE.txt rename to npm-packages/babel-preset-meteor/LICENSE.txt diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/README.md b/npm-packages/babel-preset-meteor/README.md similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/README.md rename to npm-packages/babel-preset-meteor/README.md diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/index.js b/npm-packages/babel-preset-meteor/index.js similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/index.js rename to npm-packages/babel-preset-meteor/index.js diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/modern.js b/npm-packages/babel-preset-meteor/modern.js similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/modern.js rename to npm-packages/babel-preset-meteor/modern.js diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/package-lock.json b/npm-packages/babel-preset-meteor/package-lock.json similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/package-lock.json rename to npm-packages/babel-preset-meteor/package-lock.json diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/package.json b/npm-packages/babel-preset-meteor/package.json similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/package.json rename to npm-packages/babel-preset-meteor/package.json diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/proposals.js b/npm-packages/babel-preset-meteor/proposals.js similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/proposals.js rename to npm-packages/babel-preset-meteor/proposals.js diff --git a/npm-packages/babel-preset-meteor/babel-presets-meteor/scripts/update-versions b/npm-packages/babel-preset-meteor/scripts/update-versions similarity index 100% rename from npm-packages/babel-preset-meteor/babel-presets-meteor/scripts/update-versions rename to npm-packages/babel-preset-meteor/scripts/update-versions From 0e3a2ca2519fbcc93f7850fef0285581609b4292 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 23 Jul 2022 15:55:06 -0300 Subject: [PATCH 0028/1965] Add top level await plugin to babel-preset-meteor package. Also bump its version. Babel recommends to use the plugin instead of the option "allowAwaitOutsideFunction". --- .../babel-preset-meteor/package-lock.json | 527 ++++++++++++++++-- npm-packages/babel-preset-meteor/package.json | 3 +- npm-packages/babel-preset-meteor/proposals.js | 2 + 3 files changed, 481 insertions(+), 51 deletions(-) diff --git a/npm-packages/babel-preset-meteor/package-lock.json b/npm-packages/babel-preset-meteor/package-lock.json index f8a4c3e1c7..de3eba405c 100644 --- a/npm-packages/babel-preset-meteor/package-lock.json +++ b/npm-packages/babel-preset-meteor/package-lock.json @@ -1,6 +1,6 @@ { "name": "babel-preset-meteor", - "version": "7.10.0", + "version": "7.10.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -18,26 +18,142 @@ "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==" }, "@babel/core": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.16.tgz", - "integrity": "sha512-sXHpixBiWWFti0AV2Zq7avpTasr6sIAu7Y396c608541qAU2ui4a193m0KSQmfPSKFZLnQ3cvlKDOm3XkuXm3Q==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz", + "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.16", + "@babel/generator": "^7.14.0", "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.13.14", - "@babel/helpers": "^7.13.16", - "@babel/parser": "^7.13.16", + "@babel/helper-module-transforms": "^7.14.0", + "@babel/helpers": "^7.14.0", + "@babel/parser": "^7.14.0", "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.14.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", "semver": "^6.3.0", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.9.tgz", + "integrity": "sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + } + } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz", + "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==", + "dev": true + }, + "@babel/traverse": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.9.tgz", + "integrity": "sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.9", + "@babel/types": "^7.18.9", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + } + } + }, + "@babel/types": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz", + "integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/generator": { @@ -106,6 +222,12 @@ "regexpu-core": "^4.7.1" } }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, "@babel/helper-explode-assignable-expression": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", @@ -132,6 +254,33 @@ "@babel/types": "^7.12.13" } }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/types": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz", + "integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + } + } + }, "@babel/helper-member-expression-to-functions": { "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", @@ -149,19 +298,131 @@ } }, "@babel/helper-module-transforms": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", - "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.13", - "@babel/types": "^7.13.14" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.9.tgz", + "integrity": "sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz", + "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==", + "dev": true + }, + "@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/traverse": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.9.tgz", + "integrity": "sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.9", + "@babel/types": "^7.18.9", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz", + "integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-optimise-call-expression": { @@ -199,12 +460,30 @@ } }, "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.18.6" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/types": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz", + "integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -245,14 +524,117 @@ } }, "@babel/helpers": { - "version": "7.13.17", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.17.tgz", - "integrity": "sha512-Eal4Gce4kGijo1/TGJdqp3WuhllaMLSrW6XcL0ulyUAQOuxHcCafZE8KHg9857gcTehsm/v7RcOx2+jp0Ryjsg==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.17", - "@babel/types": "^7.13.17" + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.9.tgz", + "integrity": "sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.9.tgz", + "integrity": "sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==", + "dev": true + }, + "@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/traverse": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.9.tgz", + "integrity": "sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.9", + "@babel/types": "^7.18.9", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz", + "integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -457,6 +839,21 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==" + } + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", @@ -804,6 +1201,45 @@ "to-fast-properties": "^2.0.0" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -850,7 +1286,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "colorette": { "version": "1.2.2", @@ -858,9 +1294,9 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -887,7 +1323,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "gensync": { "version": "1.0.0-beta.2", @@ -903,7 +1339,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "js-tokens": { "version": "4.0.0", @@ -916,18 +1352,9 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, "ms": { @@ -1027,7 +1454,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", diff --git a/npm-packages/babel-preset-meteor/package.json b/npm-packages/babel-preset-meteor/package.json index 60da6fdc9a..66cf971d3c 100644 --- a/npm-packages/babel-preset-meteor/package.json +++ b/npm-packages/babel-preset-meteor/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-meteor", - "version": "7.10.0", + "version": "7.10.1", "description": "Babel preset for ES2015+ features supported by Meteor", "author": "Ben Newman ", "license": "MIT", @@ -23,6 +23,7 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-transform-arrow-functions": "^7.13.0", "@babel/plugin-transform-async-to-generator": "^7.13.0", "@babel/plugin-transform-block-scoped-functions": "^7.12.13", diff --git a/npm-packages/babel-preset-meteor/proposals.js b/npm-packages/babel-preset-meteor/proposals.js index d429ab1224..045549ad73 100644 --- a/npm-packages/babel-preset-meteor/proposals.js +++ b/npm-packages/babel-preset-meteor/proposals.js @@ -18,4 +18,6 @@ exports.plugins = [ require("@babel/plugin-proposal-object-rest-spread"), require("@babel/plugin-proposal-logical-assignment-operators"), + + require("@babel/plugin-syntax-top-level-await"), ]; From caec801f6fee3fc1ffde333ba37eba0b8c5e1aa5 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sun, 24 Jul 2022 00:01:31 -0300 Subject: [PATCH 0029/1965] Add TOOL_NODE_FLAGS as a tip on the DEVELOPMENT.md file. This is already documented in the Environment Variables file, but it's a bit hidden. --- DEVELOPMENT.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index dbbdfefcdd..a34dd51841 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -43,13 +43,19 @@ can run Meteor directly from a Git checkout using these steps: $ /path/to/meteor-checkout/meteor run ``` - > _Tip:_ Consider making an easy-to-run alias for frequent use: + > _Tip 1:_ Consider making an easy-to-run alias for frequent use: > > alias mymeteor=/path/to-meteor-checkout/meteor > > This allows the use of `mymeteor` in place of `meteor`. To persist this > across shell logouts, simply add it to `~/.bashrc` or `.zshrc`. + > _Tip 2:_ When working with meteor tool, it may be helpful to use the debugger to check what's happening. You can do this using the following flag: + > + > TOOL_NODE_FLAGS="--inspect-brk" mymeteor + > + > Then you can use the chrome debugger inside `chrome://inspect`. + ### Notes when running from a checkout The following are some distinct differences you must pay attention to when running Meteor from a checkout: From 742602042d4f90a73a0b677d54f41a750eb6bef5 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 24 Oct 2022 19:38:22 -0300 Subject: [PATCH 0030/1965] Run doc fetcher with _runAsync helper. --- packages/mongo/doc_fetcher.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/mongo/doc_fetcher.js b/packages/mongo/doc_fetcher.js index 2b3412d39c..3d4740bf34 100644 --- a/packages/mongo/doc_fetcher.js +++ b/packages/mongo/doc_fetcher.js @@ -1,5 +1,3 @@ -var Fiber = Npm.require('fibers'); - export class DocFetcher { constructor(mongoConnection) { this._mongoConnection = mongoConnection; @@ -32,9 +30,9 @@ export class DocFetcher { const callbacks = [callback]; self._callbacksForOp.set(op, callbacks); - Fiber(function () { + Meteor._runAsync(async function () { try { - var doc = self._mongoConnection.findOne( + var doc = await self._mongoConnection.findOne( collectionName, {_id: id}) || null; // Return doc to all relevant callbacks. Note that this array can // continue to grow during callback excecution. @@ -43,17 +41,17 @@ export class DocFetcher { // objects that are intertwingled with each other. Clone before // popping the future, so that if clone throws, the error gets passed // to the next callback. - callbacks.pop()(null, EJSON.clone(doc)); + await callbacks.pop()(null, EJSON.clone(doc)); } } catch (e) { while (callbacks.length > 0) { - callbacks.pop()(e); + await callbacks.pop()(e); } } finally { // XXX consider keeping the doc around for a period of time before // removing from the cache self._callbacksForOp.delete(op); } - }).run(); + }); } } From 484e15a0b8522481493a89f5a55bfe6e8509811c Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Wed, 26 Oct 2022 20:14:52 -0300 Subject: [PATCH 0031/1965] Update ASL store on withValue and simplify a bit on mongo_driver.js --- packages/meteor/dynamics_nodejs.js | 2 ++ packages/mongo/mongo_driver.js | 49 ++---------------------------- 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index e0c13b496d..2a278acd5e 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -86,9 +86,11 @@ class EnvironmentVariableAsync { let ret; try { currentValues[this.slot] = value; + Meteor._updateAslStore("_meteor_dynamics", currentValues); ret = await func(); } finally { currentValues[this.slot] = saved; + Meteor._updateAslStore("_meteor_dynamics", currentValues); } return ret; diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index f6562720f5..370a321d60 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -1409,49 +1409,8 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo var stopped = false; var lastTS; - const loopWithFibers = function () { - var doc = null; - while (true) { - if (stopped) - return; - try { - doc = cursor._nextObjectPromiseWithTimeout(timeoutMS).await(); - } catch (err) { - // There's no good way to figure out if this was actually an error from - // Mongo, or just client-side (including our own timeout error). Ah - // well. But either way, we need to retry the cursor (unless the failure - // was because the observe got stopped). - doc = null; - } - // Since we awaited a promise above, we need to check again to see if - // we've been stopped before calling the callback. - if (stopped) - return; - if (doc) { - // If a tailable cursor contains a "ts" field, use it to recreate the - // cursor on error. ("ts" is a standard that Mongo uses internally for - // the oplog, and there's a special flag that lets you do binary search - // on it instead of needing to use an index.) - lastTS = doc.ts; - docCallback(doc); - } else { - var newSelector = _.clone(cursorDescription.selector); - if (lastTS) { - newSelector.ts = {$gt: lastTS}; - } - cursor = self._createSynchronousCursor(new CursorDescription( - cursorDescription.collectionName, - newSelector, - cursorDescription.options)); - // Mongo failover takes many seconds. Retry in a bit. (Without this - // setTimeout, we peg the CPU at 100% and never notice the actual - // failover. - Meteor.setTimeout(loop, 100); - break; - } - } - }; - const loop = async function () { + + Meteor.defer(async function loop() { var doc = null; while (true) { if (stopped) @@ -1492,9 +1451,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo break; } } - }; - - Meteor.defer(Meteor._isFibersEnabled ? loopWithFibers : loop); + }); return { stop: function () { From e6ff9c754b14ccb6f77505dc7b01a2d835c6cdc8 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 28 Oct 2022 11:42:50 -0300 Subject: [PATCH 0032/1965] Remove fibers future usage from oplog_tailing.js. --- packages/mongo/oplog_tailing.js | 206 +++++++------------------------- 1 file changed, 40 insertions(+), 166 deletions(-) diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index 0728b121a0..a50e3f5a1e 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -1,5 +1,3 @@ -var Future = Npm.require('fibers/future'); - import { NpmModuleMongodb } from "meteor/npm-mongo"; const { Long } = NpmModuleMongodb; @@ -8,10 +6,6 @@ OPLOG_COLLECTION = 'oplog.rs'; var TOO_FAR_BEHIND = process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000; var TAIL_TIMEOUT = +process.env.METEOR_OPLOG_TAIL_TIMEOUT || 30000; -var showTS = function (ts) { - return "Timestamp(" + ts.getHighBits() + ", " + ts.getLowBits() + ")"; -}; - idForOp = function (op) { if (op.op === 'd') return op.o._id; @@ -35,8 +29,8 @@ OplogHandle = function (oplogUrl, dbName) { self._oplogTailConnection = null; self._stopped = false; self._tailHandle = null; - self._readyFuture = new Future(); - self._isReady = false; + self._readyPromiseResolver = null; + self._readyPromise = new Promise(r => self._readyPromiseResolver = r); self._crossbar = new DDPServer._Crossbar({ factPackage: "mongo-livedata", factName: "oplog-watchers" }); @@ -73,7 +67,7 @@ OplogHandle = function (oplogUrl, dbName) { // incremented to be past its timestamp by the worker fiber. // // XXX use a priority queue or something else that's faster than an array - self._catchingUpFutures = []; + self._catchingUpResolvers = []; self._lastProcessedTS = null; self._onSkippedEntriesHook = new Hook({ @@ -83,7 +77,10 @@ OplogHandle = function (oplogUrl, dbName) { self._entryQueue = new Meteor._DoubleEndedQueue(); self._workerActive = false; - self._startTailing(); + const shouldAwait = self._startTailing(); + if (Meteor._isFibersEnabled) { + Promise.await(shouldAwait); + } }; Object.assign(OplogHandle.prototype, { @@ -96,35 +93,13 @@ Object.assign(OplogHandle.prototype, { self._tailHandle.stop(); // XXX should close connections too }, - _onOplogEntryFibers: function(trigger, callback) { + _onOplogEntry: async function(trigger, callback) { var self = this; if (self._stopped) throw new Error("Called onOplogEntry on stopped handle!"); // Calling onOplogEntry requires us to wait for the tailing to be ready. - self._readyFuture.wait(); - - var originalCallback = callback; - callback = Meteor.bindEnvironment(function (notification) { - originalCallback(notification); - }, function (err) { - Meteor._debug("Error in oplog callback", err); - }); - var listenHandle = self._crossbar.listen(trigger, callback); - return { - stop: function () { - listenHandle.stop(); - } - }; - }, - async _onOplogEntryNoFibers(trigger, callback) { - var self = this; - if (self._stopped) - throw new Error("Called onOplogEntry on stopped handle!"); - - while (!self._isReady) { - await Meteor._sleepForMs(100); - } + await self._readyPromise; var originalCallback = callback; callback = Meteor.bindEnvironment(function (notification) { @@ -140,7 +115,7 @@ Object.assign(OplogHandle.prototype, { }; }, onOplogEntry: function (trigger, callback) { - return Meteor._isFibersEnabled ? this._onOplogEntryFibers(trigger, callback) : this._onOplogEntryNoFibers(trigger, callback); + return Meteor._isFibersEnabled ? Promise.await(this._onOplogEntry(trigger, callback)) : this._onOplogEntry(trigger, callback); }, // Register a callback to be invoked any time we skip oplog entries (eg, // because we are too far behind). @@ -151,75 +126,14 @@ Object.assign(OplogHandle.prototype, { return self._onSkippedEntriesHook.register(callback); }, - _waitUntilCaughtUpFibers() { + async _waitUntilCaughtUp() { var self = this; if (self._stopped) throw new Error("Called waitUntilCaughtUp on stopped handle!"); // Calling waitUntilCaughtUp requries us to wait for the oplog connection to // be ready. - self._readyFuture.wait(); - var lastEntry; - - while (!self._stopped) { - // We need to make the selector at least as restrictive as the actual - // tailing selector (ie, we need to specify the DB name) or else we might - // find a TS that won't show up in the actual tail stream. - try { - lastEntry = self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, self._baseOplogSelector, - {fields: {ts: 1}, sort: {$natural: -1}}); - break; - } catch (e) { - // During failover (eg) if we get an exception we should log and retry - // instead of crashing. - Meteor._debug("Got exception while reading last entry", e); - Meteor._sleepForMs(100); - } - } - - if (self._stopped) - return; - - if (!lastEntry) { - // Really, nothing in the oplog? Well, we've processed everything. - return; - } - - var ts = lastEntry.ts; - if (!ts) - throw Error("oplog entry without ts: " + EJSON.stringify(lastEntry)); - - if (self._lastProcessedTS && ts.lessThanOrEqual(self._lastProcessedTS)) { - // We've already caught up to here. - return; - } - - - // Insert the future into our list. Almost always, this will be at the end, - // but it's conceivable that if we fail over from one primary to another, - // the oplog entries we see will go backwards. - var insertAfter = self._catchingUpFutures.length; - while (insertAfter - 1 > 0 && self._catchingUpFutures[insertAfter - 1].ts.greaterThan(ts)) { - insertAfter--; - } - var f = new Future; - self._catchingUpFutures.splice(insertAfter, 0, {ts: ts, future: f}); - f.wait(); - }, - - async _waitUntilCaughtUpNoFibers() { - var self = this; - if (self._stopped) - throw new Error("Called waitUntilCaughtUp on stopped handle!"); - - // TODO -> Should we wait? Is waiting needed? - // Calling waitUntilCaughtUp requries us to wait for the oplog connection to - // be ready. - while (!this._isReady) { - await Meteor._sleepForMs(100); - } - + await self._readyPromise; var lastEntry; while (!self._stopped) { @@ -255,25 +169,37 @@ Object.assign(OplogHandle.prototype, { // We've already caught up to here. return; } - }, + // Insert the future into our list. Almost always, this will be at the end, + // but it's conceivable that if we fail over from one primary to another, + // the oplog entries we see will go backwards. + var insertAfter = self._catchingUpResolvers.length; + while (insertAfter - 1 > 0 && self._catchingUpResolvers[insertAfter - 1].ts.greaterThan(ts)) { + insertAfter--; + } + let promiseResolver = null; + const promiseToAwait = new Promise(r => promiseResolver = r); + self._catchingUpResolvers.splice(insertAfter, 0, {ts: ts, resolver: promiseResolver}); + await promiseToAwait; + }, + // Calls `callback` once the oplog has been processed up to a point that is // roughly "now": specifically, once we've processed all ops that are // currently visible. // XXX become convinced that this is actually safe even if oplogConnection // is some kind of pool waitUntilCaughtUp: function () { - return Meteor._isFibersEnabled ? this._waitUntilCaughtUpFibers() : this._waitUntilCaughtUpNoFibers() + return Meteor._isFibersEnabled ? Promise.await(this._waitUntilCaughtUp()) : this._waitUntilCaughtUp(); }, - _startTailing: function () { + _startTailing: async function () { var self = this; // First, make sure that we're talking to the local database. var mongodbUri = Npm.require('mongodb-uri'); if (mongodbUri.parse(self._oplogUrl).database !== 'local') { throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + - "a Mongo replica set"); + "a Mongo replica set"); } // We make two separate connections to Mongo. The Node Mongo driver @@ -288,70 +214,17 @@ Object.assign(OplogHandle.prototype, { // The tail connection will only ever be running a single tail command, so // it only needs to make one underlying TCP connection. self._oplogTailConnection = new MongoConnection( - self._oplogUrl, {maxPoolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // XXX better docs, but: it's to get monotonic results // XXX is it safe to say "if there's an in flight query, just use its // results"? I don't think so but should consider that self._oplogLastEntryConnection = new MongoConnection( - self._oplogUrl, {maxPoolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); - if (Meteor._isFibersEnabled) { - return this._startTailingFibers(); - } - self._oplogLastEntryConnection.db.admin().command( - { ismaster: 1 }, function(_, isMasterDoc) { - if (!(isMasterDoc && isMasterDoc.setName)) { - throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + - "a Mongo replica set"); - } - - // Find the last oplog entry. - var lastOplogEntry = self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); - - var oplogSelector = Object.assign({}, self._baseOplogSelector); - if (lastOplogEntry) { - // Start after the last entry that currently exists. - oplogSelector.ts = {$gt: lastOplogEntry.ts}; - // If there are any calls to callWhenProcessedLatest before any other - // oplog entries show up, allow callWhenProcessedLatest to call its - // callback immediately. - self._lastProcessedTS = lastOplogEntry.ts; - } - - var cursorDescription = new CursorDescription( - OPLOG_COLLECTION, oplogSelector, {tailable: true}); - - // Start tailing the oplog. - // - // We restart the low-level oplog query every 30 seconds if we didn't get a - // doc. This is a workaround for #8598: the Node Mongo driver has at least - // one bug that can lead to query callbacks never getting called (even with - // an error) when leadership failover occur. - self._tailHandle = self._oplogTailConnection.tail( - cursorDescription, - function (doc) { - self._entryQueue.push(doc); - self._maybeStartWorker(); - }, - TAIL_TIMEOUT - ); - - self._isReady = true; - }); - }, - - _startTailingFibers: function() { - const self = this; - // Now, make sure that there actually is a repl set here. If not, oplog - // tailing won't ever find anything! - // More on the isMasterDoc - // https://docs.mongodb.com/manual/reference/command/isMaster/ - var f = new Future; - self._oplogLastEntryConnection.db.admin().command( - { ismaster: 1 }, f.resolver()); - var isMasterDoc = f.wait(); + const isMasterDoc = await Meteor.promisify((cb) => { + self._oplogLastEntryConnection.db.admin().command({ismaster: 1}, cb); + })(); if (!(isMasterDoc && isMasterDoc.setName)) { throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + @@ -359,10 +232,10 @@ Object.assign(OplogHandle.prototype, { } // Find the last oplog entry. - var lastOplogEntry = self._oplogLastEntryConnection.findOne( + var lastOplogEntry = await self._oplogLastEntryConnection.findOne( OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); - var oplogSelector = _.clone(self._baseOplogSelector); + var oplogSelector = Object.assign({}, self._baseOplogSelector); if (lastOplogEntry) { // Start after the last entry that currently exists. oplogSelector.ts = {$gt: lastOplogEntry.ts}; @@ -389,7 +262,8 @@ Object.assign(OplogHandle.prototype, { }, TAIL_TIMEOUT ); - self._readyFuture.return(); + + self._readyPromiseResolver(); }, _maybeStartWorker: function () { @@ -493,9 +367,9 @@ Object.assign(OplogHandle.prototype, { _setLastProcessedTS: function (ts) { var self = this; self._lastProcessedTS = ts; - while (!_.isEmpty(self._catchingUpFutures) && self._catchingUpFutures[0].ts.lessThanOrEqual(self._lastProcessedTS)) { - var sequencer = self._catchingUpFutures.shift(); - sequencer.future.return(); + while (!_.isEmpty(self._catchingUpResolvers) && self._catchingUpResolvers[0].ts.lessThanOrEqual(self._lastProcessedTS)) { + var sequencer = self._catchingUpResolvers.shift(); + sequencer.resolver(); } }, From 3186c0436b0d31ed5e13cbaa9b33e87e7f1678d1 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 31 Oct 2022 21:09:26 -0300 Subject: [PATCH 0033/1965] Trying to make oplog work without Fibers. --- packages/mongo/collection.js | 2 +- packages/mongo/mongo_driver.js | 40 ++-- packages/mongo/observe_multiplex.js | 2 +- packages/mongo/oplog_observe_driver.js | 261 ++++++++----------------- 4 files changed, 107 insertions(+), 198 deletions(-) diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index b5cdda952f..98141d83c7 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -465,7 +465,7 @@ Object.assign(Mongo.Collection, { // register stop callback (expects lambda w/ no args). sub.onStop(function() { - observeHandle.stop(); + return observeHandle.stop(); }); // return the observeHandle in case it needs to be stopped early diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 370a321d60..62b5c0723b 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -205,7 +205,7 @@ MongoConnection = function (url, options) { } }; -MongoConnection.prototype.close = function() { +MongoConnection.prototype._close = async function() { var self = this; if (! self.db) @@ -215,12 +215,16 @@ MongoConnection.prototype.close = function() { var oplogHandle = self._oplogHandle; self._oplogHandle = null; if (oplogHandle) - oplogHandle.stop(); + await oplogHandle.stop(); // Use Future.wrap so that errors get thrown. This happens to // work even outside a fiber since the 'close' method is not // actually asynchronous. - Future.wrap(_.bind(self.client.close, self.client))(true).wait(); + await self.client.close(); +}; + +MongoConnection.prototype.close = function () { + return Meteor._isFibersEnabled ? Promise.await(this._close()) : this._close(); }; // Returns the Mongo Collection object; may yield. @@ -1495,22 +1499,20 @@ Object.assign(MongoConnection.prototype, { // Find a matching ObserveMultiplexer, or create a new one. This next block is // guaranteed to not yield (and it doesn't call anything that can observe a // new query), so no other calls to this function can interleave with it. - Meteor._noYieldsAllowed(function () { - if (_.has(self._observeMultiplexers, observeKey)) { - multiplexer = self._observeMultiplexers[observeKey]; - } else { - firstHandle = true; - // Create a new ObserveMultiplexer. - multiplexer = new ObserveMultiplexer({ - ordered: ordered, - onStop: function () { - delete self._observeMultiplexers[observeKey]; - observeDriver.stop(); - } - }); - self._observeMultiplexers[observeKey] = multiplexer; - } - }); + if (_.has(self._observeMultiplexers, observeKey)) { + multiplexer = self._observeMultiplexers[observeKey]; + } else { + firstHandle = true; + // Create a new ObserveMultiplexer. + multiplexer = new ObserveMultiplexer({ + ordered: ordered, + onStop: function () { + delete self._observeMultiplexers[observeKey]; + return observeDriver.stop(); + } + }); + self._observeMultiplexers[observeKey] = multiplexer; + } var observeHandle = new ObserveHandle(multiplexer, callbacks, diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index 3b40d58ac6..4b6b64245c 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -129,7 +129,7 @@ ObserveMultiplexer = class { // all handles. "ready" must have already been called on this multiplexer. onFlush(cb) { var self = this; - this._queue.queueTask(async function () { + return this._queue.queueTask(async function () { if (!self._ready()) throw Error("only call onFlush on a multiplexer that will be ready"); await cb(); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 9efc7d5791..9edfd2703b 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -1,7 +1,5 @@ import { oplogV2V1Converter } from "./oplog_v2_converter"; -var Future = Npm.require('fibers/future'); - var PHASE = { QUERYING: "QUERYING", FETCHING: "FETCHING", @@ -12,9 +10,9 @@ var PHASE = { // enclosing call to finishIfNeedToPollQuery. var SwitchedToQuery = function () {}; var finishIfNeedToPollQuery = function (f) { - return function () { + return async function () { try { - f.apply(this, arguments); + await f.apply(this, arguments); } catch (e) { if (!(e instanceof SwitchedToQuery)) throw e; @@ -111,7 +109,7 @@ OplogObserveDriver = function (options) { // behind, say), re-poll. self._stopHandles.push(self._mongoHandle._oplogHandle.onSkippedEntries( finishIfNeedToPollQuery(function () { - self._needToPollQuery(); + return self._needToPollQuery(); }) )); @@ -124,13 +122,13 @@ OplogObserveDriver = function (options) { // Note: this call is not allowed to block on anything (especially // on waiting for oplog entries to catch up) because that will block // onOplogEntry! - self._needToPollQuery(); + return self._needToPollQuery(); } else { // All other operators should be handled depending on phase if (self._phase === PHASE.QUERYING) { - self._handleOplogEntryQuerying(op); + return self._handleOplogEntryQuerying(op); } else { - self._handleOplogEntrySteadyOrFetching(op); + return self._handleOplogEntrySteadyOrFetching(op); } } })); @@ -140,7 +138,7 @@ OplogObserveDriver = function (options) { // XXX ordering w.r.t. everything else? self._stopHandles.push(listenAll( - self._cursorDescription, function (notification) { + self._cursorDescription, function () { // If we're not in a pre-fire write fence, we don't have to do anything. var fence = DDPServer._CurrentWriteFence.get(); if (!fence || fence.fired) @@ -154,15 +152,15 @@ OplogObserveDriver = function (options) { fence._oplogObserveDrivers = {}; fence._oplogObserveDrivers[self._id] = self; - fence.onBeforeFire(function () { + fence.onBeforeFire(async function () { var drivers = fence._oplogObserveDrivers; delete fence._oplogObserveDrivers; // This fence cannot fire until we've caught up to "this point" in the // oplog, and all observers made it back to the steady state. - self._mongoHandle._oplogHandle.waitUntilCaughtUp(); + await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); - _.each(drivers, function (driver) { + for (const driver of Object.values(drivers)) { if (driver._stopped) return; @@ -171,13 +169,11 @@ OplogObserveDriver = function (options) { // Make sure that all of the callbacks have made it through the // multiplexer and been delivered to ObserveHandles before committing // writes. - driver._multiplexer.onFlush(function () { - write.committed(); - }); + await driver._multiplexer.onFlush(write.committed); } else { driver._writesToCommitWhenWeReachSteady.push(write); } - }); + } }); } )); @@ -186,21 +182,17 @@ OplogObserveDriver = function (options) { // oplog entry that got rolled back. self._stopHandles.push(self._mongoHandle._onFailover(finishIfNeedToPollQuery( function () { - self._needToPollQuery(); + return self._needToPollQuery(); }))); - - // Give _observeChanges a chance to add the new ObserveHandle to our - // multiplexer, so that the added calls get streamed. - function deferFibers() { - self._runInitialQuery(); - } - const deferNoFibers = async () => { - await self._runInitialQuery(); - }; - Meteor.defer(finishIfNeedToPollQuery(Meteor._isFibersEnabled ? deferFibers : deferNoFibers)); }; _.extend(OplogObserveDriver.prototype, { + _init: function() { + const self = this; + // Give _observeChanges a chance to add the new ObserveHandle to our + // multiplexer, so that the added calls get streamed. + return self._runInitialQuery(); + }, _addPublished: function (id, doc) { var self = this; Meteor._noYieldsAllowed(function () { @@ -492,7 +484,7 @@ _.extend(OplogObserveDriver.prototype, { self._registerPhaseChange(PHASE.FETCHING); // Defer, because nothing called from the oplog entry handler may yield, // but fetch() yields. - Meteor.defer(finishIfNeedToPollQuery(function () { + Meteor.defer(finishIfNeedToPollQuery(async function () { while (!self._stopped && !self._needToFetch.empty()) { if (self._phase === PHASE.QUERYING) { // While fetching, we decided to go into QUERYING mode, and then we @@ -509,7 +501,9 @@ _.extend(OplogObserveDriver.prototype, { var thisGeneration = ++self._fetchGeneration; self._needToFetch = new LocalCollection._IdMap; var waiting = 0; - var fut = new Future; + + let promiseResolver = null; + const awaitablePromise = new Promise(r => promiseResolver = r); // This loop is safe, because _currentlyFetching will not be updated // during this loop (in fact, it is never mutated). self._currentlyFetching.forEach(function (op, id) { @@ -542,11 +536,11 @@ _.extend(OplogObserveDriver.prototype, { // this is safe (ie, we won't call fut.return() before the // forEach is done). if (waiting === 0) - fut.return(); + promiseResolver(); } })); }); - fut.wait(); + await awaitablePromise; // Exit now if we've had a _pollQuery call (here or in another fiber). if (self._phase === PHASE.QUERYING) return; @@ -555,20 +549,20 @@ _.extend(OplogObserveDriver.prototype, { // We're done fetching, so we can be steady, unless we've had a // _pollQuery call (here or in another fiber). if (self._phase !== PHASE.QUERYING) - self._beSteady(); + await self._beSteady(); })); }); }, - _beSteady: function () { + _beSteady: async function () { var self = this; - Meteor._noYieldsAllowed(function () { + await Meteor._noYieldsAllowed(async function () { self._registerPhaseChange(PHASE.STEADY); var writes = self._writesToCommitWhenWeReachSteady; self._writesToCommitWhenWeReachSteady = []; - self._multiplexer.onFlush(function () { - _.each(writes, function (w) { - w.committed(); - }); + await self._multiplexer.onFlush(async function () { + for (const w of writes) { + await w.committed(); + } }); }); }, @@ -663,24 +657,7 @@ _.extend(OplogObserveDriver.prototype, { }); }, - _runInitialQueryFibers() { - var self = this; - if (self._stopped) - throw new Error("oplog stopped surprisingly early"); - - self._runQuery({initial: true}); // yields - - if (self._stopped) - return; // can happen on queryError - - // Allow observeChanges calls to return. (After this, it's possible for - // stop() to be called.) - self._multiplexer.ready(); - - self._doneQuerying(); // yields - }, - - async _runInitialQueryNoFibers() { + async _runInitialQueryAsync() { var self = this; if (self._stopped) throw new Error("oplog stopped surprisingly early"); @@ -692,14 +669,14 @@ _.extend(OplogObserveDriver.prototype, { // Allow observeChanges calls to return. (After this, it's possible for // stop() to be called.) - self._multiplexer.ready(); + await self._multiplexer.ready(); await self._doneQuerying(); // yields }, // Yields! _runInitialQuery: function () { - return Meteor._isFibersEnabled ? this._runInitialQueryFibers() : this._runInitialQueryNoFibers(); + return Meteor._isFibersEnabled ? Promise.await(this._runInitialQueryAsync()) : this._runInitialQueryAsync(); }, // In various circumstances, we may just want to stop processing the oplog and @@ -730,15 +707,15 @@ _.extend(OplogObserveDriver.prototype, { // Defer so that we don't yield. We don't need finishIfNeedToPollQuery // here because SwitchedToQuery is not thrown in QUERYING mode. - Meteor.defer(function () { - self._runQuery(); - self._doneQuerying(); + Meteor.defer(async function () { + await self._runQuery(); + await self._doneQuerying(); }); }); }, // Yields! - async _runQueryNoFibers(options) { + async _runQueryAsync(options) { var self = this; options = options || {}; var newResults, newBuffer; @@ -776,7 +753,7 @@ _.extend(OplogObserveDriver.prototype, { // successfully. Probably it's a bad selector or something, so we // should NOT retry. Instead, we should halt the observe (which ends // up calling `stop` on us). - self._multiplexer.queryError(e); + await self._multiplexer.queryError(e); return; } @@ -793,65 +770,9 @@ _.extend(OplogObserveDriver.prototype, { self._publishNewResults(newResults, newBuffer); }, - // Yields! - _runQueryWithFibers: function (options) { - var self = this; - options = options || {}; - var newResults, newBuffer; - - // This while loop is just to retry failures. - while (true) { - // If we've been stopped, we don't have to run anything any more. - if (self._stopped) - return; - - newResults = new LocalCollection._IdMap; - newBuffer = new LocalCollection._IdMap; - - // Query 2x documents as the half excluded from the original query will go - // into unpublished buffer to reduce additional Mongo lookups in cases - // when documents are removed from the published set and need a - // replacement. - // XXX needs more thought on non-zero skip - // XXX 2 is a "magic number" meaning there is an extra chunk of docs for - // buffer if such is needed. - var cursor = self._cursorForQuery({ limit: self._limit * 2 }); - try { - cursor.forEach(function (doc, i) { // yields - if (!self._limit || i < self._limit) { - newResults.set(doc._id, doc); - } else { - newBuffer.set(doc._id, doc); - } - }); - break; - } catch (e) { - if (options.initial && typeof(e.code) === 'number') { - // This is an error document sent to us by mongod, not a connection - // error generated by the client. And we've never seen this query work - // successfully. Probably it's a bad selector or something, so we - // should NOT retry. Instead, we should halt the observe (which ends - // up calling `stop` on us). - self._multiplexer.queryError(e); - return; - } - - // During failover (eg) if we get an exception we should log and retry - // instead of crashing. - Meteor._debug("Got exception while polling query", e); - Meteor._sleepForMs(100); - } - } - - if (self._stopped) - return; - - self._publishNewResults(newResults, newBuffer); - }, - // Yields! _runQuery: function (options) { - return Meteor._isFibersEnabled ? this._runQueryWithFibers(options) : this._runQueryNoFibers(options); + return Meteor._isFibersEnabled ? Promise.await(this._runQueryAsync(options)) : this._runQueryAsync(options); }, // Transitions to QUERYING and runs another query, or (if already in QUERYING) @@ -886,37 +807,29 @@ _.extend(OplogObserveDriver.prototype, { }, // Yields! - _doneQuerying: function () { + _doneQuerying: async function () { var self = this; if (self._stopped) return; - const afterCaughtUp = () => { - if (self._stopped) - return; - if (self._phase !== PHASE.QUERYING) - throw Error("Phase unexpectedly " + self._phase); + await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); - Meteor._noYieldsAllowed(function () { - if (self._requeryWhenDoneThisQuery) { - self._requeryWhenDoneThisQuery = false; - self._pollQuery(); - } else if (self._needToFetch.empty()) { - self._beSteady(); - } else { - self._fetchModifiedDocuments(); - } - }); - }; + if (self._stopped) + return; + if (self._phase !== PHASE.QUERYING) + throw Error("Phase unexpectedly " + self._phase); - if (Meteor._isFibersEnabled) { - self._mongoHandle._oplogHandle.waitUntilCaughtUp(); // yields - return afterCaughtUp(); - } - - // TODO -> Should we wait? This doesn't make much sense without Fibers. - return self._mongoHandle._oplogHandle.waitUntilCaughtUp().then(afterCaughtUp); + await Meteor._noYieldsAllowed(async function () { + if (self._requeryWhenDoneThisQuery) { + self._requeryWhenDoneThisQuery = false; + self._pollQuery(); + } else if (self._needToFetch.empty()) { + await self._beSteady(); + } else { + self._fetchModifiedDocuments(); + } + }); }, _cursorForQuery: function (optionsOverwrite) { @@ -1013,46 +926,40 @@ _.extend(OplogObserveDriver.prototype, { // // It's important to check self._stopped after every call in this file that // can yield! - stop: function () { + _stop: async function() { var self = this; if (self._stopped) return; self._stopped = true; - const afterStopHandles = () => { - // Note: we *don't* use multiplexer.onFlush here because this stop - // callback is actually invoked by the multiplexer itself when it has - // determined that there are no handles left. So nothing is actually going - // to get flushed (and it's probably not valid to call methods on the - // dying multiplexer). - _.each(self._writesToCommitWhenWeReachSteady, function (w) { - w.committed(); // maybe yields? - }); - self._writesToCommitWhenWeReachSteady = null; - - // Proactively drop references to potentially big things. - self._published = null; - self._unpublishedBuffer = null; - self._needToFetch = null; - self._currentlyFetching = null; - self._oplogEntryHandle = null; - self._listenersHandle = null; - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-oplog", -1); - }; - - if (Meteor._isFibersEnabled) { - self._stopHandles.forEach(handle => { - handle.stop(); - }); - return afterStopHandles(); + // Note: we *don't* use multiplexer.onFlush here because this stop + // callback is actually invoked by the multiplexer itself when it has + // determined that there are no handles left. So nothing is actually going + // to get flushed (and it's probably not valid to call methods on the + // dying multiplexer). + for (const w of self._writesToCommitWhenWeReachSteady) { + await w.committed(); } + self._writesToCommitWhenWeReachSteady = null; - Promise.all(self._stopHandles.map(async handle => { - const _h = await handle; - _h.stop(); - })).then(() => afterStopHandles()); + // Proactively drop references to potentially big things. + self._published = null; + self._unpublishedBuffer = null; + self._needToFetch = null; + self._currentlyFetching = null; + self._oplogEntryHandle = null; + self._listenersHandle = null; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-oplog", -1); + + for await (const handle of self._stopHandles) { + await handle.stop(); + } + }, + stop: function() { + const self = this; + return Meteor._isFibersEnabled ? Promise.await(self._stop()) : self._stop(); }, _registerPhaseChange: function (phase) { From 47d4e825e7ca30af02bcc87ca8755c287c1f1152 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 31 Oct 2022 21:22:18 -0300 Subject: [PATCH 0034/1965] Fix merge conflict --- packages/meteor/dynamics_nodejs.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index e36699f6c4..2a278acd5e 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -121,31 +121,6 @@ class EnvironmentVariableAsync { */ Meteor.EnvironmentVariable = Meteor._isFibersEnabled ? EnvironmentVariableFibers : EnvironmentVariableAsync; -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValueAsync - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ - -EVp._set = function (context) { - Meteor._nodeCodeMustBeInFiber(); - Fiber.current._meteor_dynamics[this.slot] = context; -}; - -EVp._setNewContextAndGetCurrent = function (value) { - Meteor._nodeCodeMustBeInFiber(); - if (!Fiber.current._meteor_dynamics) { - Fiber.current._meteor_dynamics = []; - } - const saved = Fiber.current._meteor_dynamics[this.slot]; - this._set(value); - return saved; -}; - // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from // inside a fiber and ensures it sees the values of Meteor environment From 898800a30290bb9817f98a26fce45ce07bae3077 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 31 Oct 2022 21:27:46 -0300 Subject: [PATCH 0035/1965] Fix "type" is not defined issue on TinyTest. --- packages/test-in-browser/driver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js index d0d5fa4423..e707f6de2c 100644 --- a/packages/test-in-browser/driver.js +++ b/packages/test-in-browser/driver.js @@ -451,7 +451,7 @@ Template.test.helpers({ eventsArray: function() { var events = this.events.filter(function(e) { - return e[type] != "finish"; + return e.type !== "finish"; }); var partitionBy = function(seq, func) { @@ -583,4 +583,4 @@ Template.event.helpers({ is_debuggable: function() { return !!this.cookie; } -}); \ No newline at end of file +}); From ce662196a3fe4d036cc00f0b6dd524c9d760f828 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 4 Nov 2022 14:48:48 -0300 Subject: [PATCH 0036/1965] Duplicate mongo package to preserve commit history --- packages/{mongo => mongo-async}/.gitignore | 0 packages/{mongo => mongo-async}/.npm/package/.gitignore | 0 packages/{mongo => mongo-async}/.npm/package/README | 0 packages/{mongo => mongo-async}/.npm/package/npm-shrinkwrap.json | 0 packages/{mongo => mongo-async}/README.md | 0 packages/{mongo => mongo-async}/allow_tests.js | 0 packages/{mongo => mongo-async}/collection.js | 0 packages/{mongo => mongo-async}/collection_async_tests.js | 0 packages/{mongo => mongo-async}/collection_tests.js | 0 packages/{mongo => mongo-async}/connection_options.js | 0 packages/{mongo => mongo-async}/doc_fetcher.js | 0 packages/{mongo => mongo-async}/doc_fetcher_tests.js | 0 packages/{mongo => mongo-async}/local_collection_driver.js | 0 packages/{mongo => mongo-async}/mongo_driver.js | 0 packages/{mongo => mongo-async}/mongo_livedata_tests.js | 0 packages/{mongo => mongo-async}/mongo_utils.js | 0 packages/{mongo => mongo-async}/observe_changes_tests.js | 0 packages/{mongo => mongo-async}/observe_multiplex.js | 0 packages/{mongo => mongo-async}/oplog_observe_driver.js | 0 packages/{mongo => mongo-async}/oplog_tailing.js | 0 packages/{mongo => mongo-async}/oplog_tests.js | 0 packages/{mongo => mongo-async}/oplog_v2_converter.js | 0 packages/{mongo => mongo-async}/oplog_v2_converter_tests.js | 0 packages/{mongo => mongo-async}/package.js | 0 packages/{mongo => mongo-async}/polling_observe_driver.js | 0 packages/{mongo => mongo-async}/remote_collection_driver.js | 0 packages/{mongo => mongo-async}/upsert_compatibility_test.js | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename packages/{mongo => mongo-async}/.gitignore (100%) rename packages/{mongo => mongo-async}/.npm/package/.gitignore (100%) rename packages/{mongo => mongo-async}/.npm/package/README (100%) rename packages/{mongo => mongo-async}/.npm/package/npm-shrinkwrap.json (100%) rename packages/{mongo => mongo-async}/README.md (100%) rename packages/{mongo => mongo-async}/allow_tests.js (100%) rename packages/{mongo => mongo-async}/collection.js (100%) rename packages/{mongo => mongo-async}/collection_async_tests.js (100%) rename packages/{mongo => mongo-async}/collection_tests.js (100%) rename packages/{mongo => mongo-async}/connection_options.js (100%) rename packages/{mongo => mongo-async}/doc_fetcher.js (100%) rename packages/{mongo => mongo-async}/doc_fetcher_tests.js (100%) rename packages/{mongo => mongo-async}/local_collection_driver.js (100%) rename packages/{mongo => mongo-async}/mongo_driver.js (100%) rename packages/{mongo => mongo-async}/mongo_livedata_tests.js (100%) rename packages/{mongo => mongo-async}/mongo_utils.js (100%) rename packages/{mongo => mongo-async}/observe_changes_tests.js (100%) rename packages/{mongo => mongo-async}/observe_multiplex.js (100%) rename packages/{mongo => mongo-async}/oplog_observe_driver.js (100%) rename packages/{mongo => mongo-async}/oplog_tailing.js (100%) rename packages/{mongo => mongo-async}/oplog_tests.js (100%) rename packages/{mongo => mongo-async}/oplog_v2_converter.js (100%) rename packages/{mongo => mongo-async}/oplog_v2_converter_tests.js (100%) rename packages/{mongo => mongo-async}/package.js (100%) rename packages/{mongo => mongo-async}/polling_observe_driver.js (100%) rename packages/{mongo => mongo-async}/remote_collection_driver.js (100%) rename packages/{mongo => mongo-async}/upsert_compatibility_test.js (100%) diff --git a/packages/mongo/.gitignore b/packages/mongo-async/.gitignore similarity index 100% rename from packages/mongo/.gitignore rename to packages/mongo-async/.gitignore diff --git a/packages/mongo/.npm/package/.gitignore b/packages/mongo-async/.npm/package/.gitignore similarity index 100% rename from packages/mongo/.npm/package/.gitignore rename to packages/mongo-async/.npm/package/.gitignore diff --git a/packages/mongo/.npm/package/README b/packages/mongo-async/.npm/package/README similarity index 100% rename from packages/mongo/.npm/package/README rename to packages/mongo-async/.npm/package/README diff --git a/packages/mongo/.npm/package/npm-shrinkwrap.json b/packages/mongo-async/.npm/package/npm-shrinkwrap.json similarity index 100% rename from packages/mongo/.npm/package/npm-shrinkwrap.json rename to packages/mongo-async/.npm/package/npm-shrinkwrap.json diff --git a/packages/mongo/README.md b/packages/mongo-async/README.md similarity index 100% rename from packages/mongo/README.md rename to packages/mongo-async/README.md diff --git a/packages/mongo/allow_tests.js b/packages/mongo-async/allow_tests.js similarity index 100% rename from packages/mongo/allow_tests.js rename to packages/mongo-async/allow_tests.js diff --git a/packages/mongo/collection.js b/packages/mongo-async/collection.js similarity index 100% rename from packages/mongo/collection.js rename to packages/mongo-async/collection.js diff --git a/packages/mongo/collection_async_tests.js b/packages/mongo-async/collection_async_tests.js similarity index 100% rename from packages/mongo/collection_async_tests.js rename to packages/mongo-async/collection_async_tests.js diff --git a/packages/mongo/collection_tests.js b/packages/mongo-async/collection_tests.js similarity index 100% rename from packages/mongo/collection_tests.js rename to packages/mongo-async/collection_tests.js diff --git a/packages/mongo/connection_options.js b/packages/mongo-async/connection_options.js similarity index 100% rename from packages/mongo/connection_options.js rename to packages/mongo-async/connection_options.js diff --git a/packages/mongo/doc_fetcher.js b/packages/mongo-async/doc_fetcher.js similarity index 100% rename from packages/mongo/doc_fetcher.js rename to packages/mongo-async/doc_fetcher.js diff --git a/packages/mongo/doc_fetcher_tests.js b/packages/mongo-async/doc_fetcher_tests.js similarity index 100% rename from packages/mongo/doc_fetcher_tests.js rename to packages/mongo-async/doc_fetcher_tests.js diff --git a/packages/mongo/local_collection_driver.js b/packages/mongo-async/local_collection_driver.js similarity index 100% rename from packages/mongo/local_collection_driver.js rename to packages/mongo-async/local_collection_driver.js diff --git a/packages/mongo/mongo_driver.js b/packages/mongo-async/mongo_driver.js similarity index 100% rename from packages/mongo/mongo_driver.js rename to packages/mongo-async/mongo_driver.js diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo-async/mongo_livedata_tests.js similarity index 100% rename from packages/mongo/mongo_livedata_tests.js rename to packages/mongo-async/mongo_livedata_tests.js diff --git a/packages/mongo/mongo_utils.js b/packages/mongo-async/mongo_utils.js similarity index 100% rename from packages/mongo/mongo_utils.js rename to packages/mongo-async/mongo_utils.js diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo-async/observe_changes_tests.js similarity index 100% rename from packages/mongo/observe_changes_tests.js rename to packages/mongo-async/observe_changes_tests.js diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo-async/observe_multiplex.js similarity index 100% rename from packages/mongo/observe_multiplex.js rename to packages/mongo-async/observe_multiplex.js diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo-async/oplog_observe_driver.js similarity index 100% rename from packages/mongo/oplog_observe_driver.js rename to packages/mongo-async/oplog_observe_driver.js diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo-async/oplog_tailing.js similarity index 100% rename from packages/mongo/oplog_tailing.js rename to packages/mongo-async/oplog_tailing.js diff --git a/packages/mongo/oplog_tests.js b/packages/mongo-async/oplog_tests.js similarity index 100% rename from packages/mongo/oplog_tests.js rename to packages/mongo-async/oplog_tests.js diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo-async/oplog_v2_converter.js similarity index 100% rename from packages/mongo/oplog_v2_converter.js rename to packages/mongo-async/oplog_v2_converter.js diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo-async/oplog_v2_converter_tests.js similarity index 100% rename from packages/mongo/oplog_v2_converter_tests.js rename to packages/mongo-async/oplog_v2_converter_tests.js diff --git a/packages/mongo/package.js b/packages/mongo-async/package.js similarity index 100% rename from packages/mongo/package.js rename to packages/mongo-async/package.js diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo-async/polling_observe_driver.js similarity index 100% rename from packages/mongo/polling_observe_driver.js rename to packages/mongo-async/polling_observe_driver.js diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo-async/remote_collection_driver.js similarity index 100% rename from packages/mongo/remote_collection_driver.js rename to packages/mongo-async/remote_collection_driver.js diff --git a/packages/mongo/upsert_compatibility_test.js b/packages/mongo-async/upsert_compatibility_test.js similarity index 100% rename from packages/mongo/upsert_compatibility_test.js rename to packages/mongo-async/upsert_compatibility_test.js From 37f20cd1385397a09953355d10af7b8e5e97e95b Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 4 Nov 2022 14:50:43 -0300 Subject: [PATCH 0037/1965] Restore mongo package to preserve commit history --- packages/mongo/.gitignore | 1 + packages/mongo/.npm/package/.gitignore | 1 + packages/mongo/.npm/package/README | 7 + .../mongo/.npm/package/npm-shrinkwrap.json | 10 + packages/mongo/README.md | 37 + packages/mongo/allow_tests.js | 899 +++++ packages/mongo/collection.js | 1018 +++++ packages/mongo/collection_async_tests.js | 21 + packages/mongo/collection_tests.js | 386 ++ packages/mongo/connection_options.js | 10 + packages/mongo/doc_fetcher.js | 57 + packages/mongo/doc_fetcher_tests.js | 41 + packages/mongo/local_collection_driver.js | 30 + packages/mongo/mongo_driver.js | 1802 +++++++++ packages/mongo/mongo_livedata_tests.js | 3498 +++++++++++++++++ packages/mongo/mongo_utils.js | 11 + packages/mongo/observe_changes_tests.js | 424 ++ packages/mongo/observe_multiplex.js | 231 ++ packages/mongo/oplog_observe_driver.js | 1034 +++++ packages/mongo/oplog_tailing.js | 383 ++ packages/mongo/oplog_tests.js | 190 + packages/mongo/oplog_v2_converter.js | 124 + packages/mongo/oplog_v2_converter_tests.js | 86 + packages/mongo/package.js | 105 + packages/mongo/polling_observe_driver.js | 325 ++ packages/mongo/remote_collection_driver.js | 48 + packages/mongo/upsert_compatibility_test.js | 151 + 27 files changed, 10930 insertions(+) create mode 100644 packages/mongo/.gitignore create mode 100644 packages/mongo/.npm/package/.gitignore create mode 100644 packages/mongo/.npm/package/README create mode 100644 packages/mongo/.npm/package/npm-shrinkwrap.json create mode 100644 packages/mongo/README.md create mode 100644 packages/mongo/allow_tests.js create mode 100644 packages/mongo/collection.js create mode 100644 packages/mongo/collection_async_tests.js create mode 100644 packages/mongo/collection_tests.js create mode 100644 packages/mongo/connection_options.js create mode 100644 packages/mongo/doc_fetcher.js create mode 100644 packages/mongo/doc_fetcher_tests.js create mode 100644 packages/mongo/local_collection_driver.js create mode 100644 packages/mongo/mongo_driver.js create mode 100644 packages/mongo/mongo_livedata_tests.js create mode 100644 packages/mongo/mongo_utils.js create mode 100644 packages/mongo/observe_changes_tests.js create mode 100644 packages/mongo/observe_multiplex.js create mode 100644 packages/mongo/oplog_observe_driver.js create mode 100644 packages/mongo/oplog_tailing.js create mode 100644 packages/mongo/oplog_tests.js create mode 100644 packages/mongo/oplog_v2_converter.js create mode 100644 packages/mongo/oplog_v2_converter_tests.js create mode 100644 packages/mongo/package.js create mode 100644 packages/mongo/polling_observe_driver.js create mode 100644 packages/mongo/remote_collection_driver.js create mode 100644 packages/mongo/upsert_compatibility_test.js diff --git a/packages/mongo/.gitignore b/packages/mongo/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/mongo/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/mongo/.npm/package/.gitignore b/packages/mongo/.npm/package/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/mongo/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/mongo/.npm/package/README b/packages/mongo/.npm/package/README new file mode 100644 index 0000000000..3d492553a4 --- /dev/null +++ b/packages/mongo/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/mongo/.npm/package/npm-shrinkwrap.json b/packages/mongo/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000..ac53d03428 --- /dev/null +++ b/packages/mongo/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 1, + "dependencies": { + "mongodb-uri": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz", + "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" + } + } +} diff --git a/packages/mongo/README.md b/packages/mongo/README.md new file mode 100644 index 0000000000..701883a0a1 --- /dev/null +++ b/packages/mongo/README.md @@ -0,0 +1,37 @@ +# mongo +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/mongo) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/mongo) +*** + +The `mongo` package is a [full stack database +driver](https://www.meteor.com/full-stack-db-drivers) that provides +several paramount pieces of functionality to work with MongoDB in +Meteor: + +- an efficient [Livequery][livequery] implementation providing real-time + updates from the database by consuming the MongoDB replication log +- a fall-back Livequery implementation for cases when the replication log is not + available, implemented by polling the database +- DDP RPC end-points for updating the data from clients connected over the wire +- Serialization and deserialization of updates to the DDP format + +To learn more about Livequery, see the [project page on +www.meteor.com][livequery]. + +[livequery]: https://www.meteor.com/livequery + +## Direct access to npm mongodb API + +On the server, the `mongo` package is implemented using the +[npm `mongodb` module](https://www.npmjs.com/package/mongodb). If you'd like +direct access to this module, you can find it at +`MongoInternals.NpmModules.mongodb.module`. Its version can be read at +`MongoInternals.NpmModules.mongodb.version`. + +Additionally, you can call `c.rawCollection()` or `c.rawDatabase()` on any +`Mongo.Collection` to get the object from the npm `mongodb` module corresponding +to the collection or database. This is documented at +http://mongodb.github.io/node-mongodb-native/ + +The version of `mongo` used may change incompatibly from version to version of +Meteor (or we may even replace it with an entirely different implementation); +use at your own risk. diff --git a/packages/mongo/allow_tests.js b/packages/mongo/allow_tests.js new file mode 100644 index 0000000000..d6e669d425 --- /dev/null +++ b/packages/mongo/allow_tests.js @@ -0,0 +1,899 @@ +if (Meteor.isServer) { + // Set up allow/deny rules for test collections + + var allowCollections = {}; + + // We create the collections in the publisher (instead of using a method or + // something) because if we made them with a method, we'd need to follow the + // method with some subscribes, and it's possible that the method call would + // be delayed by a wait method and the subscribe messages would be sent before + // it and fail due to the collection not yet existing. So we are very hacky + // and use a publish. + Meteor.publish("allowTests", function (nonce, idGeneration) { + check(nonce, String); + check(idGeneration, String); + var cursors = []; + var needToConfigure; + + // helper for defining a collection. we are careful to create just one + // Mongo.Collection even if the sub body is rerun, by caching them. + var defineCollection = function(name, insecure, transform) { + var fullName = name + idGeneration + nonce; + + var collection; + if (_.has(allowCollections, fullName)) { + collection = allowCollections[fullName]; + if (needToConfigure === true) + throw new Error("collections inconsistently exist"); + needToConfigure = false; + } else { + collection = new Mongo.Collection( + fullName, {idGeneration: idGeneration, transform: transform}); + allowCollections[fullName] = collection; + if (needToConfigure === false) + throw new Error("collections inconsistently don't exist"); + needToConfigure = true; + collection._insecure = insecure; + var m = {}; + m["clear-collection-" + fullName] = function() { + collection.remove({}); + }; + Meteor.methods(m); + } + + cursors.push(collection.find()); + return collection; + }; + + var insecureCollection = defineCollection( + "collection-insecure", true /*insecure*/); + // totally locked down collection + var lockedDownCollection = defineCollection( + "collection-locked-down", false /*insecure*/); + // restricted collection with same allowed modifications, both with and + // without the `insecure` package + var restrictedCollectionDefaultSecure = defineCollection( + "collection-restrictedDefaultSecure", false /*insecure*/); + var restrictedCollectionDefaultInsecure = defineCollection( + "collection-restrictedDefaultInsecure", true /*insecure*/); + var restrictedCollectionForUpdateOptionsTest = defineCollection( + "collection-restrictedForUpdateOptionsTest", true /*insecure*/); + var restrictedCollectionForPartialAllowTest = defineCollection( + "collection-restrictedForPartialAllowTest", true /*insecure*/); + var restrictedCollectionForPartialDenyTest = defineCollection( + "collection-restrictedForPartialDenyTest", true /*insecure*/); + var restrictedCollectionForFetchTest = defineCollection( + "collection-restrictedForFetchTest", true /*insecure*/); + var restrictedCollectionForFetchAllTest = defineCollection( + "collection-restrictedForFetchAllTest", true /*insecure*/); + var restrictedCollectionWithTransform = defineCollection( + "withTransform", false, function (doc) { + return doc.a; + }); + var restrictedCollectionForInvalidTransformTest = defineCollection( + "collection-restrictedForInvalidTransform", false /*insecure*/); + var restrictedCollectionForClientIdTest = defineCollection( + "collection-restrictedForClientIdTest", false /*insecure*/); + + if (needToConfigure) { + restrictedCollectionWithTransform.allow({ + insert: function (userId, doc) { + return doc.foo === "foo"; + }, + update: function (userId, doc) { + return doc.foo === "foo"; + }, + remove: function (userId, doc) { + return doc.bar === "bar"; + } + }); + restrictedCollectionWithTransform.allow({ + // transform: null means that doc here is the top level, not the 'a' + // element. + transform: null, + insert: function (userId, doc) { + return !!doc.topLevelField; + }, + update: function (userId, doc) { + return !!doc.topLevelField; + } + }); + restrictedCollectionForInvalidTransformTest.allow({ + // transform must return an object which is not a mongo id + transform: function (doc) { return doc._id; }, + insert: function () { return true; } + }); + restrictedCollectionForClientIdTest.allow({ + // This test just requires the collection to trigger the restricted + // case. + insert: function () { return true; } + }); + + // two calls to allow to verify that either validator is sufficient. + var allows = [{ + insert: function(userId, doc) { + return doc.canInsert; + }, + update: function(userId, doc) { + return doc.canUpdate; + }, + remove: function (userId, doc) { + return doc.canRemove; + } + }, { + insert: function(userId, doc) { + return doc.canInsert2; + }, + update: function(userId, doc, fields, modifier) { + return -1 !== _.indexOf(fields, 'canUpdate2'); + }, + remove: function(userId, doc) { + return doc.canRemove2; + } + }]; + + // two calls to deny to verify that either one blocks the change. + var denies = [{ + insert: function(userId, doc) { + return doc.cantInsert; + }, + remove: function (userId, doc) { + return doc.cantRemove; + } + }, { + insert: function(userId, doc) { + // Don't allow explicit ID to be set by the client. + return _.has(doc, '_id'); + }, + update: function(userId, doc, fields, modifier) { + return -1 !== _.indexOf(fields, 'verySecret'); + } + }]; + + _.each([ + restrictedCollectionDefaultSecure, + restrictedCollectionDefaultInsecure, + restrictedCollectionForUpdateOptionsTest + ], function (collection) { + _.each(allows, function (allow) { + collection.allow(allow); + }); + _.each(denies, function (deny) { + collection.deny(deny); + }); + }); + + // just restrict one operation so that we can verify that others + // fail + restrictedCollectionForPartialAllowTest.allow({ + insert: function() {} + }); + restrictedCollectionForPartialDenyTest.deny({ + insert: function() {} + }); + + // verify that we only fetch the fields specified - we should + // be fetching just field1, field2, and field3. + restrictedCollectionForFetchTest.allow({ + insert: function() { return true; }, + update: function(userId, doc) { + // throw fields in doc so that we can inspect them in test + throw new Meteor.Error( + 999, "Test: Fields in doc: " + _.keys(doc).sort().join(',')); + }, + remove: function(userId, doc) { + // throw fields in doc so that we can inspect them in test + throw new Meteor.Error( + 999, "Test: Fields in doc: " + _.keys(doc).sort().join(',')); + }, + fetch: ['field1'] + }); + restrictedCollectionForFetchTest.allow({ + fetch: ['field2'] + }); + restrictedCollectionForFetchTest.deny({ + fetch: ['field3'] + }); + + // verify that not passing fetch to one of the calls to allow + // causes all fields to be fetched + restrictedCollectionForFetchAllTest.allow({ + insert: function() { return true; }, + update: function(userId, doc) { + // throw fields in doc so that we can inspect them in test + throw new Meteor.Error( + 999, "Test: Fields in doc: " + _.keys(doc).sort().join(',')); + }, + remove: function(userId, doc) { + // throw fields in doc so that we can inspect them in test + throw new Meteor.Error( + 999, "Test: Fields in doc: " + _.keys(doc).sort().join(',')); + }, + fetch: ['field1'] + }); + restrictedCollectionForFetchAllTest.allow({ + update: function() { return true; } + }); + } + + return cursors; + }); +} + +if (Meteor.isClient) { + _.each(['STRING', 'MONGO'], function (idGeneration) { + // Set up a bunch of test collections... on the client! They match the ones + // created by setUpAllowTestsCollections. + + var nonce = Random.id(); + // Tell the server to make, configure, and publish a set of collections unique + // to our test run. Since the method does not unblock, this will complete + // running on the server before anything else happens. + Meteor.subscribe('allowTests', nonce, idGeneration); + + // helper for defining a collection, subscribing to it, and defining + // a method to clear it + var defineCollection = function(name, transform) { + var fullName = name + idGeneration + nonce; + var collection = new Mongo.Collection( + fullName, {idGeneration: idGeneration, transform: transform}); + + collection.callClearMethod = function (callback) { + Meteor.call("clear-collection-" + fullName, callback); + }; + collection.unnoncedName = name + idGeneration; + return collection; + }; + + // totally insecure collection + var insecureCollection = defineCollection("collection-insecure"); + + // totally locked down collection + var lockedDownCollection = defineCollection("collection-locked-down"); + + // restricted collection with same allowed modifications, both with and + // without the `insecure` package + var restrictedCollectionDefaultSecure = defineCollection( + "collection-restrictedDefaultSecure"); + var restrictedCollectionDefaultInsecure = defineCollection( + "collection-restrictedDefaultInsecure"); + var restrictedCollectionForUpdateOptionsTest = defineCollection( + "collection-restrictedForUpdateOptionsTest"); + var restrictedCollectionForPartialAllowTest = defineCollection( + "collection-restrictedForPartialAllowTest"); + var restrictedCollectionForPartialDenyTest = defineCollection( + "collection-restrictedForPartialDenyTest"); + var restrictedCollectionForFetchTest = defineCollection( + "collection-restrictedForFetchTest"); + var restrictedCollectionForFetchAllTest = defineCollection( + "collection-restrictedForFetchAllTest"); + var restrictedCollectionWithTransform = defineCollection( + "withTransform", function (doc) { + return doc.a; + }); + var restrictedCollectionForInvalidTransformTest = defineCollection( + "collection-restrictedForInvalidTransform"); + var restrictedCollectionForClientIdTest = defineCollection( + "collection-restrictedForClientIdTest"); + + // test that if allow is called once then the collection is + // restricted, and that other mutations aren't allowed + testAsyncMulti("collection - partial allow, " + idGeneration, [ + function (test, expect) { + restrictedCollectionForPartialAllowTest.update( + 'foo', {$set: {updated: true}}, expect(function (err, res) { + test.equal(err.error, 403); + })); + } + ]); + + // test that if deny is called once then the collection is + // restricted, and that other mutations aren't allowed + testAsyncMulti("collection - partial deny, " + idGeneration, [ + function (test, expect) { + restrictedCollectionForPartialDenyTest.update( + 'foo', {$set: {updated: true}}, expect(function (err, res) { + test.equal(err.error, 403); + })); + } + ]); + + + // test that we only fetch the fields specified + testAsyncMulti("collection - fetch, " + idGeneration, [ + function (test, expect) { + var fetchId = restrictedCollectionForFetchTest.insert( + {field1: 1, field2: 1, field3: 1, field4: 1}); + var fetchAllId = restrictedCollectionForFetchAllTest.insert( + {field1: 1, field2: 1, field3: 1, field4: 1}); + restrictedCollectionForFetchTest.update( + fetchId, {$set: {updated: true}}, expect(function (err, res) { + test.equal(err.reason, + "Test: Fields in doc: _id,field1,field2,field3"); + })); + restrictedCollectionForFetchTest.remove( + fetchId, expect(function (err, res) { + test.equal(err.reason, + "Test: Fields in doc: _id,field1,field2,field3"); + })); + + restrictedCollectionForFetchAllTest.update( + fetchAllId, {$set: {updated: true}}, expect(function (err, res) { + test.equal(err.reason, + "Test: Fields in doc: _id,field1,field2,field3,field4"); + })); + restrictedCollectionForFetchAllTest.remove( + fetchAllId, expect(function (err, res) { + test.equal(err.reason, + "Test: Fields in doc: _id,field1,field2,field3,field4"); + })); + } + ]); + + (function(){ + testAsyncMulti("collection - restricted factories " + idGeneration, [ + function (test, expect) { + restrictedCollectionWithTransform.callClearMethod(expect(function () { + test.equal(restrictedCollectionWithTransform.find().count(), 0); + })); + }, + function (test, expect) { + var self = this; + restrictedCollectionWithTransform.insert({ + a: {foo: "foo", bar: "bar", baz: "baz"} + }, expect(function (e, res) { + test.isFalse(e); + test.isTrue(res); + self.item1 = res; + })); + restrictedCollectionWithTransform.insert({ + a: {foo: "foo", bar: "quux", baz: "quux"}, + b: "potato" + }, expect(function (e, res) { + test.isFalse(e); + test.isTrue(res); + self.item2 = res; + })); + restrictedCollectionWithTransform.insert({ + a: {foo: "adsfadf", bar: "quux", baz: "quux"}, + b: "potato" + }, expect(function (e, res) { + test.isTrue(e); + })); + restrictedCollectionWithTransform.insert({ + a: {foo: "bar"}, + topLevelField: true + }, expect(function (e, res) { + test.isFalse(e); + test.isTrue(res); + self.item3 = res; + })); + }, + function (test, expect) { + var self = this; + // This should work, because there is an update allow for things with + // topLevelField. + restrictedCollectionWithTransform.update( + self.item3, { $set: { xxx: true } }, expect(function (e, res) { + test.isFalse(e); + test.equal(1, res); + })); + }, + function (test, expect) { + var self = this; + test.equal( + restrictedCollectionWithTransform.findOne(self.item1), + {_id: self.item1, foo: "foo", bar: "bar", baz: "baz"}); + restrictedCollectionWithTransform.remove( + self.item1, expect(function (e, res) { + test.isFalse(e); + })); + restrictedCollectionWithTransform.remove( + self.item2, expect(function (e, res) { + test.isTrue(e); + })); + } + ]); + })(); + + testAsyncMulti("collection - insecure, " + idGeneration, [ + function (test, expect) { + insecureCollection.callClearMethod(expect(function () { + test.equal(insecureCollection.find().count(), 0); + })); + }, + function (test, expect) { + var id = insecureCollection.insert({foo: 'bar'}, expect(function(err, res) { + test.equal(res, id); + test.equal(insecureCollection.find(id).count(), 1); + test.equal(insecureCollection.findOne(id).foo, 'bar'); + })); + test.equal(insecureCollection.find(id).count(), 1); + test.equal(insecureCollection.findOne(id).foo, 'bar'); + } + ]); + + testAsyncMulti("collection - locked down, " + idGeneration, [ + function (test, expect) { + lockedDownCollection.callClearMethod(expect(function() { + test.equal(lockedDownCollection.find().count(), 0); + })); + }, + function (test, expect) { + lockedDownCollection.insert({foo: 'bar'}, expect(function (err, res) { + test.equal(err.error, 403); + test.equal(lockedDownCollection.find().count(), 0); + })); + } + ]); + + (function () { + var collection = restrictedCollectionForUpdateOptionsTest; + var id1, id2; + testAsyncMulti("collection - update options, " + idGeneration, [ + // init + function (test, expect) { + collection.callClearMethod(expect(function () { + test.equal(collection.find().count(), 0); + })); + }, + // put a few objects + function (test, expect) { + var doc = {canInsert: true, canUpdate: true}; + id1 = collection.insert(doc); + id2 = collection.insert(doc); + collection.insert(doc); + collection.insert(doc, expect(function (err, res) { + test.isFalse(err); + test.equal(collection.find().count(), 4); + })); + }, + // update by id + function (test, expect) { + collection.update( + id1, + {$set: {updated: true}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + test.equal(collection.find({updated: true}).count(), 1); + })); + }, + // update by id in an object + function (test, expect) { + collection.update( + {_id: id2}, + {$set: {updated: true}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + test.equal(collection.find({updated: true}).count(), 2); + })); + }, + // update with replacement operator not allowed, and has nice error. + function (test, expect) { + collection.update( + {_id: id2}, + {_id: id2, updated: true}, + expect(function (err, res) { + test.equal(err.error, 403); + test.matches(err.reason, /In a restricted/); + // unchanged + test.equal(collection.find({updated: true}).count(), 2); + })); + }, + // upsert not allowed, and has nice error. + function (test, expect) { + collection.update( + {_id: id2}, + {$set: { upserted: true }}, + { upsert: true }, + expect(function (err, res) { + test.equal(err.error, 403); + test.matches(err.reason, /in a restricted/); + test.equal(collection.find({ upserted: true }).count(), 0); + })); + }, + // update with rename operator not allowed, and has nice error. + function (test, expect) { + collection.update( + {_id: id2}, + {$rename: {updated: 'asdf'}}, + expect(function (err, res) { + test.equal(err.error, 403); + test.matches(err.reason, /not allowed/); + // unchanged + test.equal(collection.find({updated: true}).count(), 2); + })); + }, + // update method with a non-ID selector is not allowed + function (test, expect) { + // We shouldn't even send the method... + test.throws(function () { + collection.update( + {updated: {$exists: false}}, + {$set: {updated: true}}); + }); + // ... but if we did, the server would reject it too. + Meteor.call( + '/' + collection._name + '/update', + {updated: {$exists: false}}, + {$set: {updated: true}}, + expect(function (err, res) { + test.equal(err.error, 403); + // unchanged + test.equal(collection.find({updated: true}).count(), 2); + })); + }, + // make sure it doesn't think that {_id: 'foo', something: else} is ok. + function (test, expect) { + test.throws(function () { + collection.update( + {_id: id1, updated: {$exists: false}}, + {$set: {updated: true}}); + }); + }, + // remove method with a non-ID selector is not allowed + function (test, expect) { + // We shouldn't even send the method... + test.throws(function () { + collection.remove({updated: true}); + }); + // ... but if we did, the server would reject it too. + Meteor.call( + '/' + collection._name + '/remove', + {updated: true}, + expect(function (err, res) { + test.equal(err.error, 403); + // unchanged + test.equal(collection.find({updated: true}).count(), 2); + })); + } + ]); + }) (); + + _.each( + [restrictedCollectionDefaultInsecure, restrictedCollectionDefaultSecure], + function(collection) { + var canUpdateId, canRemoveId; + + testAsyncMulti("collection - " + collection.unnoncedName, [ + // init + function (test, expect) { + collection.callClearMethod(expect(function () { + test.equal(collection.find().count(), 0); + })); + }, + + // insert with no allows passing. request is denied. + function (test, expect) { + collection.insert( + {}, + expect(function (err, res) { + test.equal(err.error, 403); + test.equal(collection.find().count(), 0); + })); + }, + // insert with one allow and one deny. denied. + function (test, expect) { + collection.insert( + {canInsert: true, cantInsert: true}, + expect(function (err, res) { + test.equal(err.error, 403); + test.equal(collection.find().count(), 0); + })); + }, + // insert with one allow and other deny. denied. + function (test, expect) { + collection.insert( + {canInsert: true, _id: Random.id()}, + expect(function (err, res) { + test.equal(err.error, 403); + test.equal(collection.find().count(), 0); + })); + }, + // insert one allow passes. allowed. + function (test, expect) { + collection.insert( + {canInsert: true}, + expect(function (err, res) { + test.isFalse(err); + test.equal(collection.find().count(), 1); + })); + }, + // insert other allow passes. allowed. + // includes canUpdate for later. + function (test, expect) { + canUpdateId = collection.insert( + {canInsert2: true, canUpdate: true}, + expect(function (err, res) { + test.isFalse(err); + test.equal(collection.find().count(), 2); + })); + }, + // yet a third insert executes. this one has canRemove and + // cantRemove set for later. + function (test, expect) { + canRemoveId = collection.insert( + {canInsert: true, canRemove: true, cantRemove: true}, + expect(function (err, res) { + test.isFalse(err); + test.equal(collection.find().count(), 3); + })); + }, + + // can't update with a non-operator mutation + function (test, expect) { + collection.update( + canUpdateId, {newObject: 1}, + expect(function (err, res) { + test.equal(err.error, 403); + test.equal(collection.find().count(), 3); + })); + }, + + // updating dotted fields works as if we are changing their + // top part + function (test, expect) { + collection.update( + canUpdateId, {$set: {"dotted.field": 1}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + test.equal(collection.findOne(canUpdateId).dotted.field, 1); + })); + }, + function (test, expect) { + collection.update( + canUpdateId, {$set: {"verySecret.field": 1}}, + expect(function (err, res) { + test.equal(err.error, 403); + test.equal(collection.find({verySecret: {$exists: true}}).count(), 0); + })); + }, + + // update doesn't do anything if no docs match + function (test, expect) { + collection.update( + "doesn't exist", + {$set: {updated: true}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 0); + // nothing has changed + test.equal(collection.find().count(), 3); + test.equal(collection.find({updated: true}).count(), 0); + })); + }, + // update fails when access is denied trying to set `verySecret` + function (test, expect) { + collection.update( + canUpdateId, {$set: {verySecret: true}}, + expect(function (err, res) { + test.equal(err.error, 403); + // nothing has changed + test.equal(collection.find().count(), 3); + test.equal(collection.find({updated: true}).count(), 0); + })); + }, + // update fails when trying to set two fields, one of which is + // `verySecret` + function (test, expect) { + collection.update( + canUpdateId, {$set: {updated: true, verySecret: true}}, + expect(function (err, res) { + test.equal(err.error, 403); + // nothing has changed + test.equal(collection.find().count(), 3); + test.equal(collection.find({updated: true}).count(), 0); + })); + }, + // update fails when trying to modify docs that don't + // have `canUpdate` set + function (test, expect) { + collection.update( + canRemoveId, + {$set: {updated: true}}, + expect(function (err, res) { + test.equal(err.error, 403); + // nothing has changed + test.equal(collection.find().count(), 3); + test.equal(collection.find({updated: true}).count(), 0); + })); + }, + // update executes when it should + function (test, expect) { + collection.update( + canUpdateId, + {$set: {updated: true}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + test.equal(collection.find({updated: true}).count(), 1); + })); + }, + + // remove fails when trying to modify a doc with no `canRemove` set + function (test, expect) { + collection.remove(canUpdateId, + expect(function (err, res) { + test.equal(err.error, 403); + // nothing has changed + test.equal(collection.find().count(), 3); + })); + }, + // remove fails when trying to modify an doc with `cantRemove` + // set + function (test, expect) { + collection.remove(canRemoveId, + expect(function (err, res) { + test.equal(err.error, 403); + // nothing has changed + test.equal(collection.find().count(), 3); + })); + }, + + // update the doc to remove cantRemove. + function (test, expect) { + collection.update( + canRemoveId, + {$set: {cantRemove: false, canUpdate2: true}}, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + test.equal(collection.find({cantRemove: true}).count(), 0); + })); + }, + + // now remove can remove it. + function (test, expect) { + collection.remove(canRemoveId, + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 1); + // successfully removed + test.equal(collection.find().count(), 2); + })); + }, + + // try to remove a doc that doesn't exist. see we remove no docs. + function (test, expect) { + collection.remove('some-random-id-that-never-matches', + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 0); + // nothing removed + test.equal(collection.find().count(), 2); + })); + }, + + // methods can still bypass restrictions + function (test, expect) { + collection.callClearMethod( + expect(function (err, res) { + test.isFalse(err); + // successfully removed + test.equal(collection.find().count(), 0); + })); + } + ]); + }); + testAsyncMulti( + "collection - allow/deny transform must return object, " + idGeneration, + [function (test, expect) { + restrictedCollectionForInvalidTransformTest.insert({}, expect(function (err, res) { + test.isTrue(err); + })); + }]); + testAsyncMulti( + "collection - restricted collection allows client-side id, " + idGeneration, + [function (test, expect) { + var self = this; + self.id = Random.id(); + restrictedCollectionForClientIdTest.insert({_id: self.id}, expect(function (err, res) { + test.isFalse(err); + test.equal(res, self.id); + test.equal(restrictedCollectionForClientIdTest.findOne(self.id), + {_id: self.id}); + })); + }]); + }); // end idGeneration loop +} // end if isClient + + + +// A few simple server-only tests which don't need to coordinate collections +// with the client.. +if (Meteor.isServer) { + Tinytest.add("collection - allow and deny validate options", function (test) { + var collection = new Mongo.Collection(null); + + test.throws(function () { + collection.allow({invalidOption: true}); + }); + test.throws(function () { + collection.deny({invalidOption: true}); + }); + + _.each(['insert', 'update', 'remove', 'fetch'], function (key) { + var options = {}; + options[key] = true; + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + _.each(['insert', 'update', 'remove'], function (key) { + var options = {}; + options[key] = false; + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + _.each(['insert', 'update', 'remove'], function (key) { + var options = {}; + options[key] = undefined; + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + _.each(['insert', 'update', 'remove'], function (key) { + var options = {}; + options[key] = ['an array']; // this should be a function, not an array + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + test.throws(function () { + collection.allow({fetch: function () {}}); // this should be an array + }); + }); + + Tinytest.add("collection - calling allow restricts", function (test) { + var collection = new Mongo.Collection(null); + test.equal(collection._restricted, false); + collection.allow({ + insert: function() {} + }); + test.equal(collection._restricted, true); + }); + + Tinytest.add("collection - global insecure", function (test) { + // note: This test alters the global insecure status, by sneakily hacking + // the global Package object! + var insecurePackage = Package.insecure; + + Package.insecure = {}; + var collection = new Mongo.Collection(null); + test.equal(collection._isInsecure(), true); + + Package.insecure = undefined; + test.equal(collection._isInsecure(), false); + + delete Package.insecure; + test.equal(collection._isInsecure(), false); + + collection._insecure = true; + test.equal(collection._isInsecure(), true); + + if (insecurePackage) + Package.insecure = insecurePackage; + else + delete Package.insecure; + }); +} diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js new file mode 100644 index 0000000000..98141d83c7 --- /dev/null +++ b/packages/mongo/collection.js @@ -0,0 +1,1018 @@ +// options.connection, if given, is a LivedataClient or LivedataServer +// XXX presently there is no way to destroy/clean up a Collection +import { + ASYNC_COLLECTION_METHODS, + getAsyncMethodName +} from "meteor/minimongo/constants"; + +import { normalizeProjection } from "./mongo_utils"; + +/** + * @summary Namespace for MongoDB-related items + * @namespace + */ +Mongo = {}; + +/** + * @summary Constructor for a Collection + * @locus Anywhere + * @instancename collection + * @class + * @param {String} name The name of the collection. If null, creates an unmanaged (unsynchronized) local collection. + * @param {Object} [options] + * @param {Object} options.connection The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling [`DDP.connect`](#ddp_connect) to specify a different server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection. + * @param {String} options.idGeneration The method of generating the `_id` fields of new documents in this collection. Possible values: + + - **`'STRING'`**: random strings + - **`'MONGO'`**: random [`Mongo.ObjectID`](#mongo_object_id) values + +The default id generation technique is `'STRING'`. + * @param {Function} options.transform An optional transformation function. Documents will be passed through this function before being returned from `fetch` or `findOne`, and before being passed to callbacks of `observe`, `map`, `forEach`, `allow`, and `deny`. Transforms are *not* applied for the callbacks of `observeChanges` or to cursors returned from publish functions. + * @param {Boolean} options.defineMutationMethods Set to `false` to skip setting up the mutation methods that enable insert/update/remove from client code. Default `true`. + */ +Mongo.Collection = function Collection(name, options) { + if (!name && name !== null) { + Meteor._debug( + 'Warning: creating anonymous collection. It will not be ' + + 'saved or synchronized over the network. (Pass null for ' + + 'the collection name to turn off this warning.)' + ); + name = null; + } + + if (name !== null && typeof name !== 'string') { + throw new Error( + 'First argument to new Mongo.Collection must be a string or null' + ); + } + + if (options && options.methods) { + // Backwards compatibility hack with original signature (which passed + // "connection" directly instead of in options. (Connections must have a "methods" + // method.) + // XXX remove before 1.0 + options = { connection: options }; + } + // Backwards compatibility: "connection" used to be called "manager". + if (options && options.manager && !options.connection) { + options.connection = options.manager; + } + + options = { + connection: undefined, + idGeneration: 'STRING', + transform: null, + _driver: undefined, + _preventAutopublish: false, + ...options, + }; + + switch (options.idGeneration) { + case 'MONGO': + this._makeNewID = function() { + var src = name + ? DDP.randomStream('/collection/' + name) + : Random.insecure; + return new Mongo.ObjectID(src.hexString(24)); + }; + break; + case 'STRING': + default: + this._makeNewID = function() { + var src = name + ? DDP.randomStream('/collection/' + name) + : Random.insecure; + return src.id(); + }; + break; + } + + this._transform = LocalCollection.wrapTransform(options.transform); + + if (!name || options.connection === null) + // note: nameless collections never have a connection + this._connection = null; + else if (options.connection) this._connection = options.connection; + else if (Meteor.isClient) this._connection = Meteor.connection; + else this._connection = Meteor.server; + + if (!options._driver) { + // XXX This check assumes that webapp is loaded so that Meteor.server !== + // null. We should fully support the case of "want to use a Mongo-backed + // collection from Node code without webapp", but we don't yet. + // #MeteorServerNull + if ( + name && + this._connection === Meteor.server && + typeof MongoInternals !== 'undefined' && + MongoInternals.defaultRemoteCollectionDriver + ) { + options._driver = MongoInternals.defaultRemoteCollectionDriver(); + } else { + const { LocalCollectionDriver } = require('./local_collection_driver.js'); + options._driver = LocalCollectionDriver; + } + } + + this._collection = options._driver.open(name, this._connection); + this._name = name; + this._driver = options._driver; + + this._maybeSetUpReplication(name, options); + + // XXX don't define these until allow or deny is actually used for this + // collection. Could be hard if the security rules are only defined on the + // server. + if (options.defineMutationMethods !== false) { + try { + this._defineMutationMethods({ + useExisting: options._suppressSameNameError === true, + }); + } catch (error) { + // Throw a more understandable error on the server for same collection name + if ( + error.message === `A method named '/${name}/insert' is already defined` + ) + throw new Error(`There is already a collection named "${name}"`); + throw error; + } + } + + // autopublish + if ( + Package.autopublish && + !options._preventAutopublish && + this._connection && + this._connection.publish + ) { + this._connection.publish(null, () => this.find(), { + is_auto: true, + }); + } +}; + +Object.assign(Mongo.Collection.prototype, { + _maybeSetUpReplication(name, { _suppressSameNameError = false }) { + const self = this; + if (!(self._connection && self._connection.registerStore)) { + return; + } + + // OK, we're going to be a slave, replicating some remote + // database, except possibly with some temporary divergence while + // we have unacknowledged RPC's. + const ok = self._connection.registerStore(name, { + // Called at the beginning of a batch of updates. batchSize is the number + // of update calls to expect. + // + // XXX This interface is pretty janky. reset probably ought to go back to + // being its own function, and callers shouldn't have to calculate + // batchSize. The optimization of not calling pause/remove should be + // delayed until later: the first call to update() should buffer its + // message, and then we can either directly apply it at endUpdate time if + // it was the only update, or do pauseObservers/apply/apply at the next + // update() if there's another one. + beginUpdate(batchSize, reset) { + // pause observers so users don't see flicker when updating several + // objects at once (including the post-reconnect reset-and-reapply + // stage), and so that a re-sorting of a query can take advantage of the + // full _diffQuery moved calculation instead of applying change one at a + // time. + if (batchSize > 1 || reset) self._collection.pauseObservers(); + + if (reset) self._collection.remove({}); + }, + + // Apply an update. + // XXX better specify this interface (not in terms of a wire message)? + update(msg) { + var mongoId = MongoID.idParse(msg.id); + var doc = self._collection._docs.get(mongoId); + + //When the server's mergebox is disabled for a collection, the client must gracefully handle it when: + // *We receive an added message for a document that is already there. Instead, it will be changed + // *We reeive a change message for a document that is not there. Instead, it will be added + // *We receive a removed messsage for a document that is not there. Instead, noting wil happen. + + //Code is derived from client-side code originally in peerlibrary:control-mergebox + //https://github.com/peerlibrary/meteor-control-mergebox/blob/master/client.coffee + + //For more information, refer to discussion "Initial support for publication strategies in livedata server": + //https://github.com/meteor/meteor/pull/11151 + if (Meteor.isClient) { + if (msg.msg === 'added' && doc) { + msg.msg = 'changed'; + } else if (msg.msg === 'removed' && !doc) { + return; + } else if (msg.msg === 'changed' && !doc) { + msg.msg = 'added'; + _ref = msg.fields; + for (field in _ref) { + value = _ref[field]; + if (value === void 0) { + delete msg.fields[field]; + } + } + } + } + + // Is this a "replace the whole doc" message coming from the quiescence + // of method writes to an object? (Note that 'undefined' is a valid + // value meaning "remove it".) + if (msg.msg === 'replace') { + var replace = msg.replace; + if (!replace) { + if (doc) self._collection.remove(mongoId); + } else if (!doc) { + self._collection.insert(replace); + } else { + // XXX check that replace has no $ ops + self._collection.update(mongoId, replace); + } + return; + } else if (msg.msg === 'added') { + if (doc) { + throw new Error( + 'Expected not to find a document already present for an add' + ); + } + self._collection.insert({ _id: mongoId, ...msg.fields }); + } else if (msg.msg === 'removed') { + if (!doc) + throw new Error( + 'Expected to find a document already present for removed' + ); + self._collection.remove(mongoId); + } else if (msg.msg === 'changed') { + if (!doc) throw new Error('Expected to find a document to change'); + const keys = Object.keys(msg.fields); + if (keys.length > 0) { + var modifier = {}; + keys.forEach(key => { + const value = msg.fields[key]; + if (EJSON.equals(doc[key], value)) { + return; + } + if (typeof value === 'undefined') { + if (!modifier.$unset) { + modifier.$unset = {}; + } + modifier.$unset[key] = 1; + } else { + if (!modifier.$set) { + modifier.$set = {}; + } + modifier.$set[key] = value; + } + }); + if (Object.keys(modifier).length > 0) { + self._collection.update(mongoId, modifier); + } + } + } else { + throw new Error("I don't know how to deal with this message"); + } + }, + + // Called at the end of a batch of updates. + endUpdate() { + self._collection.resumeObservers(); + }, + + // Called around method stub invocations to capture the original versions + // of modified documents. + saveOriginals() { + self._collection.saveOriginals(); + }, + retrieveOriginals() { + return self._collection.retrieveOriginals(); + }, + + // Used to preserve current versions of documents across a store reset. + getDoc(id) { + return self.findOne(id); + }, + + // To be able to get back to the collection from the store. + _getCollection() { + return self; + }, + }); + + if (!ok) { + const message = `There is already a collection named "${name}"`; + if (_suppressSameNameError === true) { + // XXX In theory we do not have to throw when `ok` is falsy. The + // store is already defined for this collection name, but this + // will simply be another reference to it and everything should + // work. However, we have historically thrown an error here, so + // for now we will skip the error only when _suppressSameNameError + // is `true`, allowing people to opt in and give this some real + // world testing. + console.warn ? console.warn(message) : console.log(message); + } else { + throw new Error(message); + } + } + }, + + /// + /// Main collection API + /// + + _getFindSelector(args) { + if (args.length == 0) return {}; + else return args[0]; + }, + + _getFindOptions(args) { + const [, options] = args || []; + const newOptions = normalizeProjection(options); + + var self = this; + if (args.length < 2) { + return { transform: self._transform }; + } else { + check( + newOptions, + Match.Optional( + Match.ObjectIncluding({ + projection: Match.Optional(Match.OneOf(Object, undefined)), + sort: Match.Optional( + Match.OneOf(Object, Array, Function, undefined) + ), + limit: Match.Optional(Match.OneOf(Number, undefined)), + skip: Match.Optional(Match.OneOf(Number, undefined)), + }) + ) + ); + + + return { + transform: self._transform, + ...newOptions, + }; + } + }, + + /** + * @summary Find the documents in a collection that match the selector. + * @locus Anywhere + * @method find + * @memberof Mongo.Collection + * @instance + * @param {MongoSelector} [selector] A query describing the documents to find + * @param {Object} [options] + * @param {MongoSortSpecifier} options.sort Sort order (default: natural order) + * @param {Number} options.skip Number of results to skip at the beginning + * @param {Number} options.limit Maximum number of results to return + * @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude. + * @param {Boolean} options.reactive (Client only) Default `true`; pass `false` to disable reactivity + * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections) for this cursor. Pass `null` to disable transformation. + * @param {Boolean} options.disableOplog (Server only) Pass true to disable oplog-tailing on this query. This affects the way server processes calls to `observe` on this query. Disabling the oplog can be useful when working with data that updates in large batches. + * @param {Number} options.pollingIntervalMs (Server only) When oplog is disabled (through the use of `disableOplog` or when otherwise not available), the frequency (in milliseconds) of how often to poll this query when observing on the server. Defaults to 10000ms (10 seconds). + * @param {Number} options.pollingThrottleMs (Server only) When oplog is disabled (through the use of `disableOplog` or when otherwise not available), the minimum time (in milliseconds) to allow between re-polling when observing on the server. Increasing this will save CPU and mongo load at the expense of slower updates to users. Decreasing this is not recommended. Defaults to 50ms. + * @param {Number} options.maxTimeMs (Server only) If set, instructs MongoDB to set a time limit for this cursor's operations. If the operation reaches the specified time limit (in milliseconds) without the having been completed, an exception will be thrown. Useful to prevent an (accidental or malicious) unoptimized query from causing a full collection scan that would disrupt other database users, at the expense of needing to handle the resulting error. + * @param {String|Object} options.hint (Server only) Overrides MongoDB's default index selection and query optimization process. Specify an index to force its use, either by its name or index specification. You can also specify `{ $natural : 1 }` to force a forwards collection scan, or `{ $natural : -1 }` for a reverse collection scan. Setting this is only recommended for advanced users. + * @param {String} options.readPreference (Server only) Specifies a custom MongoDB [`readPreference`](https://docs.mongodb.com/manual/core/read-preference) for this particular cursor. Possible values are `primary`, `primaryPreferred`, `secondary`, `secondaryPreferred` and `nearest`. + * @returns {Mongo.Cursor} + */ + find(...args) { + // Collection.find() (return all docs) behaves differently + // from Collection.find(undefined) (return 0 docs). so be + // careful about the length of arguments. + return this._collection.find( + this._getFindSelector(args), + this._getFindOptions(args) + ); + }, + + /** + * @summary Finds the first document that matches the selector, as ordered by sort and skip options. Returns `undefined` if no matching document is found. + * @locus Anywhere + * @method findOne + * @memberof Mongo.Collection + * @instance + * @param {MongoSelector} [selector] A query describing the documents to find + * @param {Object} [options] + * @param {MongoSortSpecifier} options.sort Sort order (default: natural order) + * @param {Number} options.skip Number of results to skip at the beginning + * @param {MongoFieldSpecifier} options.fields Dictionary of fields to return or exclude. + * @param {Boolean} options.reactive (Client only) Default true; pass false to disable reactivity + * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections) for this cursor. Pass `null` to disable transformation. + * @param {String} options.readPreference (Server only) Specifies a custom MongoDB [`readPreference`](https://docs.mongodb.com/manual/core/read-preference) for fetching the document. Possible values are `primary`, `primaryPreferred`, `secondary`, `secondaryPreferred` and `nearest`. + * @returns {Object} + */ + findOne(...args) { + return this._collection.findOne( + this._getFindSelector(args), + this._getFindOptions(args) + ); + }, +}); + +Object.assign(Mongo.Collection, { + _publishCursorFibers(cursor, sub, collection) { + var observeHandle = cursor.observeChanges( + { + added: function(id, fields) { + sub.added(collection, id, fields); + }, + changed: function(id, fields) { + sub.changed(collection, id, fields); + }, + removed: function(id) { + sub.removed(collection, id); + }, + }, + // Publications don't mutate the documents + // This is tested by the `livedata - publish callbacks clone` test + { nonMutatingCallbacks: true } + ); + + // 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(); + }); + + // return the observeHandle in case it needs to be stopped early + return observeHandle; + }, + + async _publishCursorNoFibers(cursor, sub, collection) { + var observeHandle = await cursor.observeChanges( + { + added: function(id, fields) { + sub.added(collection, id, fields); + }, + changed: function(id, fields) { + sub.changed(collection, id, fields); + }, + removed: function(id) { + sub.removed(collection, id); + }, + }, + // Publications don't mutate the documents + // This is tested by the `livedata - publish callbacks clone` test + { nonMutatingCallbacks: true } + ); + + // 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() { + return observeHandle.stop(); + }); + + // return the observeHandle in case it needs to be stopped early + return observeHandle; + }, + + _publishCursor(cursor, sub, collection) { + return Meteor._isFibersEnabled + ? this._publishCursorFibers(cursor, sub, collection) + : this._publishCursorNoFibers(cursor, sub, collection); + }, + + // protect against dangerous selectors. falsey and {_id: falsey} are both + // likely programmer error, and not what you want, particularly for destructive + // operations. If a falsey _id is sent in, a new string _id will be + // generated and returned; if a fallbackId is provided, it will be returned + // instead. + _rewriteSelector(selector, { fallbackId } = {}) { + // shorthand -- scalars match _id + if (LocalCollection._selectorIsId(selector)) selector = { _id: selector }; + + if (Array.isArray(selector)) { + // This is consistent with the Mongo console itself; if we don't do this + // check passing an empty array ends up selecting all items + throw new Error("Mongo selector can't be an array."); + } + + if (!selector || ('_id' in selector && !selector._id)) { + // can't match anything + return { _id: fallbackId || Random.id() }; + } + + return selector; + }, +}); + +Object.assign(Mongo.Collection.prototype, { + // 'insert' immediately returns the inserted document's new _id. + // The others return values immediately if you are in a stub, an in-memory + // unmanaged collection, or a mongo-backed collection and you don't pass a + // callback. 'update' and 'remove' return the number of affected + // documents. 'upsert' returns an object with keys 'numberAffected' and, if an + // insert happened, 'insertedId'. + // + // Otherwise, the semantics are exactly like other methods: they take + // a callback as an optional last argument; if no callback is + // provided, they block until the operation is complete, and throw an + // exception if it fails; if a callback is provided, then they don't + // necessarily block, and they call the callback when they finish with error and + // result arguments. (The insert method provides the document ID as its result; + // update and remove provide the number of affected docs as the result; upsert + // provides an object with numberAffected and maybe insertedId.) + // + // On the client, blocking is impossible, so if a callback + // isn't provided, they just return immediately and any error + // information is lost. + // + // There's one more tweak. On the client, if you don't provide a + // callback, then if there is an error, a message will be logged with + // Meteor._debug. + // + // The intent (though this is actually determined by the underlying + // drivers) is that the operations should be done synchronously, not + // generating their result until the database has acknowledged + // them. In the future maybe we should provide a flag to turn this + // off. + + _insertSync(doc, callback) { + // Make sure we were passed a document to insert + if (!doc) { + throw new Error('insert requires an argument'); + } + + // Make a shallow clone of the document, preserving its prototype. + doc = Object.create( + Object.getPrototypeOf(doc), + Object.getOwnPropertyDescriptors(doc) + ); + + if ('_id' in doc) { + if ( + !doc._id || + !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) + ) { + throw new Error( + 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' + ); + } + } else { + let generateId = true; + + // Don't generate the id if we're the client and the 'outermost' call + // This optimization saves us passing both the randomSeed and the id + // Passing both is redundant. + if (this._isRemoteCollection()) { + const enclosing = DDP._CurrentMethodInvocation.get(); + if (!enclosing) { + generateId = false; + } + } + + if (generateId) { + doc._id = this._makeNewID(); + } + } + + // On inserts, always return the id that we generated; on all other + // operations, just return the result from the collection. + var chooseReturnValueFromCollectionResult = function(result) { + if (doc._id) { + return doc._id; + } + + // XXX what is this for?? + // It's some iteraction between the callback to _callMutatorMethod and + // the return value conversion + doc._id = result; + + return result; + }; + + const wrappedCallback = wrapCallback( + callback, + chooseReturnValueFromCollectionResult + ); + + if (this._isRemoteCollection()) { + const result = this._callMutatorMethod('insert', [doc], wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } + + // it's my collection. descend into the collection object + // and propagate any exception. + try { + // If the user provided a callback and the collection implements this + // operation asynchronously, then queryRet will be undefined, and the + // result will be returned through the callback instead. + const result = this._collection.insert(doc, wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } catch (e) { + if (callback) { + callback(e); + return null; + } + throw e; + } + }, + + async _insertAsync(doc, callback) { + // Make sure we were passed a document to insert + if (!doc) { + throw new Error('insert requires an argument'); + } + + // Make a shallow clone of the document, preserving its prototype. + doc = Object.create( + Object.getPrototypeOf(doc), + Object.getOwnPropertyDescriptors(doc) + ); + + if ('_id' in doc) { + if ( + !doc._id || + !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) + ) { + throw new Error( + 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' + ); + } + } else { + let generateId = true; + + // Don't generate the id if we're the client and the 'outermost' call + // This optimization saves us passing both the randomSeed and the id + // Passing both is redundant. + if (this._isRemoteCollection()) { + const enclosing = DDP._CurrentMethodInvocation.get(); + if (!enclosing) { + generateId = false; + } + } + + if (generateId) { + doc._id = this._makeNewID(); + } + } + + // On inserts, always return the id that we generated; on all other + // operations, just return the result from the collection. + var chooseReturnValueFromCollectionResult = function(result) { + if (doc._id) { + return doc._id; + } + + // XXX what is this for?? + // It's some iteraction between the callback to _callMutatorMethod and + // the return value conversion + doc._id = result; + + return result; + }; + + const wrappedCallback = wrapCallback( + callback, + chooseReturnValueFromCollectionResult + ); + + if (this._isRemoteCollection()) { + const result = this._callMutatorMethod('insert', [doc], wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } + + // it's my collection. descend into the collection object + // and propagate any exception. + try { + // If the user provided a callback and the collection implements this + // operation asynchronously, then queryRet will be undefined, and the + // result will be returned through the callback instead. + const result = await this._collection.insert(doc, wrappedCallback); + return chooseReturnValueFromCollectionResult(result); + } catch (e) { + if (callback) { + callback(e); + return null; + } + throw e; + } + }, + + /** + * @summary Insert a document in the collection. Returns its unique _id. + * @locus Anywhere + * @method insert + * @memberof Mongo.Collection + * @instance + * @param {Object} doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. + * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. + */ + insert(doc, callback) { + return Meteor._isFibersEnabled ? this._insertSync(doc, callback) : this._insertAsync(doc, callback); + }, + + /** + * @summary Modify one or more documents in the collection. Returns the number of matched documents. + * @locus Anywhere + * @method update + * @memberof Mongo.Collection + * @instance + * @param {MongoSelector} selector Specifies which documents to modify + * @param {MongoModifier} modifier Specifies how to modify the documents + * @param {Object} [options] + * @param {Boolean} options.multi True to modify all matching documents; false to only modify one of the matching documents (the default). + * @param {Boolean} options.upsert True to insert a document if no matching documents are found. + * @param {Array} options.arrayFilters Optional. Used in combination with MongoDB [filtered positional operator](https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) to specify which elements to modify in an array field. + * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second. + */ + update(selector, modifier, ...optionsAndCallback) { + const callback = popCallbackFromArgs(optionsAndCallback); + + // We've already popped off the callback, so we are left with an array + // of one or zero items + const options = { ...(optionsAndCallback[0] || null) }; + let insertedId; + if (options && options.upsert) { + // set `insertedId` if absent. `insertedId` is a Meteor extension. + if (options.insertedId) { + if ( + !( + typeof options.insertedId === 'string' || + options.insertedId instanceof Mongo.ObjectID + ) + ) + throw new Error('insertedId must be string or ObjectID'); + insertedId = options.insertedId; + } else if (!selector || !selector._id) { + insertedId = this._makeNewID(); + options.generatedId = true; + options.insertedId = insertedId; + } + } + + selector = Mongo.Collection._rewriteSelector(selector, { + fallbackId: insertedId, + }); + + const wrappedCallback = wrapCallback(callback); + + if (this._isRemoteCollection()) { + const args = [selector, modifier, options]; + + return this._callMutatorMethod('update', args, wrappedCallback); + } + + // it's my collection. descend into the collection object + // and propagate any exception. + try { + // If the user provided a callback and the collection implements this + // operation asynchronously, then queryRet will be undefined, and the + // result will be returned through the callback instead. + return this._collection.update( + selector, + modifier, + options, + wrappedCallback + ); + } catch (e) { + if (callback) { + callback(e); + return null; + } + throw e; + } + }, + + /** + * @summary Remove documents from the collection + * @locus Anywhere + * @method remove + * @memberof Mongo.Collection + * @instance + * @param {MongoSelector} selector Specifies which documents to remove + * @param {Function} [callback] Optional. If present, called with an error object as its argument. + */ + remove(selector, callback) { + selector = Mongo.Collection._rewriteSelector(selector); + + const wrappedCallback = wrapCallback(callback); + + if (this._isRemoteCollection()) { + return this._callMutatorMethod('remove', [selector], wrappedCallback); + } + + // it's my collection. descend into the collection object + // and propagate any exception. + try { + // If the user provided a callback and the collection implements this + // operation asynchronously, then queryRet will be undefined, and the + // result will be returned through the callback instead. + return this._collection.remove(selector, wrappedCallback); + } catch (e) { + if (callback) { + callback(e); + return null; + } + throw e; + } + }, + + // Determine if this collection is simply a minimongo representation of a real + // database on another server + _isRemoteCollection() { + // XXX see #MeteorServerNull + return this._connection && this._connection !== Meteor.server; + }, + + /** + * @summary Modify one or more documents in the collection, or insert one if no matching documents were found. Returns an object with keys `numberAffected` (the number of documents modified) and `insertedId` (the unique _id of the document that was inserted, if any). + * @locus Anywhere + * @method upsert + * @memberof Mongo.Collection + * @instance + * @param {MongoSelector} selector Specifies which documents to modify + * @param {MongoModifier} modifier Specifies how to modify the documents + * @param {Object} [options] + * @param {Boolean} options.multi True to modify all matching documents; false to only modify one of the matching documents (the default). + * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second. + */ + upsert(selector, modifier, options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = {}; + } + + return this.update( + selector, + modifier, + { + ...options, + _returnObject: true, + upsert: true, + }, + callback + ); + }, + + // We'll actually design an index API later. For now, we just pass through to + // Mongo's, but make it synchronous. + _ensureIndex(index, options) { + var self = this; + if (!self._collection._ensureIndex || !self._collection.createIndex) + throw new Error('Can only call createIndex on server collections'); + if (self._collection.createIndex) { + self._collection.createIndex(index, options); + } else { + import { Log } from 'meteor/logging'; + Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) + self._collection._ensureIndex(index, options); + } + }, + + /** + * @summary Creates the specified index on the collection. + * @locus server + * @method createIndex + * @memberof Mongo.Collection + * @instance + * @param {Object} index A document that contains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of `1`; for descending index, specify a value of `-1`. Use `text` for text indexes. + * @param {Object} [options] All options are listed in [MongoDB documentation](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options) + * @param {String} options.name Name of the index + * @param {Boolean} options.unique Define that the index values must be unique, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-unique/) + * @param {Boolean} options.sparse Define that the index is sparse, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-sparse/) + */ + createIndex(index, options) { + var self = this; + if (!self._collection.createIndex) + throw new Error('Can only call createIndex on server collections'); + try { + self._collection.createIndex(index, options); + } catch (e) { + if (e.message.includes('An equivalent index already exists with the same name but different options.') && Meteor.settings?.packages?.mongo?.reCreateIndexOnOptionMismatch) { + import { Log } from 'meteor/logging'; + + Log.info(`Re-creating index ${index} for ${self._name} due to options mismatch.`); + self._collection._dropIndex(index); + self._collection.createIndex(index, options); + } else { + throw new Meteor.Error(`An error occurred when creating an index for collection "${self._name}: ${e.message}`); + } + } + }, + + _dropIndex(index) { + var self = this; + if (!self._collection._dropIndex) + throw new Error('Can only call _dropIndex on server collections'); + self._collection._dropIndex(index); + }, + + _dropCollection() { + var self = this; + if (!self._collection.dropCollection) + throw new Error('Can only call _dropCollection on server collections'); + self._collection.dropCollection(); + }, + + _createCappedCollection(byteSize, maxDocuments) { + var self = this; + if (!self._collection._createCappedCollection) + throw new Error( + 'Can only call _createCappedCollection on server collections' + ); + self._collection._createCappedCollection(byteSize, maxDocuments); + }, + + /** + * @summary Returns the [`Collection`](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html) object corresponding to this collection from the [npm `mongodb` driver module](https://www.npmjs.com/package/mongodb) which is wrapped by `Mongo.Collection`. + * @locus Server + * @memberof Mongo.Collection + * @instance + */ + rawCollection() { + var self = this; + if (!self._collection.rawCollection) { + throw new Error('Can only call rawCollection on server collections'); + } + return self._collection.rawCollection(); + }, + + /** + * @summary Returns the [`Db`](http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html) object corresponding to this collection's database connection from the [npm `mongodb` driver module](https://www.npmjs.com/package/mongodb) which is wrapped by `Mongo.Collection`. + * @locus Server + * @memberof Mongo.Collection + * @instance + */ + rawDatabase() { + var self = this; + if (!(self._driver.mongo && self._driver.mongo.db)) { + throw new Error('Can only call rawDatabase on server collections'); + } + return self._driver.mongo.db; + }, +}); + +// Convert the callback to not return a result if there is an error +function wrapCallback(callback, convertResult) { + return ( + callback && + function(error, result) { + if (error) { + callback(error); + } else if (typeof convertResult === 'function') { + callback(error, convertResult(result)); + } else { + callback(error, result); + } + } + ); +} + +/** + * @summary Create a Mongo-style `ObjectID`. If you don't specify a `hexString`, the `ObjectID` will generated randomly (not using MongoDB's ID construction rules). + * @locus Anywhere + * @class + * @param {String} [hexString] Optional. The 24-character hexadecimal contents of the ObjectID to create + */ +Mongo.ObjectID = MongoID.ObjectID; + +/** + * @summary To create a cursor, use find. To access the documents in a cursor, use forEach, map, or fetch. + * @class + * @instanceName cursor + */ +Mongo.Cursor = LocalCollection.Cursor; + +/** + * @deprecated in 0.9.1 + */ +Mongo.Collection.Cursor = Mongo.Cursor; + +/** + * @deprecated in 0.9.1 + */ +Mongo.Collection.ObjectID = Mongo.ObjectID; + +/** + * @deprecated in 0.9.1 + */ +Meteor.Collection = Mongo.Collection; + +// Allow deny stuff is now in the allow-deny package +Object.assign(Meteor.Collection.prototype, AllowDeny.CollectionPrototype); + +function popCallbackFromArgs(args) { + // Pull off any callback (or perhaps a 'callback' variable that was passed + // in undefined, like how 'upsert' does it). + if ( + args.length && + (args[args.length - 1] === undefined || + args[args.length - 1] instanceof Function) + ) { + return args.pop(); + } +} + +ASYNC_COLLECTION_METHODS.forEach(methodName => { + const methodNameAsync = getAsyncMethodName(methodName); + Mongo.Collection.prototype[methodNameAsync] = function(...args) { + return Promise.resolve(this[methodName](...args)); + }; +}); diff --git a/packages/mongo/collection_async_tests.js b/packages/mongo/collection_async_tests.js new file mode 100644 index 0000000000..5d3a277fa0 --- /dev/null +++ b/packages/mongo/collection_async_tests.js @@ -0,0 +1,21 @@ +Tinytest.add('async collection - check for methods presence', function (test) { + const isFunction = fn => test.equal(typeof fn, 'function'); + + const collection = new Mongo.Collection('myAsyncCollection' + test.id); + isFunction(collection.createCappedCollectionAsync); + isFunction(collection.createIndexAsync); + isFunction(collection.dropCollectionAsync); + isFunction(collection.dropIndexAsync); + isFunction(collection.findOneAsync); + isFunction(collection.insertAsync); + isFunction(collection.removeAsync); + isFunction(collection.updateAsync); + isFunction(collection.upsertAsync); + + const cursor = collection.find(); + isFunction(cursor.countAsync); + isFunction(cursor.fetchAsync); + isFunction(cursor.forEachAsync); + isFunction(cursor.mapAsync); + isFunction(cursor[Symbol.asyncIterator]); +}); diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js new file mode 100644 index 0000000000..78da9a1f18 --- /dev/null +++ b/packages/mongo/collection_tests.js @@ -0,0 +1,386 @@ + +var MongoDB = NpmModuleMongodb; + +Tinytest.add( + 'collection - call Mongo.Collection without new', + function (test) { + test.throws(function () { + Mongo.Collection(null); + }); + } +); + +Tinytest.add('collection - call new Mongo.Collection multiple times', + function (test) { + var collectionName = 'multiple_times_1_' + test.id; + new Mongo.Collection(collectionName); + + test.throws( + function () { + new Mongo.Collection(collectionName); + }, + /There is already a collection named/ + ); + } +); + +Tinytest.add('collection - call new Mongo.Collection multiple times with _suppressSameNameError=true', + function (test) { + var collectionName = 'multiple_times_2_' + test.id; + new Mongo.Collection(collectionName); + + try { + new Mongo.Collection(collectionName, {_suppressSameNameError: true}); + test.ok(); + } catch (error) { + console.log(error); + test.fail('Expected new Mongo.Collection not to throw an error when called twice with the same name'); + } + } +); + +Tinytest.add('collection - call new Mongo.Collection with defineMutationMethods=false', + function (test) { + var handlerPropName = Meteor.isClient ? '_methodHandlers' : 'method_handlers'; + + var methodCollectionName = 'hasmethods' + test.id; + var hasmethods = new Mongo.Collection(methodCollectionName); + test.equal(typeof hasmethods._connection[handlerPropName]['/' + methodCollectionName + '/insert'], 'function'); + + var noMethodCollectionName = 'nomethods' + test.id; + var nomethods = new Mongo.Collection(noMethodCollectionName, {defineMutationMethods: false}); + test.equal(nomethods._connection[handlerPropName]['/' + noMethodCollectionName + '/insert'], undefined); + } +); + +Tinytest.add('collection - call find with sort function', + function (test) { + var initialize = function (collection) { + collection.insert({a: 2}); + collection.insert({a: 3}); + collection.insert({a: 1}); + }; + + var sorter = function (a, b) { + return a.a - b.a; + }; + + var getSorted = function (collection) { + return collection.find({}, {sort: sorter}).map(function (doc) { return doc.a; }); + }; + + var collectionName = 'sort' + test.id; + var localCollection = new Mongo.Collection(null); + var namedCollection = new Mongo.Collection(collectionName, {connection: null}); + + initialize(localCollection); + test.equal(getSorted(localCollection), [1, 2, 3]); + + initialize(namedCollection); + test.equal(getSorted(namedCollection), [1, 2, 3]); + } +); + +Tinytest.add('collection - call native find with sort function', + function (test) { + var collectionName = 'sortNative' + test.id; + var nativeCollection = new Mongo.Collection(collectionName); + + if (Meteor.isServer) { + test.throws( + function () { + nativeCollection + .find({}, { + sort: function () {}, + }) + .map(function (doc) { + return doc.a; + }); + }, + /Invalid sort format: undefined Sort must be a valid object/ + ); + } + } +); + +Tinytest.add('collection - calling native find with maxTimeMs should timeout', + function(test) { + var collectionName = 'findOptions1' + test.id; + var collection = new Mongo.Collection(collectionName); + collection.insert({a: 1}); + + function doTest() { + return collection.find({$where: "sleep(100) || true"}, {maxTimeMs: 50}).count(); + } + if (Meteor.isServer) { + test.throws(doTest); + } + } +); + + +Tinytest.add('collection - calling native find with $reverse hint should reverse on server', + function(test) { + var collectionName = 'findOptions2' + test.id; + var collection = new Mongo.Collection(collectionName); + collection.insert({a: 1}); + collection.insert({a: 2}); + + function m(doc) { return doc.a; } + var fwd = collection.find({}, {hint: {$natural: 1}}).map(m); + var rev = collection.find({}, {hint: {$natural: -1}}).map(m); + if (Meteor.isServer) { + test.equal(fwd, rev.reverse()); + } else { + // NOTE: should be documented that hints don't work on client + test.equal(fwd, rev); + } + } +); + +Tinytest.addAsync('collection - calling native find with good hint and maxTimeMs should succeed', + function(test, done) { + var collectionName = 'findOptions3' + test.id; + var collection = new Mongo.Collection(collectionName); + collection.insert({a: 1}); + + Promise.resolve( + Meteor.isServer && + collection.rawCollection().createIndex({ a: 1 }) + ).then(() => { + test.equal(collection.find({}, { + hint: {a: 1}, + maxTimeMs: 1000 + }).count(), 1); + done(); + }).catch(error => test.fail(error.message)); + } +); + +Tinytest.add('collection - calling find with a valid readPreference', + function(test) { + if (Meteor.isServer) { + const defaultReadPreference = 'primary'; + const customReadPreference = 'secondaryPreferred'; + const collection = new Mongo.Collection('readPreferenceTest' + test.id); + const defaultCursor = collection.find(); + const customCursor = collection.find( + {}, + { readPreference: customReadPreference } + ); + + // Trigger the creation of _synchronousCursor + defaultCursor.count(); + customCursor.count(); + + // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore + // as the cursor options are now private + // You can check on abstract_cursor.ts the exposed public getters + test.equal( + defaultCursor._synchronousCursor._dbCursor.readPreference + .mode, + defaultReadPreference + ); + test.equal( + customCursor._synchronousCursor._dbCursor.readPreference.mode, + customReadPreference + ); + } + } +); + +Tinytest.add('collection - calling find with an invalid readPreference', + function(test) { + if (Meteor.isServer) { + const invalidReadPreference = 'INVALID'; + const collection = new Mongo.Collection('readPreferenceTest2' + test.id); + const cursor = collection.find( + {}, + { readPreference: invalidReadPreference } + ); + + test.throws(function() { + // Trigger the creation of _synchronousCursor + cursor.count(); + }, `Invalid read preference mode "${invalidReadPreference}"`); + } + } +); + +Tinytest.add('collection - inserting a document with a binary should return a document with a binary', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary1'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 6) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof MongoDB.Binary + ); + test.equal( + doc.binary.buffer, + Buffer.from('hello world') + ); + } + } +); + +Tinytest.add('collection - inserting a document with a binary (sub type 0) should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary8'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 0) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - updating a document with a binary should return a document with a binary', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary2'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 6) } }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof MongoDB.Binary + ); + test.equal( + doc.binary.buffer, + Buffer.from('hello world') + ); + } + } +); + +Tinytest.add('collection - updating a document with a binary (sub type 0) should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary7'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 0) } }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - inserting a document with a uint8array should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary3'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new Uint8Array(Buffer.from('hello world')) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - updating a document with a uint8array should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary4'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update( + { _id }, + { $set: { binary: new Uint8Array(Buffer.from('hello world')) } } + ) + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - finding with a query with a uint8array field should return the correct document', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary5'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new Uint8Array(Buffer.from('hello world')) + }); + + const doc = collection.findOne({ binary: new Uint8Array(Buffer.from('hello world')) }); + test.equal( + doc._id, + _id + ); + collection.remove({}); + } + } +); + +Tinytest.add('collection - finding with a query with a binary field should return the correct document', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary6'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 6) + }); + + const doc = collection.findOne({ binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); + test.equal( + doc._id, + _id + ); + collection.remove({}); + } + } +); diff --git a/packages/mongo/connection_options.js b/packages/mongo/connection_options.js new file mode 100644 index 0000000000..6cff3813fc --- /dev/null +++ b/packages/mongo/connection_options.js @@ -0,0 +1,10 @@ +/** + * @summary Allows for user specified connection options + * @example http://mongodb.github.io/node-mongodb-native/3.0/reference/connecting/connection-settings/ + * @locus Server + * @param {Object} options User specified Mongo connection options + */ +Mongo.setConnectionOptions = function setConnectionOptions (options) { + check(options, Object); + Mongo._connectionOptions = options; +}; \ No newline at end of file diff --git a/packages/mongo/doc_fetcher.js b/packages/mongo/doc_fetcher.js new file mode 100644 index 0000000000..3d4740bf34 --- /dev/null +++ b/packages/mongo/doc_fetcher.js @@ -0,0 +1,57 @@ +export class DocFetcher { + constructor(mongoConnection) { + this._mongoConnection = mongoConnection; + // Map from op -> [callback] + this._callbacksForOp = new Map; + } + + // Fetches document "id" from collectionName, returning it or null if not + // found. + // + // If you make multiple calls to fetch() with the same op reference, + // DocFetcher may assume that they all return the same document. (It does + // not check to see if collectionName/id match.) + // + // You may assume that callback is never called synchronously (and in fact + // OplogObserveDriver does so). + fetch(collectionName, id, op, callback) { + const self = this; + + check(collectionName, String); + check(op, Object); + + // If there's already an in-progress fetch for this cache key, yield until + // it's done and return whatever it returns. + if (self._callbacksForOp.has(op)) { + self._callbacksForOp.get(op).push(callback); + return; + } + + const callbacks = [callback]; + self._callbacksForOp.set(op, callbacks); + + Meteor._runAsync(async function () { + try { + var doc = await self._mongoConnection.findOne( + collectionName, {_id: id}) || null; + // Return doc to all relevant callbacks. Note that this array can + // continue to grow during callback excecution. + while (callbacks.length > 0) { + // Clone the document so that the various calls to fetch don't return + // objects that are intertwingled with each other. Clone before + // popping the future, so that if clone throws, the error gets passed + // to the next callback. + await callbacks.pop()(null, EJSON.clone(doc)); + } + } catch (e) { + while (callbacks.length > 0) { + await callbacks.pop()(e); + } + } finally { + // XXX consider keeping the doc around for a period of time before + // removing from the cache + self._callbacksForOp.delete(op); + } + }); + } +} diff --git a/packages/mongo/doc_fetcher_tests.js b/packages/mongo/doc_fetcher_tests.js new file mode 100644 index 0000000000..484b5f6d03 --- /dev/null +++ b/packages/mongo/doc_fetcher_tests.js @@ -0,0 +1,41 @@ +var Fiber = Npm.require('fibers'); +var Future = Npm.require('fibers/future'); +import { DocFetcher } from "./doc_fetcher.js"; + +testAsyncMulti("mongo-livedata - doc fetcher", [ + function (test, expect) { + var self = this; + var collName = "docfetcher-" + Random.id(); + var collection = new Mongo.Collection(collName); + var id1 = collection.insert({x: 1}); + var id2 = collection.insert({y: 2}); + + var fetcher = new DocFetcher( + MongoInternals.defaultRemoteCollectionDriver().mongo); + + // Test basic operation. + const fakeOp1 = {}; + const fakeOp2 = {}; + fetcher.fetch(collName, id1, fakeOp1, expect(null, {_id: id1, x: 1})); + fetcher.fetch(collName, "nonexistent!", fakeOp2, expect(null, null)); + + var fetched = false; + var fakeOp3 = {}; + var expected = {_id: id2, y: 2}; + fetcher.fetch(collName, id2, fakeOp3, expect(function (e, d) { + fetched = true; + test.isFalse(e); + test.equal(d, expected); + })); + // The fetcher yields. + test.isFalse(fetched); + + // Now ask for another document with the same op reference. Because a + // fetch for that op is in flight, we will get the other fetch's + // document, not this random document. + fetcher.fetch(collName, Random.id(), fakeOp3, expect(function (e, d) { + test.isFalse(e); + test.equal(d, expected); + })); + } +]); diff --git a/packages/mongo/local_collection_driver.js b/packages/mongo/local_collection_driver.js new file mode 100644 index 0000000000..375902f117 --- /dev/null +++ b/packages/mongo/local_collection_driver.js @@ -0,0 +1,30 @@ +// singleton +export const LocalCollectionDriver = new (class LocalCollectionDriver { + constructor() { + this.noConnCollections = Object.create(null); + } + + open(name, conn) { + if (! name) { + return new LocalCollection; + } + + if (! conn) { + return ensureCollection(name, this.noConnCollections); + } + + if (! conn._mongo_livedata_collections) { + conn._mongo_livedata_collections = Object.create(null); + } + + // XXX is there a way to keep track of a connection's collections without + // dangling it off the connection object? + return ensureCollection(name, conn._mongo_livedata_collections); + } +}); + +function ensureCollection(name, collections) { + return (name in collections) + ? collections[name] + : collections[name] = new LocalCollection(name); +} diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js new file mode 100644 index 0000000000..8c8cf093eb --- /dev/null +++ b/packages/mongo/mongo_driver.js @@ -0,0 +1,1802 @@ +import { normalizeProjection } from "./mongo_utils"; + +/** + * Provide a synchronous Collection API using fibers, backed by + * MongoDB. This is only for use on the server, and mostly identical + * to the client API. + * + * NOTE: the public API methods must be run within a fiber. If you call + * these outside of a fiber they will explode! + */ + +const path = require("path"); +const util = require("util"); + +/** @type {import('mongodb')} */ +var MongoDB = NpmModuleMongodb; +var Future = Npm.require('fibers/future'); +import { DocFetcher } from "./doc_fetcher.js"; +import { + ASYNC_CURSOR_METHODS, + getAsyncMethodName +} from "meteor/minimongo/constants"; + +MongoInternals = {}; + +MongoInternals.NpmModules = { + mongodb: { + version: NpmModuleMongodbVersion, + module: MongoDB + } +}; + +// Older version of what is now available via +// MongoInternals.NpmModules.mongodb.module. It was never documented, but +// people do use it. +// XXX COMPAT WITH 1.0.3.2 +MongoInternals.NpmModule = MongoDB; + +const FILE_ASSET_SUFFIX = 'Asset'; +const ASSETS_FOLDER = 'assets'; +const APP_FOLDER = 'app'; + +// This is used to add or remove EJSON from the beginning of everything nested +// inside an EJSON custom type. It should only be called on pure JSON! +var replaceNames = function (filter, thing) { + if (typeof thing === "object" && thing !== null) { + if (_.isArray(thing)) { + return _.map(thing, _.bind(replaceNames, null, filter)); + } + var ret = {}; + _.each(thing, function (value, key) { + ret[filter(key)] = replaceNames(filter, value); + }); + return ret; + } + return thing; +}; + +// Ensure that EJSON.clone keeps a Timestamp as a Timestamp (instead of just +// doing a structural clone). +// XXX how ok is this? what if there are multiple copies of MongoDB loaded? +MongoDB.Timestamp.prototype.clone = function () { + // Timestamps should be immutable. + return this; +}; + +var makeMongoLegal = function (name) { return "EJSON" + name; }; +var unmakeMongoLegal = function (name) { return name.substr(5); }; + +var replaceMongoAtomWithMeteor = function (document) { + if (document instanceof MongoDB.Binary) { + // for backwards compatibility + if (document.sub_type !== 0) { + return document; + } + var buffer = document.value(true); + return new Uint8Array(buffer); + } + if (document instanceof MongoDB.ObjectID) { + return new Mongo.ObjectID(document.toHexString()); + } + if (document instanceof MongoDB.Decimal128) { + return Decimal(document.toString()); + } + if (document["EJSON$type"] && document["EJSON$value"] && _.size(document) === 2) { + return EJSON.fromJSONValue(replaceNames(unmakeMongoLegal, document)); + } + if (document instanceof MongoDB.Timestamp) { + // For now, the Meteor representation of a Mongo timestamp type (not a date! + // this is a weird internal thing used in the oplog!) is the same as the + // Mongo representation. We need to do this explicitly or else we would do a + // structural clone and lose the prototype. + return document; + } + return undefined; +}; + +var replaceMeteorAtomWithMongo = function (document) { + if (EJSON.isBinary(document)) { + // This does more copies than we'd like, but is necessary because + // MongoDB.BSON only looks like it takes a Uint8Array (and doesn't actually + // serialize it correctly). + return new MongoDB.Binary(Buffer.from(document)); + } + if (document instanceof MongoDB.Binary) { + return document; + } + if (document instanceof Mongo.ObjectID) { + return new MongoDB.ObjectID(document.toHexString()); + } + if (document instanceof MongoDB.Timestamp) { + // For now, the Meteor representation of a Mongo timestamp type (not a date! + // this is a weird internal thing used in the oplog!) is the same as the + // Mongo representation. We need to do this explicitly or else we would do a + // structural clone and lose the prototype. + return document; + } + if (document instanceof Decimal) { + return MongoDB.Decimal128.fromString(document.toString()); + } + if (EJSON._isCustomType(document)) { + return replaceNames(makeMongoLegal, EJSON.toJSONValue(document)); + } + // It is not ordinarily possible to stick dollar-sign keys into mongo + // so we don't bother checking for things that need escaping at this time. + return undefined; +}; + +var replaceTypes = function (document, atomTransformer) { + if (typeof document !== 'object' || document === null) + return document; + + var replacedTopLevelAtom = atomTransformer(document); + if (replacedTopLevelAtom !== undefined) + return replacedTopLevelAtom; + + var ret = document; + _.each(document, function (val, key) { + var valReplaced = replaceTypes(val, atomTransformer); + if (val !== valReplaced) { + // Lazy clone. Shallow copy. + if (ret === document) + ret = _.clone(document); + ret[key] = valReplaced; + } + }); + return ret; +}; + + +MongoConnection = function (url, options) { + var self = this; + options = options || {}; + self._observeMultiplexers = {}; + self._onFailoverHook = new Hook; + + const userOptions = { + ...(Mongo._connectionOptions || {}), + ...(Meteor.settings?.packages?.mongo?.options || {}) + }; + + var mongoOptions = Object.assign({ + ignoreUndefined: true, + }, userOptions); + + + + // Internally the oplog connections specify their own maxPoolSize + // which we don't want to overwrite with any user defined value + if (_.has(options, 'maxPoolSize')) { + // If we just set this for "server", replSet will override it. If we just + // set it for replSet, it will be ignored if we're not using a replSet. + mongoOptions.maxPoolSize = options.maxPoolSize; + } + + // Transform options like "tlsCAFileAsset": "filename.pem" into + // "tlsCAFile": "//filename.pem" + Object.entries(mongoOptions || {}) + .filter(([key]) => key && key.endsWith(FILE_ASSET_SUFFIX)) + .forEach(([key, value]) => { + const optionName = key.replace(FILE_ASSET_SUFFIX, ''); + mongoOptions[optionName] = path.join(Assets.getServerDir(), + ASSETS_FOLDER, APP_FOLDER, value); + delete mongoOptions[key]; + }); + + self.db = null; + self._oplogHandle = null; + self._docFetcher = null; + + self.client = new MongoDB.MongoClient(url, mongoOptions); + self.db = self.client.db(); + + self.client.on('serverDescriptionChanged', Meteor.bindEnvironment(event => { + // When the connection is no longer against the primary node, execute all + // failover hooks. This is important for the driver as it has to re-pool the + // query when it happens. + if ( + event.previousDescription.type !== 'RSPrimary' && + event.newDescription.type === 'RSPrimary' + ) { + self._onFailoverHook.each(callback => { + callback(); + return true; + }); + } + })); + + if (options.oplogUrl && ! Package['disable-oplog']) { + self._oplogHandle = new OplogHandle(options.oplogUrl, self.db.databaseName); + self._docFetcher = new DocFetcher(self); + } +}; + +MongoConnection.prototype._close = async function() { + var self = this; + + if (! self.db) + throw Error("close called before Connection created?"); + + // XXX probably untested + var oplogHandle = self._oplogHandle; + self._oplogHandle = null; + if (oplogHandle) + await oplogHandle.stop(); + + // Use Future.wrap so that errors get thrown. This happens to + // work even outside a fiber since the 'close' method is not + // actually asynchronous. + await self.client.close(); +}; + +MongoConnection.prototype.close = function () { + return Meteor._isFibersEnabled ? Promise.await(this._close()) : this._close(); +}; + +// Returns the Mongo Collection object; may yield. +MongoConnection.prototype.rawCollection = function (collectionName) { + var self = this; + + if (! self.db) + throw Error("rawCollection called before Connection created?"); + + return self.db.collection(collectionName); +}; + +MongoConnection.prototype._createCappedCollection = function ( + collectionName, byteSize, maxDocuments) { + var self = this; + + if (! self.db) + throw Error("_createCappedCollection called before Connection created?"); + + var future = new Future(); + self.db.createCollection( + collectionName, + { capped: true, size: byteSize, max: maxDocuments }, + future.resolver()); + future.wait(); +}; + +// This should be called synchronously with a write, to create a +// transaction on the current write fence, if any. After we can read +// the write, and after observers have been notified (or at least, +// after the observer notifiers have added themselves to the write +// fence), you should call 'committed()' on the object returned. +MongoConnection.prototype._maybeBeginWrite = function () { + var fence = DDPServer._CurrentWriteFence.get(); + if (fence) { + return fence.beginWrite(); + } else { + return {committed: function () {}}; + } +}; + +// Internal interface: adds a callback which is called when the Mongo primary +// changes. Returns a stop handle. +MongoConnection.prototype._onFailover = function (callback) { + return this._onFailoverHook.register(callback); +}; + + +//////////// Public API ////////// + +// The write methods block until the database has confirmed the write (it may +// not be replicated or stable on disk, but one server has confirmed it) if no +// callback is provided. If a callback is provided, then they call the callback +// when the write is confirmed. They return nothing on success, and raise an +// exception on failure. +// +// After making a write (with insert, update, remove), observers are +// notified asynchronously. If you want to receive a callback once all +// of the observer notifications have landed for your write, do the +// writes inside a write fence (set DDPServer._CurrentWriteFence to a new +// _WriteFence, and then set a callback on the write fence.) +// +// Since our execution environment is single-threaded, this is +// well-defined -- a write "has been made" if it's returned, and an +// observer "has been notified" if its callback has returned. + +var writeCallback = function (write, refresh, callback) { + return function (err, result) { + if (! err) { + // XXX We don't have to run this on error, right? + try { + refresh(); + } catch (refreshErr) { + if (callback) { + callback(refreshErr); + return; + } else { + throw refreshErr; + } + } + } + write.committed(); + if (callback) { + callback(err, result); + } else if (err) { + throw err; + } + }; +}; + +var bindEnvironmentForWrite = function (callback) { + return Meteor.bindEnvironment(callback, "Mongo write"); +}; + +MongoConnection.prototype._insert = function (collection_name, document, + callback) { + var self = this; + + var sendError = function (e) { + if (callback) + return callback(e); + throw e; + }; + + if (collection_name === "___meteor_failure_test_collection") { + var e = new Error("Failure test"); + e._expectedByTest = true; + sendError(e); + return; + } + + if (!(LocalCollection._isPlainObject(document) && + !EJSON._isCustomType(document))) { + sendError(new Error( + "Only plain objects may be inserted into MongoDB")); + return; + } + + var write = self._maybeBeginWrite(); + var refresh = function () { + Meteor.refresh({collection: collection_name, id: document._id }); + }; + callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); + try { + var collection = self.rawCollection(collection_name); + collection.insertOne( + replaceTypes(document, replaceMeteorAtomWithMongo), + { + safe: true, + } + ).then(({insertedId}) => { + callback(null, insertedId); + }).catch((e) => { + callback(e, null) + }); + } catch (err) { + write.committed(); + throw err; + } +}; + +// Cause queries that may be affected by the selector to poll in this write +// fence. +MongoConnection.prototype._refresh = function (collectionName, selector) { + var refreshKey = {collection: collectionName}; + // If we know which documents we're removing, don't poll queries that are + // specific to other documents. (Note that multiple notifications here should + // not cause multiple polls, since all our listener is doing is enqueueing a + // poll.) + var specificIds = LocalCollection._idsMatchedBySelector(selector); + if (specificIds) { + _.each(specificIds, function (id) { + Meteor.refresh(_.extend({id: id}, refreshKey)); + }); + } else { + Meteor.refresh(refreshKey); + } +}; + +MongoConnection.prototype._remove = function (collection_name, selector, + callback) { + var self = this; + + if (collection_name === "___meteor_failure_test_collection") { + var e = new Error("Failure test"); + e._expectedByTest = true; + if (callback) { + return callback(e); + } else { + throw e; + } + } + + var write = self._maybeBeginWrite(); + var refresh = function () { + self._refresh(collection_name, selector); + }; + callback = bindEnvironmentForWrite(writeCallback(write, refresh, callback)); + + try { + var collection = self.rawCollection(collection_name); + collection + .deleteMany(replaceTypes(selector, replaceMeteorAtomWithMongo), { + safe: true, + }) + .then(({ deletedCount }) => { + callback(null, transformResult({ result : {modifiedCount : deletedCount} }).numberAffected); + }).catch((err) => { + callback(err); + }); + } catch (err) { + write.committed(); + throw err; + } +}; + +MongoConnection.prototype._dropCollection = function (collectionName, cb) { + var self = this; + + var write = self._maybeBeginWrite(); + var refresh = function () { + Meteor.refresh({collection: collectionName, id: null, + dropCollection: true}); + }; + cb = bindEnvironmentForWrite(writeCallback(write, refresh, cb)); + + try { + var collection = self.rawCollection(collectionName); + collection.drop(cb); + } catch (e) { + write.committed(); + throw e; + } +}; + +// For testing only. Slightly better than `c.rawDatabase().dropDatabase()` +// because it lets the test's fence wait for it to be complete. +MongoConnection.prototype._dropDatabase = function (cb) { + var self = this; + + var write = self._maybeBeginWrite(); + var refresh = function () { + Meteor.refresh({ dropDatabase: true }); + }; + cb = bindEnvironmentForWrite(writeCallback(write, refresh, cb)); + + try { + self.db.dropDatabase(cb); + } catch (e) { + write.committed(); + throw e; + } +}; + +MongoConnection.prototype._update = function (collection_name, selector, mod, + options, callback) { + var self = this; + + if (! callback && options instanceof Function) { + callback = options; + options = null; + } + + if (collection_name === "___meteor_failure_test_collection") { + var e = new Error("Failure test"); + e._expectedByTest = true; + if (callback) { + return callback(e); + } else { + throw e; + } + } + + // explicit safety check. null and undefined can crash the mongo + // driver. Although the node driver and minimongo do 'support' + // non-object modifier in that they don't crash, they are not + // meaningful operations and do not do anything. Defensively throw an + // error here. + if (!mod || typeof mod !== 'object') { + const error = new Error("Invalid modifier. Modifier must be an object."); + + if (callback) { + return callback(error); + } else { + throw error; + } + } + + if (!(LocalCollection._isPlainObject(mod) && + !EJSON._isCustomType(mod))) { + const error = new Error( + "Only plain objects may be used as replacement" + + " documents in MongoDB"); + + if (callback) { + return callback(error); + } else { + throw error; + } + } + + if (!options) options = {}; + + var write = self._maybeBeginWrite(); + var refresh = function () { + self._refresh(collection_name, selector); + }; + callback = writeCallback(write, refresh, callback); + try { + var collection = self.rawCollection(collection_name); + var mongoOpts = {safe: true}; + // Add support for filtered positional operator + if (options.arrayFilters !== undefined) mongoOpts.arrayFilters = options.arrayFilters; + // explictly enumerate options that minimongo supports + if (options.upsert) mongoOpts.upsert = true; + if (options.multi) mongoOpts.multi = true; + // Lets you get a more more full result from MongoDB. Use with caution: + // might not work with C.upsert (as opposed to C.update({upsert:true}) or + // with simulated upsert. + if (options.fullResult) mongoOpts.fullResult = true; + + var mongoSelector = replaceTypes(selector, replaceMeteorAtomWithMongo); + var mongoMod = replaceTypes(mod, replaceMeteorAtomWithMongo); + + var isModify = LocalCollection._isModificationMod(mongoMod); + + if (options._forbidReplace && !isModify) { + var err = new Error("Invalid modifier. Replacements are forbidden."); + if (callback) { + return callback(err); + } else { + throw err; + } + } + + // We've already run replaceTypes/replaceMeteorAtomWithMongo on + // selector and mod. We assume it doesn't matter, as far as + // the behavior of modifiers is concerned, whether `_modify` + // is run on EJSON or on mongo-converted EJSON. + + // Run this code up front so that it fails fast if someone uses + // a Mongo update operator we don't support. + let knownId; + if (options.upsert) { + try { + let newDoc = LocalCollection._createUpsertDocument(selector, mod); + knownId = newDoc._id; + } catch (err) { + if (callback) { + return callback(err); + } else { + throw err; + } + } + } + + if (options.upsert && + ! isModify && + ! knownId && + options.insertedId && + ! (options.insertedId instanceof Mongo.ObjectID && + options.generatedId)) { + // In case of an upsert with a replacement, where there is no _id defined + // in either the query or the replacement doc, mongo will generate an id itself. + // Therefore we need this special strategy if we want to control the id ourselves. + + // We don't need to do this when: + // - This is not a replacement, so we can add an _id to $setOnInsert + // - The id is defined by query or mod we can just add it to the replacement doc + // - The user did not specify any id preference and the id is a Mongo ObjectId, + // then we can just let Mongo generate the id + + simulateUpsertWithInsertedId( + collection, mongoSelector, mongoMod, options, + // This callback does not need to be bindEnvironment'ed because + // simulateUpsertWithInsertedId() wraps it and then passes it through + // bindEnvironmentForWrite. + function (error, result) { + // If we got here via a upsert() call, then options._returnObject will + // be set and we should return the whole object. Otherwise, we should + // just return the number of affected docs to match the mongo API. + if (result && ! options._returnObject) { + callback(error, result.numberAffected); + } else { + callback(error, result); + } + } + ); + } else { + + if (options.upsert && !knownId && options.insertedId && isModify) { + if (!mongoMod.hasOwnProperty('$setOnInsert')) { + mongoMod.$setOnInsert = {}; + } + knownId = options.insertedId; + Object.assign(mongoMod.$setOnInsert, replaceTypes({_id: options.insertedId}, replaceMeteorAtomWithMongo)); + } + + const strings = Object.keys(mongoMod).filter((key) => !key.startsWith("$")); + let updateMethod = strings.length > 0 ? 'replaceOne' : 'updateMany'; + updateMethod = + updateMethod === 'updateMany' && !mongoOpts.multi + ? 'updateOne' + : updateMethod; + collection[updateMethod].bind(collection)( + mongoSelector, mongoMod, mongoOpts, + // mongo driver now returns undefined for err in the callback + bindEnvironmentForWrite(function (err = null, result) { + if (! err) { + var meteorResult = transformResult({result}); + if (meteorResult && options._returnObject) { + // If this was an upsert() call, and we ended up + // inserting a new doc and we know its id, then + // return that id as well. + if (options.upsert && meteorResult.insertedId) { + if (knownId) { + meteorResult.insertedId = knownId; + } else if (meteorResult.insertedId instanceof MongoDB.ObjectID) { + meteorResult.insertedId = new Mongo.ObjectID(meteorResult.insertedId.toHexString()); + } + } + + callback(err, meteorResult); + } else { + callback(err, meteorResult.numberAffected); + } + } else { + callback(err); + } + })); + } + } catch (e) { + write.committed(); + throw e; + } +}; + +var transformResult = function (driverResult) { + var meteorResult = { numberAffected: 0 }; + if (driverResult) { + var mongoResult = driverResult.result; + // On updates with upsert:true, the inserted values come as a list of + // upserted values -- even with options.multi, when the upsert does insert, + // it only inserts one element. + if (mongoResult.upsertedCount) { + meteorResult.numberAffected = mongoResult.upsertedCount; + + if (mongoResult.upsertedId) { + meteorResult.insertedId = mongoResult.upsertedId; + } + } else { + // n was used before Mongo 5.0, in Mongo 5.0 we are not receiving this n + // field and so we are using modifiedCount instead + meteorResult.numberAffected = mongoResult.n || mongoResult.matchedCount || mongoResult.modifiedCount; + } + } + + return meteorResult; +}; + + +var NUM_OPTIMISTIC_TRIES = 3; + +// exposed for testing +MongoConnection._isCannotChangeIdError = function (err) { + + // Mongo 3.2.* returns error as next Object: + // {name: String, code: Number, errmsg: String} + // Older Mongo returns: + // {name: String, code: Number, err: String} + var error = err.errmsg || err.err; + + // We don't use the error code here + // because the error code we observed it producing (16837) appears to be + // a far more generic error code based on examining the source. + if (error.indexOf('The _id field cannot be changed') === 0 + || error.indexOf("the (immutable) field '_id' was found to have been altered to _id") !== -1) { + return true; + } + + return false; +}; + +var simulateUpsertWithInsertedId = function (collection, selector, mod, + options, callback) { + // STRATEGY: First try doing an upsert with a generated ID. + // If this throws an error about changing the ID on an existing document + // then without affecting the database, we know we should probably try + // an update without the generated ID. If it affected 0 documents, + // then without affecting the database, we the document that first + // gave the error is probably removed and we need to try an insert again + // We go back to step one and repeat. + // Like all "optimistic write" schemes, we rely on the fact that it's + // unlikely our writes will continue to be interfered with under normal + // circumstances (though sufficiently heavy contention with writers + // disagreeing on the existence of an object will cause writes to fail + // in theory). + + var insertedId = options.insertedId; // must exist + var mongoOptsForUpdate = { + safe: true, + multi: options.multi + }; + var mongoOptsForInsert = { + safe: true, + upsert: true + }; + + var replacementWithId = Object.assign( + replaceTypes({_id: insertedId}, replaceMeteorAtomWithMongo), + mod); + + var tries = NUM_OPTIMISTIC_TRIES; + + var doUpdate = function () { + tries--; + if (! tries) { + callback(new Error("Upsert failed after " + NUM_OPTIMISTIC_TRIES + " tries.")); + } else { + let method = collection.updateMany; + if(!Object.keys(mod).some(key => key.startsWith("$"))){ + method = collection.replaceOne.bind(collection); + } + method( + selector, + mod, + mongoOptsForUpdate, + bindEnvironmentForWrite(function(err, result) { + if (err) { + callback(err); + } else if (result && (result.modifiedCount || result.upsertedCount)) { + callback(null, { + numberAffected: result.modifiedCount || result.upsertedCount, + insertedId: result.upsertedId || undefined, + }); + } else { + doConditionalInsert(); + } + }) + ); + } + }; + + var doConditionalInsert = function() { + collection.replaceOne( + selector, + replacementWithId, + mongoOptsForInsert, + bindEnvironmentForWrite(function(err, result) { + if (err) { + // figure out if this is a + // "cannot change _id of document" error, and + // if so, try doUpdate() again, up to 3 times. + if (MongoConnection._isCannotChangeIdError(err)) { + doUpdate(); + } else { + callback(err); + } + } else { + callback(null, { + numberAffected: result.upsertedCount, + insertedId: result.upsertedId, + }); + } + }) + ); + }; + + doUpdate(); +}; + +_.each(["insert", "update", "remove", "dropCollection", "dropDatabase"], function (method) { + MongoConnection.prototype[method] = function (/* arguments */) { + var self = this; + return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : Meteor.promisify(self[`_${method}`]).apply(self, arguments); + }; +}); + +// XXX MongoConnection.upsert() does not return the id of the inserted document +// unless you set it explicitly in the selector or modifier (as a replacement +// doc). +MongoConnection.prototype.upsert = function (collectionName, selector, mod, + options, callback) { + var self = this; + if (typeof options === "function" && ! callback) { + callback = options; + options = {}; + } + + return self.update(collectionName, selector, mod, + _.extend({}, options, { + upsert: true, + _returnObject: true + }), callback); +}; + +MongoConnection.prototype.find = function (collectionName, selector, options) { + var self = this; + + if (arguments.length === 1) + selector = {}; + + return new Cursor( + self, new CursorDescription(collectionName, selector, options)); +}; + +MongoConnection.prototype._findOneFibers = function (collection_name, selector, options) { + var self = this; + if (arguments.length === 1) + selector = {}; + + options = options || {}; + options.limit = 1; + return self.find(collection_name, selector, options).fetch()[0]; +}; + +MongoConnection.prototype._findOneNoFibers = async function (collection_name, selector, options) { + var self = this; + if (arguments.length === 1) { + selector = {}; + } + + options = options || {}; + options.limit = 1; + + const results = await self.find(collection_name, selector, options).fetch(); + + return results[0]; +}; + +MongoConnection.prototype.findOne = function (...args) { + return Meteor._isFibersEnabled ? this._findOneFibers(...args) : this._findOneNoFibers(...args); +}; + +// We'll actually design an index API later. For now, we just pass through to +// Mongo's, but make it synchronous. +MongoConnection.prototype.createIndex = function (collectionName, index, + options) { + var self = this; + + // We expect this function to be called at startup, not from within a method, + // so we don't interact with the write fence. + var collection = self.rawCollection(collectionName); + var future = new Future; + var indexName = collection.createIndex(index, options, future.resolver()); + future.wait(); +}; + +MongoConnection.prototype._ensureIndex = MongoConnection.prototype.createIndex; + +MongoConnection.prototype._dropIndex = function (collectionName, index) { + var self = this; + + // This function is only used by test code, not within a method, so we don't + // interact with the write fence. + var collection = self.rawCollection(collectionName); + var future = new Future; + var indexName = collection.dropIndex(index, future.resolver()); + future.wait(); +}; + +// CURSORS + +// There are several classes which relate to cursors: +// +// CursorDescription represents the arguments used to construct a cursor: +// collectionName, selector, and (find) options. Because it is used as a key +// for cursor de-dup, everything in it should either be JSON-stringifiable or +// not affect observeChanges output (eg, options.transform functions are not +// stringifiable but do not affect observeChanges). +// +// SynchronousCursor is a wrapper around a MongoDB cursor +// which includes fully-synchronous versions of forEach, etc. +// +// Cursor is the cursor object returned from find(), which implements the +// documented Mongo.Collection cursor API. It wraps a CursorDescription and a +// SynchronousCursor (lazily: it doesn't contact Mongo until you call a method +// like fetch or forEach on it). +// +// ObserveHandle is the "observe handle" returned from observeChanges. It has a +// reference to an ObserveMultiplexer. +// +// ObserveMultiplexer allows multiple identical ObserveHandles to be driven by a +// single observe driver. +// +// There are two "observe drivers" which drive ObserveMultiplexers: +// - PollingObserveDriver caches the results of a query and reruns it when +// necessary. +// - OplogObserveDriver follows the Mongo operation log to directly observe +// database changes. +// Both implementations follow the same simple interface: when you create them, +// they start sending observeChanges callbacks (and a ready() invocation) to +// their ObserveMultiplexer, and you stop them by calling their stop() method. + +CursorDescription = function (collectionName, selector, options) { + var self = this; + self.collectionName = collectionName; + self.selector = Mongo.Collection._rewriteSelector(selector); + self.options = options || {}; +}; + +Cursor = function (mongo, cursorDescription) { + var self = this; + + self._mongo = mongo; + self._cursorDescription = cursorDescription; + self._synchronousCursor = null; +}; + +function setupSynchronousCursor(cursor, method) { + // You can only observe a tailable cursor. + if (cursor._cursorDescription.options.tailable) + throw new Error('Cannot call ' + method + ' on a tailable cursor'); + + if (!cursor._synchronousCursor) { + cursor._synchronousCursor = cursor._mongo._createSynchronousCursor( + cursor._cursorDescription, + { + // Make sure that the "cursor" argument to forEach/map callbacks is the + // Cursor, not the SynchronousCursor. + selfForIteration: cursor, + useTransform: true, + } + ); + } + + return cursor._synchronousCursor; +} + +[...ASYNC_CURSOR_METHODS, Symbol.iterator, Symbol.asyncIterator].forEach(methodName => { + Cursor.prototype[methodName] = function (...args) { + const cursor = setupSynchronousCursor(this, methodName); + return cursor[methodName](...args); + }; + + // These methods are handled separately. + if (methodName === Symbol.iterator || methodName === Symbol.asyncIterator) { + return; + } + + const methodNameAsync = getAsyncMethodName(methodName); + Cursor.prototype[methodNameAsync] = function (...args) { + return Promise.resolve(this[methodName](...args)); + }; +}); + +Cursor.prototype.getTransform = function () { + return this._cursorDescription.options.transform; +}; + +// When you call Meteor.publish() with a function that returns a Cursor, we need +// to transmute it into the equivalent subscription. This is the function that +// does that. + +Cursor.prototype._publishCursor = function (sub) { + var self = this; + var collection = self._cursorDescription.collectionName; + return Mongo.Collection._publishCursor(self, sub, collection); +}; + +// 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); +}; + +Cursor.prototype.observeChanges = function (callbacks, options = {}) { + var self = this; + var methods = [ + 'addedAt', + 'added', + 'changedAt', + 'changed', + 'removedAt', + 'removed', + 'movedTo' + ]; + var ordered = LocalCollection._observeChangesCallbacksAreOrdered(callbacks); + + let exceptionName = callbacks._fromObserve ? 'observe' : 'observeChanges'; + exceptionName += ' callback'; + methods.forEach(function (method) { + if (callbacks[method] && typeof callbacks[method] == "function") { + callbacks[method] = Meteor.bindEnvironment(callbacks[method], method + exceptionName); + } + }); + + return self._mongo._observeChanges( + self._cursorDescription, ordered, callbacks, options.nonMutatingCallbacks); +}; + +MongoConnection.prototype._createSynchronousCursor = function( + cursorDescription, options) { + var self = this; + options = _.pick(options || {}, 'selfForIteration', 'useTransform'); + + var collection = self.rawCollection(cursorDescription.collectionName); + var cursorOptions = cursorDescription.options; + var mongoOptions = { + sort: cursorOptions.sort, + limit: cursorOptions.limit, + skip: cursorOptions.skip, + projection: cursorOptions.fields || cursorOptions.projection, + readPreference: cursorOptions.readPreference, + }; + + // Do we want a tailable cursor (which only works on capped collections)? + if (cursorOptions.tailable) { + mongoOptions.numberOfRetries = -1; + } + + var dbCursor = collection.find( + replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), + mongoOptions); + + // Do we want a tailable cursor (which only works on capped collections)? + if (cursorOptions.tailable) { + // We want a tailable cursor... + dbCursor.addCursorFlag("tailable", true) + // ... and for the server to wait a bit if any getMore has no data (rather + // than making us put the relevant sleeps in the client)... + dbCursor.addCursorFlag("awaitData", true) + + // And if this is on the oplog collection and the cursor specifies a 'ts', + // then set the undocumented oplog replay flag, which does a special scan to + // find the first document (instead of creating an index on ts). This is a + // very hard-coded Mongo flag which only works on the oplog collection and + // only works with the ts field. + if (cursorDescription.collectionName === OPLOG_COLLECTION && + cursorDescription.selector.ts) { + dbCursor.addCursorFlag("oplogReplay", true) + } + } + + if (typeof cursorOptions.maxTimeMs !== 'undefined') { + dbCursor = dbCursor.maxTimeMS(cursorOptions.maxTimeMs); + } + if (typeof cursorOptions.hint !== 'undefined') { + dbCursor = dbCursor.hint(cursorOptions.hint); + } + + return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options, collection) : new AsynchronousCursor(dbCursor, cursorDescription, options, collection); +}; + +/** + * This is just a light wrapper for the cursor. The goal here is to ensure compatibility even if + * there are breaking changes on the MongoDB driver. + * + * @constructor + */ +class AsynchronousCursor { + constructor(dbCursor, cursorDescription, options) { + this._cursor = dbCursor; + this._cursorDescription = cursorDescription + + this._selfForIteration = options.selfForIteration || this; + if (options.useTransform && cursorDescription.options.transform) { + this._transform = LocalCollection.wrapTransform( + cursorDescription.options.transform); + } else { + this._transform = null; + } + + this._visitedIds = new LocalCollection._IdMap; + } + + [Symbol.iterator]() { + return this._cursor[Symbol.iterator](); + } + + // Returns a Promise for the next object from the underlying cursor (before + // the Mongo->Meteor type replacement). + async _rawNextObjectPromise() { + try { + return this._cursor.next(); + } catch (e) { + console.error(e); + } + } + + // Returns a Promise for the next object from the cursor, skipping those whose + // IDs we've already seen and replacing Mongo atoms with Meteor atoms. + async _nextObjectPromise () { + while (true) { + var doc = await this._rawNextObjectPromise(); + + if (!doc) return null; + doc = replaceTypes(doc, replaceMongoAtomWithMeteor); + + if (!this._cursorDescription.options.tailable && _.has(doc, '_id')) { + // Did Mongo give us duplicate documents in the same cursor? If so, + // ignore this one. (Do this before the transform, since transform might + // return some unrelated value.) We don't do this for tailable cursors, + // because we want to maintain O(1) memory usage. And if there isn't _id + // for some reason (maybe it's the oplog), then we don't do this either. + // (Be careful to do this for falsey but existing _id, though.) + if (this._visitedIds.has(doc._id)) continue; + this._visitedIds.set(doc._id, true); + } + + if (this._transform) + doc = this._transform(doc); + + return doc; + } + } + + // Returns a promise which is resolved with the next object (like with + // _nextObjectPromise) or rejected if the cursor doesn't return within + // timeoutMS ms. + _nextObjectPromiseWithTimeout(timeoutMS) { + if (!timeoutMS) { + return this._nextObjectPromise(); + } + const nextObjectPromise = this._nextObjectPromise(); + const timeoutErr = new Error('Client-side timeout waiting for next object'); + const timeoutPromise = new Promise((resolve, reject) => { + setTimeout(() => { + reject(timeoutErr); + }, timeoutMS); + }); + return Promise.race([nextObjectPromise, timeoutPromise]) + .catch((err) => { + if (err === timeoutErr) { + this.close(); + } + throw err; + }); + } + + async forEach(callback, thisArg) { + // Get back to the beginning. + this._rewind(); + + let idx = 0; + while (true) { + const doc = await this._nextObjectPromise(); + if (!doc) return; + await callback.call(thisArg, doc, idx++, this._selfForIteration); + } + } + + async map(callback, thisArg) { + const results = []; + await this.forEach(async (doc, index) => { + results.push(await callback.call(thisArg, doc, index, this._selfForIteration)); + }); + + return results; + } + + _rewind() { + // known to be synchronous + this._cursor.rewind(); + + this._visitedIds = new LocalCollection._IdMap; + } + + // Mostly usable for tailable cursors. + close() { + this._cursor.close(); + } + + fetch() { + return this.map(_.identity); + } + + /** + * FIXME: (node:34680) [MONGODB DRIVER] Warning: cursor.count is deprecated and will be + * removed in the next major version, please use `collection.estimatedDocumentCount` or + * `collection.countDocuments` instead. + */ + count() { + return this._cursor.count(); + } + + // This method is NOT wrapped in Cursor. + async getRawObjects(ordered) { + var self = this; + if (ordered) { + return self.fetch(); + } else { + var results = new LocalCollection._IdMap; + await self.forEach(function (doc) { + results.set(doc._id, doc); + }); + return results; + } + } +} + +var SynchronousCursor = function (dbCursor, cursorDescription, options, collection) { + var self = this; + options = _.pick(options || {}, 'selfForIteration', 'useTransform'); + + self._dbCursor = dbCursor; + self._cursorDescription = cursorDescription; + // The "self" argument passed to forEach/map callbacks. If we're wrapped + // inside a user-visible Cursor, we want to provide the outer cursor! + self._selfForIteration = options.selfForIteration || self; + if (options.useTransform && cursorDescription.options.transform) { + self._transform = LocalCollection.wrapTransform( + cursorDescription.options.transform); + } else { + self._transform = null; + } + + self._synchronousCount = Future.wrap( + collection.countDocuments.bind( + collection, + replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), + replaceTypes(cursorDescription.options, replaceMeteorAtomWithMongo), + ) + ); + self._visitedIds = new LocalCollection._IdMap; +}; + +_.extend(SynchronousCursor.prototype, { + // Returns a Promise for the next object from the underlying cursor (before + // the Mongo->Meteor type replacement). + _rawNextObjectPromise: function () { + const self = this; + return new Promise((resolve, reject) => { + self._dbCursor.next((err, doc) => { + if (err) { + reject(err); + } else { + resolve(doc); + } + }); + }); + }, + + // Returns a Promise for the next object from the cursor, skipping those whose + // IDs we've already seen and replacing Mongo atoms with Meteor atoms. + _nextObjectPromise: async function () { + var self = this; + + while (true) { + var doc = await self._rawNextObjectPromise(); + + if (!doc) return null; + doc = replaceTypes(doc, replaceMongoAtomWithMeteor); + + if (!self._cursorDescription.options.tailable && _.has(doc, '_id')) { + // Did Mongo give us duplicate documents in the same cursor? If so, + // ignore this one. (Do this before the transform, since transform might + // return some unrelated value.) We don't do this for tailable cursors, + // because we want to maintain O(1) memory usage. And if there isn't _id + // for some reason (maybe it's the oplog), then we don't do this either. + // (Be careful to do this for falsey but existing _id, though.) + if (self._visitedIds.has(doc._id)) continue; + self._visitedIds.set(doc._id, true); + } + + if (self._transform) + doc = self._transform(doc); + + return doc; + } + }, + + // Returns a promise which is resolved with the next object (like with + // _nextObjectPromise) or rejected if the cursor doesn't return within + // timeoutMS ms. + _nextObjectPromiseWithTimeout: function (timeoutMS) { + const self = this; + if (!timeoutMS) { + return self._nextObjectPromise(); + } + const nextObjectPromise = self._nextObjectPromise(); + const timeoutErr = new Error('Client-side timeout waiting for next object'); + const timeoutPromise = new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(timeoutErr); + }, timeoutMS); + }); + return Promise.race([nextObjectPromise, timeoutPromise]) + .catch((err) => { + if (err === timeoutErr) { + self.close(); + } + throw err; + }); + }, + + _nextObject: function () { + var self = this; + return self._nextObjectPromise().await(); + }, + + forEach: function (callback, thisArg) { + var self = this; + + // Get back to the beginning. + self._rewind(); + + // We implement the loop ourself instead of using self._dbCursor.each, + // because "each" will call its callback outside of a fiber which makes it + // much more complex to make this function synchronous. + var index = 0; + while (true) { + var doc = self._nextObject(); + if (!doc) return; + callback.call(thisArg, doc, index++, self._selfForIteration); + } + }, + + // XXX Allow overlapping callback executions if callback yields. + map: function (callback, thisArg) { + var self = this; + var res = []; + self.forEach(function (doc, index) { + res.push(callback.call(thisArg, doc, index, self._selfForIteration)); + }); + return res; + }, + + _rewind: function () { + var self = this; + + // known to be synchronous + self._dbCursor.rewind(); + + self._visitedIds = new LocalCollection._IdMap; + }, + + // Mostly usable for tailable cursors. + close: function () { + var self = this; + + self._dbCursor.close(); + }, + + fetch: function () { + var self = this; + return self.map(_.identity); + }, + + count: function () { + var self = this; + return self._synchronousCount().wait(); + }, + + // This method is NOT wrapped in Cursor. + getRawObjects: function (ordered) { + var self = this; + if (ordered) { + return self.fetch(); + } else { + var results = new LocalCollection._IdMap; + self.forEach(function (doc) { + results.set(doc._id, doc); + }); + return results; + } + } +}); + +SynchronousCursor.prototype[Symbol.iterator] = function () { + var self = this; + + // Get back to the beginning. + self._rewind(); + + return { + next() { + const doc = self._nextObject(); + return doc ? { + value: doc + } : { + done: true + }; + } + }; +}; + +SynchronousCursor.prototype[Symbol.asyncIterator] = function () { + const syncResult = this[Symbol.iterator](); + return { + async next() { + return Promise.resolve(syncResult.next()); + } + }; +} + +// Tails the cursor described by cursorDescription, most likely on the +// oplog. Calls docCallback with each document found. Ignores errors and just +// restarts the tail on error. +// +// If timeoutMS is set, then if we don't get a new document every timeoutMS, +// kill and restart the cursor. This is primarily a workaround for #8598. +MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeoutMS) { + var self = this; + if (!cursorDescription.options.tailable) + throw new Error("Can only tail a tailable cursor"); + + var cursor = self._createSynchronousCursor(cursorDescription); + + var stopped = false; + var lastTS; + + Meteor.defer(async function loop() { + var doc = null; + while (true) { + if (stopped) + return; + try { + doc = await cursor._nextObjectPromiseWithTimeout(timeoutMS); + } catch (err) { + // There's no good way to figure out if this was actually an error from + // Mongo, or just client-side (including our own timeout error). Ah + // well. But either way, we need to retry the cursor (unless the failure + // was because the observe got stopped). + doc = null; + } + // Since we awaited a promise above, we need to check again to see if + // we've been stopped before calling the callback. + if (stopped) + return; + if (doc) { + // If a tailable cursor contains a "ts" field, use it to recreate the + // cursor on error. ("ts" is a standard that Mongo uses internally for + // the oplog, and there's a special flag that lets you do binary search + // on it instead of needing to use an index.) + lastTS = doc.ts; + docCallback(doc); + } else { + var newSelector = _.clone(cursorDescription.selector); + if (lastTS) { + newSelector.ts = {$gt: lastTS}; + } + cursor = self._createSynchronousCursor(new CursorDescription( + cursorDescription.collectionName, + newSelector, + cursorDescription.options)); + // Mongo failover takes many seconds. Retry in a bit. (Without this + // setTimeout, we peg the CPU at 100% and never notice the actual + // failover. + setTimeout(loop, 100); + break; + } + } + }); + + return { + stop: function () { + stopped = true; + cursor.close(); + } + }; +}; + +Object.assign(MongoConnection.prototype, { + _observeChanges: function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + return Meteor._isFibersEnabled + ? this._observeChangesFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks) + : this._observeChangesNoFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks); + }, + + _observeChangesNoFibers: async function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + var self = this; + + if (cursorDescription.options.tailable) { + return self._observeChangesTailable(cursorDescription, ordered, callbacks); + } + + // You may not filter out _id when observing changes, because the id is a core + // part of the observeChanges API. + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { + throw Error("You may not observe a cursor with {fields: {_id: 0}}"); + } + + var observeKey = EJSON.stringify( + _.extend({ordered: ordered}, cursorDescription)); + + var multiplexer, observeDriver; + var firstHandle = false; + + // Find a matching ObserveMultiplexer, or create a new one. This next block is + // guaranteed to not yield (and it doesn't call anything that can observe a + // new query), so no other calls to this function can interleave with it. + if (_.has(self._observeMultiplexers, observeKey)) { + multiplexer = self._observeMultiplexers[observeKey]; + } else { + firstHandle = true; + // Create a new ObserveMultiplexer. + multiplexer = new ObserveMultiplexer({ + ordered: ordered, + onStop: function () { + delete self._observeMultiplexers[observeKey]; + return observeDriver.stop(); + } + }); + self._observeMultiplexers[observeKey] = multiplexer; + } + + var observeHandle = new ObserveHandle(multiplexer, + callbacks, + nonMutatingCallbacks, + ); + + if (firstHandle) { + var matcher, sorter; + var canUseOplog = _.all([ + function () { + // At a bare minimum, using the oplog requires us to have an oplog, to + // want unordered callbacks, and to not want a callback on the polls + // that won't happen. + return self._oplogHandle && !ordered && + !callbacks._testOnlyPollCallback; + }, function () { + // We need to be able to compile the selector. Fall back to polling for + // some newfangled $selector that minimongo doesn't support yet. + try { + matcher = new Minimongo.Matcher(cursorDescription.selector); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }, function () { + // ... and the selector itself needs to support oplog. + return OplogObserveDriver.cursorSupported(cursorDescription, matcher); + }, function () { + // And we need to be able to compile the sort, if any. eg, can't be + // {$natural: 1}. + if (!cursorDescription.options.sort) + return true; + try { + sorter = new Minimongo.Sorter(cursorDescription.options.sort); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }], function (f) { return f(); }); // invoke each function + + var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; + observeDriver = new driverClass({ + cursorDescription: cursorDescription, + mongoHandle: self, + multiplexer: multiplexer, + ordered: ordered, + matcher: matcher, // ignored by polling + sorter: sorter, // ignored by polling + _testOnlyPollCallback: callbacks._testOnlyPollCallback + }); + + if (observeDriver._init) { + await observeDriver._init(); + } + + // This field is only set for use in tests. + multiplexer._observeDriver = observeDriver; + } + + // Blocks until the initial adds have been sent. + await multiplexer.addHandleAndSendInitialAdds(observeHandle); + + return observeHandle; + }, + + _observeChangesFibers: function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + var self = this; + + if (cursorDescription.options.tailable) { + return self._observeChangesTailable(cursorDescription, ordered, callbacks); + } + + // You may not filter out _id when observing changes, because the id is a core + // part of the observeChanges API. + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { + throw Error("You may not observe a cursor with {fields: {_id: 0}}"); + } + + var observeKey = EJSON.stringify( + _.extend({ordered: ordered}, cursorDescription)); + + var multiplexer, observeDriver; + var firstHandle = false; + + // Find a matching ObserveMultiplexer, or create a new one. This next block is + // guaranteed to not yield (and it doesn't call anything that can observe a + // new query), so no other calls to this function can interleave with it. + Meteor._noYieldsAllowed(function () { + if (_.has(self._observeMultiplexers, observeKey)) { + multiplexer = self._observeMultiplexers[observeKey]; + } else { + firstHandle = true; + // Create a new ObserveMultiplexer. + multiplexer = new ObserveMultiplexer({ + ordered: ordered, + onStop: function () { + delete self._observeMultiplexers[observeKey]; + observeDriver.stop(); + } + }); + self._observeMultiplexers[observeKey] = multiplexer; + } + }); + + var observeHandle = new ObserveHandle(multiplexer, + callbacks, + nonMutatingCallbacks, + ); + + if (firstHandle) { + var matcher, sorter; + var canUseOplog = _.all([ + function () { + // At a bare minimum, using the oplog requires us to have an oplog, to + // want unordered callbacks, and to not want a callback on the polls + // that won't happen. + return self._oplogHandle && !ordered && + !callbacks._testOnlyPollCallback; + }, function () { + // We need to be able to compile the selector. Fall back to polling for + // some newfangled $selector that minimongo doesn't support yet. + try { + matcher = new Minimongo.Matcher(cursorDescription.selector); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }, function () { + // ... and the selector itself needs to support oplog. + return OplogObserveDriver.cursorSupported(cursorDescription, matcher); + }, function () { + // And we need to be able to compile the sort, if any. eg, can't be + // {$natural: 1}. + if (!cursorDescription.options.sort) + return true; + try { + sorter = new Minimongo.Sorter(cursorDescription.options.sort); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }], function (f) { return f(); }); // invoke each function + + var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; + observeDriver = new driverClass({ + cursorDescription: cursorDescription, + mongoHandle: self, + multiplexer: multiplexer, + ordered: ordered, + matcher: matcher, // ignored by polling + sorter: sorter, // ignored by polling + _testOnlyPollCallback: callbacks._testOnlyPollCallback + }); + + if (observeDriver._init) { + observeDriver._init(); + } + + // This field is only set for use in tests. + multiplexer._observeDriver = observeDriver; + } + + // Blocks until the initial adds have been sent. + multiplexer.addHandleAndSendInitialAdds(observeHandle); + + return observeHandle; + }, +}); + + +// Listen for the invalidation messages that will trigger us to poll the +// database for changes. If this selector specifies specific IDs, specify them +// here, so that updates to different specific IDs don't cause us to poll. +// listenCallback is the same kind of (notification, complete) callback passed +// to InvalidationCrossbar.listen. + +listenAll = function (cursorDescription, listenCallback) { + var listeners = []; + forEachTrigger(cursorDescription, function (trigger) { + listeners.push(DDPServer._InvalidationCrossbar.listen( + trigger, listenCallback)); + }); + + return { + stop: function () { + _.each(listeners, function (listener) { + listener.stop(); + }); + } + }; +}; + +forEachTrigger = function (cursorDescription, triggerCallback) { + var key = {collection: cursorDescription.collectionName}; + var specificIds = LocalCollection._idsMatchedBySelector( + cursorDescription.selector); + if (specificIds) { + _.each(specificIds, function (id) { + triggerCallback(_.extend({id: id}, key)); + }); + triggerCallback(_.extend({dropCollection: true, id: null}, key)); + } else { + triggerCallback(key); + } + // Everyone cares about the database being dropped. + triggerCallback({ dropDatabase: true }); +}; + +// observeChanges for tailable cursors on capped collections. +// +// Some differences from normal cursors: +// - Will never produce anything other than 'added' or 'addedBefore'. If you +// do update a document that has already been produced, this will not notice +// it. +// - If you disconnect and reconnect from Mongo, it will essentially restart +// the query, which will lead to duplicate results. This is pretty bad, +// but if you include a field called 'ts' which is inserted as +// new MongoInternals.MongoTimestamp(0, 0) (which is initialized to the +// current Mongo-style timestamp), we'll be able to find the place to +// restart properly. (This field is specifically understood by Mongo with an +// optimization which allows it to find the right place to start without +// an index on ts. It's how the oplog works.) +// - No callbacks are triggered synchronously with the call (there's no +// differentiation between "initial data" and "later changes"; everything +// that matches the query gets sent asynchronously). +// - De-duplication is not implemented. +// - Does not yet interact with the write fence. Probably, this should work by +// ignoring removes (which don't work on capped collections) and updates +// (which don't affect tailable cursors), and just keeping track of the ID +// of the inserted object, and closing the write fence once you get to that +// ID (or timestamp?). This doesn't work well if the document doesn't match +// the query, though. On the other hand, the write fence can close +// immediately if it does not match the query. So if we trust minimongo +// enough to accurately evaluate the query against the write fence, we +// should be able to do this... Of course, minimongo doesn't even support +// Mongo Timestamps yet. +MongoConnection.prototype._observeChangesTailable = function ( + cursorDescription, ordered, callbacks) { + var self = this; + + // Tailable cursors only ever call added/addedBefore callbacks, so it's an + // error if you didn't provide them. + if ((ordered && !callbacks.addedBefore) || + (!ordered && !callbacks.added)) { + throw new Error("Can't observe an " + (ordered ? "ordered" : "unordered") + + " tailable cursor without a " + + (ordered ? "addedBefore" : "added") + " callback"); + } + + return self.tail(cursorDescription, function (doc) { + var id = doc._id; + delete doc._id; + // The ts is an implementation detail. Hide it. + delete doc.ts; + if (ordered) { + callbacks.addedBefore(id, doc, null); + } else { + callbacks.added(id, doc); + } + }); +}; + +// XXX We probably need to find a better way to expose this. Right now +// it's only used by tests, but in fact you need it in normal +// operation to interact with capped collections. +MongoInternals.MongoTimestamp = MongoDB.Timestamp; + +MongoInternals.Connection = MongoConnection; diff --git a/packages/mongo/mongo_livedata_tests.js b/packages/mongo/mongo_livedata_tests.js new file mode 100644 index 0000000000..c6a2484728 --- /dev/null +++ b/packages/mongo/mongo_livedata_tests.js @@ -0,0 +1,3498 @@ +// This is a magic collection that fails its writes on the server when +// the selector (or inserted document) contains fail: true. + +var TRANSFORMS = {}; + +// We keep track of the collections, so we can refer to them by name +var COLLECTIONS = {}; + +if (Meteor.isServer) { + Meteor.methods({ + createInsecureCollection: function (name, options) { + check(name, String); + check(options, Match.Optional({ + transformName: Match.Optional(String), + idGeneration: Match.Optional(String) + })); + + if (options && options.transformName) { + options.transform = TRANSFORMS[options.transformName]; + } + var c = new Mongo.Collection(name, options); + COLLECTIONS[name] = c; + c._insecure = true; + Meteor.publish('c-' + name, function () { + return c.find(); + }); + }, + dropInsecureCollection: function(name) { + var c = COLLECTIONS[name]; + c._dropCollection(); + } + }); +} + +// We store the generated id, keyed by collection, for each insert +// This is so we can test the stub and the server generate the same id +var INSERTED_IDS = {}; + +Meteor.methods({ + insertObjects: function (collectionName, doc, count) { + var c = COLLECTIONS[collectionName]; + var ids = []; + for (var i = 0; i < count; i++) { + var id = c.insert(doc); + INSERTED_IDS[collectionName] = (INSERTED_IDS[collectionName] || []).concat([id]); + ids.push(id); + } + return ids; + }, + upsertObject: function (collectionName, selector, modifier) { + var c = COLLECTIONS[collectionName]; + return c.upsert(selector, modifier); + }, + doMeteorCall: function (name /*, arguments */) { + var args = Array.prototype.slice.call(arguments); + + return Meteor.call.apply(null, args); + } +}); + +var runInFence = function (f) { + if (Meteor.isClient) { + f(); + } else { + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); + fence.armAndWait(); + } +}; + +// Helpers for upsert tests + +var stripId = function (obj) { + delete obj._id; +}; + +var compareResults = function (test, skipIds, actual, expected) { + if (skipIds) { + _.map(actual, stripId); + _.map(expected, stripId); + } + // (technically should ignore order in comparison) + test.equal(actual, expected); +}; + +var upsert = function (coll, useUpdate, query, mod, options, callback) { + if (! callback && typeof options === "function") { + callback = options; + options = {}; + } + + if (useUpdate) { + if (callback) + return coll.update(query, mod, + _.extend({ upsert: true }, options), + function (err, result) { + callback(err, ! err && { + numberAffected: result + }); + }); + return { + numberAffected: coll.update(query, mod, + _.extend({ upsert: true }, options)) + }; + } else { + return coll.upsert(query, mod, options, callback); + } +}; + +var upsertTestMethod = "livedata_upsert_test_method"; +var upsertTestMethodColl; + +// This is the implementation of the upsert test method on both the client and +// the server. On the client, we get a test object. On the server, we just throw +// errors if something doesn't go according to plan, and when the client +// receives those errors it will cause the test to fail. +// +// Client-side exceptions in here will NOT cause the test to fail! Because it's +// a stub, those exceptions will get caught and logged. +var upsertTestMethodImpl = function (coll, useUpdate, test) { + coll.remove({}); + var result1 = upsert(coll, useUpdate, { foo: "bar" }, { foo: "bar" }); + + if (! test) { + test = { + equal: function (a, b) { + if (! EJSON.equals(a, b)) + throw new Error("Not equal: " + + JSON.stringify(a) + ", " + JSON.stringify(b)); + }, + isTrue: function (a) { + if (! a) + throw new Error("Not truthy: " + JSON.stringify(a)); + }, + isFalse: function (a) { + if (a) + throw new Error("Not falsey: " + JSON.stringify(a)); + } + }; + } + + // if we don't test this, then testing result1.numberAffected will throw, + // which will get caught and logged and the whole test will pass! + test.isTrue(result1); + + test.equal(result1.numberAffected, 1); + if (! useUpdate) + test.isTrue(result1.insertedId); + var fooId = result1.insertedId; + var obj = coll.findOne({ foo: "bar" }); + test.isTrue(obj); + if (! useUpdate) + test.equal(obj._id, result1.insertedId); + var result2 = upsert(coll, useUpdate, { _id: fooId }, + { $set: { foo: "baz " } }); + test.isTrue(result2); + test.equal(result2.numberAffected, 1); + test.isFalse(result2.insertedId); +}; + +if (Meteor.isServer) { + var m = {}; + m[upsertTestMethod] = function (run, useUpdate, options) { + check(run, String); + check(useUpdate, Boolean); + upsertTestMethodColl = new Mongo.Collection(upsertTestMethod + "_collection_" + run, options); + upsertTestMethodImpl(upsertTestMethodColl, useUpdate); + }; + Meteor.methods(m); +} + +Meteor._FailureTestCollection = + new Mongo.Collection("___meteor_failure_test_collection"); + +// For test "document with a custom type" +var Dog = function (name, color, actions) { + var self = this; + self.color = color; + self.name = name; + self.actions = actions || [{name: "wag"}, {name: "swim"}]; +}; +_.extend(Dog.prototype, { + getName: function () { return this.name;}, + getColor: function () { return this.name;}, + equals: function (other) { return other.name === this.name && + other.color === this.color && + EJSON.equals(other.actions, this.actions);}, + toJSONValue: function () { return {color: this.color, name: this.name, actions: this.actions};}, + typeName: function () { return "dog"; }, + clone: function () { return new Dog(this.name, this.color); }, + speak: function () { return "woof"; } +}); +EJSON.addType("dog", function (o) { return new Dog(o.name, o.color, o.actions);}); + + +// Parameterize tests. +_.each( ['STRING', 'MONGO'], function(idGeneration) { + +var collectionOptions = { idGeneration: idGeneration}; + +testAsyncMulti("mongo-livedata - database error reporting. " + idGeneration, [ + function (test, expect) { + var ftc = Meteor._FailureTestCollection; + + var exception = function (err, res) { + test.instanceOf(err, Error); + }; + + _.each(["insert", "remove", "update"], function (op) { + var arg = (op === "insert" ? {} : 'bla'); + var arg2 = {}; + + var callOp = function (callback) { + if (op === "update") { + ftc[op](arg, arg2, callback); + } else { + ftc[op](arg, callback); + } + }; + + if (Meteor.isServer) { + test.throws(function () { + callOp(); + }); + + callOp(expect(exception)); + } + + if (Meteor.isClient) { + callOp(expect(exception)); + + // This would log to console in normal operation. + Meteor._suppress_log(1); + callOp(); + } + }); + } +]); + + +Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll, coll2; + if (Meteor.isClient) { + coll = new Mongo.Collection(null, collectionOptions) ; // local, unmanaged + coll2 = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + coll2 = new Mongo.Collection("livedata_test_collection_2_"+run, collectionOptions); + } + + var log = ''; + var obs = coll.find({run: run}, {sort: ["x"]}).observe({ + addedAt: function (doc, before_index, before) { + log += 'a(' + doc.x + ',' + before_index + ',' + before + ')'; + }, + changedAt: function (new_doc, old_doc, at_index) { + log += 'c(' + new_doc.x + ',' + at_index + ',' + old_doc.x + ')'; + }, + movedTo: function (doc, old_index, new_index) { + log += 'm(' + doc.x + ',' + old_index + ',' + new_index + ')'; + }, + removedAt: function (doc, at_index) { + log += 'r(' + doc.x + ',' + at_index + ')'; + } + }); + + var captureObserve = function (f) { + if (Meteor.isClient) { + f(); + } else { + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); + fence.armAndWait(); + } + + var ret = log; + log = ''; + return ret; + }; + + var expectObserve = function (expected, f) { + if (!(expected instanceof Array)) + expected = [expected]; + + test.include(expected, captureObserve(f)); + }; + + test.equal(coll.find({run: run}).count(), 0); + test.equal(coll.findOne("abc"), undefined); + test.equal(coll.findOne({run: run}), undefined); + + expectObserve('a(1,0,null)', function () { + var id = coll.insert({run: run, x: 1}); + test.equal(coll.find({run: run}).count(), 1); + test.equal(coll.findOne(id).x, 1); + test.equal(coll.findOne({run: run}).x, 1); + }); + + expectObserve('a(4,1,null)', function () { + var id2 = coll.insert({run: run, x: 4}); + test.equal(coll.find({run: run}).count(), 2); + test.equal(coll.find({_id: id2}).count(), 1); + test.equal(coll.findOne(id2).x, 4); + }); + + test.equal(coll.findOne({run: run}, {sort: ["x"], skip: 0}).x, 1); + test.equal(coll.findOne({run: run}, {sort: ["x"], skip: 1}).x, 4); + test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 0}).x, 4); + test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 1}).x, 1); + + + // - applySkipLimit is no longer an option + // Note that the current behavior is inconsistent on the client. + // (https://github.com/meteor/meteor/issues/1201) + if (Meteor.isServer) { + test.equal(coll.find({run: run}, {limit: 1}).count(), 1); + } + + var cur = coll.find({run: run}, {sort: ["x"]}); + var total = 0; + var index = 0; + var context = {}; + cur.forEach(function (doc, i, cursor) { + test.equal(i, index++); + test.isTrue(cursor === cur); + test.isTrue(context === this); + total *= 10; + if (Meteor.isServer) { + // Verify that the callbacks from forEach run sequentially and that + // forEach waits for them to complete (issue# 321). If they do not run + // sequentially, then the second callback could execute during the first + // callback's sleep sleep and the *= 10 will occur before the += 1, then + // total (at test.equal time) will be 5. If forEach does not wait for the + // callbacks to complete, then total (at test.equal time) will be 0. + Meteor._sleepForMs(5); + } + total += doc.x; + // verify the meteor environment is set up here + coll2.insert({total:total}); + }, context); + test.equal(total, 14); + + index = 0; + test.equal(cur.map(function (doc, i, cursor) { + // XXX we could theoretically make map run its iterations in parallel or + // something which would make this fail + test.equal(i, index++); + test.isTrue(cursor === cur); + test.isTrue(context === this); + return doc.x * 2; + }, context), [2, 8]); + + test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [4, 1]); + + expectObserve('', function () { + var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); + test.equal(count, 0); + }); + + expectObserve('c(3,0,1)c(6,1,4)', function () { + var count = coll.update({run: run}, {$inc: {x: 2}}, {multi: true}); + test.equal(count, 2); + test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [6, 3]); + }); + + expectObserve(['c(13,0,3)m(13,0,1)', 'm(6,1,0)c(13,1,3)', + 'c(13,0,3)m(6,1,0)', 'm(3,0,1)c(13,1,3)'], function () { + coll.update({run: run, x: 3}, {$inc: {x: 10}}, {multi: true}); + test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [13, 6]); + }); + + expectObserve('r(13,1)', function () { + var count = coll.remove({run: run, x: {$gt: 10}}); + test.equal(count, 1); + test.equal(coll.find({run: run}).count(), 1); + }); + + expectObserve('r(6,0)', function () { + coll.remove({run: run}); + test.equal(coll.find({run: run}).count(), 0); + }); + + expectObserve('', function () { + var count = coll.remove({run: run}); + test.equal(count, 0); + test.equal(coll.find({run: run}).count(), 0); + }); + + obs.stop(); + onComplete(); +}); + +Tinytest.addAsync("mongo-livedata - fuzz test, " + idGeneration, function(test, onComplete) { + + var run = Random.id(); + var coll; + if (Meteor.isClient) { + coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + } + + // fuzz test of observe(), especially the server-side diffing + var actual = []; + var correct = []; + var counters = {add: 0, change: 0, move: 0, remove: 0}; + + var obs = coll.find({run: run}, {sort: ["x"]}).observe({ + addedAt: function (doc, before_index) { + counters.add++; + actual.splice(before_index, 0, doc.x); + }, + changedAt: function (new_doc, old_doc, at_index) { + counters.change++; + test.equal(actual[at_index], old_doc.x); + actual[at_index] = new_doc.x; + }, + movedTo: function (doc, old_index, new_index) { + counters.move++; + test.equal(actual[old_index], doc.x); + actual.splice(old_index, 1); + actual.splice(new_index, 0, doc.x); + }, + removedAt: function (doc, at_index) { + counters.remove++; + test.equal(actual[at_index], doc.x); + actual.splice(at_index, 1); + } + }); + + if (Meteor.isServer) { + // For now, has to be polling (not oplog) because it is ordered observe. + test.isTrue(obs._multiplexer._observeDriver._suspendPolling); + } + + var step = 0; + + // Use non-deterministic randomness so we can have a shorter fuzz + // test (fewer iterations). For deterministic (fully seeded) + // randomness, remove the call to Random.fraction(). + var seededRandom = new SeededRandom("foobard" + Random.fraction()); + // Random integer in [0,n) + var rnd = function (n) { + return seededRandom.nextIntBetween(0, n-1); + }; + + var finishObserve = function (f) { + if (Meteor.isClient) { + f(); + } else { + var fence = new DDPServer._WriteFence; + DDPServer._CurrentWriteFence.withValue(fence, f); + fence.armAndWait(); + } + }; + + var doStep = function () { + if (step++ === 5) { // run N random tests + obs.stop(); + onComplete(); + return; + } + + var max_counters = _.clone(counters); + + finishObserve(function () { + if (Meteor.isServer) + obs._multiplexer._observeDriver._suspendPolling(); + + // Do a batch of 1-10 operations + var batch_count = rnd(10) + 1; + for (var i = 0; i < batch_count; i++) { + // 25% add, 25% remove, 25% change in place, 25% change and move + var x; + var op = rnd(4); + var which = rnd(correct.length); + if (op === 0 || step < 2 || !correct.length) { + // Add + x = rnd(1000000); + coll.insert({run: run, x: x}); + correct.push(x); + max_counters.add++; + } else if (op === 1 || op === 2) { + var val; + x = correct[which]; + if (op === 1) { + // Small change, not likely to cause a move + val = x + (rnd(2) ? -1 : 1); + } else { + // Large change, likely to cause a move + val = rnd(1000000); + } + coll.update({run: run, x: x}, {$set: {x: val}}); + correct[which] = val; + max_counters.change++; + max_counters.move++; + } else { + coll.remove({run: run, x: correct[which]}); + correct.splice(which, 1); + max_counters.remove++; + } + } + if (Meteor.isServer) + obs._multiplexer._observeDriver._resumePolling(); + + }); + + // Did we actually deliver messages that mutated the array in the + // right way? + correct.sort(function (a,b) {return a-b;}); + test.equal(actual, correct); + + // Did we limit ourselves to one 'moved' message per change, + // rather than O(results) moved messages? + _.each(max_counters, function (v, k) { + test.isTrue(max_counters[k] >= counters[k], k); + }); + + Meteor.defer(doStep); + }; + + doStep(); + +}); + +Tinytest.addAsync("mongo-livedata - scribbling, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll; + if (Meteor.isClient) { + coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + } + + var numAddeds = 0; + var handle = coll.find({run: run}).observe({ + addedAt: function (o) { + // test that we can scribble on the object we get back from Mongo without + // breaking anything. The worst possible scribble is messing with _id. + delete o._id; + numAddeds++; + } + }); + _.each([123, 456, 789], function (abc) { + runInFence(function () { + coll.insert({run: run, abc: abc}); + }); + }); + handle.stop(); + // will be 6 (1+2+3) if we broke diffing! + test.equal(numAddeds, 3); + + onComplete(); +}); + +if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - extended scribbling, " + idGeneration, function (test, onComplete) { + function error() { + throw new Meteor.Error('unsafe object mutation'); + } + + const denyModifications = { + get(target, key) { + const type = Object.prototype.toString.call(target[key]); + if (type === '[object Object]' || type === '[object Array]') { + return freeze(target[key]); + } else { + return target[key]; + } + }, + set: error, + deleteProperty: error, + defineProperty: error, + }; + + // Object.freeze only throws in silent mode + // So we make our own version that always throws. + function freeze(obj) { + return new Proxy(obj, denyModifications); + } + + const origApplyCallback = ObserveMultiplexer.prototype._applyCallback; + ObserveMultiplexer.prototype._applyCallback = function(callback, args) { + // Make sure that if anything touches the original object, this will throw + return origApplyCallback.call(this, callback, freeze(args)); + } + + const run = test.runId(); + const coll = new Mongo.Collection(`livedata_test_scribble_collection_${run}`, collectionOptions); + const expectMutatable = (o) => { + try { + o.a[0].c = 3; + } catch (error) { + test.fail(); + } + } + const expectNotMutatable = (o) => { + try { + o.a[0].c = 3; + test.fail(); + } catch (error) {} + } + const handle = coll.find({run}).observe({ + addedAt: expectMutatable, + changedAt: function(id, o) { + expectMutatable(o); + } + }); + + const handle2 = coll.find({run}).observeChanges({ + added: expectNotMutatable, + changed: function(id, o) { + expectNotMutatable(o); + } + }, { nonMutatingCallbacks: true }); + + runInFence(function () { + coll.insert({run, a: [ {c: 1} ]}); + coll.update({run}, { $set: { 'a.0.c': 2 } }); + }); + + handle.stop(); + handle2.stop(); + + ObserveMultiplexer.prototype._applyCallback = origApplyCallback; + onComplete(); + }); +} + +Tinytest.addAsync("mongo-livedata - stop handle in callback, " + idGeneration, function (test, onComplete) { + var run = Random.id(); + var coll; + if (Meteor.isClient) { + coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("stopHandleInCallback-"+run, collectionOptions); + } + + var output = []; + + var handle = coll.find().observe({ + added: function (doc) { + output.push({added: doc._id}); + }, + changed: function (newDoc) { + output.push('changed'); + handle.stop(); + } + }); + + test.equal(output, []); + + // Insert a document. Observe that the added callback is called. + var docId; + runInFence(function () { + docId = coll.insert({foo: 42}); + }); + test.length(output, 1); + test.equal(output.shift(), {added: docId}); + + // Update it. Observe that the changed callback is called. This should also + // stop the observation. + runInFence(function() { + coll.update(docId, {$set: {bar: 10}}); + }); + test.length(output, 1); + test.equal(output.shift(), 'changed'); + + // Update again. This shouldn't call the callback because we stopped the + // observation. + runInFence(function() { + coll.update(docId, {$set: {baz: 40}}); + }); + test.length(output, 0); + + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(docId), + {_id: docId, foo: 42, bar: 10, baz: 40}); + + onComplete(); +}); + +// This behavior isn't great, but it beats deadlock. +if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - recursive observe throws, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("observeInCallback-"+run, collectionOptions); + + var callbackCalled = false; + var handle = coll.find({}).observe({ + added: function (newDoc) { + callbackCalled = true; + test.throws(function () { + coll.find({}).observe(); + }); + } + }); + test.isFalse(callbackCalled); + // Insert a document. Observe that the added callback is called. + runInFence(function () { + coll.insert({foo: 42}); + }); + test.isTrue(callbackCalled); + + handle.stop(); + + onComplete(); + }); + + Tinytest.addAsync("mongo-livedata - cursor dedup, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("cursorDedup-"+run, collectionOptions); + + var observer = function (noAdded) { + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + } + }; + if (!noAdded) { + callbacks.added = function (doc) { + output.push({added: doc._id}); + }; + } + var handle = coll.find({foo: 22}).observe(callbacks); + return {output: output, handle: handle}; + }; + + // Insert a doc and start observing. + var docId1 = coll.insert({foo: 22}); + var o1 = observer(); + // Initial add. + test.length(o1.output, 1); + test.equal(o1.output.shift(), {added: docId1}); + + // Insert another doc (blocking until observes have fired). + var docId2; + runInFence(function () { + docId2 = coll.insert({foo: 22, bar: 5}); + }); + // Observed add. + test.length(o1.output, 1); + test.equal(o1.output.shift(), {added: docId2}); + + // Second identical observe. + var o2 = observer(); + // Initial adds. + test.length(o2.output, 2); + test.include([docId1, docId2], o2.output[0].added); + test.include([docId1, docId2], o2.output[1].added); + test.notEqual(o2.output[0].added, o2.output[1].added); + o2.output.length = 0; + // Original observe not affected. + test.length(o1.output, 0); + + // White-box test: both observes should share an ObserveMultiplexer. + var observeMultiplexer = o1.handle._multiplexer; + test.isTrue(observeMultiplexer); + test.isTrue(observeMultiplexer === o2.handle._multiplexer); + + // Update. Both observes fire. + runInFence(function () { + coll.update(docId1, {$set: {x: 'y'}}); + }); + test.length(o1.output, 1); + test.length(o2.output, 1); + test.equal(o1.output.shift(), {changed: docId1}); + test.equal(o2.output.shift(), {changed: docId1}); + + // Stop first handle. Second handle still around. + o1.handle.stop(); + test.length(o1.output, 0); + test.length(o2.output, 0); + + // Another update. Just the second handle should fire. + runInFence(function () { + coll.update(docId2, {$set: {z: 'y'}}); + }); + test.length(o1.output, 0); + test.length(o2.output, 1); + test.equal(o2.output.shift(), {changed: docId2}); + + // Stop second handle. Nothing should happen, but the multiplexer should + // be stopped. + test.isTrue(observeMultiplexer._handles); // This will change. + o2.handle.stop(); + test.length(o1.output, 0); + test.length(o2.output, 0); + // White-box: ObserveMultiplexer has nulled its _handles so you can't + // accidentally join to it. + test.isNull(observeMultiplexer._handles); + + // Start yet another handle on the same query. + var o3 = observer(); + // Initial adds. + test.length(o3.output, 2); + test.include([docId1, docId2], o3.output[0].added); + test.include([docId1, docId2], o3.output[1].added); + test.notEqual(o3.output[0].added, o3.output[1].added); + // Old observers not called. + test.length(o1.output, 0); + test.length(o2.output, 0); + // White-box: Different ObserveMultiplexer. + test.isTrue(observeMultiplexer !== o3.handle._multiplexer); + + // Start another handle with no added callback. Regression test for #589. + var o4 = observer(true); + + o3.handle.stop(); + o4.handle.stop(); + + onComplete(); + }); + + Tinytest.addAsync("mongo-livedata - async server-side insert, " + idGeneration, function (test, onComplete) { + // Tests that insert returns before the callback runs. Relies on the fact + // that mongo does not run the callback before spinning off the event loop. + var cname = Random.id(); + var coll = new Mongo.Collection(cname); + var doc = { foo: "bar" }; + var x = 0; + coll.insert(doc, function (err, result) { + test.equal(err, null); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + + Tinytest.addAsync("mongo-livedata - async server-side update, " + idGeneration, function (test, onComplete) { + // Tests that update returns before the callback runs. + var cname = Random.id(); + var coll = new Mongo.Collection(cname); + var doc = { foo: "bar" }; + var x = 0; + var id = coll.insert(doc); + coll.update(id, { $set: { foo: "baz" } }, function (err, result) { + test.equal(err, null); + test.equal(result, 1); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + + Tinytest.addAsync("mongo-livedata - async server-side remove, " + idGeneration, function (test, onComplete) { + // Tests that remove returns before the callback runs. + var cname = Random.id(); + var coll = new Mongo.Collection(cname); + var doc = { foo: "bar" }; + var x = 0; + var id = coll.insert(doc); + coll.remove(id, function (err, result) { + test.equal(err, null); + test.isFalse(coll.findOne(id)); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + + // compares arrays a and b w/o looking at order + var setsEqual = function (a, b) { + a = _.map(a, EJSON.stringify); + b = _.map(b, EJSON.stringify); + return _.isEmpty(_.difference(a, b)) && _.isEmpty(_.difference(b, a)); + }; + + // This test mainly checks the correctness of oplog code dealing with limited + // queries. Compitablity with poll-diff is added as well. + Tinytest.add("mongo-livedata - observe sorted, limited " + idGeneration, function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = coll.find({foo: 22}, + {sort: {bar: 1}, limit: 3}).observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + + var ins = function (doc) { + var id; runInFence(function () { id = coll.insert(doc); }); + return id; + }; + var rem = function (sel) { runInFence(function () { coll.remove(sel); }); }; + var upd = function (sel, mod, opt) { + runInFence(function () { + coll.update(sel, mod, opt); + }); + }; + // tests '_id' subfields for all documents in oplog buffer + var testOplogBufferIds = function (ids) { + if (!usesOplog) + return; + var bufferIds = []; + o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { + bufferIds.push(id); + }); + + test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); + }; + var testSafeAppendToBufferFlag = function (expected) { + if (!usesOplog) + return; + test.equal(o.handle._multiplexer._observeDriver._safeAppendToBuffer, + expected); + }; + + // We'll describe our state as follows. 5:1 means "the document with + // _id=docId1 and bar=5". We list documents as + // [ currently published | in the buffer ] outside the buffer + // If safeToAppendToBuffer is true, we'll say ]! instead. + + // Insert a doc and start observing. + var docId1 = ins({foo: 22, bar: 5}); + waitUntilOplogCaughtUp(); + + // State: [ 5:1 | ]! + var o = observer(); + var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; + // Initial add. + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId1}); + testSafeAppendToBufferFlag(true); + + // Insert another doc (blocking until observes have fired). + // State: [ 5:1 6:2 | ]! + var docId2 = ins({foo: 22, bar: 6}); + // Observed add. + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId2}); + testSafeAppendToBufferFlag(true); + + var docId3 = ins({ foo: 22, bar: 3 }); + // State: [ 3:3 5:1 6:2 | ]! + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId3}); + testSafeAppendToBufferFlag(true); + + // Add a non-matching document + ins({ foo: 13 }); + // It shouldn't be added + test.length(o.output, 0); + + // Add something that matches but is too big to fit in + var docId4 = ins({ foo: 22, bar: 7 }); + // State: [ 3:3 5:1 6:2 | 7:4 ]! + // It shouldn't be added but should end up in the buffer. + test.length(o.output, 0); + testOplogBufferIds([docId4]); + testSafeAppendToBufferFlag(true); + + // Let's add something small enough to fit in + var docId5 = ins({ foo: 22, bar: -1 }); + // State: [ -1:5 3:3 5:1 | 6:2 7:4 ]! + // We should get an added and a removed events + test.length(o.output, 2); + // doc 2 was removed from the published set as it is too big to be in + test.isTrue(setsEqual(o.output, [{added: docId5}, {removed: docId2}])); + clearOutput(o); + testOplogBufferIds([docId2, docId4]); + testSafeAppendToBufferFlag(true); + + // Now remove something and that doc 2 should be right back + rem(docId5); + // State: [ 3:3 5:1 6:2 | 7:4 ]! + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId5}, {added: docId2}])); + clearOutput(o); + testOplogBufferIds([docId4]); + testSafeAppendToBufferFlag(true); + + // Add some negative numbers overflowing the buffer. + // New documents will take the published place, [3 5 6] will take the buffer + // and 7 will be outside of the buffer in MongoDB. + var docId6 = ins({ foo: 22, bar: -1 }); + var docId7 = ins({ foo: 22, bar: -2 }); + var docId8 = ins({ foo: 22, bar: -3 }); + // State: [ -3:8 -2:7 -1:6 | 3:3 5:1 6:2 ] 7:4 + test.length(o.output, 6); + var expected = [{added: docId6}, {removed: docId2}, + {added: docId7}, {removed: docId1}, + {added: docId8}, {removed: docId3}]; + test.isTrue(setsEqual(o.output, expected)); + clearOutput(o); + testOplogBufferIds([docId1, docId2, docId3]); + testSafeAppendToBufferFlag(false); + + // If we update first 3 docs (increment them by 20), it would be + // interesting. + upd({ bar: { $lt: 0 }}, { $inc: { bar: 20 } }, { multi: true }); + // State: [ 3:3 5:1 6:2 | ] 7:4 17:8 18:7 19:6 + // which triggers re-poll leaving us at + // State: [ 3:3 5:1 6:2 | 7:4 17:8 18:7 ] 19:6 + + // The updated documents can't find their place in published and they can't + // be buffered as we are not aware of the situation outside of the buffer. + // But since our buffer becomes empty, it will be refilled partially with + // updated documents. + test.length(o.output, 6); + var expectedRemoves = [{removed: docId6}, + {removed: docId7}, + {removed: docId8}]; + var expectedAdds = [{added: docId3}, + {added: docId1}, + {added: docId2}]; + + test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); + clearOutput(o); + testOplogBufferIds([docId4, docId7, docId8]); + testSafeAppendToBufferFlag(false); + + // Remove first 4 docs (3, 1, 2, 4) forcing buffer to become empty and + // schedule a repoll. + rem({ bar: { $lt: 10 } }); + // State: [ 17:8 18:7 19:6 | ]! + + // XXX the oplog code analyzes the events one by one: one remove after + // another. Poll-n-diff code, on the other side, analyzes the batch action + // of multiple remove. Because of that difference, expected outputs differ. + if (usesOplog) { + expectedRemoves = [{removed: docId3}, {removed: docId1}, + {removed: docId2}, {removed: docId4}]; + expectedAdds = [{added: docId4}, {added: docId8}, + {added: docId7}, {added: docId6}]; + + test.length(o.output, 8); + } else { + expectedRemoves = [{removed: docId3}, {removed: docId1}, + {removed: docId2}]; + expectedAdds = [{added: docId8}, {added: docId7}, {added: docId6}]; + + test.length(o.output, 6); + } + + test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); + clearOutput(o); + testOplogBufferIds([]); + testSafeAppendToBufferFlag(true); + + var docId9 = ins({ foo: 22, bar: 21 }); + var docId10 = ins({ foo: 22, bar: 31 }); + var docId11 = ins({ foo: 22, bar: 41 }); + var docId12 = ins({ foo: 22, bar: 51 }); + // State: [ 17:8 18:7 19:6 | 21:9 31:10 41:11 ] 51:12 + + testOplogBufferIds([docId9, docId10, docId11]); + testSafeAppendToBufferFlag(false); + test.length(o.output, 0); + upd({ bar: { $lt: 20 } }, { $inc: { bar: 5 } }, { multi: true }); + // State: [ 21:9 22:8 23:7 | 24:6 31:10 41:11 ] 51:12 + test.length(o.output, 4); + test.isTrue(setsEqual(o.output, [{removed: docId6}, + {added: docId9}, + {changed: docId7}, + {changed: docId8}])); + clearOutput(o); + testOplogBufferIds([docId6, docId10, docId11]); + testSafeAppendToBufferFlag(false); + + rem(docId9); + // State: [ 22:8 23:7 24:6 | 31:10 41:11 ] 51:12 + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId9}, {added: docId6}])); + clearOutput(o); + testOplogBufferIds([docId10, docId11]); + testSafeAppendToBufferFlag(false); + + upd({ bar: { $gt: 25 } }, { $inc: { bar: -7.5 } }, { multi: true }); + // State: [ 22:8 23:7 23.5:10 | 24:6 ] 33.5:11 43.5:12 + // 33.5 doesn't update in-place in buffer, because it the driver is not sure + // it can do it: because the buffer does not have the safe append flag set, + // for all it knows there is a different doc which is less than 33.5. + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId6}, {added: docId10}])); + clearOutput(o); + testOplogBufferIds([docId6]); + testSafeAppendToBufferFlag(false); + + // Force buffer objects to be moved into published set so we can check them + rem(docId7); + rem(docId8); + rem(docId10); + // State: [ 24:6 | ] 33.5:11 43.5:12 + // triggers repoll + // State: [ 24:6 33.5:11 43.5:12 | ]! + test.length(o.output, 6); + test.isTrue(setsEqual(o.output, [{removed: docId7}, {removed: docId8}, + {removed: docId10}, {added: docId6}, + {added: docId11}, {added: docId12}])); + + test.length(_.keys(o.state), 3); + test.equal(o.state[docId6], { _id: docId6, foo: 22, bar: 24 }); + test.equal(o.state[docId11], { _id: docId11, foo: 22, bar: 33.5 }); + test.equal(o.state[docId12], { _id: docId12, foo: 22, bar: 43.5 }); + clearOutput(o); + testOplogBufferIds([]); + testSafeAppendToBufferFlag(true); + + var docId13 = ins({ foo: 22, bar: 50 }); + var docId14 = ins({ foo: 22, bar: 51 }); + var docId15 = ins({ foo: 22, bar: 52 }); + var docId16 = ins({ foo: 22, bar: 53 }); + // State: [ 24:6 33.5:11 43.5:12 | 50:13 51:14 52:15 ] 53:16 + test.length(o.output, 0); + testOplogBufferIds([docId13, docId14, docId15]); + testSafeAppendToBufferFlag(false); + + // Update something that's outside the buffer to be in the buffer, writing + // only to the sort key. + upd(docId16, {$set: {bar: 10}}); + // State: [ 10:16 24:6 33.5:11 | 43.5:12 50:13 51:14 ] 52:15 + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId12}, {added: docId16}])); + clearOutput(o); + testOplogBufferIds([docId12, docId13, docId14]); + testSafeAppendToBufferFlag(false); + + o.handle.stop(); + }); + + Tinytest.addAsync("mongo-livedata - observe sorted, limited, sort fields " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = coll.find({}, {sort: {x: 1}, + limit: 2, + fields: {y: 1}}).observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + var ins = function (doc) { + var id; runInFence(function () { id = coll.insert(doc); }); + return id; + }; + var rem = function (id) { + runInFence(function () { coll.remove(id); }); + }; + + var o = observer(); + + var docId1 = ins({ x: 1, y: 1222 }); + var docId2 = ins({ x: 5, y: 5222 }); + + test.length(o.output, 2); + test.equal(o.output, [{added: docId1}, {added: docId2}]); + clearOutput(o); + + var docId3 = ins({ x: 7, y: 7222 }); + test.length(o.output, 0); + + var docId4 = ins({ x: -1, y: -1222 }); + + // Becomes [docId4 docId1 | docId2 docId3] + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{added: docId4}, {removed: docId2}])); + + test.equal(_.size(o.state), 2); + test.equal(o.state[docId4], {_id: docId4, y: -1222}); + test.equal(o.state[docId1], {_id: docId1, y: 1222}); + clearOutput(o); + + rem(docId2); + // Becomes [docId4 docId1 | docId3] + test.length(o.output, 0); + + rem(docId4); + // Becomes [docId1 docId3] + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{added: docId3}, {removed: docId4}])); + + test.equal(_.size(o.state), 2); + test.equal(o.state[docId3], {_id: docId3, y: 7222}); + test.equal(o.state[docId1], {_id: docId1, y: 1222}); + clearOutput(o); + + onComplete(); + }); + + Tinytest.add("mongo-livedata - observe sorted, limited, big initial set" + idGeneration, function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = coll.find({}, {sort: {x: 1, y: 1}, limit: 3}) + .observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + var ins = function (doc) { + var id; runInFence(function () { id = coll.insert(doc); }); + return id; + }; + var rem = function (id) { + runInFence(function () { coll.remove(id); }); + }; + // tests '_id' subfields for all documents in oplog buffer + var testOplogBufferIds = function (ids) { + var bufferIds = []; + o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { + bufferIds.push(id); + }); + + test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); + }; + var testSafeAppendToBufferFlag = function (expected) { + if (expected) { + test.isTrue(o.handle._multiplexer._observeDriver._safeAppendToBuffer); + } else { + test.isFalse(o.handle._multiplexer._observeDriver._safeAppendToBuffer); + } + }; + + var ids = {}; + _.each([2, 4, 1, 3, 5, 5, 9, 1, 3, 2, 5], function (x, i) { + ids[i] = ins({ x: x, y: i }); + }); + + // Ensure that we are past all the 'i' entries before we run the query, so + // that we get the expected phase transitions. + waitUntilOplogCaughtUp(); + + var o = observer(); + var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; + // x: [1 1 2 | 2 3 3] 4 5 5 5 9 + // id: [2 7 0 | 9 3 8] 1 4 5 10 6 + + test.length(o.output, 3); + test.isTrue(setsEqual([{added: ids[2]}, {added: ids[7]}, {added: ids[0]}], o.output)); + usesOplog && testOplogBufferIds([ids[9], ids[3], ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + rem(ids[0]); + // x: [1 1 2 | 3 3] 4 5 5 5 9 + // id: [2 7 9 | 3 8] 1 4 5 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[0]}, {added: ids[9]}], o.output)); + usesOplog && testOplogBufferIds([ids[3], ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + rem(ids[7]); + // x: [1 2 3 | 3] 4 5 5 5 9 + // id: [2 9 3 | 8] 1 4 5 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[7]}, {added: ids[3]}], o.output)); + usesOplog && testOplogBufferIds([ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + rem(ids[3]); + // x: [1 2 3 | 4 5 5] 5 9 + // id: [2 9 8 | 1 4 5] 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[3]}, {added: ids[8]}], o.output)); + usesOplog && testOplogBufferIds([ids[1], ids[4], ids[5]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + rem({ x: {$lt: 4} }); + // x: [4 5 5 | 5 9] + // id: [1 4 5 | 10 6] + test.length(o.output, 6); + test.isTrue(setsEqual([{removed: ids[2]}, {removed: ids[9]}, {removed: ids[8]}, + {added: ids[5]}, {added: ids[4]}, {added: ids[1]}], o.output)); + usesOplog && testOplogBufferIds([ids[10], ids[6]]); + usesOplog && testSafeAppendToBufferFlag(true); + clearOutput(o); + }); +} + + +testAsyncMulti('mongo-livedata - empty documents, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var coll = new Mongo.Collection(this.collectionName, collectionOptions); + + coll.insert({}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + var cursor = coll.find(); + test.equal(cursor.count(), 1); + })); + } +]); + +// Regression test for #2413. +testAsyncMulti('mongo-livedata - upsert without callback, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var coll = new Mongo.Collection(this.collectionName, collectionOptions); + + // No callback! Before fixing #2413, this method never returned and + // so no future DDP methods worked either. + coll.upsert('foo', {bar: 1}); + // Do something else on the same method and expect it to actually work. + // (If the bug comes back, this will 'async batch timeout'.) + coll.insert({}, expect(function(){})); + } +]); + +// Regression test for https://github.com/meteor/meteor/issues/8666. +testAsyncMulti('mongo-livedata - upsert with an undefined selector, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var coll = new Mongo.Collection(this.collectionName, collectionOptions); + var testWidget = { + name: 'Widget name' + }; + coll.upsert(testWidget._id, testWidget, expect(function (error, insertDetails) { + test.isFalse(error); + test.equal( + coll.findOne(insertDetails.insertedId), + Object.assign({ _id: insertDetails.insertedId }, testWidget) + ); + })); + } +]); + +// See https://github.com/meteor/meteor/issues/594. +testAsyncMulti('mongo-livedata - document with length, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var self = this; + var coll = self.coll = new Mongo.Collection(self.collectionName, collectionOptions); + + coll.insert({foo: 'x', length: 0}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + self.docId = id; + test.equal(coll.findOne(self.docId), + {_id: self.docId, foo: 'x', length: 0}); + })); + }, + function (test, expect) { + var self = this; + var coll = self.coll; + coll.update(self.docId, {$set: {length: 5}}, expect(function (err) { + test.isFalse(err); + test.equal(coll.findOne(self.docId), + {_id: self.docId, foo: 'x', length: 5}); + })); + } +]); + +testAsyncMulti('mongo-livedata - document with a date, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + + var coll = new Mongo.Collection(this.collectionName, collectionOptions); + var docId; + coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + docId = id; + var cursor = coll.find(); + test.equal(cursor.count(), 1); + test.equal(coll.findOne().d.getFullYear(), 2012); + })); + } +]); + +testAsyncMulti('mongo-livedata - document goes through a transform, ' + idGeneration, [ + function (test, expect) { + var self = this; + var seconds = function (doc) { + doc.seconds = function () {return doc.d.getSeconds();}; + return doc; + }; + TRANSFORMS["seconds"] = seconds; + self.collectionOptions = { + idGeneration: idGeneration, + transform: seconds, + transformName: "seconds" + }; + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(self.collectionName, self.collectionOptions); + var obs; + var expectAdd = expect(function (doc) { + test.equal(doc.seconds(), 50); + }); + var expectRemove = expect(function (doc) { + test.equal(doc.seconds(), 50); + obs.stop(); + }); + self.coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + var cursor = self.coll.find(); + obs = cursor.observe({ + added: expectAdd, + removed: expectRemove + }); + test.equal(cursor.count(), 1); + test.equal(cursor.fetch()[0].seconds(), 50); + test.equal(self.coll.findOne().seconds(), 50); + test.equal(self.coll.findOne({}, {transform: null}).seconds, undefined); + test.equal(self.coll.findOne({}, { + transform: function (doc) {return {seconds: doc.d.getSeconds()};} + }).seconds, 50); + self.coll.remove(id); + })); + }, + function (test, expect) { + var self = this; + self.coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + self.id1 = id; + })); + self.coll.insert({d: new Date(1356152391004)}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + self.id2 = id; + })); + } +]); + +testAsyncMulti('mongo-livedata - transform sets _id if not present, ' + idGeneration, [ + function (test, expect) { + var self = this; + var justId = function (doc) { + return _.omit(doc, '_id'); + }; + TRANSFORMS["justId"] = justId; + var collectionOptions = { + idGeneration: idGeneration, + transform: justId, + transformName: "justId" + }; + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(this.collectionName, collectionOptions); + self.coll.insert({}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + test.equal(self.coll.findOne()._id, id); + })); + } +]); + +var bin = Base64.decode( + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyBy" + + "ZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJv" + + "bSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhl" + + "IG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdo" + + "dCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdl" + + "bmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9y" + + "dCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); + +testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ + function (test, expect) { + // XXX probably shouldn't use EJSON's private test symbols + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var coll = new Mongo.Collection(this.collectionName, collectionOptions); + var docId; + coll.insert({b: bin}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + docId = id; + var cursor = coll.find(); + test.equal(cursor.count(), 1); + var inColl = coll.findOne(); + test.isTrue(EJSON.isBinary(inColl.b)); + test.equal(inColl.b, bin); + })); + } +]); + +testAsyncMulti('mongo-livedata - document with a custom type, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, + + function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(this.collectionName, collectionOptions); + var docId; + // Dog is implemented at the top of the file, outside of the idGeneration + // loop (so that we only call EJSON.addType once). + var d = new Dog("reginald", null); + self.coll.insert({d: d}, expect(function (err, id) { + test.isFalse(err); + test.isTrue(id); + docId = id; + self.docId = docId; + var cursor = self.coll.find(); + test.equal(cursor.count(), 1); + var inColl = self.coll.findOne(); + test.isTrue(inColl); + inColl && test.equal(inColl.d.speak(), "woof"); + inColl && test.isNull(inColl.d.color); + })); + }, + + function (test, expect) { + var self = this; + self.coll.insert(new Dog("rover", "orange"), expect(function (err, id) { + test.isTrue(err); + test.isFalse(id); + })); + }, + + function (test, expect) { + var self = this; + self.coll.update( + self.docId, new Dog("rover", "orange"), expect(function (err) { + test.isTrue(err); + })); + } +]); + +if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - update return values, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); + + coll.insert({ foo: "bar" }); + coll.insert({ foo: "baz" }); + test.equal(coll.update({}, { $set: { foo: "qux" } }, { multi: true }), + 2); + coll.update({}, { $set: { foo: "quux" } }, { multi: true }, function (err, result) { + test.isFalse(err); + test.equal(result, 2); + onComplete(); + }); + }); + + Tinytest.addAsync("mongo-livedata - remove return values, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); + + coll.insert({ foo: "bar" }); + coll.insert({ foo: "baz" }); + test.equal(coll.remove({}), 2); + coll.insert({ foo: "bar" }); + coll.insert({ foo: "baz" }); + coll.remove({}, function (err, result) { + test.isFalse(err); + test.equal(result, 2); + onComplete(); + }); + }); + + + Tinytest.addAsync("mongo-livedata - id-based invalidation, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_invalidation_collection_"+run, collectionOptions); + + coll.allow({ + update: function () {return true;}, + remove: function () {return true;} + }); + + var id1 = coll.insert({x: 42, is1: true}); + var id2 = coll.insert({x: 50, is2: true}); + + var polls = {}; + var handlesToStop = []; + var observe = function (name, query) { + var handle = coll.find(query).observeChanges({ + // Make sure that we only poll on invalidation, not due to time, and + // keep track of when we do. Note: this option disables the use of + // oplogs (which admittedly is somewhat irrelevant to this feature). + _testOnlyPollCallback: function () { + polls[name] = (name in polls ? polls[name] + 1 : 1); + } + }); + handlesToStop.push(handle); + }; + + observe("all", {}); + observe("id1Direct", id1); + observe("id1InQuery", {_id: id1, z: null}); + observe("id2Direct", id2); + observe("id2InQuery", {_id: id2, z: null}); + observe("bothIds", {_id: {$in: [id1, id2]}}); + + var resetPollsAndRunInFence = function (f) { + polls = {}; + runInFence(f); + }; + + // Update id1 directly. This should poll all but the "id2" queries. "all" + // and "bothIds" increment by 2 because they are looking at both. + resetPollsAndRunInFence(function () { + coll.update(id1, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id1Direct: 1, id1InQuery: 1, bothIds: 1}); + + // Update id2 using a funny query. This should poll all but the "id1" + // queries. + resetPollsAndRunInFence(function () { + coll.update({_id: id2, q: null}, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id2Direct: 1, id2InQuery: 1, bothIds: 1}); + + // Update both using a $in query. Should poll each of them exactly once. + resetPollsAndRunInFence(function () { + coll.update({_id: {$in: [id1, id2]}, q: null}, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id1Direct: 1, id1InQuery: 1, id2Direct: 1, id2InQuery: 1, + bothIds: 1}); + + _.each(handlesToStop, function (h) {h.stop();}); + onComplete(); + }); + + Tinytest.add("mongo-livedata - upsert error parse, " + idGeneration, function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_upsert_errorparse_collection_"+run, collectionOptions); + + coll.insert({_id:'foobar', foo: 'bar'}); + var err; + try { + coll.update({foo: 'bar'}, {_id: 'cowbar'}); + } catch (e) { + err = e; + } + test.isTrue(err); + test.isTrue(MongoInternals.Connection._isCannotChangeIdError(err)); + + try { + coll.insert({_id: 'foobar'}); + } catch (e) { + err = e; + } + test.isTrue(err); + // duplicate id error is not same as change id error + test.isFalse(MongoInternals.Connection._isCannotChangeIdError(err)); + }); + +} // end Meteor.isServer + +// This test is duplicated below (with some changes) for async upserts that go +// over the network. +_.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { + _.each([true, false], function (useUpdate) { + _.each([true, false], function (useDirectCollection) { + Tinytest.add("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert" + (minimongo ? " minimongo" : "") + (useDirectCollection ? " direct collection " : "") + ", " + idGeneration, function (test) { + var run = test.runId(); + var options = collectionOptions; + // We don't get ids back when we use update() to upsert, or when we are + // directly calling MongoConnection.upsert(). + var skipIds = useUpdate || (! minimongo && useDirectCollection); + if (minimongo) + options = _.extend({}, collectionOptions, { connection: null }); + var coll = new Mongo.Collection( + "livedata_upsert_collection_"+run+ + (useUpdate ? "_update_" : "") + + (minimongo ? "_minimongo_" : "") + + (useDirectCollection ? "_direct_" : "") + "", + options + ); + if (useDirectCollection) + coll = coll._collection; + + var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); + test.equal(result1.numberAffected, 1); + if (! skipIds) + test.isTrue(result1.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); + + var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); + test.equal(result2.numberAffected, 1); + if (! skipIds) + test.isFalse(result2.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); + + coll.remove({}); + + // Test values that require transformation to go into Mongo: + + var t1 = new Mongo.ObjectID(); + var t2 = new Mongo.ObjectID(); + var result3 = upsert(coll, useUpdate, {foo: t1}, {foo: t1}); + test.equal(result3.numberAffected, 1); + if (! skipIds) + test.isTrue(result3.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{foo: t1, _id: result3.insertedId}]); + + var result4 = upsert(coll, useUpdate, {foo: t1}, {foo: t2}); + test.equal(result2.numberAffected, 1); + if (! skipIds) + test.isFalse(result2.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); + + coll.remove({}); + + // Test modification by upsert + + var result5 = upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 1}}); + test.equal(result5.numberAffected, 1); + if (! skipIds) + test.isTrue(result5.insertedId); + var davidId = result5.insertedId; + compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 1, _id: davidId}]); + + test.throws(function () { + // test that bad modifier fails fast + upsert(coll, useUpdate, {name: 'David'}, {$blah: {foo: 2}}); + }); + + + var result6 = upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 2}}); + test.equal(result6.numberAffected, 1); + if (! skipIds) + test.isFalse(result6.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, + _id: result5.insertedId}]); + + var emilyId = coll.insert({name: 'Emily', foo: 2}); + compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, _id: davidId}, + {name: 'Emily', foo: 2, _id: emilyId}]); + + // multi update by upsert + var result7 = upsert(coll, useUpdate, {foo: 2}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}); + test.equal(result7.numberAffected, 2); + if (! skipIds) + test.isFalse(result7.insertedId); + compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}]); + + // insert by multi upsert + var result8 = upsert(coll, useUpdate, {foo: 3}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}); + test.equal(result8.numberAffected, 1); + if (! skipIds) + test.isTrue(result8.insertedId); + var fredId = result8.insertedId; + compareResults(test, skipIds, coll.find().fetch(), + [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); + + // test `insertedId` option + var result9 = upsert(coll, useUpdate, {name: 'Steve'}, + {name: 'Steve'}, + {insertedId: 'steve'}); + test.equal(result9.numberAffected, 1); + if (! skipIds) + test.equal(result9.insertedId, 'steve'); + compareResults(test, skipIds, coll.find().fetch(), + [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}, + {name: 'Steve', _id: 'steve'}]); + test.isTrue(coll.findOne('steve')); + test.isFalse(coll.findOne('fred')); + + // Test $ operator in selectors. + + var result10 = upsert(coll, useUpdate, + {$or: [{name: 'David'}, {name: 'Emily'}]}, + {$set: {foo: 3}}, {multi: true}); + test.equal(result10.numberAffected, 2); + if (! skipIds) + test.isFalse(result10.insertedId); + compareResults(test, skipIds, + [coll.findOne({name: 'David'}), coll.findOne({name: 'Emily'})], + [{name: 'David', foo: 3, bar: 7, _id: davidId}, + {name: 'Emily', foo: 3, bar: 7, _id: emilyId}] + ); + + var result11 = upsert( + coll, useUpdate, + { + name: 'Charlie', + $or: [{ foo: 2}, { bar: 7 }] + }, + { $set: { foo: 3 } } + ); + test.equal(result11.numberAffected, 1); + if (! skipIds) + test.isTrue(result11.insertedId); + var charlieId = result11.insertedId; + compareResults(test, skipIds, + coll.find({ name: 'Charlie' }).fetch(), + [{name: 'Charlie', foo: 3, _id: charlieId}]); + }); + }); + }); +}); + +var asyncUpsertTestName = function (useNetwork, useDirectCollection, + useUpdate, idGeneration) { + return "mongo-livedata - async " + + (useUpdate ? "update " : "") + + "upsert " + + (useNetwork ? "over network " : "") + + (useDirectCollection ? ", direct collection " : "") + + idGeneration; +}; + +// This is a duplicate of the test above, with some changes to make it work for +// callback style. On the client, we test server-backed and in-memory +// collections, and run the tests for both the Mongo.Collection and the +// LocalCollection. On the server, we test mongo-backed collections, for both +// the Mongo.Collection and the MongoConnection. +// +// XXX Rewrite with testAsyncMulti, that would simplify things a lot! +_.each(Meteor.isServer ? [false] : [true, false], function (useNetwork) { + _.each(useNetwork ? [false] : [true, false], function (useDirectCollection) { + _.each([true, false], function (useUpdate) { + Tinytest.addAsync(asyncUpsertTestName(useNetwork, useDirectCollection, useUpdate, idGeneration), function (test, onComplete) { + var coll; + var run = test.runId(); + var collName = "livedata_upsert_collection_"+run+ + (useUpdate ? "_update_" : "") + + (useNetwork ? "_network_" : "") + + (useDirectCollection ? "_direct_" : ""); + + var next0 = function () { + // Test starts here. + upsert(coll, useUpdate, {_id: 'foo'}, {_id: 'foo', foo: 'bar'}, next1); + }; + + if (useNetwork) { + Meteor.call("createInsecureCollection", collName, collectionOptions); + coll = new Mongo.Collection(collName, collectionOptions); + Meteor.subscribe("c-" + collName, next0); + } else { + var opts = _.clone(collectionOptions); + if (Meteor.isClient) + opts.connection = null; + coll = new Mongo.Collection(collName, opts); + if (useDirectCollection) + coll = coll._collection; + } + + var result1; + var next1 = function (err, result) { + result1 = result; + test.equal(result1.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result1.insertedId); + test.equal(result1.insertedId, 'foo'); + } + compareResults(test, useUpdate, coll.find().fetch(), [{foo: 'bar', _id: 'foo'}]); + upsert(coll, useUpdate, {_id: 'foo'}, {foo: 'baz'}, next2); + }; + + if (! useNetwork) { + next0(); + } + + var t1, t2, result2; + var next2 = function (err, result) { + result2 = result; + test.equal(result2.numberAffected, 1); + if (! useUpdate) + test.isFalse(result2.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); + coll.remove({_id: 'foo'}); + compareResults(test, useUpdate, coll.find().fetch(), []); + + // Test values that require transformation to go into Mongo: + + t1 = new Mongo.ObjectID(); + t2 = new Mongo.ObjectID(); + upsert(coll, useUpdate, {_id: t1}, {_id: t1, foo: 'bar'}, next3); + }; + + var result3; + var next3 = function (err, result) { + result3 = result; + test.equal(result3.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result3.insertedId); + test.equal(t1, result3.insertedId); + } + compareResults(test, useUpdate, coll.find().fetch(), [{_id: t1, foo: 'bar'}]); + + upsert(coll, useUpdate, {_id: t1}, {foo: t2}, next4); + }; + + var next4 = function (err, result4) { + test.equal(result2.numberAffected, 1); + if (! useUpdate) + test.isFalse(result2.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); + + coll.remove({_id: t1}); + + // Test modification by upsert + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 1}}, next5); + }; + + var result5; + var next5 = function (err, result) { + result5 = result; + test.equal(result5.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result5.insertedId); + test.equal(result5.insertedId, 'David'); + } + var davidId = result5.insertedId; + compareResults(test, useUpdate, coll.find().fetch(), [{foo: 1, _id: davidId}]); + + if (! Meteor.isClient && useDirectCollection) { + // test that bad modifier fails + // The stub throws an exception about the invalid modifier, which + // livedata logs (so we suppress it). + Meteor._suppress_log(1); + upsert(coll, useUpdate, {_id: 'David'}, {$blah: {foo: 2}}, function (err) { + if (! (Meteor.isClient && useDirectCollection)) + test.isTrue(err); + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); + }); + } else { + // XXX skip this test for now for LocalCollection; the fact that + // we're in a nested sequence of callbacks means we're inside a + // Meteor.defer, which means the exception just gets + // logged. Something should be done about this at some point? Maybe + // LocalCollection callbacks don't really have to be deferred. + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); + } + }; + + var result6; + var next6 = function (err, result) { + result6 = result; + test.equal(result6.numberAffected, 1); + if (! useUpdate) + test.isFalse(result6.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}]); + + var emilyId = coll.insert({_id: 'Emily', foo: 2}); + compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2}]); + + // multi update by upsert. + // We can't actually update multiple documents since we have to do it by + // id, but at least make sure the multi flag doesn't mess anything up. + upsert(coll, useUpdate, {_id: 'Emily'}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}, next7); + }; + + var result7; + var next7 = function (err, result) { + result7 = result; + test.equal(result7.numberAffected, 1); + if (! useUpdate) + test.isFalse(result7.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2, bar: 7}]); + + // insert by multi upsert + upsert(coll, useUpdate, {_id: 'Fred'}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}, next8); + + }; + + var result8; + var next8 = function (err, result) { + result8 = result; + + test.equal(result8.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result8.insertedId); + test.equal(result8.insertedId, 'Fred'); + } + var fredId = result8.insertedId; + compareResults(test, useUpdate, coll.find().fetch(), + [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2, bar: 7}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); + onComplete(); + }; + }); + }); + }); +}); + +if (Meteor.isClient) { + Tinytest.addAsync("mongo-livedata - async update/remove return values over network " + idGeneration, function (test, onComplete) { + var coll; + var run = test.runId(); + var collName = "livedata_upsert_collection_"+run; + Meteor.call("createInsecureCollection", collName, collectionOptions); + coll = new Mongo.Collection(collName, collectionOptions); + Meteor.subscribe("c-" + collName, function () { + coll.insert({ _id: "foo" }); + coll.insert({ _id: "bar" }); + coll.update({ _id: "foo" }, { $set: { foo: 1 } }, { multi: true }, function (err, result) { + test.isFalse(err); + test.equal(result, 1); + coll.update({ _id: "foo" }, { _id: "foo", foo: 2 }, function (err, result) { + test.isFalse(err); + test.equal(result, 1); + coll.update({ _id: "baz" }, { $set: { foo: 1 } }, function (err, result) { + test.isFalse(err); + test.equal(result, 0); + coll.remove({ _id: "foo" }, function (err, result) { + test.equal(result, 1); + coll.remove({ _id: "baz" }, function (err, result) { + test.equal(result, 0); + onComplete(); + }); + }); + }); + }); + }); + }); + }); +} + +// Runs a method and its stub which do some upserts. The method throws an error +// if we don't get the right return values. +if (Meteor.isClient) { + _.each([true, false], function (useUpdate) { + Tinytest.addAsync("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert in method, " + idGeneration, function (test, onComplete) { + var run = test.runId(); + upsertTestMethodColl = new Mongo.Collection(upsertTestMethod + "_collection_" + run, collectionOptions); + var m = {}; + delete Meteor.connection._methodHandlers[upsertTestMethod]; + m[upsertTestMethod] = function (run, useUpdate, options) { + upsertTestMethodImpl(upsertTestMethodColl, useUpdate, test); + }; + Meteor.methods(m); + Meteor.call(upsertTestMethod, run, useUpdate, collectionOptions, function (err, result) { + test.isFalse(err); + onComplete(); + }); + }); + }); +} + +_.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { + _.each([true, false], function (useUpdate) { + Tinytest.add("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert by id" + (minimongo ? " minimongo" : "") + ", " + idGeneration, function (test) { + var run = test.runId(); + var options = collectionOptions; + if (minimongo) + options = _.extend({}, collectionOptions, { connection: null }); + var coll = new Mongo.Collection("livedata_upsert_by_id_collection_"+run, options); + + var ret; + ret = upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 1}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'foo'); + compareResults(test, useUpdate, coll.find().fetch(), + [{_id: 'foo', x: 1}]); + + ret = upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 2}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), + [{_id: 'foo', x: 2}]); + + ret = upsert(coll, useUpdate, {_id: 'bar'}, {$set: {x: 1}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'bar'); + compareResults(test, useUpdate, coll.find().fetch(), + [{_id: 'foo', x: 2}, + {_id: 'bar', x: 1}]); + + coll.remove({}); + ret = upsert(coll, useUpdate, {_id: 'traq'}, {x: 1}); + + test.equal(ret.numberAffected, 1); + var myId = ret.insertedId; + if (useUpdate) { + myId = coll.findOne()._id; + } + // Starting with Mongo 2.6, upsert with entire document takes _id from the + // query, so the above upsert actually does an insert with _id traq + // instead of a random _id. Whenever we are using our simulated upsert, + // we have this behavior (whether running against Mongo 2.4 or 2.6). + // https://jira.mongodb.org/browse/SERVER-5289 + test.equal(myId, 'traq'); + compareResults(test, useUpdate, coll.find().fetch(), + [{x: 1, _id: 'traq'}]); + + // this time, insert as _id 'traz' + ret = upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 2}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'traz'); + compareResults(test, useUpdate, coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 2, _id: 'traz'}]); + + // now update _id 'traz' + ret = upsert(coll, useUpdate, {_id: 'traz'}, {x: 3}); + test.equal(ret.numberAffected, 1); + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 3, _id: 'traz'}]); + + // now update, passing _id (which is ok as long as it's the same) + ret = upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 4}); + test.equal(ret.numberAffected, 1); + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 4, _id: 'traz'}]); + + }); + }); +}); + +}); // end idGeneration parametrization + +Tinytest.add('mongo-livedata - rewrite selector', function (test) { + + test.equal(Mongo.Collection._rewriteSelector('foo'), + {_id: 'foo'}); + + + var oid = new Mongo.ObjectID(); + test.equal(Mongo.Collection._rewriteSelector(oid), + {_id: oid}); + + test.matches( + Mongo.Collection._rewriteSelector({ _id: null })._id, + /^\S+$/, + 'Passing in a falsey selector _id should return a selector with a new ' + + 'auto-generated _id string' + ); + test.equal( + Mongo.Collection._rewriteSelector({ _id: null }, { fallbackId: oid }), + { _id: oid }, + 'Passing in a falsey selector _id and a fallback ID should return a ' + + 'selector with an _id using the fallback ID' + ); +}); + +testAsyncMulti('mongo-livedata - specified _id', [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, function (test, expect) { + var expectError = expect(function (err, result) { + test.isTrue(err); + var doc = coll.findOne(); + test.equal(doc.name, "foo"); + }); + var coll = new Mongo.Collection(this.collectionName); + coll.insert({_id: "foo", name: "foo"}, expect(function (err1, id) { + test.equal(id, "foo"); + var doc = coll.findOne(); + test.equal(doc._id, "foo"); + Meteor._suppress_log(1); + coll.insert({_id: "foo", name: "bar"}, expectError); + })); + } +]); + + +// Consistent id generation tests +function collectionInsert (test, expect, coll, index) { + var clientSideId = coll.insert({name: "foo"}, expect(function (err1, id) { + test.equal(id, clientSideId); + var o = coll.findOne(id); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function collectionUpsert (test, expect, coll, index) { + var upsertId = '123456' + index; + + coll.upsert(upsertId, {$set: {name: "foo"}}, expect(function (err1, result) { + test.equal(result.insertedId, upsertId); + test.equal(result.numberAffected, 1); + + var o = coll.findOne(upsertId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function collectionUpsertExisting (test, expect, coll, index) { + var clientSideId = coll.insert({name: "foo"}, expect(function (err1, id) { + test.equal(id, clientSideId); + + var o = coll.findOne(id); + test.isTrue(_.isObject(o)); + // We're not testing sequencing/visibility rules here, so skip this check + // test.equal(o.name, 'foo'); + })); + + coll.upsert(clientSideId, {$set: {name: "bar"}}, expect(function (err1, result) { + test.equal(result.insertedId, clientSideId); + test.equal(result.numberAffected, 1); + + var o = coll.findOne(clientSideId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'bar'); + })); +} + +function functionCallsInsert (test, expect, coll, index) { + Meteor.call("insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); + var stubId = INSERTED_IDS[coll._name][index]; + + test.equal(ids.length, 1); + test.equal(ids[0], stubId); + + var o = coll.findOne(stubId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function functionCallsUpsert (test, expect, coll, index) { + var upsertId = '123456' + index; + Meteor.call("upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(function (err1, result) { + test.equal(result.insertedId, upsertId); + test.equal(result.numberAffected, 1); + + var o = coll.findOne(upsertId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function functionCallsUpsertExisting (test, expect, coll, index) { + var id = coll.insert({name: "foo"}); + + var o = coll.findOne(id); + test.notEqual(null, o); + test.equal(o.name, 'foo'); + + Meteor.call("upsertObject", coll._name, id, {$set:{name: "bar"}}, expect(function (err1, result) { + test.equal(result.numberAffected, 1); + test.equal(result.insertedId, undefined); + + var o = coll.findOne(id); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'bar'); + })); +} + +function functionCalls3Inserts (test, expect, coll, index) { + Meteor.call("insertObjects", coll._name, {name: "foo"}, 3, expect(function (err1, ids) { + test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); + test.equal(ids.length, 3); + + for (var i = 0; i < 3; i++) { + var stubId = INSERTED_IDS[coll._name][(3 * index) + i]; + test.equal(ids[i], stubId); + + var o = coll.findOne(stubId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + } + })); +} + +function functionChainInsert (test, expect, coll, index) { + Meteor.call("doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); + var stubId = INSERTED_IDS[coll._name][index]; + + test.equal(ids.length, 1); + test.equal(ids[0], stubId); + + var o = coll.findOne(stubId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function functionChain2Insert (test, expect, coll, index) { + Meteor.call("doMeteorCall", "doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); + var stubId = INSERTED_IDS[coll._name][index]; + + test.equal(ids.length, 1); + test.equal(ids[0], stubId); + + var o = coll.findOne(stubId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +function functionChain2Upsert (test, expect, coll, index) { + var upsertId = '123456' + index; + Meteor.call("doMeteorCall", "doMeteorCall", "upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(function (err1, result) { + test.equal(result.insertedId, upsertId); + test.equal(result.numberAffected, 1); + + var o = coll.findOne(upsertId); + test.isTrue(_.isObject(o)); + test.equal(o.name, 'foo'); + })); +} + +_.each( {collectionInsert: collectionInsert, + collectionUpsert: collectionUpsert, + functionCallsInsert: functionCallsInsert, + functionCallsUpsert: functionCallsUpsert, + functionCallsUpsertExisting: functionCallsUpsertExisting, + functionCalls3Insert: functionCalls3Inserts, + functionChainInsert: functionChainInsert, + functionChain2Insert: functionChain2Insert, + functionChain2Upsert: functionChain2Upsert}, function (fn, name) { +_.each( [1, 3], function (repetitions) { +_.each( [1, 3], function (collectionCount) { +_.each( ['STRING', 'MONGO'], function (idGeneration) { + + testAsyncMulti('mongo-livedata - consistent _id generation ' + name + ', ' + repetitions + ' repetitions on ' + collectionCount + ' collections, idGeneration=' + idGeneration, [ function (test, expect) { + var collectionOptions = { idGeneration: idGeneration }; + + var cleanups = this.cleanups = []; + this.collections = _.times(collectionCount, function () { + var collectionName = "consistentid_" + Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', collectionName, collectionOptions); + Meteor.subscribe('c-' + collectionName, expect()); + cleanups.push(function (expect) { Meteor.call('dropInsecureCollection', collectionName, expect(function () {})); }); + } + + var collection = new Mongo.Collection(collectionName, collectionOptions); + if (Meteor.isServer) { + cleanups.push(function () { collection._dropCollection(); }); + } + COLLECTIONS[collectionName] = collection; + return collection; + }); + }, function (test, expect) { + // now run the actual test + for (var i = 0; i < repetitions; i++) { + for (var j = 0; j < collectionCount; j++) { + fn(test, expect, this.collections[j], i); + } + } + }, function (test, expect) { + // Run any registered cleanup functions (e.g. to drop collections) + _.each(this.cleanups, function(cleanup) { + cleanup(expect); + }); + }]); + +}); +}); +}); +}); + + + +testAsyncMulti('mongo-livedata - empty string _id', [ + function (test, expect) { + var self = this; + self.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', self.collectionName); + Meteor.subscribe('c-' + self.collectionName, expect()); + } + self.coll = new Mongo.Collection(self.collectionName); + try { + self.coll.insert({_id: "", f: "foo"}); + test.fail("Insert with an empty _id should fail"); + } catch (e) { + // ok + } + self.coll.insert({_id: "realid", f: "bar"}, expect(function (err, res) { + test.equal(res, "realid"); + })); + }, + function (test, expect) { + var self = this; + var docs = self.coll.find().fetch(); + test.equal(docs, [{_id: "realid", f: "bar"}]); + }, + function (test, expect) { + var self = this; + if (Meteor.isServer) { + self.coll._collection.insert({_id: "", f: "baz"}); + test.equal(self.coll.find().fetch().length, 2); + } + } +]); + + +if (Meteor.isServer) { + testAsyncMulti("mongo-livedata - minimongo observe on server", [ + function (test, expect) { + var self = this; + self.id = Random.id(); + self.C = new Mongo.Collection("ServerMinimongoObserve_" + self.id); + self.events = []; + + Meteor.publish(self.id, function () { + return self.C.find(); + }); + + self.conn = DDP.connect(Meteor.absoluteUrl()); + pollUntil(expect, function () { + return self.conn.status().connected; + }, 10000); + }, + + function (test, expect) { + var self = this; + if (self.conn.status().connected) { + self.miniC = new Mongo.Collection("ServerMinimongoObserve_" + self.id, { + connection: self.conn + }); + var exp = expect(function (err) { + test.isFalse(err); + }); + self.conn.subscribe(self.id, { + onError: exp, + onReady: exp + }); + } + }, + + function (test, expect) { + var self = this; + if (self.miniC) { + self.obs = self.miniC.find().observeChanges({ + added: function (id, fields) { + self.events.push({evt: "a", id: id}); + Meteor._sleepForMs(200); + self.events.push({evt: "b", id: id}); + if (! self.two) { + self.two = self.C.insert({}); + } + } + }); + self.one = self.C.insert({}); + pollUntil(expect, function () { + return self.events.length === 4; + }, 10000); + } + }, + + function (test, expect) { + var self = this; + if (self.miniC) { + test.equal(self.events, [ + {evt: "a", id: self.one}, + {evt: "b", id: self.one}, + {evt: "a", id: self.two}, + {evt: "b", id: self.two} + ]); + } + self.obs && self.obs.stop(); + } + ]); +} + +Tinytest.addAsync("mongo-livedata - local collections with different connections", function (test, onComplete) { + var cname = Random.id(); + var cname2 = Random.id(); + var coll1 = new Mongo.Collection(cname); + var doc = { foo: "bar" }; + var coll2 = new Mongo.Collection(cname2, { connection: null }); + coll2.insert(doc, function (err, id) { + test.equal(coll1.find(doc).count(), 0); + test.equal(coll2.find(doc).count(), 1); + onComplete(); + }); +}); + +Tinytest.addAsync("mongo-livedata - local collection with null connection, w/ callback", function (test, onComplete) { + var cname = Random.id(); + var coll1 = new Mongo.Collection(cname, { connection: null }); + var doc = { foo: "bar" }; + var docId = coll1.insert(doc, function (err, id) { + test.equal(docId, id); + test.equal(coll1.findOne(doc)._id, id); + onComplete(); + }); +}); + +Tinytest.addAsync("mongo-livedata - local collection with null connection, w/o callback", function (test, onComplete) { + var cname = Random.id(); + var coll1 = new Mongo.Collection(cname, { connection: null }); + var doc = { foo: "bar" }; + var docId = coll1.insert(doc); + test.equal(coll1.findOne(doc)._id, docId); + onComplete(); +}); + +testAsyncMulti("mongo-livedata - update handles $push with $each correctly", [ + function (test, expect) { + var self = this; + var collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', collectionName); + Meteor.subscribe('c-' + collectionName, expect()); + } + + self.collection = new Mongo.Collection(collectionName); + + self.id = self.collection.insert( + {name: 'jens', elements: ['X', 'Y']}, expect(function (err, res) { + test.isFalse(err); + test.equal(self.id, res); + })); + }, + function (test, expect) { + var self = this; + self.collection.update(self.id, { + $push: { + elements: { + $each: ['A', 'B', 'C'], + $slice: -4 + }}}, expect(function (err, res) { + test.isFalse(err); + test.equal( + self.collection.findOne(self.id), + {_id: self.id, name: 'jens', elements: ['Y', 'A', 'B', 'C']}); + })); + } +]); + +if (Meteor.isServer) { + Tinytest.add("mongo-livedata - upsert handles $push with $each correctly", function (test) { + var collection = new Mongo.Collection(Random.id()); + + var result = collection.upsert( + {name: 'jens'}, + {$push: { + elements: { + $each: ['A', 'B', 'C'], + $slice: -4 + }}}); + + test.equal(collection.findOne(result.insertedId), + {_id: result.insertedId, + name: 'jens', + elements: ['A', 'B', 'C']}); + + var id = collection.insert({name: "david", elements: ['X', 'Y']}); + result = collection.upsert( + {name: 'david'}, + {$push: { + elements: { + $each: ['A', 'B', 'C'], + $slice: -4 + }}}); + + test.equal(collection.findOne(id), + {_id: id, + name: 'david', + elements: ['Y', 'A', 'B', 'C']}); + }); + + Tinytest.add("mongo-livedata - upsert handles dotted selectors corrrectly", function (test) { + var collection = new Mongo.Collection(Random.id()); + + var result1 = collection.upsert({ + "subdocument.a": 1 + }, { + $set: {message: "upsert 1"} + }); + + test.equal(collection.findOne(result1.insertedId),{ + _id: result1.insertedId, + subdocument: {a: 1}, + message: "upsert 1" + }); + + var result2 = collection.upsert({ + "subdocument.a": 1 + }, { + $set: {message: "upsert 2"} + }); + + test.equal(result2, {numberAffected: 1}); + + test.equal(collection.findOne(result1.insertedId),{ + _id: result1.insertedId, + subdocument: {a: 1}, + message: "upsert 2" + }); + + var result3 = collection.upsert({ + "subdocument.a.b": 1, + "subdocument.c": 2 + }, { + $set: {message: "upsert3"} + }); + + test.equal(collection.findOne(result3.insertedId),{ + _id: result3.insertedId, + subdocument: {a: {b: 1}, c: 2}, + message: "upsert3" + }); + + var result4 = collection.upsert({ + "subdocument.a": 4 + }, { + $set: {"subdocument.a": "upsert 4"} + }); + + test.equal(collection.findOne(result4.insertedId), { + _id: result4.insertedId, + subdocument: {a: "upsert 4"} + }); + + var result5 = collection.upsert({ + "subdocument.a": "upsert 4" + }, { + $set: {"subdocument.a": "upsert 5"} + }); + + test.equal(result5, {numberAffected: 1}); + + test.equal(collection.findOne(result4.insertedId), { + _id: result4.insertedId, + subdocument: {a: "upsert 5"} + }); + + var result6 = collection.upsert({ + "subdocument.a": "upsert 5" + }, { + $set: {"subdocument": "upsert 6"} + }); + + test.equal(result6, {numberAffected: 1}); + + test.equal(collection.findOne(result4.insertedId), { + _id: result4.insertedId, + subdocument: "upsert 6" + }); + + var result7 = collection.upsert({ + "subdocument.a.b": 7 + }, { + $set: { + "subdocument.a.c": "upsert7" + } + }); + + test.equal(collection.findOne(result7.insertedId), { + _id: result7.insertedId, + subdocument: { + a: {b: 7, c: "upsert7"} + } + }); + + var result8 = collection.upsert({ + "subdocument.a.b": 7 + }, { + $set: { + "subdocument.a.c": "upsert8" + } + }); + + test.equal(result8, {numberAffected: 1}); + + test.equal(collection.findOne(result7.insertedId), { + _id: result7.insertedId, + subdocument: { + a: {b: 7, c: "upsert8"} + } + }); + + var result9 = collection.upsert({ + "subdocument.a.b": 7 + }, { + $set: { + "subdocument.a.b": "upsert9" + } + }); + + test.equal(result9, {numberAffected: 1}); + + test.equal(collection.findOne(result7.insertedId), { + _id: result7.insertedId, + subdocument: { + a: {b: "upsert9", c: "upsert8"} + } + }); + + }); +} + +// This is a VERY white-box test. +Meteor.isServer && Tinytest.add("mongo-livedata - oplog - _disableOplog", function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection(collName); + if (MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle) { + var observeWithOplog = coll.find({x: 5}) + .observeChanges({added: function () {}}); + test.isTrue(observeWithOplog._multiplexer._observeDriver._usesOplog); + observeWithOplog.stop(); + } + var observeWithoutOplog = coll.find({x: 6}, {_disableOplog: true}) + .observeChanges({added: function () {}}); + test.isFalse(observeWithoutOplog._multiplexer._observeDriver._usesOplog); + observeWithoutOplog.stop(); +}); + +Meteor.isServer && Tinytest.add("mongo-livedata - oplog - include selector fields", function (test) { + var collName = "includeSelector" + Random.id(); + var coll = new Mongo.Collection(collName); + + var docId = coll.insert({a: 1, b: [3, 2], c: 'foo'}); + test.isTrue(docId); + + // Wait until we've processed the insert oplog entry. (If the insert shows up + // during the observeChanges, the bug in question is not consistently + // reproduced.) We don't have to do this for polling observe (eg + // --disable-oplog). + waitUntilOplogCaughtUp(); + + var output = []; + var handle = coll.find({a: 1, b: 2}, {fields: {c: 1}}).observeChanges({ + added: function (id, fields) { + output.push(['added', id, fields]); + }, + changed: function (id, fields) { + output.push(['changed', id, fields]); + }, + removed: function (id) { + output.push(['removed', id]); + } + }); + // Initially should match the document. + test.length(output, 1); + test.equal(output.shift(), ['added', docId, {c: 'foo'}]); + + // Update in such a way that, if we only knew about the published field 'c' + // and the changed field 'b' (but not the field 'a'), we would think it didn't + // match any more. (This is a regression test for a bug that existed because + // we used to not use the shared projection in the initial query.) + runInFence(function () { + coll.update(docId, {$set: {'b.0': 2, c: 'bar'}}); + }); + test.length(output, 1); + test.equal(output.shift(), ['changed', docId, {c: 'bar'}]); + + handle.stop(); +}); + +Meteor.isServer && Tinytest.add("mongo-livedata - oplog - transform", function (test) { + var collName = "oplogTransform" + Random.id(); + var coll = new Mongo.Collection(collName); + + var docId = coll.insert({a: 25, x: {x: 5, y: 9}}); + test.isTrue(docId); + + // Wait until we've processed the insert oplog entry. (If the insert shows up + // during the observeChanges, the bug in question is not consistently + // reproduced.) We don't have to do this for polling observe (eg + // --disable-oplog). + waitUntilOplogCaughtUp(); + + var cursor = coll.find({}, {transform: function (doc) { + return doc.x; + }}); + + var changesOutput = []; + var changesHandle = cursor.observeChanges({ + added: function (id, fields) { + changesOutput.push(['added', fields]); + } + }); + // We should get untransformed fields via observeChanges. + test.length(changesOutput, 1); + test.equal(changesOutput.shift(), ['added', {a: 25, x: {x: 5, y: 9}}]); + changesHandle.stop(); + + var transformedOutput = []; + var transformedHandle = cursor.observe({ + added: function (doc) { + transformedOutput.push(['added', doc]); + } + }); + test.length(transformedOutput, 1); + test.equal(transformedOutput.shift(), ['added', {x: 5, y: 9}]); + transformedHandle.stop(); +}); + + +Meteor.isServer && Tinytest.add("mongo-livedata - oplog - drop collection/db", function (test) { + // This test uses a random database, so it can be dropped without affecting + // anything else. + var mongodbUri = Npm.require('mongodb-uri'); + var parsedUri = mongodbUri.parse(process.env.MONGO_URL); + parsedUri.database = 'dropDB' + Random.id(); + var driver = new MongoInternals.RemoteCollectionDriver( + mongodbUri.format(parsedUri), { + oplogUrl: process.env.MONGO_OPLOG_URL + } + ); + + var collName = "dropCollection" + Random.id(); + var coll = new Mongo.Collection(collName, { _driver: driver }); + + var doc1Id = coll.insert({a: 'foo', c: 1}); + var doc2Id = coll.insert({b: 'bar'}); + var doc3Id = coll.insert({a: 'foo', c: 2}); + var tmp; + + var output = []; + var handle = coll.find({a: 'foo'}).observeChanges({ + added: function (id, fields) { + output.push(['added', id, fields]); + }, + changed: function (id) { + output.push(['changed']); + }, + removed: function (id) { + output.push(['removed', id]); + } + }); + test.length(output, 2); + // make order consistent + if (output.length === 2 && output[0][1] === doc3Id) { + tmp = output[0]; + output[0] = output[1]; + output[1] = tmp; + } + test.equal(output.shift(), ['added', doc1Id, {a: 'foo', c: 1}]); + test.equal(output.shift(), ['added', doc3Id, {a: 'foo', c: 2}]); + + // Wait until we've processed the insert oplog entry, so that we are in a + // steady state (and we don't see the dropped docs because we are FETCHING). + waitUntilOplogCaughtUp(); + + // Drop the collection. Should remove all docs. + runInFence(function () { + coll._dropCollection(); + }); + + test.length(output, 2); + // make order consistent + if (output.length === 2 && output[0][1] === doc3Id) { + tmp = output[0]; + output[0] = output[1]; + output[1] = tmp; + } + test.equal(output.shift(), ['removed', doc1Id]); + test.equal(output.shift(), ['removed', doc3Id]); + + // Put something back in. + var doc4Id; + runInFence(function () { + doc4Id = coll.insert({a: 'foo', c: 3}); + }); + + test.length(output, 1); + test.equal(output.shift(), ['added', doc4Id, {a: 'foo', c: 3}]); + + // XXX: this was intermittently failing for unknown reasons. + // Now drop the database. Should remove all docs again. + // runInFence(function () { + // driver.mongo.dropDatabase(); + // }); + // + // test.length(output, 1); + // test.equal(output.shift(), ['removed', doc4Id]); + + handle.stop(); + driver.mongo.close(); +}); + +var TestCustomType = function (head, tail) { + // use different field names on the object than in JSON, to ensure we are + // actually treating this as an opaque object. + this.myHead = head; + this.myTail = tail; +}; +_.extend(TestCustomType.prototype, { + clone: function () { + return new TestCustomType(this.myHead, this.myTail); + }, + equals: function (other) { + return other instanceof TestCustomType + && EJSON.equals(this.myHead, other.myHead) + && EJSON.equals(this.myTail, other.myTail); + }, + typeName: function () { + return 'someCustomType'; + }, + toJSONValue: function () { + return {head: this.myHead, tail: this.myTail}; + } +}); + +EJSON.addType('someCustomType', function (json) { + return new TestCustomType(json.head, json.tail); +}); + +testAsyncMulti("mongo-livedata - oplog - update EJSON", [ + function (test, expect) { + var self = this; + var collectionName = "ejson" + Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', collectionName); + Meteor.subscribe('c-' + collectionName, expect()); + } + + self.collection = new Mongo.Collection(collectionName); + self.date = new Date; + self.objId = new Mongo.ObjectID; + + self.id = self.collection.insert( + {d: self.date, oi: self.objId, + custom: new TestCustomType('a', 'b')}, + expect(function (err, res) { + test.isFalse(err); + test.equal(self.id, res); + })); + }, + function (test, expect) { + var self = this; + self.changes = []; + self.handle = self.collection.find({}).observeChanges({ + added: function (id, fields) { + self.changes.push(['a', id, fields]); + }, + changed: function (id, fields) { + self.changes.push(['c', id, fields]); + }, + removed: function (id) { + self.changes.push(['r', id]); + } + }); + test.length(self.changes, 1); + test.equal(self.changes.shift(), + ['a', self.id, + {d: self.date, oi: self.objId, + custom: new TestCustomType('a', 'b')}]); + + // First, replace the entire custom object. + // (runInFence is useful for the server, using expect() is useful for the + // client) + runInFence(function () { + self.collection.update( + self.id, {$set: {custom: new TestCustomType('a', 'c')}}, + expect(function (err) { + test.isFalse(err); + })); + }); + }, + function (test, expect) { + var self = this; + test.length(self.changes, 1); + test.equal(self.changes.shift(), + ['c', self.id, {custom: new TestCustomType('a', 'c')}]); + + // Now, sneakily replace just a piece of it. Meteor won't do this, but + // perhaps you are accessing Mongo directly. + runInFence(function () { + self.collection.update( + self.id, {$set: {'custom.EJSON$value.EJSONtail': 'd'}}, + expect(function (err) { + test.isFalse(err); + })); + }); + }, + function (test, expect) { + var self = this; + test.length(self.changes, 1); + test.equal(self.changes.shift(), + ['c', self.id, {custom: new TestCustomType('a', 'd')}]); + + // Update a date and an ObjectID too. + self.date2 = new Date(self.date.valueOf() + 1000); + self.objId2 = new Mongo.ObjectID; + runInFence(function () { + self.collection.update( + self.id, {$set: {d: self.date2, oi: self.objId2}}, + expect(function (err) { + test.isFalse(err); + })); + }); + }, + function (test, expect) { + var self = this; + test.length(self.changes, 1); + test.equal(self.changes.shift(), + ['c', self.id, {d: self.date2, oi: self.objId2}]); + + self.handle.stop(); + } +]); + + +function waitUntilOplogCaughtUp() { + var oplogHandle = + MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; + if (oplogHandle) + oplogHandle.waitUntilCaughtUp(); +} + + +Meteor.isServer && Tinytest.add("mongo-livedata - cursor dedup stop", function (test) { + var coll = new Mongo.Collection(Random.id()); + _.times(100, function () { + coll.insert({foo: 'baz'}); + }); + var handler = coll.find({}).observeChanges({ + added: function (id) { + coll.update(id, {$set: {foo: 'bar'}}); + } + }); + handler.stop(); + // Previously, this would print + // Exception in queued task: TypeError: Object.keys called on non-object + // Unfortunately, this test didn't fail before the bugfix, but it at least + // would print the error and no longer does. + // See https://github.com/meteor/meteor/issues/2070 +}); + +testAsyncMulti("mongo-livedata - undefined find options", [ + function (test, expect) { + var self = this; + self.collName = Random.id(); + if (Meteor.isClient) { + Meteor.call("createInsecureCollection", self.collName); + Meteor.subscribe("c-" + self.collName, expect()); + } + }, + function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(self.collName); + self.doc = { foo: 1, bar: 2, _id: "foobar" }; + self.coll.insert(self.doc, expect(function (err, id) { + test.isFalse(err); + })); + }, + function (test, expect) { + var self = this; + var result = self.coll.findOne({ foo: 1 }, { + fields: undefined, + sort: undefined, + limit: undefined, + skip: undefined + }); + test.equal(result, self.doc); + } +]); + +// Regression test for #2274. +Meteor.isServer && testAsyncMulti("mongo-livedata - observe limit bug", [ + function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(Random.id()); + var state = {}; + var callbacks = { + changed: function (newDoc) { + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + delete state[oldDoc._id]; + } + }; + self.observe = self.coll.find( + {}, {limit: 1, sort: {sortField: -1}}).observe(callbacks); + + // Insert some documents. + runInFence(function () { + self.id0 = self.coll.insert({sortField: 0, toDelete: true}); + self.id1 = self.coll.insert({sortField: 1, toDelete: true}); + self.id2 = self.coll.insert({sortField: 2, toDelete: true}); + }); + test.equal(_.keys(state), [self.id2]); + + // Mutate the one in the unpublished buffer and the one below the + // buffer. Before the fix for #2274, this left the observe state machine in + // a broken state where the buffer was empty but it wasn't try to re-fill + // it. + runInFence(function () { + self.coll.update({_id: {$ne: self.id2}}, + {$set: {toDelete: false}}, + {multi: 1}); + }); + test.equal(_.keys(state), [self.id2]); + + // Now remove the one published document. This should slide up id1 from the + // buffer, but this didn't work before the #2274 fix. + runInFence(function () { + self.coll.remove({toDelete: true}); + }); + test.equal(_.keys(state), [self.id1]); + } +]); + +Meteor.isServer && testAsyncMulti("mongo-livedata - update with replace forbidden", [ + function (test, expect) { + var c = new Mongo.Collection(Random.id()); + + var id = c.insert({ foo: "bar" }); + + c.update(id, { foo2: "bar2" }); + test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + + test.throws(function () { + c.update(id, { foo3: "bar3" }, { _forbidReplace: true }); + }, "Replacements are forbidden"); + test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + + test.throws(function () { + c.update(id, { foo3: "bar3", $set: { blah: 1 } }); + }, "cannot have both modifier and non-modifier fields"); + test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + } +]); + +Meteor.isServer && Tinytest.add( + "mongo-livedata - connection failure throws", + function (test) { + // Exception happens in 30s + test.throws(function () { + const connection = new MongoInternals.Connection('mongodb://this-does-not-exist.test/asdf'); + + // Same as `MongoInternals.defaultRemoteCollectionDriver`. + Promise.await(connection.client.connect()); + }); + } +); + +Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { + // Make sure the version number looks like a version number. + test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); + test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); + test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), + 'function'); + + var c = new Mongo.Collection(Random.id()); + var rawCollection = c.rawCollection(); + test.isTrue(rawCollection); + test.isTrue(rawCollection.findOneAndUpdate); + var rawDb = c.rawDatabase(); + test.isTrue(rawDb); + test.isTrue(rawDb.admin); +}); + +if (Meteor.isServer) { + Tinytest.add("mongo-livedata - update/remove don't accept an array as a selector #4804", function (test) { + var collection = new Mongo.Collection(Random.id()); + + _.times(10, function () { + collection.insert({ data: "Hello" }); + }); + + test.equal(collection.find().count(), 10); + + // Test several array-related selectors + _.each([[], [1, 2, 3], [{}]], function (selector) { + test.throws(function () { + collection.remove(selector); + }); + + test.throws(function () { + collection.update(selector, {$set: 5}); + }); + }); + + test.equal(collection.find().count(), 10); + }); +} + +// This is a regression test for https://github.com/meteor/meteor/issues/4839. +// Prior to fixing the issue (but after applying +// https://github.com/meteor/meteor/pull/4694), doing a Mongo write from a +// timeout that ran after a method body (invoked via the client) would throw an +// error "fence has already activated -- too late to add a callback" and not +// properly call the Mongo write's callback. In this test: +// - The client invokes a method (fenceOnBeforeFireError1) which +// - Starts an observe on a query +// - Creates a timeout (which shares a write fence with the method) +// - Lets the method return (firing the write fence) +// - The timeout runs and does a Mongo write. This write is inside a write +// fence (because timeouts preserve the fence, see dcd26415) but the write +// fence already fired. +// - The Mongo write's callback confirms that there is no error. This was +// not the case before fixing the bug! (Note that the observe was necessary +// for the error to occur, because the error was thrown from the observe's +// crossbar listener callback). It puts the confirmation into a Future. +// - The client invokes another method which reads the confirmation from +// the future. (Well, the invocation happened earlier but the use of the +// Future sequences it so that the confirmation only gets read at this point.) +if (Meteor.isClient) { + testAsyncMulti("mongo-livedata - fence onBeforeFire error", [ + function (test, expect) { + var self = this; + self.nonce = Random.id(); + Meteor.call('fenceOnBeforeFireError1', self.nonce, expect(function (err) { + test.isFalse(err); + })); + }, + function (test, expect) { + var self = this; + Meteor.call('fenceOnBeforeFireError2', self.nonce, expect( + function (err, success) { + test.isFalse(err); + test.isTrue(success); + } + )); + } + ]); +} else { + var fenceOnBeforeFireErrorCollection = new Mongo.Collection("FOBFE"); + var Future = Npm.require('fibers/future'); + var futuresByNonce = {}; + Meteor.methods({ + fenceOnBeforeFireError1: function (nonce) { + futuresByNonce[nonce] = new Future; + var observe = fenceOnBeforeFireErrorCollection.find({nonce: nonce}) + .observeChanges({added: function (){}}); + Meteor.setTimeout(function () { + fenceOnBeforeFireErrorCollection.insert( + {nonce: nonce}, + function (err, result) { + var success = !err && result; + futuresByNonce[nonce].return(success); + observe.stop(); + } + ); + }, 10); + }, + fenceOnBeforeFireError2: function (nonce) { + try { + return futuresByNonce[nonce].wait(); + } finally { + delete futuresByNonce[nonce]; + } + } + }); +} + +if (Meteor.isServer) { + Tinytest.add('mongo update/upsert - returns nMatched as numberAffected', function (test, onComplete) { + var collName = Random.id(); + var coll = new Mongo.Collection('update_nmatched'+collName); + + coll.insert({animal: 'cat', legs: 4}); + coll.insert({animal: 'dog', legs: 4}); + coll.insert({animal: 'echidna', legs: 4}); + coll.insert({animal: 'platypus', legs: 4}); + coll.insert({animal: 'starfish', legs: 5}); + + var affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}); + test.equal(affected, 1); + + //Changes only 3 but matched 4 documents + affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + test.equal(affected, 4); + + //Again, changes nothing but returns nModified + affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + test.equal(affected, 4); + + //upsert:true changes nothing, 4 modified + affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}); + test.equal(affected, 4); + + //upsert method works as upsert:true + var result = coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + test.equal(result.numberAffected, 4); + }); + + Tinytest.addAsync('mongo livedata - update/upsert callback returns nMatched as numberAffected', function (test, onComplete) { + var collName = Random.id(); + var coll = new Mongo.Collection('update_nmatched'+collName); + + coll.insert({animal: 'cat', legs: 4}); + coll.insert({animal: 'dog', legs: 4}); + coll.insert({animal: 'echidna', legs: 4}); + coll.insert({animal: 'platypus', legs: 4}); + coll.insert({animal: 'starfish', legs: 5}); + + var test1 = function () { + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, function (err, result) { + test.equal(result, 1); + test2(); + }); + }; + + var test2 = function () { + //Changes only 3 but matched 4 documents + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result, 4); + test3(); + }); + }; + + var test3 = function () { + //Again, changes nothing but returns nModified + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result, 4); + test4(); + }); + }; + + var test4 = function () { + //upsert:true changes nothing, 4 modified + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}, function (err, result) { + test.equal(result, 4); + test5(); + }); + }; + + var test5 = function () { + //upsert method works as upsert:true + coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result.numberAffected, 4); + onComplete(); + }); + }; + + test1(); + }); +} + +if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - transaction", function (test) { + const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo; + + const Collection = new Mongo.Collection(`transaction_test_${test.runId()}`); + const rawCollection = Collection.rawCollection(); + + Collection.insert({ _id: "a" }); + Collection.insert({ _id: "b" }); + + let changeCount = 0; + + return new Promise(resolve => { + function finalize() { + observeHandle.stop(); + Meteor.clearTimeout(timeout); + resolve(); + } + + const observeHandle = Collection.find().observeChanges({ + changed(id, fields) { + let expectedValue; + + if (id === "a") { + expectedValue = "updated1"; + } else if (id === "b") { + expectedValue = "updated2"; + } + + test.equal(fields.field, expectedValue); + changeCount += 1; + + if (changeCount === 2) { + finalize(); + } + } + }); + + const timeout = Meteor.setTimeout(() => { + test.fail("Didn't receive all transaction operations in two seconds."); + finalize(); + }, 2000); + + const session = client.startSession(); + session.withTransaction(session => { + let promise = Promise.resolve(); + ["a", "b"].forEach((id, index) => { + promise = promise.then(() => rawCollection.updateMany( + { _id: id }, + { $set: { field: `updated${index + 1}` } }, + { session } + )); + }); + return promise; + }).finally(() => { + session.endSession(); + }); + }); + }); +} diff --git a/packages/mongo/mongo_utils.js b/packages/mongo/mongo_utils.js new file mode 100644 index 0000000000..e97e722fd3 --- /dev/null +++ b/packages/mongo/mongo_utils.js @@ -0,0 +1,11 @@ +export const normalizeProjection = options => { + // transform fields key in projection + const { fields, projection, ...otherOptions } = options || {}; + // TODO: enable this comment when deprecating the fields option + // Log.debug(`fields option has been deprecated, please use the new 'projection' instead`) + + return { + ...otherOptions, + ...(projection || fields ? { projection: fields || projection } : {}), + }; +}; diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js new file mode 100644 index 0000000000..7088229d65 --- /dev/null +++ b/packages/mongo/observe_changes_tests.js @@ -0,0 +1,424 @@ +var makeCollection = function () { + if (Meteor.isServer) { + return new Mongo.Collection(Random.id()); + } else { + return new Mongo.Collection(null); + } +}; + +_.each ([{added: 'added', forceOrdered: true}, + {added: 'added', forceOrdered: false}, + {added: 'addedBefore', forceOrdered: false}], function (options) { + var added = options.added; + var forceOrdered = options.forceOrdered; + + Tinytest.addAsync("observeChanges - single id - basics " + added + + (forceOrdered ? " force ordered" : ""), + function (test, onComplete) { + var c = makeCollection(); + var counter = 0; + var callbacks = [added, "changed", "removed"]; + if (forceOrdered) + callbacks.push("movedBefore"); + withCallbackLogger(test, + callbacks, + Meteor.isServer, + function (logger) { + var barid = c.insert({thing: "stuff"}); + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + + var handle = c.find(fooid).observeChanges(logger); + if (added === 'added') { + logger.expectResult(added, [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); + } else { + logger.expectResult(added, + [fooid, {noodles: "good", bacon: "bad", apples: "ok"}, null]); + } + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + logger.expectResult("changed", + [fooid, {noodles: "alright", potatoes: "tasty", bacon: undefined}]); + + c.remove(fooid); + logger.expectResult("removed", [fooid]); + + logger.expectNoResult(() => { + c.remove(barid); + c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + }); + + handle.stop(); + + const badCursor = c.find({}, {fields: {noodles: 1, _id: false}}); + test.throws(function () { + badCursor.observeChanges(logger); + }); + + onComplete(); + }); + }); +}); + +Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var handles = []; + var cursor = c.find(); + handles.push(cursor.observeChanges(logger)); + // fields-tampering observer + handles.push(cursor.observeChanges({ + added: function(id, fields) { + fields.apples = 'green'; + }, + changed: function(id, fields) { + fields.apples = 'green'; + }, + })); + + var fooid = c.insert({apples: "ok"}); + logger.expectResult("added", [fooid, {apples: "ok"}]); + + c.update(fooid, {apples: "not ok"}); + logger.expectResult("changed", [fooid, {apples: "not ok"}]); + + test.equal(c.findOne(fooid).apples, "not ok"); + + _.each(handles, function(handle) { handle.stop(); }); + onComplete(); + }); + +}); + +Tinytest.addAsync("observeChanges - single id - initial adds", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var handle = c.find(fooid).observeChanges(logger); + logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); +}); + + + +Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, onComplete) { + var c = makeCollection(); + 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"}); + var handle = c.find().observeChanges(logger); + logger.expectResultUnordered([ + {callback: "added", + args: [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]}, + {callback: "added", + args: [barid, {noodles: "good", bacon: "weird", apples: "ok"}]} + ]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); +}); + +Tinytest.addAsync("observeChanges - unordered - basics", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var handle = c.find().observeChanges(logger); + var barid = c.insert({thing: "stuff"}); + logger.expectResultOnly("added", [barid, {thing: "stuff"}]); + + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + + logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); + + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + logger.expectResultOnly("changed", + [fooid, {noodles: "alright", potatoes: "tasty", bacon: undefined}]); + c.remove(fooid); + logger.expectResultOnly("removed", [fooid]); + c.remove(barid); + logger.expectResultOnly("removed", [barid]); + + fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + + logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); +}); + +if (Meteor.isServer) { + Tinytest.addAsync("observeChanges - unordered - specific fields", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var handle = c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); + var barid = c.insert({thing: "stuff"}); + logger.expectResultOnly("added", [barid, {}]); + + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + + logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); + + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + logger.expectResultOnly("changed", + [fooid, {noodles: "alright", bacon: undefined}]); + c.update(fooid, {noodles: "alright", potatoes: "meh", apples: "ok"}); + c.remove(fooid); + logger.expectResultOnly("removed", [fooid]); + c.remove(barid); + logger.expectResultOnly("removed", [barid]); + + fooid = c.insert({noodles: "good", bacon: "bad"}); + + logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); + }); + + Tinytest.addAsync("observeChanges - unordered - specific fields + selector on excluded fields", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var handle = c.find({ mac: 1, cheese: 2 }, + {fields:{noodles: 1, bacon: 1, eggs: 1}}).observeChanges(logger); + var barid = c.insert({thing: "stuff", mac: 1, cheese: 2}); + logger.expectResultOnly("added", [barid, {}]); + + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); + + logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); + + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok", mac: 1, cheese: 2}); + logger.expectResultOnly("changed", + [fooid, {noodles: "alright", bacon: undefined}]); + + // Doesn't get update event, since modifies only hidden fields + logger.expectNoResult(() => { + c.update(fooid, { + noodles: "alright", + potatoes: "meh", + apples: "ok", + mac: 1, + cheese: 2 + }); + }); + + c.remove(fooid); + logger.expectResultOnly("removed", [fooid]); + c.remove(barid); + logger.expectResultOnly("removed", [barid]); + + fooid = c.insert({noodles: "good", bacon: "bad", mac: 1, cheese: 2}); + + logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); + }); +} + +Tinytest.addAsync("observeChanges - unordered - specific fields + modify on excluded fields", function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + var handle = c.find({ mac: 1, cheese: 2 }, + {fields:{noodles: 1, bacon: 1, eggs: 1}}).observeChanges(logger); + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); + + logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); + + + // Noodles go into shadow, mac appears as eggs + c.update(fooid, {$rename: { noodles: 'shadow', apples: 'eggs' }}); + logger.expectResultOnly("changed", + [fooid, {eggs:"ok", noodles: undefined}]); + + c.remove(fooid); + logger.expectResultOnly("removed", [fooid]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); +}); + +Tinytest.addAsync( + "observeChanges - unordered - unset parent of observed field", + function (test, onComplete) { + var c = makeCollection(); + withCallbackLogger( + test, ['added', 'changed', 'removed'], Meteor.isServer, + function (logger) { + var handle = c.find({}, {fields: {'type.name': 1}}).observeChanges(logger); + var id = c.insert({ type: { name: 'foobar' } }); + logger.expectResultOnly('added', [id, { type: { name: 'foobar' } }]); + + c.update(id, { $unset: { type: 1 } }); + test.equal(c.find().fetch(), [{ _id: id }]); + logger.expectResultOnly('changed', [id, { type: undefined }]); + + handle.stop(); + onComplete(); + } + ); + } +); + + + +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) { + var handle = c.find({noodles: "good"}).observeChanges(logger); + var barid = c.insert({thing: "stuff"}); + + var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); + + c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + logger.expectResultOnly("removed", + [fooid]); + c.remove(fooid); + c.remove(barid); + + fooid = c.insert({noodles: "ok", bacon: "bad", apples: "ok"}); + c.update(fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}); + logger.expectResult("added", [fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}]); + logger.expectNoResult(); + handle.stop(); + onComplete(); + }); +}); + + +if (Meteor.isServer) { + testAsyncMulti("observeChanges - tailable", [ + function (test, expect) { + var self = this; + var collName = "cap_" + Random.id(); + var coll = new Mongo.Collection(collName); + coll._createCappedCollection(1000000); + self.xs = []; + self.expects = []; + self.insert = function (fields) { + coll.insert(_.extend({ts: new MongoInternals.MongoTimestamp(0, 0)}, + fields)); + }; + + // Tailable observe shouldn't show things that are in the initial + // contents. + self.insert({x: 1}); + // Wait for one added call before going to the next test function. + self.expects.push(expect()); + + var cursor = coll.find({y: {$ne: 7}}, {tailable: true}); + self.handle = cursor.observeChanges({ + added: function (id, fields) { + self.xs.push(fields.x); + test.notEqual(self.expects.length, 0); + self.expects.pop()(); + }, + changed: function () { + test.fail({unexpected: "changed"}); + }, + removed: function () { + test.fail({unexpected: "removed"}); + } + }); + + // Nothing happens synchronously. + test.equal(self.xs, []); + }, + function (test, expect) { + var self = this; + // The cursors sees the first element. + test.equal(self.xs, [1]); + self.xs = []; + + self.insert({x: 2, y: 3}); + self.insert({x: 3, y: 7}); // filtered out by the query + self.insert({x: 4}); + // Expect two added calls to happen. + self.expects = [expect(), expect()]; + }, + function (test, expect) { + var self = this; + test.equal(self.xs, [2, 4]); + self.xs = []; + self.handle.stop(); + + self.insert({x: 5}); + // XXX This timeout isn't perfect but it's pretty hard to prove that an + // event WON'T happen without something like a write fence. + Meteor.setTimeout(expect(), 1000); + }, + function (test, expect) { + var self = this; + test.equal(self.xs, []); + } + ]); +} + + +testAsyncMulti("observeChanges - bad query", [ + function (test, expect) { + var c = makeCollection(); + var observeThrows = function () { + test.throws(function () { + c.find({__id: {$in: null}}).observeChanges({ + added: function () { + test.fail("added shouldn't be called"); + } + }); + }, '$in needs an array'); + }; + + if (Meteor.isClient) { + observeThrows(); + return; + } + + // Test that if two copies of the same bad observeChanges run in parallel + // and are de-duped, both observeChanges calls will throw. + var Fiber = Npm.require('fibers'); + var Future = Npm.require('fibers/future'); + var f1 = new Future; + var f2 = new Future; + Fiber(function () { + // The observeChanges call in here will yield when we talk to mongod, + // which will allow the second Fiber to start and observe a duplicate + // query. + observeThrows(); + f1['return'](); + }).run(); + Fiber(function () { + test.isFalse(f1.isResolved()); // first observe hasn't thrown yet + observeThrows(); + f2['return'](); + }).run(); + f1.wait(); + f2.wait(); + } +]); + +if (Meteor.isServer) { + Tinytest.addAsync( + "observeChanges - EnvironmentVariable", + function (test, onComplete) { + var c = makeCollection(); + var environmentVariable = new Meteor.EnvironmentVariable; + environmentVariable.withValue(true, function() { + var handle = c.find({}, { fields: { 'type.name': 1 }}).observeChanges({ + added: function() { + test.isTrue(environmentVariable.get()); + handle.stop(); + onComplete(); + } + }); + }); + c.insert({ type: { name: 'foobar' } }); + } + ); +} diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js new file mode 100644 index 0000000000..4b6b64245c --- /dev/null +++ b/packages/mongo/observe_multiplex.js @@ -0,0 +1,231 @@ +let nextObserveHandleId = 1; + +ObserveMultiplexer = class { + constructor({ ordered, onStop = () => {} } = {}) { + if (ordered === undefined) throw Error("must specify ordered"); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-multiplexers", 1); + + this._ordered = ordered; + this._onStop = onStop; + this._queue = new Meteor._AsynchronousQueue(); + this._handles = {}; + this._resolver = null; + this._readyPromise = new Promise(r => this._resolver = r).then(() => this._isReady = true); + this._cache = new LocalCollection._CachingChangeObserver({ + ordered}); + // Number of addHandleAndSendInitialAdds tasks scheduled but not yet + // running. removeHandle uses this to know if it's time to call the onStop + // callback. + this._addHandleTasksScheduledButNotPerformed = 0; + + const self = this; + this.callbackNames().forEach(callbackName => { + this[callbackName] = function(/* ... */) { + self._applyCallback(callbackName, _.toArray(arguments)); + }; + }); + } + + addHandleAndSendInitialAdds(handle) { + return Meteor._isFibersEnabled ? Promise.await(this._addHandleAndSendInitialAdds(handle)) : this._addHandleAndSendInitialAdds(handle); + } + + async _addHandleAndSendInitialAdds(handle) { + ++this._addHandleTasksScheduledButNotPerformed; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-handles", 1); + + const self = this; + await this._queue.runTask(function () { + self._handles[handle._id] = handle; + // Send out whatever adds we have so far (whether the + // multiplexer is ready). + self._sendAdds(handle); + --self._addHandleTasksScheduledButNotPerformed; + }); + await this._readyPromise; + } + + // Remove an observe handle. If it was the last observe handle, call the + // onStop callback; you cannot add any more observe handles after this. + // + // This is not synchronized with polls and handle additions: this means that + // you can safely call it from within an observe callback, but it also means + // that we have to be careful when we iterate over _handles. + async removeHandle(id) { + // This should not be possible: you can only call removeHandle by having + // access to the ObserveHandle, which isn't returned to user code until the + // multiplex is ready. + if (!this._ready()) + throw new Error("Can't remove handles until the multiplex is ready"); + + delete this._handles[id]; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-handles", -1); + + if (_.isEmpty(this._handles) && + this._addHandleTasksScheduledButNotPerformed === 0) { + await this._stop(); + } + } + async _stop(options) { + options = options || {}; + + // It shouldn't be possible for us to stop when all our handles still + // haven't been returned from observeChanges! + if (! this._ready() && ! options.fromQueryError) + throw Error("surprising _stop: not ready"); + + // Call stop callback (which kills the underlying process which sends us + // callbacks and removes us from the connection's dictionary). + await this._onStop(); + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-multiplexers", -1); + + // Cause future addHandleAndSendInitialAdds calls to throw (but the onStop + // callback should make our connection forget about us). + this._handles = null; + } + + // Allows all addHandleAndSendInitialAdds calls to return, once all preceding + // adds have been processed. Does not block. + ready() { + const self = this; + this._queue.queueTask(function () { + if (self._ready()) + throw Error("can't make ObserveMultiplex ready twice!"); + + if (!self._resolver) { + throw new Error("Missing resolver"); + } + + self._resolver(); + self._isReady = true; + }); + } + + // If trying to execute the query results in an error, call this. This is + // intended for permanent errors, not transient network errors that could be + // fixed. It should only be called before ready(), because if you called ready + // that meant that you managed to run the query once. It will stop this + // ObserveMultiplex and cause addHandleAndSendInitialAdds calls (and thus + // observeChanges calls) to throw the error. + async queryError(err) { + var self = this; + await this._queue.runTask(function () { + if (self._ready()) + throw Error("can't claim query has an error after it worked!"); + self._stop({fromQueryError: true}); + throw err; + }); + } + + // Calls "cb" once the effects of all "ready", "addHandleAndSendInitialAdds" + // and observe callbacks which came before this call have been propagated to + // all handles. "ready" must have already been called on this multiplexer. + onFlush(cb) { + var self = this; + return this._queue.queueTask(async function () { + if (!self._ready()) + throw Error("only call onFlush on a multiplexer that will be ready"); + await cb(); + }); + } + callbackNames() { + if (this._ordered) + return ["addedBefore", "changed", "movedBefore", "removed"]; + else + return ["added", "changed", "removed"]; + } + _ready() { + return !!this._isReady; + } + _applyCallback(callbackName, args) { + const self = this; + this._queue.queueTask(async function () { + // If we stopped in the meantime, do nothing. + if (!self._handles) + return; + + // First, apply the change to the cache. + await self._cache.applyChange[callbackName].apply(null, args); + // If we haven't finished the initial adds, then we should only be getting + // adds. + if (!self._ready() && + (callbackName !== 'added' && callbackName !== 'addedBefore')) { + throw new Error("Got " + callbackName + " during initial adds"); + } + + // Now multiplex the callbacks out to all observe handles. It's OK if + // these calls yield; since we're inside a task, no other use of our queue + // can continue until these are done. (But we do have to be careful to not + // use a handle that got removed, because removeHandle does not use the + // queue; thus, we iterate over an array of keys that we control.) + const toAwait = Object.keys(self._handles).map(async (handleId) => { + var handle = self._handles && self._handles[handleId]; + if (!handle) + return; + var callback = handle['_' + callbackName]; + // clone arguments so that callbacks can mutate their arguments + callback && await callback.apply(null, + handle.nonMutatingCallbacks ? args : EJSON.clone(args)); + }); + + await Promise.all(toAwait); + }); + } + + // Sends initial adds to a handle. It should only be called from within a task + // (the task that is processing the addHandleAndSendInitialAdds call). It + // synchronously invokes the handle's added or addedBefore; there's no need to + // flush the queue afterwards to ensure that the callbacks get out. + async _sendAdds(handle) { + var add = this._ordered ? handle._addedBefore : handle._added; + if (!add) + return; + // note: docs may be an _IdMap or an OrderedDict + await this._cache.docs.forEachAsync(async (doc, id) => { + if (!_.has(this._handles, handle._id)) + throw Error("handle got removed before sending initial adds!"); + const { _id, ...fields } = handle.nonMutatingCallbacks ? doc + : EJSON.clone(doc); + if (this._ordered) + await add(id, fields, null); // we're going in order, so add at end + else + await add(id, fields); + }); + } +}; + +// When the callbacks do not mutate the arguments, we can skip a lot of data clones +ObserveHandle = class { + constructor(multiplexer, callbacks, nonMutatingCallbacks = false) { + this._multiplexer = multiplexer; + multiplexer.callbackNames().forEach((name) => { + if (callbacks[name]) { + this['_' + name] = callbacks[name]; + } else if (name === "addedBefore" && callbacks.added) { + // Special case: if you specify "added" and "movedBefore", you get an + // ordered observe where for some reason you don't get ordering data on + // the adds. I dunno, we wrote tests for it, there must have been a + // reason. + this._addedBefore = function (id, fields, before) { + callbacks.added(id, fields); + }; + } + }); + this._stopped = false; + this._id = nextObserveHandleId++; + this.nonMutatingCallbacks = nonMutatingCallbacks; + } + + async stop() { + if (this._stopped) return; + this._stopped = true; + await this._multiplexer.removeHandle(this._id); + } +}; diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js new file mode 100644 index 0000000000..9edfd2703b --- /dev/null +++ b/packages/mongo/oplog_observe_driver.js @@ -0,0 +1,1034 @@ +import { oplogV2V1Converter } from "./oplog_v2_converter"; + +var PHASE = { + QUERYING: "QUERYING", + FETCHING: "FETCHING", + STEADY: "STEADY" +}; + +// Exception thrown by _needToPollQuery which unrolls the stack up to the +// enclosing call to finishIfNeedToPollQuery. +var SwitchedToQuery = function () {}; +var finishIfNeedToPollQuery = function (f) { + return async function () { + try { + await f.apply(this, arguments); + } catch (e) { + if (!(e instanceof SwitchedToQuery)) + throw e; + } + }; +}; + +var currentId = 0; + +// OplogObserveDriver is an alternative to PollingObserveDriver which follows +// the Mongo operation log instead of just re-polling the query. It obeys the +// same simple interface: constructing it starts sending observeChanges +// callbacks (and a ready() invocation) to the ObserveMultiplexer, and you stop +// it by calling the stop() method. +OplogObserveDriver = function (options) { + var self = this; + self._usesOplog = true; // tests look at this + + self._id = currentId; + currentId++; + + self._cursorDescription = options.cursorDescription; + self._mongoHandle = options.mongoHandle; + self._multiplexer = options.multiplexer; + + if (options.ordered) { + throw Error("OplogObserveDriver only supports unordered observeChanges"); + } + + var sorter = options.sorter; + // We don't support $near and other geo-queries so it's OK to initialize the + // comparator only once in the constructor. + var comparator = sorter && sorter.getComparator(); + + if (options.cursorDescription.options.limit) { + // There are several properties ordered driver implements: + // - _limit is a positive number + // - _comparator is a function-comparator by which the query is ordered + // - _unpublishedBuffer is non-null Min/Max Heap, + // the empty buffer in STEADY phase implies that the + // everything that matches the queries selector fits + // into published set. + // - _published - Max Heap (also implements IdMap methods) + + var heapOptions = { IdMap: LocalCollection._IdMap }; + self._limit = self._cursorDescription.options.limit; + self._comparator = comparator; + self._sorter = sorter; + self._unpublishedBuffer = new MinMaxHeap(comparator, heapOptions); + // We need something that can find Max value in addition to IdMap interface + self._published = new MaxHeap(comparator, heapOptions); + } else { + self._limit = 0; + self._comparator = null; + self._sorter = null; + self._unpublishedBuffer = null; + self._published = new LocalCollection._IdMap; + } + + // Indicates if it is safe to insert a new document at the end of the buffer + // for this query. i.e. it is known that there are no documents matching the + // selector those are not in published or buffer. + self._safeAppendToBuffer = false; + + self._stopped = false; + self._stopHandles = []; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-oplog", 1); + + self._registerPhaseChange(PHASE.QUERYING); + + self._matcher = options.matcher; + // we are now using projection, not fields in the cursor description even if you pass {fields} + // in the cursor construction + var projection = self._cursorDescription.options.fields || self._cursorDescription.options.projection || {}; + self._projectionFn = LocalCollection._compileProjection(projection); + // Projection function, result of combining important fields for selector and + // existing fields projection + self._sharedProjection = self._matcher.combineIntoProjection(projection); + if (sorter) + self._sharedProjection = sorter.combineIntoProjection(self._sharedProjection); + self._sharedProjectionFn = LocalCollection._compileProjection( + self._sharedProjection); + + self._needToFetch = new LocalCollection._IdMap; + self._currentlyFetching = null; + self._fetchGeneration = 0; + + self._requeryWhenDoneThisQuery = false; + self._writesToCommitWhenWeReachSteady = []; + + // If the oplog handle tells us that it skipped some entries (because it got + // behind, say), re-poll. + self._stopHandles.push(self._mongoHandle._oplogHandle.onSkippedEntries( + finishIfNeedToPollQuery(function () { + return self._needToPollQuery(); + }) + )); + + forEachTrigger(self._cursorDescription, function (trigger) { + self._stopHandles.push(self._mongoHandle._oplogHandle.onOplogEntry( + trigger, function (notification) { + Meteor._noYieldsAllowed(finishIfNeedToPollQuery(function () { + var op = notification.op; + if (notification.dropCollection || notification.dropDatabase) { + // Note: this call is not allowed to block on anything (especially + // on waiting for oplog entries to catch up) because that will block + // onOplogEntry! + return self._needToPollQuery(); + } else { + // All other operators should be handled depending on phase + if (self._phase === PHASE.QUERYING) { + return self._handleOplogEntryQuerying(op); + } else { + return self._handleOplogEntrySteadyOrFetching(op); + } + } + })); + } + )); + }); + + // XXX ordering w.r.t. everything else? + self._stopHandles.push(listenAll( + self._cursorDescription, function () { + // If we're not in a pre-fire write fence, we don't have to do anything. + var fence = DDPServer._CurrentWriteFence.get(); + if (!fence || fence.fired) + return; + + if (fence._oplogObserveDrivers) { + fence._oplogObserveDrivers[self._id] = self; + return; + } + + fence._oplogObserveDrivers = {}; + fence._oplogObserveDrivers[self._id] = self; + + fence.onBeforeFire(async function () { + var drivers = fence._oplogObserveDrivers; + delete fence._oplogObserveDrivers; + + // This fence cannot fire until we've caught up to "this point" in the + // oplog, and all observers made it back to the steady state. + await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); + + for (const driver of Object.values(drivers)) { + if (driver._stopped) + return; + + var write = fence.beginWrite(); + if (driver._phase === PHASE.STEADY) { + // Make sure that all of the callbacks have made it through the + // multiplexer and been delivered to ObserveHandles before committing + // writes. + await driver._multiplexer.onFlush(write.committed); + } else { + driver._writesToCommitWhenWeReachSteady.push(write); + } + } + }); + } + )); + + // When Mongo fails over, we need to repoll the query, in case we processed an + // oplog entry that got rolled back. + self._stopHandles.push(self._mongoHandle._onFailover(finishIfNeedToPollQuery( + function () { + return self._needToPollQuery(); + }))); +}; + +_.extend(OplogObserveDriver.prototype, { + _init: function() { + const self = this; + // Give _observeChanges a chance to add the new ObserveHandle to our + // multiplexer, so that the added calls get streamed. + return self._runInitialQuery(); + }, + _addPublished: function (id, doc) { + var self = this; + Meteor._noYieldsAllowed(function () { + var fields = _.clone(doc); + delete fields._id; + self._published.set(id, self._sharedProjectionFn(doc)); + self._multiplexer.added(id, self._projectionFn(fields)); + + // After adding this document, the published set might be overflowed + // (exceeding capacity specified by limit). If so, push the maximum + // element to the buffer, we might want to save it in memory to reduce the + // amount of Mongo lookups in the future. + if (self._limit && self._published.size() > self._limit) { + // XXX in theory the size of published is no more than limit+1 + if (self._published.size() !== self._limit + 1) { + throw new Error("After adding to published, " + + (self._published.size() - self._limit) + + " documents are overflowing the set"); + } + + var overflowingDocId = self._published.maxElementId(); + var overflowingDoc = self._published.get(overflowingDocId); + + if (EJSON.equals(overflowingDocId, id)) { + throw new Error("The document just added is overflowing the published set"); + } + + self._published.remove(overflowingDocId); + self._multiplexer.removed(overflowingDocId); + self._addBuffered(overflowingDocId, overflowingDoc); + } + }); + }, + _removePublished: function (id) { + var self = this; + Meteor._noYieldsAllowed(function () { + self._published.remove(id); + self._multiplexer.removed(id); + if (! self._limit || self._published.size() === self._limit) + return; + + if (self._published.size() > self._limit) + throw Error("self._published got too big"); + + // OK, we are publishing less than the limit. Maybe we should look in the + // buffer to find the next element past what we were publishing before. + + if (!self._unpublishedBuffer.empty()) { + // There's something in the buffer; move the first thing in it to + // _published. + var newDocId = self._unpublishedBuffer.minElementId(); + var newDoc = self._unpublishedBuffer.get(newDocId); + self._removeBuffered(newDocId); + self._addPublished(newDocId, newDoc); + return; + } + + // There's nothing in the buffer. This could mean one of a few things. + + // (a) We could be in the middle of re-running the query (specifically, we + // could be in _publishNewResults). In that case, _unpublishedBuffer is + // empty because we clear it at the beginning of _publishNewResults. In + // this case, our caller already knows the entire answer to the query and + // we don't need to do anything fancy here. Just return. + if (self._phase === PHASE.QUERYING) + return; + + // (b) We're pretty confident that the union of _published and + // _unpublishedBuffer contain all documents that match selector. Because + // _unpublishedBuffer is empty, that means we're confident that _published + // contains all documents that match selector. So we have nothing to do. + if (self._safeAppendToBuffer) + return; + + // (c) Maybe there are other documents out there that should be in our + // buffer. But in that case, when we emptied _unpublishedBuffer in + // _removeBuffered, we should have called _needToPollQuery, which will + // either put something in _unpublishedBuffer or set _safeAppendToBuffer + // (or both), and it will put us in QUERYING for that whole time. So in + // fact, we shouldn't be able to get here. + + throw new Error("Buffer inexplicably empty"); + }); + }, + _changePublished: function (id, oldDoc, newDoc) { + var self = this; + Meteor._noYieldsAllowed(function () { + self._published.set(id, self._sharedProjectionFn(newDoc)); + var projectedNew = self._projectionFn(newDoc); + var projectedOld = self._projectionFn(oldDoc); + var changed = DiffSequence.makeChangedFields( + projectedNew, projectedOld); + if (!_.isEmpty(changed)) + self._multiplexer.changed(id, changed); + }); + }, + _addBuffered: function (id, doc) { + var self = this; + Meteor._noYieldsAllowed(function () { + self._unpublishedBuffer.set(id, self._sharedProjectionFn(doc)); + + // If something is overflowing the buffer, we just remove it from cache + if (self._unpublishedBuffer.size() > self._limit) { + var maxBufferedId = self._unpublishedBuffer.maxElementId(); + + self._unpublishedBuffer.remove(maxBufferedId); + + // Since something matching is removed from cache (both published set and + // buffer), set flag to false + self._safeAppendToBuffer = false; + } + }); + }, + // Is called either to remove the doc completely from matching set or to move + // it to the published set later. + _removeBuffered: function (id) { + var self = this; + Meteor._noYieldsAllowed(function () { + self._unpublishedBuffer.remove(id); + // To keep the contract "buffer is never empty in STEADY phase unless the + // everything matching fits into published" true, we poll everything as + // soon as we see the buffer becoming empty. + if (! self._unpublishedBuffer.size() && ! self._safeAppendToBuffer) + self._needToPollQuery(); + }); + }, + // Called when a document has joined the "Matching" results set. + // Takes responsibility of keeping _unpublishedBuffer in sync with _published + // and the effect of limit enforced. + _addMatching: function (doc) { + var self = this; + Meteor._noYieldsAllowed(function () { + var id = doc._id; + if (self._published.has(id)) + throw Error("tried to add something already published " + id); + if (self._limit && self._unpublishedBuffer.has(id)) + throw Error("tried to add something already existed in buffer " + id); + + var limit = self._limit; + var comparator = self._comparator; + var maxPublished = (limit && self._published.size() > 0) ? + self._published.get(self._published.maxElementId()) : null; + var maxBuffered = (limit && self._unpublishedBuffer.size() > 0) + ? self._unpublishedBuffer.get(self._unpublishedBuffer.maxElementId()) + : null; + // The query is unlimited or didn't publish enough documents yet or the + // new document would fit into published set pushing the maximum element + // out, then we need to publish the doc. + var toPublish = ! limit || self._published.size() < limit || + comparator(doc, maxPublished) < 0; + + // Otherwise we might need to buffer it (only in case of limited query). + // Buffering is allowed if the buffer is not filled up yet and all + // matching docs are either in the published set or in the buffer. + var canAppendToBuffer = !toPublish && self._safeAppendToBuffer && + self._unpublishedBuffer.size() < limit; + + // Or if it is small enough to be safely inserted to the middle or the + // beginning of the buffer. + var canInsertIntoBuffer = !toPublish && maxBuffered && + comparator(doc, maxBuffered) <= 0; + + var toBuffer = canAppendToBuffer || canInsertIntoBuffer; + + if (toPublish) { + self._addPublished(id, doc); + } else if (toBuffer) { + self._addBuffered(id, doc); + } else { + // dropping it and not saving to the cache + self._safeAppendToBuffer = false; + } + }); + }, + // Called when a document leaves the "Matching" results set. + // Takes responsibility of keeping _unpublishedBuffer in sync with _published + // and the effect of limit enforced. + _removeMatching: function (id) { + var self = this; + Meteor._noYieldsAllowed(function () { + if (! self._published.has(id) && ! self._limit) + throw Error("tried to remove something matching but not cached " + id); + + if (self._published.has(id)) { + self._removePublished(id); + } else if (self._unpublishedBuffer.has(id)) { + self._removeBuffered(id); + } + }); + }, + _handleDoc: function (id, newDoc) { + var self = this; + Meteor._noYieldsAllowed(function () { + var matchesNow = newDoc && self._matcher.documentMatches(newDoc).result; + + var publishedBefore = self._published.has(id); + var bufferedBefore = self._limit && self._unpublishedBuffer.has(id); + var cachedBefore = publishedBefore || bufferedBefore; + + if (matchesNow && !cachedBefore) { + self._addMatching(newDoc); + } else if (cachedBefore && !matchesNow) { + self._removeMatching(id); + } else if (cachedBefore && matchesNow) { + var oldDoc = self._published.get(id); + var comparator = self._comparator; + var minBuffered = self._limit && self._unpublishedBuffer.size() && + self._unpublishedBuffer.get(self._unpublishedBuffer.minElementId()); + var maxBuffered; + + if (publishedBefore) { + // Unlimited case where the document stays in published once it + // matches or the case when we don't have enough matching docs to + // publish or the changed but matching doc will stay in published + // anyways. + // + // XXX: We rely on the emptiness of buffer. Be sure to maintain the + // fact that buffer can't be empty if there are matching documents not + // published. Notably, we don't want to schedule repoll and continue + // relying on this property. + var staysInPublished = ! self._limit || + self._unpublishedBuffer.size() === 0 || + comparator(newDoc, minBuffered) <= 0; + + if (staysInPublished) { + self._changePublished(id, oldDoc, newDoc); + } else { + // after the change doc doesn't stay in the published, remove it + self._removePublished(id); + // but it can move into buffered now, check it + maxBuffered = self._unpublishedBuffer.get( + self._unpublishedBuffer.maxElementId()); + + var toBuffer = self._safeAppendToBuffer || + (maxBuffered && comparator(newDoc, maxBuffered) <= 0); + + if (toBuffer) { + self._addBuffered(id, newDoc); + } else { + // Throw away from both published set and buffer + self._safeAppendToBuffer = false; + } + } + } else if (bufferedBefore) { + oldDoc = self._unpublishedBuffer.get(id); + // remove the old version manually instead of using _removeBuffered so + // we don't trigger the querying immediately. if we end this block + // with the buffer empty, we will need to trigger the query poll + // manually too. + self._unpublishedBuffer.remove(id); + + var maxPublished = self._published.get( + self._published.maxElementId()); + maxBuffered = self._unpublishedBuffer.size() && + self._unpublishedBuffer.get( + self._unpublishedBuffer.maxElementId()); + + // the buffered doc was updated, it could move to published + var toPublish = comparator(newDoc, maxPublished) < 0; + + // or stays in buffer even after the change + var staysInBuffer = (! toPublish && self._safeAppendToBuffer) || + (!toPublish && maxBuffered && + comparator(newDoc, maxBuffered) <= 0); + + if (toPublish) { + self._addPublished(id, newDoc); + } else if (staysInBuffer) { + // stays in buffer but changes + self._unpublishedBuffer.set(id, newDoc); + } else { + // Throw away from both published set and buffer + self._safeAppendToBuffer = false; + // Normally this check would have been done in _removeBuffered but + // we didn't use it, so we need to do it ourself now. + if (! self._unpublishedBuffer.size()) { + self._needToPollQuery(); + } + } + } else { + throw new Error("cachedBefore implies either of publishedBefore or bufferedBefore is true."); + } + } + }); + }, + _fetchModifiedDocuments: function () { + var self = this; + Meteor._noYieldsAllowed(function () { + self._registerPhaseChange(PHASE.FETCHING); + // Defer, because nothing called from the oplog entry handler may yield, + // but fetch() yields. + Meteor.defer(finishIfNeedToPollQuery(async function () { + while (!self._stopped && !self._needToFetch.empty()) { + if (self._phase === PHASE.QUERYING) { + // While fetching, we decided to go into QUERYING mode, and then we + // saw another oplog entry, so _needToFetch is not empty. But we + // shouldn't fetch these documents until AFTER the query is done. + break; + } + + // Being in steady phase here would be surprising. + if (self._phase !== PHASE.FETCHING) + throw new Error("phase in fetchModifiedDocuments: " + self._phase); + + self._currentlyFetching = self._needToFetch; + var thisGeneration = ++self._fetchGeneration; + self._needToFetch = new LocalCollection._IdMap; + var waiting = 0; + + let promiseResolver = null; + const awaitablePromise = new Promise(r => promiseResolver = r); + // This loop is safe, because _currentlyFetching will not be updated + // during this loop (in fact, it is never mutated). + self._currentlyFetching.forEach(function (op, id) { + waiting++; + self._mongoHandle._docFetcher.fetch( + self._cursorDescription.collectionName, id, op, + finishIfNeedToPollQuery(function (err, doc) { + try { + if (err) { + Meteor._debug("Got exception while fetching documents", + err); + // If we get an error from the fetcher (eg, trouble + // connecting to Mongo), let's just abandon the fetch phase + // altogether and fall back to polling. It's not like we're + // getting live updates anyway. + if (self._phase !== PHASE.QUERYING) { + self._needToPollQuery(); + } + } else if (!self._stopped && self._phase === PHASE.FETCHING + && self._fetchGeneration === thisGeneration) { + // We re-check the generation in case we've had an explicit + // _pollQuery call (eg, in another fiber) which should + // effectively cancel this round of fetches. (_pollQuery + // increments the generation.) + self._handleDoc(id, doc); + } + } finally { + waiting--; + // Because fetch() never calls its callback synchronously, + // this is safe (ie, we won't call fut.return() before the + // forEach is done). + if (waiting === 0) + promiseResolver(); + } + })); + }); + await awaitablePromise; + // Exit now if we've had a _pollQuery call (here or in another fiber). + if (self._phase === PHASE.QUERYING) + return; + self._currentlyFetching = null; + } + // We're done fetching, so we can be steady, unless we've had a + // _pollQuery call (here or in another fiber). + if (self._phase !== PHASE.QUERYING) + await self._beSteady(); + })); + }); + }, + _beSteady: async function () { + var self = this; + await Meteor._noYieldsAllowed(async function () { + self._registerPhaseChange(PHASE.STEADY); + var writes = self._writesToCommitWhenWeReachSteady; + self._writesToCommitWhenWeReachSteady = []; + await self._multiplexer.onFlush(async function () { + for (const w of writes) { + await w.committed(); + } + }); + }); + }, + _handleOplogEntryQuerying: function (op) { + var self = this; + Meteor._noYieldsAllowed(function () { + self._needToFetch.set(idForOp(op), op); + }); + }, + _handleOplogEntrySteadyOrFetching: function (op) { + var self = this; + Meteor._noYieldsAllowed(function () { + var id = idForOp(op); + // If we're already fetching this one, or about to, we can't optimize; + // make sure that we fetch it again if necessary. + if (self._phase === PHASE.FETCHING && + ((self._currentlyFetching && self._currentlyFetching.has(id)) || + self._needToFetch.has(id))) { + self._needToFetch.set(id, op); + return; + } + + if (op.op === 'd') { + if (self._published.has(id) || + (self._limit && self._unpublishedBuffer.has(id))) + self._removeMatching(id); + } else if (op.op === 'i') { + if (self._published.has(id)) + throw new Error("insert found for already-existing ID in published"); + if (self._unpublishedBuffer && self._unpublishedBuffer.has(id)) + throw new Error("insert found for already-existing ID in buffer"); + + // XXX what if selector yields? for now it can't but later it could + // have $where + if (self._matcher.documentMatches(op.o).result) + self._addMatching(op.o); + } else if (op.op === 'u') { + // we are mapping the new oplog format on mongo 5 + // to what we know better, $set + op.o = oplogV2V1Converter(op.o) + // Is this a modifier ($set/$unset, which may require us to poll the + // database to figure out if the whole document matches the selector) or + // a replacement (in which case we can just directly re-evaluate the + // selector)? + // oplog format has changed on mongodb 5, we have to support both now + // diff is the format in Mongo 5+ (oplog v2) + var isReplace = !_.has(op.o, '$set') && !_.has(op.o, 'diff') && !_.has(op.o, '$unset'); + // If this modifier modifies something inside an EJSON custom type (ie, + // anything with EJSON$), then we can't try to use + // LocalCollection._modify, since that just mutates the EJSON encoding, + // not the actual object. + var canDirectlyModifyDoc = + !isReplace && modifierCanBeDirectlyApplied(op.o); + + var publishedBefore = self._published.has(id); + var bufferedBefore = self._limit && self._unpublishedBuffer.has(id); + + if (isReplace) { + self._handleDoc(id, _.extend({_id: id}, op.o)); + } else if ((publishedBefore || bufferedBefore) && + canDirectlyModifyDoc) { + // Oh great, we actually know what the document is, so we can apply + // this directly. + var newDoc = self._published.has(id) + ? self._published.get(id) : self._unpublishedBuffer.get(id); + newDoc = EJSON.clone(newDoc); + + newDoc._id = id; + try { + LocalCollection._modify(newDoc, op.o); + } catch (e) { + if (e.name !== "MinimongoError") + throw e; + // We didn't understand the modifier. Re-fetch. + self._needToFetch.set(id, op); + if (self._phase === PHASE.STEADY) { + self._fetchModifiedDocuments(); + } + return; + } + self._handleDoc(id, self._sharedProjectionFn(newDoc)); + } else if (!canDirectlyModifyDoc || + self._matcher.canBecomeTrueByModifier(op.o) || + (self._sorter && self._sorter.affectedByModifier(op.o))) { + self._needToFetch.set(id, op); + if (self._phase === PHASE.STEADY) + self._fetchModifiedDocuments(); + } + } else { + throw Error("XXX SURPRISING OPERATION: " + op); + } + }); + }, + + async _runInitialQueryAsync() { + var self = this; + if (self._stopped) + throw new Error("oplog stopped surprisingly early"); + + await self._runQuery({initial: true}); // yields + + if (self._stopped) + return; // can happen on queryError + + // Allow observeChanges calls to return. (After this, it's possible for + // stop() to be called.) + await self._multiplexer.ready(); + + await self._doneQuerying(); // yields + }, + + // Yields! + _runInitialQuery: function () { + return Meteor._isFibersEnabled ? Promise.await(this._runInitialQueryAsync()) : this._runInitialQueryAsync(); + }, + + // In various circumstances, we may just want to stop processing the oplog and + // re-run the initial query, just as if we were a PollingObserveDriver. + // + // This function may not block, because it is called from an oplog entry + // handler. + // + // XXX We should call this when we detect that we've been in FETCHING for "too + // long". + // + // XXX We should call this when we detect Mongo failover (since that might + // mean that some of the oplog entries we have processed have been rolled + // back). The Node Mongo driver is in the middle of a bunch of huge + // refactorings, including the way that it notifies you when primary + // changes. Will put off implementing this until driver 1.4 is out. + _pollQuery: function () { + var self = this; + Meteor._noYieldsAllowed(function () { + if (self._stopped) + return; + + // Yay, we get to forget about all the things we thought we had to fetch. + self._needToFetch = new LocalCollection._IdMap; + self._currentlyFetching = null; + ++self._fetchGeneration; // ignore any in-flight fetches + self._registerPhaseChange(PHASE.QUERYING); + + // Defer so that we don't yield. We don't need finishIfNeedToPollQuery + // here because SwitchedToQuery is not thrown in QUERYING mode. + Meteor.defer(async function () { + await self._runQuery(); + await self._doneQuerying(); + }); + }); + }, + + // Yields! + async _runQueryAsync(options) { + var self = this; + options = options || {}; + var newResults, newBuffer; + + // This while loop is just to retry failures. + while (true) { + // If we've been stopped, we don't have to run anything any more. + if (self._stopped) + return; + + newResults = new LocalCollection._IdMap; + newBuffer = new LocalCollection._IdMap; + + // Query 2x documents as the half excluded from the original query will go + // into unpublished buffer to reduce additional Mongo lookups in cases + // when documents are removed from the published set and need a + // replacement. + // XXX needs more thought on non-zero skip + // XXX 2 is a "magic number" meaning there is an extra chunk of docs for + // buffer if such is needed. + var cursor = self._cursorForQuery({ limit: self._limit * 2 }); + try { + await cursor.forEach(function (doc, i) { // yields + if (!self._limit || i < self._limit) { + newResults.set(doc._id, doc); + } else { + newBuffer.set(doc._id, doc); + } + }); + break; + } catch (e) { + if (options.initial && typeof(e.code) === 'number') { + // This is an error document sent to us by mongod, not a connection + // error generated by the client. And we've never seen this query work + // successfully. Probably it's a bad selector or something, so we + // should NOT retry. Instead, we should halt the observe (which ends + // up calling `stop` on us). + await self._multiplexer.queryError(e); + return; + } + + // During failover (eg) if we get an exception we should log and retry + // instead of crashing. + Meteor._debug("Got exception while polling query", e); + await Meteor._sleepForMs(100); + } + } + + if (self._stopped) + return; + + self._publishNewResults(newResults, newBuffer); + }, + + // Yields! + _runQuery: function (options) { + return Meteor._isFibersEnabled ? Promise.await(this._runQueryAsync(options)) : this._runQueryAsync(options); + }, + + // Transitions to QUERYING and runs another query, or (if already in QUERYING) + // ensures that we will query again later. + // + // This function may not block, because it is called from an oplog entry + // handler. However, if we were not already in the QUERYING phase, it throws + // an exception that is caught by the closest surrounding + // finishIfNeedToPollQuery call; this ensures that we don't continue running + // close that was designed for another phase inside PHASE.QUERYING. + // + // (It's also necessary whenever logic in this file yields to check that other + // phases haven't put us into QUERYING mode, though; eg, + // _fetchModifiedDocuments does this.) + _needToPollQuery: function () { + var self = this; + Meteor._noYieldsAllowed(function () { + if (self._stopped) + return; + + // If we're not already in the middle of a query, we can query now + // (possibly pausing FETCHING). + if (self._phase !== PHASE.QUERYING) { + self._pollQuery(); + throw new SwitchedToQuery; + } + + // We're currently in QUERYING. Set a flag to ensure that we run another + // query when we're done. + self._requeryWhenDoneThisQuery = true; + }); + }, + + // Yields! + _doneQuerying: async function () { + var self = this; + + if (self._stopped) + return; + + await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); + + if (self._stopped) + return; + if (self._phase !== PHASE.QUERYING) + throw Error("Phase unexpectedly " + self._phase); + + await Meteor._noYieldsAllowed(async function () { + if (self._requeryWhenDoneThisQuery) { + self._requeryWhenDoneThisQuery = false; + self._pollQuery(); + } else if (self._needToFetch.empty()) { + await self._beSteady(); + } else { + self._fetchModifiedDocuments(); + } + }); + }, + + _cursorForQuery: function (optionsOverwrite) { + var self = this; + return Meteor._noYieldsAllowed(function () { + // The query we run is almost the same as the cursor we are observing, + // with a few changes. We need to read all the fields that are relevant to + // the selector, not just the fields we are going to publish (that's the + // "shared" projection). And we don't want to apply any transform in the + // cursor, because observeChanges shouldn't use the transform. + var options = _.clone(self._cursorDescription.options); + + // Allow the caller to modify the options. Useful to specify different + // skip and limit values. + _.extend(options, optionsOverwrite); + + options.fields = self._sharedProjection; + delete options.transform; + // We are NOT deep cloning fields or selector here, which should be OK. + var description = new CursorDescription( + self._cursorDescription.collectionName, + self._cursorDescription.selector, + options); + return new Cursor(self._mongoHandle, description); + }); + }, + + + // Replace self._published with newResults (both are IdMaps), invoking observe + // callbacks on the multiplexer. + // Replace self._unpublishedBuffer with newBuffer. + // + // XXX This is very similar to LocalCollection._diffQueryUnorderedChanges. We + // should really: (a) Unify IdMap and OrderedDict into Unordered/OrderedDict + // (b) Rewrite diff.js to use these classes instead of arrays and objects. + _publishNewResults: function (newResults, newBuffer) { + var self = this; + Meteor._noYieldsAllowed(function () { + + // If the query is limited and there is a buffer, shut down so it doesn't + // stay in a way. + if (self._limit) { + self._unpublishedBuffer.clear(); + } + + // First remove anything that's gone. Be careful not to modify + // self._published while iterating over it. + var idsToRemove = []; + self._published.forEach(function (doc, id) { + if (!newResults.has(id)) + idsToRemove.push(id); + }); + _.each(idsToRemove, function (id) { + self._removePublished(id); + }); + + // Now do adds and changes. + // If self has a buffer and limit, the new fetched result will be + // limited correctly as the query has sort specifier. + newResults.forEach(function (doc, id) { + self._handleDoc(id, doc); + }); + + // Sanity-check that everything we tried to put into _published ended up + // there. + // XXX if this is slow, remove it later + if (self._published.size() !== newResults.size()) { + console.error('The Mongo server and the Meteor query disagree on how ' + + 'many documents match your query. Cursor description: ', + self._cursorDescription); + throw Error( + "The Mongo server and the Meteor query disagree on how " + + "many documents match your query. Maybe it is hitting a Mongo " + + "edge case? The query is: " + + EJSON.stringify(self._cursorDescription.selector)); + } + self._published.forEach(function (doc, id) { + if (!newResults.has(id)) + throw Error("_published has a doc that newResults doesn't; " + id); + }); + + // Finally, replace the buffer + newBuffer.forEach(function (doc, id) { + self._addBuffered(id, doc); + }); + + self._safeAppendToBuffer = newBuffer.size() < self._limit; + }); + }, + + // This stop function is invoked from the onStop of the ObserveMultiplexer, so + // it shouldn't actually be possible to call it until the multiplexer is + // ready. + // + // It's important to check self._stopped after every call in this file that + // can yield! + _stop: async function() { + var self = this; + if (self._stopped) + return; + self._stopped = true; + + // Note: we *don't* use multiplexer.onFlush here because this stop + // callback is actually invoked by the multiplexer itself when it has + // determined that there are no handles left. So nothing is actually going + // to get flushed (and it's probably not valid to call methods on the + // dying multiplexer). + for (const w of self._writesToCommitWhenWeReachSteady) { + await w.committed(); + } + self._writesToCommitWhenWeReachSteady = null; + + // Proactively drop references to potentially big things. + self._published = null; + self._unpublishedBuffer = null; + self._needToFetch = null; + self._currentlyFetching = null; + self._oplogEntryHandle = null; + self._listenersHandle = null; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-oplog", -1); + + for await (const handle of self._stopHandles) { + await handle.stop(); + } + }, + stop: function() { + const self = this; + return Meteor._isFibersEnabled ? Promise.await(self._stop()) : self._stop(); + }, + + _registerPhaseChange: function (phase) { + var self = this; + Meteor._noYieldsAllowed(function () { + var now = new Date; + + if (self._phase) { + var timeDiff = now - self._phaseStartTime; + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "time-spent-in-" + self._phase + "-phase", timeDiff); + } + + self._phase = phase; + self._phaseStartTime = now; + }); + } +}); + +// Does our oplog tailing code support this cursor? For now, we are being very +// conservative and allowing only simple queries with simple options. +// (This is a "static method".) +OplogObserveDriver.cursorSupported = function (cursorDescription, matcher) { + // First, check the options. + var options = cursorDescription.options; + + // Did the user say no explicitly? + // underscored version of the option is COMPAT with 1.2 + if (options.disableOplog || options._disableOplog) + return false; + + // skip is not supported: to support it we would need to keep track of all + // "skipped" documents or at least their ids. + // limit w/o a sort specifier is not supported: current implementation needs a + // deterministic way to order documents. + if (options.skip || (options.limit && !options.sort)) return false; + + // If a fields projection option is given check if it is supported by + // minimongo (some operators are not supported). + const fields = options.fields || options.projection; + if (fields) { + try { + LocalCollection._checkSupportedProjection(fields); + } catch (e) { + if (e.name === "MinimongoError") { + return false; + } else { + throw e; + } + } + } + + // We don't allow the following selectors: + // - $where (not confident that we provide the same JS environment + // as Mongo, and can yield!) + // - $near (has "interesting" properties in MongoDB, like the possibility + // of returning an ID multiple times, though even polling maybe + // have a bug there) + // XXX: once we support it, we would need to think more on how we + // initialize the comparators when we create the driver. + return !matcher.hasWhere() && !matcher.hasGeoQuery(); +}; + +var modifierCanBeDirectlyApplied = function (modifier) { + return _.all(modifier, function (fields, operation) { + return _.all(fields, function (value, field) { + return !/EJSON\$/.test(field); + }); + }); +}; + +MongoInternals.OplogObserveDriver = OplogObserveDriver; diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js new file mode 100644 index 0000000000..a50e3f5a1e --- /dev/null +++ b/packages/mongo/oplog_tailing.js @@ -0,0 +1,383 @@ +import { NpmModuleMongodb } from "meteor/npm-mongo"; +const { Long } = NpmModuleMongodb; + +OPLOG_COLLECTION = 'oplog.rs'; + +var TOO_FAR_BEHIND = process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000; +var TAIL_TIMEOUT = +process.env.METEOR_OPLOG_TAIL_TIMEOUT || 30000; + +idForOp = function (op) { + if (op.op === 'd') + return op.o._id; + else if (op.op === 'i') + return op.o._id; + else if (op.op === 'u') + return op.o2._id; + else if (op.op === 'c') + throw Error("Operator 'c' doesn't supply an object with id: " + + EJSON.stringify(op)); + else + throw Error("Unknown op: " + EJSON.stringify(op)); +}; + +OplogHandle = function (oplogUrl, dbName) { + var self = this; + self._oplogUrl = oplogUrl; + self._dbName = dbName; + + self._oplogLastEntryConnection = null; + self._oplogTailConnection = null; + self._stopped = false; + self._tailHandle = null; + self._readyPromiseResolver = null; + self._readyPromise = new Promise(r => self._readyPromiseResolver = r); + self._crossbar = new DDPServer._Crossbar({ + factPackage: "mongo-livedata", factName: "oplog-watchers" + }); + self._baseOplogSelector = { + ns: new RegExp("^(?:" + [ + Meteor._escapeRegExp(self._dbName + "."), + Meteor._escapeRegExp("admin.$cmd"), + ].join("|") + ")"), + + $or: [ + { op: { $in: ['i', 'u', 'd'] } }, + // drop collection + { op: 'c', 'o.drop': { $exists: true } }, + { op: 'c', 'o.dropDatabase': 1 }, + { op: 'c', 'o.applyOps': { $exists: true } }, + ] + }; + + // Data structures to support waitUntilCaughtUp(). Each oplog entry has a + // MongoTimestamp object on it (which is not the same as a Date --- it's a + // combination of time and an incrementing counter; see + // http://docs.mongodb.org/manual/reference/bson-types/#timestamps). + // + // _catchingUpFutures is an array of {ts: MongoTimestamp, future: Future} + // objects, sorted by ascending timestamp. _lastProcessedTS is the + // MongoTimestamp of the last oplog entry we've processed. + // + // Each time we call waitUntilCaughtUp, we take a peek at the final oplog + // entry in the db. If we've already processed it (ie, it is not greater than + // _lastProcessedTS), waitUntilCaughtUp immediately returns. Otherwise, + // waitUntilCaughtUp makes a new Future and inserts it along with the final + // timestamp entry that it read, into _catchingUpFutures. waitUntilCaughtUp + // then waits on that future, which is resolved once _lastProcessedTS is + // incremented to be past its timestamp by the worker fiber. + // + // XXX use a priority queue or something else that's faster than an array + self._catchingUpResolvers = []; + self._lastProcessedTS = null; + + self._onSkippedEntriesHook = new Hook({ + debugPrintExceptions: "onSkippedEntries callback" + }); + + self._entryQueue = new Meteor._DoubleEndedQueue(); + self._workerActive = false; + + const shouldAwait = self._startTailing(); + if (Meteor._isFibersEnabled) { + Promise.await(shouldAwait); + } +}; + +Object.assign(OplogHandle.prototype, { + stop: function () { + var self = this; + if (self._stopped) + return; + self._stopped = true; + if (self._tailHandle) + self._tailHandle.stop(); + // XXX should close connections too + }, + _onOplogEntry: async function(trigger, callback) { + var self = this; + if (self._stopped) + throw new Error("Called onOplogEntry on stopped handle!"); + + // Calling onOplogEntry requires us to wait for the tailing to be ready. + await self._readyPromise; + + var originalCallback = callback; + callback = Meteor.bindEnvironment(function (notification) { + originalCallback(notification); + }, function (err) { + Meteor._debug("Error in oplog callback", err); + }); + var listenHandle = self._crossbar.listen(trigger, callback); + return { + stop: function () { + listenHandle.stop(); + } + }; + }, + onOplogEntry: function (trigger, callback) { + return Meteor._isFibersEnabled ? Promise.await(this._onOplogEntry(trigger, callback)) : this._onOplogEntry(trigger, callback); + }, + // Register a callback to be invoked any time we skip oplog entries (eg, + // because we are too far behind). + onSkippedEntries: function (callback) { + var self = this; + if (self._stopped) + throw new Error("Called onSkippedEntries on stopped handle!"); + return self._onSkippedEntriesHook.register(callback); + }, + + async _waitUntilCaughtUp() { + var self = this; + if (self._stopped) + throw new Error("Called waitUntilCaughtUp on stopped handle!"); + + // Calling waitUntilCaughtUp requries us to wait for the oplog connection to + // be ready. + await self._readyPromise; + var lastEntry; + + while (!self._stopped) { + // We need to make the selector at least as restrictive as the actual + // tailing selector (ie, we need to specify the DB name) or else we might + // find a TS that won't show up in the actual tail stream. + try { + lastEntry = await self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, self._baseOplogSelector, + {fields: {ts: 1}, sort: {$natural: -1}}); + break; + } catch (e) { + // During failover (eg) if we get an exception we should log and retry + // instead of crashing. + Meteor._debug("Got exception while reading last entry", e); + await Meteor._sleepForMs(100); + } + } + + if (self._stopped) + return; + + if (!lastEntry) { + // Really, nothing in the oplog? Well, we've processed everything. + return; + } + + var ts = lastEntry.ts; + if (!ts) + throw Error("oplog entry without ts: " + EJSON.stringify(lastEntry)); + + if (self._lastProcessedTS && ts.lessThanOrEqual(self._lastProcessedTS)) { + // We've already caught up to here. + return; + } + + + // Insert the future into our list. Almost always, this will be at the end, + // but it's conceivable that if we fail over from one primary to another, + // the oplog entries we see will go backwards. + var insertAfter = self._catchingUpResolvers.length; + while (insertAfter - 1 > 0 && self._catchingUpResolvers[insertAfter - 1].ts.greaterThan(ts)) { + insertAfter--; + } + let promiseResolver = null; + const promiseToAwait = new Promise(r => promiseResolver = r); + self._catchingUpResolvers.splice(insertAfter, 0, {ts: ts, resolver: promiseResolver}); + await promiseToAwait; + }, + + // Calls `callback` once the oplog has been processed up to a point that is + // roughly "now": specifically, once we've processed all ops that are + // currently visible. + // XXX become convinced that this is actually safe even if oplogConnection + // is some kind of pool + waitUntilCaughtUp: function () { + return Meteor._isFibersEnabled ? Promise.await(this._waitUntilCaughtUp()) : this._waitUntilCaughtUp(); + }, + + _startTailing: async function () { + var self = this; + // First, make sure that we're talking to the local database. + var mongodbUri = Npm.require('mongodb-uri'); + if (mongodbUri.parse(self._oplogUrl).database !== 'local') { + throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + + "a Mongo replica set"); + } + + // We make two separate connections to Mongo. The Node Mongo driver + // implements a naive round-robin connection pool: each "connection" is a + // pool of several (5 by default) TCP connections, and each request is + // rotated through the pools. Tailable cursor queries block on the server + // until there is some data to return (or until a few seconds have + // passed). So if the connection pool used for tailing cursors is the same + // pool used for other queries, the other queries will be delayed by seconds + // 1/5 of the time. + // + // The tail connection will only ever be running a single tail command, so + // it only needs to make one underlying TCP connection. + self._oplogTailConnection = new MongoConnection( + self._oplogUrl, {maxPoolSize: 1}); + // XXX better docs, but: it's to get monotonic results + // XXX is it safe to say "if there's an in flight query, just use its + // results"? I don't think so but should consider that + self._oplogLastEntryConnection = new MongoConnection( + self._oplogUrl, {maxPoolSize: 1}); + + + const isMasterDoc = await Meteor.promisify((cb) => { + self._oplogLastEntryConnection.db.admin().command({ismaster: 1}, cb); + })(); + + if (!(isMasterDoc && isMasterDoc.setName)) { + throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + + "a Mongo replica set"); + } + + // Find the last oplog entry. + var lastOplogEntry = await self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); + + var oplogSelector = Object.assign({}, self._baseOplogSelector); + if (lastOplogEntry) { + // Start after the last entry that currently exists. + oplogSelector.ts = {$gt: lastOplogEntry.ts}; + // If there are any calls to callWhenProcessedLatest before any other + // oplog entries show up, allow callWhenProcessedLatest to call its + // callback immediately. + self._lastProcessedTS = lastOplogEntry.ts; + } + + var cursorDescription = new CursorDescription( + OPLOG_COLLECTION, oplogSelector, {tailable: true}); + + // Start tailing the oplog. + // + // We restart the low-level oplog query every 30 seconds if we didn't get a + // doc. This is a workaround for #8598: the Node Mongo driver has at least + // one bug that can lead to query callbacks never getting called (even with + // an error) when leadership failover occur. + self._tailHandle = self._oplogTailConnection.tail( + cursorDescription, + function (doc) { + self._entryQueue.push(doc); + self._maybeStartWorker(); + }, + TAIL_TIMEOUT + ); + + self._readyPromiseResolver(); + }, + + _maybeStartWorker: function () { + var self = this; + if (self._workerActive) return; + self._workerActive = true; + + Meteor.defer(function () { + // May be called recursively in case of transactions. + function handleDoc(doc) { + if (doc.ns === "admin.$cmd") { + if (doc.o.applyOps) { + // This was a successful transaction, so we need to apply the + // operations that were involved. + let nextTimestamp = doc.ts; + doc.o.applyOps.forEach(op => { + // See https://github.com/meteor/meteor/issues/10420. + if (!op.ts) { + op.ts = nextTimestamp; + nextTimestamp = nextTimestamp.add(Long.ONE); + } + handleDoc(op); + }); + return; + } + throw new Error("Unknown command " + EJSON.stringify(doc)); + } + + const trigger = { + dropCollection: false, + dropDatabase: false, + op: doc, + }; + + if (typeof doc.ns === "string" && + doc.ns.startsWith(self._dbName + ".")) { + trigger.collection = doc.ns.slice(self._dbName.length + 1); + } + + // Is it a special command and the collection name is hidden + // somewhere in operator? + if (trigger.collection === "$cmd") { + if (doc.o.dropDatabase) { + delete trigger.collection; + trigger.dropDatabase = true; + } else if (_.has(doc.o, "drop")) { + trigger.collection = doc.o.drop; + trigger.dropCollection = true; + trigger.id = null; + } else { + throw Error("Unknown command " + EJSON.stringify(doc)); + } + + } else { + // All other ops have an id. + trigger.id = idForOp(doc); + } + + self._crossbar.fire(trigger); + } + + try { + while (! self._stopped && + ! self._entryQueue.isEmpty()) { + // Are we too far behind? Just tell our observers that they need to + // repoll, and drop our queue. + if (self._entryQueue.length > TOO_FAR_BEHIND) { + var lastEntry = self._entryQueue.pop(); + self._entryQueue.clear(); + + self._onSkippedEntriesHook.each(function (callback) { + callback(); + return true; + }); + + // Free any waitUntilCaughtUp() calls that were waiting for us to + // pass something that we just skipped. + self._setLastProcessedTS(lastEntry.ts); + continue; + } + + const doc = self._entryQueue.shift(); + + // Fire trigger(s) for this doc. + handleDoc(doc); + + // Now that we've processed this operation, process pending + // sequencers. + if (doc.ts) { + self._setLastProcessedTS(doc.ts); + } else { + throw Error("oplog entry without ts: " + EJSON.stringify(doc)); + } + } + } finally { + self._workerActive = false; + } + }); + }, + + _setLastProcessedTS: function (ts) { + var self = this; + self._lastProcessedTS = ts; + while (!_.isEmpty(self._catchingUpResolvers) && self._catchingUpResolvers[0].ts.lessThanOrEqual(self._lastProcessedTS)) { + var sequencer = self._catchingUpResolvers.shift(); + sequencer.resolver(); + } + }, + + //Methods used on tests to dinamically change TOO_FAR_BEHIND + _defineTooFarBehind: function(value) { + TOO_FAR_BEHIND = value; + }, + _resetTooFarBehind: function() { + TOO_FAR_BEHIND = process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000; + } +}); diff --git a/packages/mongo/oplog_tests.js b/packages/mongo/oplog_tests.js new file mode 100644 index 0000000000..bb3374f8fb --- /dev/null +++ b/packages/mongo/oplog_tests.js @@ -0,0 +1,190 @@ +var OplogCollection = new Mongo.Collection("oplog-" + Random.id()); + +Tinytest.add("mongo-livedata - oplog - cursorSupported", function (test) { + var oplogEnabled = + !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; + + var supported = function (expected, selector, options) { + var cursor = OplogCollection.find(selector, options); + var handle = cursor.observeChanges({added: function () {}}); + // If there's no oplog at all, we shouldn't ever use it. + if (!oplogEnabled) + expected = false; + test.equal(!!handle._multiplexer._observeDriver._usesOplog, expected); + handle.stop(); + }; + + supported(true, "asdf"); + supported(true, 1234); + supported(true, new Mongo.ObjectID()); + + supported(true, {_id: "asdf"}); + supported(true, {_id: 1234}); + supported(true, {_id: new Mongo.ObjectID()}); + + supported(true, {foo: "asdf", + bar: 1234, + baz: new Mongo.ObjectID(), + eeney: true, + miney: false, + moe: null}); + + supported(true, {}); + + supported(true, {$and: [{foo: "asdf"}, {bar: "baz"}]}); + supported(true, {foo: {x: 1}}); + supported(true, {foo: {$gt: 1}}); + supported(true, {foo: [1, 2, 3]}); + + // No $where. + supported(false, {$where: "xxx"}); + supported(false, {$and: [{foo: "adsf"}, {$where: "xxx"}]}); + // No geoqueries. + supported(false, {x: {$near: [1,1]}}); + // Nothing Minimongo doesn't understand. (Minimongo happens to fail to + // implement $elemMatch inside $all which MongoDB supports.) + supported(false, {x: {$all: [{$elemMatch: {y: 2}}]}}); + + supported(true, {}, { sort: {x:1} }); + supported(true, {}, { sort: {x:1}, limit: 5 }); + supported(false, {}, { sort: {$natural:1}, limit: 5 }); + supported(false, {}, { limit: 5 }); + supported(false, {}, { skip: 2, limit: 5 }); + supported(false, {}, { skip: 2 }); +}); + +process.env.MONGO_OPLOG_URL && testAsyncMulti( + "mongo-livedata - oplog - entry skipping", [ + function (test, expect) { + var self = this; + self.collectionName = Random.id(); + self.collection = new Mongo.Collection(self.collectionName); + self.collection.createIndex({species: 1}); + + // Fill collection with lots of irrelevant objects (red cats) and some + // relevant ones (blue dogs). + + // After updating to mongo 3.2 with the 2.1.18 driver it was no longer + // possible to make this test fail with TOO_FAR_BEHIND = 2000. + // The documents waiting to be processed would hardly go beyond 1000 + // using mongo 3.2 with WiredTiger + MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle._defineTooFarBehind(500); + + self.IRRELEVANT_SIZE = 15000; + self.RELEVANT_SIZE = 10; + var docs = []; + var i; + for (i = 0; i < self.IRRELEVANT_SIZE; ++i) { + docs.push({ + name: "cat " + i, + species: 'cat', + color: 'red' + }); + } + for (i = 0; i < self.RELEVANT_SIZE; ++i) { + docs.push({ + name: "dog " + i, + species: 'dog', + color: 'blue' + }); + } + // XXX implement bulk insert #1255 + var rawCollection = self.collection.rawCollection(); + rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { + test.isFalse(err); + }))); + }, + + function (test, expect) { + var self = this; + + test.equal(self.collection.find().count(), + self.IRRELEVANT_SIZE + self.RELEVANT_SIZE); + + var blueDog5Id = null; + var gotSpot = false; + + // Watch for blue dogs. + const gotSpotPromise = new Promise(resolve => { + self.subHandle = self.collection.find({ + species: 'dog', + color: 'blue', + }).observeChanges({ + added(id, fields) { + if (fields.name === 'dog 5') { + blueDog5Id = id; + } + }, + changed(id, fields) { + if (EJSON.equals(id, blueDog5Id) && + fields.name === 'spot') { + gotSpot = true; + resolve(); + } + }, + }); + }); + + test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); + test.isTrue(blueDog5Id); + test.isFalse(gotSpot); + + self.skipped = false; + self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle.onSkippedEntries(function () { + self.skipped = true; + }); + + // Dye all the cats blue. This adds lots of oplog mentries that look like + // they might in theory be relevant (since they say "something you didn't + // know about is now blue", and who knows, maybe it's a dog) which puts + // the OplogObserveDriver into FETCHING mode, which performs poorly. + self.collection.update({species: 'cat'}, + {$set: {color: 'blue'}}, + {multi: true}); + self.collection.update(blueDog5Id, {$set: {name: 'spot'}}); + + // We ought to see the spot change soon! + return gotSpotPromise; + }, + + function (test, expect) { + var self = this; + test.isTrue(self.skipped); + + //This gets the TOO_FAR_BEHIND back to its initial value + MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle._resetTooFarBehind(); + + self.skipHandle.stop(); + self.subHandle.stop(); + self.collection.remove({}); + } + ] +); + + +// Meteor.isServer && Tinytest.addAsync( +// "mongo-livedata - oplog - _onFailover", +// async function (test) { +// const driver = MongoInternals.defaultRemoteCollectionDriver(); +// const failoverPromise = new Promise(resolve => { +// driver.mongo._onFailover(() => { +// resolve(true); +// }); +// }); +// +// +// await driver.mongo.db.admin().command({ +// replSetStepDown: 1, +// force: true +// }); +// +// try { +// const result = await failoverPromise; +// test.isTrue(result); +// } catch (e) { +// test.fail({ message: "Error waiting on Promise", value: JSON.stringify(e) }); +// } +// }); diff --git a/packages/mongo/oplog_v2_converter.js b/packages/mongo/oplog_v2_converter.js new file mode 100644 index 0000000000..952a37478f --- /dev/null +++ b/packages/mongo/oplog_v2_converter.js @@ -0,0 +1,124 @@ +// Converter of the new MongoDB Oplog format (>=5.0) to the one that Meteor +// handles well, i.e., `$set` and `$unset`. The new format is completely new, +// and looks as follows: +// +// { $v: 2, diff: Diff } +// +// where `Diff` is a recursive structure: +// +// { +// // Nested updates (sometimes also represented with an s-field). +// // Example: `{ $set: { 'foo.bar': 1 } }`. +// i: { : , ... }, +// +// // Top-level updates. +// // Example: `{ $set: { foo: { bar: 1 } } }`. +// u: { : , ... }, +// +// // Unsets. +// // Example: `{ $unset: { foo: '' } }`. +// d: { : false, ... }, +// +// // Array operations. +// // Example: `{ $push: { foo: 'bar' } }`. +// s: { a: true, u: , ... }, +// ... +// +// // Nested operations (sometimes also represented in the `i` field). +// // Example: `{ $set: { 'foo.bar': 1 } }`. +// s: Diff, +// ... +// } +// +// (all fields are optional). + +function join(prefix, key) { + return prefix ? `${prefix}.${key}` : key; +} + +const arrayOperatorKeyRegex = /^(a|u\d+)$/; + +function isArrayOperatorKey(field) { + return arrayOperatorKeyRegex.test(field); +} + +function isArrayOperator(operator) { + return operator.a === true && Object.keys(operator).every(isArrayOperatorKey); +} + +function flattenObjectInto(target, source, prefix) { + if (Array.isArray(source) || typeof source !== 'object' || source === null) { + target[prefix] = source; + } else { + const entries = Object.entries(source); + if (entries.length) { + entries.forEach(([key, value]) => { + flattenObjectInto(target, value, join(prefix, key)); + }); + } else { + target[prefix] = source; + } + } +} + +const logDebugMessages = !!process.env.OPLOG_CONVERTER_DEBUG; + +function convertOplogDiff(oplogEntry, diff, prefix) { + if (logDebugMessages) { + console.log(`convertOplogDiff(${JSON.stringify(oplogEntry)}, ${JSON.stringify(diff)}, ${JSON.stringify(prefix)})`); + } + + Object.entries(diff).forEach(([diffKey, value]) => { + if (diffKey === 'd') { + // Handle `$unset`s. + oplogEntry.$unset ??= {}; + Object.keys(value).forEach(key => { + oplogEntry.$unset[join(prefix, key)] = true; + }); + } else if (diffKey === 'i') { + // Handle (potentially) nested `$set`s. + oplogEntry.$set ??= {}; + flattenObjectInto(oplogEntry.$set, value, prefix); + } else if (diffKey === 'u') { + // Handle flat `$set`s. + oplogEntry.$set ??= {}; + Object.entries(value).forEach(([key, value]) => { + oplogEntry.$set[join(prefix, key)] = value; + }); + } else { + // Handle s-fields. + const key = diffKey.slice(1); + if (isArrayOperator(value)) { + // Array operator. + Object.entries(value).forEach(([position, value]) => { + if (position === 'a') { + return; + } + + const positionKey = join(join(prefix, key), position.slice(1)); + if (value === null) { + oplogEntry.$unset ??= {}; + oplogEntry.$unset[positionKey] = true; + } else { + oplogEntry.$set ??= {}; + oplogEntry.$set[positionKey] = value; + } + }); + } else if (key) { + // Nested object. + convertOplogDiff(oplogEntry, value, join(prefix, key)); + } + } + }); +} + +export function oplogV2V1Converter(oplogEntry) { + // Pass-through v1 and (probably) invalid entries. + if (oplogEntry.$v !== 2 || !oplogEntry.diff) { + return oplogEntry; + } + + const convertedOplogEntry = { $v: 2 }; + convertOplogDiff(convertedOplogEntry, oplogEntry.diff, ''); + return convertedOplogEntry; +} diff --git a/packages/mongo/oplog_v2_converter_tests.js b/packages/mongo/oplog_v2_converter_tests.js new file mode 100644 index 0000000000..f87c8877f3 --- /dev/null +++ b/packages/mongo/oplog_v2_converter_tests.js @@ -0,0 +1,86 @@ +import { oplogV2V1Converter } from './oplog_v2_converter'; + +const cases = [ + [ + { $v: 2, diff: { scustom: { sEJSON$value: { u: { EJSONtail: 'd' } } } } }, + { $v: 2, $set: { 'custom.EJSON$value.EJSONtail': 'd' } }, + ], + [ + { $v: 2, diff: { u: { d: '2', oi: 'asdas' } } }, + { $v: 2, $set: { d: '2', oi: 'asdas' } }, + ], + [ + { $v: 2, diff: { sasd: { a: true, u0: 2 } } }, + { $v: 2, $set: { 'asd.0': 2 } }, + ], + [ + { $v: 2, diff: { sasd: { a: true, u0: null } } }, + { $v: 2, $unset: { 'asd.0': true } }, + ], + [ + { $v: 2, diff: { i: { a: { b: 2 } } } }, + { $v: 2, $set: { 'a.b': 2 } }, + ], + [ + { $v: 2, diff: { u: { count: 1 }, i: { nested: { state: {} } } } }, + { $v: 2, $set: { 'nested.state': {}, count: 1 } }, + ], + [ + { $v: 2, diff: { sa: { i: { b: 3, c: 1 } } } }, + { $v: 2, $set: { 'a.b': 3, 'a.c': 1 } }, + ], + [ + { $v: 2, diff: { sa: { d: { b: false } } } }, + { $v: 2, $unset: { 'a.b': true } }, + ], + [ + { $v: 2, diff: { u: { c: 'bar' }, sb: { a: true, u0: 2 } } }, + { $v: 2, $set: { 'b.0': 2, c: 'bar' } }, + ], + [ + { $v: 2, diff: { sservices: { sresume: { u: { loginTokens: [] } } } } }, + { $v: 2, $set: { 'services.resume.loginTokens': [] } }, + ], + [ + { $v: 2, diff: { i: { tShirt: { sizes: ['small', 'medium', 'large'] } } } }, + { $v: 2, $set: { 'tShirt.sizes': ['small', 'medium', 'large'] } }, + ], + [ + { $v: 2, diff: { slist: { a: true, u3: 'i', u4: 'h' } } }, + { $v: 2, $set: { 'list.3': 'i', 'list.4': 'h' } }, + ], + [ + { $v: 2, $set: { 'services.resume.loginTokens': [ { when: '2022-01-06T23:58:35.704Z', hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=' }, { when: '2022-01-06T23:58:35.704Z', hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=' }, ], }, }, + { $v: 2, $set: { 'services.resume.loginTokens': [ { when: '2022-01-06T23:58:35.704Z', hashedToken: 'RlalW6ZSvPPJLH6sW3B1b+vrUnPy+Ox5oMv3O3S7jwg=' }, { when: '2022-01-06T23:58:35.704Z', hashedToken: 'DWG0Qw/+nZ48wAIhKR2r9H41wLpth9BM+Br6aZsl2bU=' }, ], }, }, + ], + [ + { $v: 2, diff: { sobject: { u: { array: ['2', '2', '4', '3'] } } } }, + { $v: 2, $set: { 'object.array': ['2', '2', '4', '3'] } }, + ], + [ + { $v: 2, diff: { slayout: { sjourneyStepIds: { sj4aqp3tiK6xCPCYu8: { a: true, u2: 'zTkxivNrKuBi2iJ2m' } } } } }, + { $v: 2, $set: { 'layout.journeyStepIds.j4aqp3tiK6xCPCYu8.2': 'zTkxivNrKuBi2iJ2m' } }, + ], + [ + { $v: 2, diff: { sarray: { a: true, s2: { u: { a: 'something' } } } } }, + { $v: 2, $set: { 'array.2.a': 'something' } }, + ], + [ + { $v: 2, diff: { u: { params: { d: 5 } } } }, + { $v: 2, $set: { params: { d: 5 } } }, + ], + [ + { $v: 2, diff: { u: { params: { a: 5, d: 5 } } } }, + { $v: 2, $set: { params: { a: 5, d: 5 } } }, + ], + [ + { $v: 2, diff: { u: { params: { e: { _str: '5f953cde8ceca90030bdb86f' } } } } }, + { $v: 2, $set: { params: { e: { _str: '5f953cde8ceca90030bdb86f' } } } }, + ], +]; + +Tinytest.add('oplog - v2/v1 conversion', function (test) { + cases.forEach(([input, output]) => { + test.equal(oplogV2V1Converter(input), output); + }); +}); diff --git a/packages/mongo/package.js b/packages/mongo/package.js new file mode 100644 index 0000000000..f31b7efe27 --- /dev/null +++ b/packages/mongo/package.js @@ -0,0 +1,105 @@ +// XXX We should revisit how we factor MongoDB support into (1) the +// server-side node.js driver [which you might use independently of +// livedata, after all], (2) minimongo [ditto], and (3) Collection, +// which is the class that glues the two of them to Livedata, but also +// is generally the "public interface for newbies" to Mongo in the +// Meteor universe. We want to allow the components to be used +// independently, but we don't want to overwhelm the user with +// minutiae. + +Package.describe({ + summary: "Adaptor for using MongoDB and Minimongo over DDP", + version: '1.16.0' +}); + +Npm.depends({ + "mongodb-uri": "0.9.7" +}); + +Npm.strip({ + mongodb: ["test/"] +}); + +Package.onUse(function (api) { + api.use('npm-mongo', 'server'); + api.use('allow-deny'); + + api.use([ + 'random', + 'ejson', + 'minimongo', + 'ddp', + 'tracker', + 'diff-sequence', + 'mongo-id', + 'check', + 'ecmascript', + 'mongo-dev-server', + 'logging' + ]); + + // Make weak use of Decimal type on client + api.use('mongo-decimal', 'client', {weak: true}); + api.use('mongo-decimal', 'server'); + + api.use('underscore', 'server'); + + // Binary Heap data structure is used to optimize oplog observe driver + // performance. + api.use('binary-heap', 'server'); + + // Allow us to detect 'insecure'. + api.use('insecure', {weak: true}); + + // Allow us to detect 'autopublish', and publish collections if it's loaded. + api.use('autopublish', 'server', {weak: true}); + + // Allow us to detect 'disable-oplog', which turns off oplog tailing for your + // app even if it's configured in the environment. (This package will be + // probably be removed before 1.0.) + api.use('disable-oplog', 'server', {weak: true}); + + // defaultRemoteCollectionDriver gets its deployConfig from something that is + // (for questionable reasons) initialized by the webapp package. + api.use('webapp', 'server', {weak: true}); + + // If the facts package is loaded, publish some statistics. + api.use('facts-base', 'server', {weak: true}); + + api.use('callback-hook', 'server'); + + // Stuff that should be exposed via a real API, but we haven't yet. + api.export('MongoInternals', 'server'); + + api.export("Mongo"); + api.export('ObserveMultiplexer', 'server', {testOnly: true}); + + api.addFiles(['mongo_driver.js', 'oplog_tailing.js', + 'observe_multiplex.js', 'doc_fetcher.js', + 'polling_observe_driver.js','oplog_observe_driver.js', 'oplog_v2_converter.js'], + 'server'); + api.addFiles('local_collection_driver.js', ['client', 'server']); + api.addFiles('remote_collection_driver.js', 'server'); + api.addFiles('collection.js', ['client', 'server']); + api.addFiles('connection_options.js', 'server'); +}); + +Package.onTest(function (api) { + api.use('mongo'); + api.use('check'); + api.use('ecmascript'); + api.use('npm-mongo', 'server'); + api.use(['tinytest', 'underscore', 'test-helpers', 'ejson', 'random', + 'ddp', 'base64']); + // XXX test order dependency: the allow_tests "partial allow" test + // fails if it is run before mongo_livedata_tests. + api.addFiles('mongo_livedata_tests.js', ['client', 'server']); + api.addFiles('upsert_compatibility_test.js', 'server'); + api.addFiles('allow_tests.js', ['client', 'server']); + api.addFiles('collection_tests.js', ['client', 'server']); + api.addFiles('collection_async_tests.js', ['client', 'server']); + api.addFiles('observe_changes_tests.js', ['client', 'server']); + api.addFiles('oplog_tests.js', 'server'); + api.addFiles('oplog_v2_converter_tests.js', 'server'); + api.addFiles('doc_fetcher_tests.js', 'server'); +}); diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js new file mode 100644 index 0000000000..32744abeba --- /dev/null +++ b/packages/mongo/polling_observe_driver.js @@ -0,0 +1,325 @@ +var POLLING_THROTTLE_MS = +process.env.METEOR_POLLING_THROTTLE_MS || 50; +var POLLING_INTERVAL_MS = +process.env.METEOR_POLLING_INTERVAL_MS || 10 * 1000; + +PollingObserveDriver = function (options) { + var self = this; + + self._cursorDescription = options.cursorDescription; + self._mongoHandle = options.mongoHandle; + self._ordered = options.ordered; + self._multiplexer = options.multiplexer; + self._stopCallbacks = []; + self._stopped = false; + + self._cursor = self._mongoHandle._createSynchronousCursor( + self._cursorDescription); + + // previous results snapshot. on each poll cycle, diffs against + // results drives the callbacks. + self._results = null; + + // The number of _pollMongo calls that have been added to self._taskQueue but + // have not started running. Used to make sure we never schedule more than one + // _pollMongo (other than possibly the one that is currently running). It's + // also used by _suspendPolling to pretend there's a poll scheduled. Usually, + // it's either 0 (for "no polls scheduled other than maybe one currently + // running") or 1 (for "a poll scheduled that isn't running yet"), but it can + // also be 2 if incremented by _suspendPolling. + self._pollsScheduledButNotStarted = 0; + self._pendingWrites = []; // people to notify when polling completes + + // Make sure to create a separately throttled function for each + // PollingObserveDriver object. + self._ensurePollIsScheduled = _.throttle( + self._unthrottledEnsurePollIsScheduled, + self._cursorDescription.options.pollingThrottleMs || POLLING_THROTTLE_MS /* ms */); + + // XXX figure out if we still need a queue + self._taskQueue = new Meteor._SynchronousQueue(); + + var listenersHandle = listenAll( + self._cursorDescription, function (notification) { + // When someone does a transaction that might affect us, schedule a poll + // of the database. If that transaction happens inside of a write fence, + // block the fence until we've polled and notified observers. + var fence = DDPServer._CurrentWriteFence.get(); + if (fence) + self._pendingWrites.push(fence.beginWrite()); + // Ensure a poll is scheduled... but if we already know that one is, + // don't hit the throttled _ensurePollIsScheduled function (which might + // lead to us calling it unnecessarily in ms). + if (self._pollsScheduledButNotStarted === 0) + self._ensurePollIsScheduled(); + } + ); + self._stopCallbacks.push(function () { listenersHandle.stop(); }); + + // every once and a while, poll even if we don't think we're dirty, for + // eventual consistency with database writes from outside the Meteor + // universe. + // + // For testing, there's an undocumented callback argument to observeChanges + // which disables time-based polling and gets called at the beginning of each + // poll. + if (options._testOnlyPollCallback) { + self._testOnlyPollCallback = options._testOnlyPollCallback; + } else { + var pollingInterval = + self._cursorDescription.options.pollingIntervalMs || + self._cursorDescription.options._pollingInterval || // COMPAT with 1.2 + POLLING_INTERVAL_MS; + var intervalHandle = Meteor.setInterval( + _.bind(self._ensurePollIsScheduled, self), pollingInterval); + self._stopCallbacks.push(function () { + Meteor.clearInterval(intervalHandle); + }); + } +}; + +_.extend(PollingObserveDriver.prototype, { + _initAsync: async function () { + // Make sure we actually poll soon! + await this._unthrottledEnsurePollIsScheduled(); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", 1); + }, + _init() { + if (!Meteor._isFibersEnabled) { + return this._initAsync(); + } + + var self = this; + // Make sure we actually poll soon! + self._unthrottledEnsurePollIsScheduled(); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", 1); + }, + // This is always called through _.throttle (except once at startup). + _unthrottledEnsurePollIsScheduled: function () { + var self = this; + if (self._pollsScheduledButNotStarted > 0) + return; + ++self._pollsScheduledButNotStarted; + self._taskQueue.queueTask(function () { + self._pollMongo(); + }); + }, + + // test-only interface for controlling polling. + // + // _suspendPolling blocks until any currently running and scheduled polls are + // done, and prevents any further polls from being scheduled. (new + // ObserveHandles can be added and receive their initial added callbacks, + // though.) + // + // _resumePolling immediately polls, and allows further polls to occur. + _suspendPolling: function() { + var self = this; + // Pretend that there's another poll scheduled (which will prevent + // _ensurePollIsScheduled from queueing any more polls). + ++self._pollsScheduledButNotStarted; + // Now block until all currently running or scheduled polls are done. + self._taskQueue.runTask(function() {}); + + // Confirm that there is only one "poll" (the fake one we're pretending to + // have) scheduled. + if (self._pollsScheduledButNotStarted !== 1) + throw new Error("_pollsScheduledButNotStarted is " + + self._pollsScheduledButNotStarted); + }, + _resumePolling: function() { + var self = this; + // We should be in the same state as in the end of _suspendPolling. + if (self._pollsScheduledButNotStarted !== 1) + throw new Error("_pollsScheduledButNotStarted is " + + self._pollsScheduledButNotStarted); + // Run a poll synchronously (which will counteract the + // ++_pollsScheduledButNotStarted from _suspendPolling). + self._taskQueue.runTask(function () { + self._pollMongo(); + }); + }, + + _pollMongoFibers: function () { + var self = this; + --self._pollsScheduledButNotStarted; + + if (self._stopped) + return; + + var first = false; + var newResults; + var oldResults = self._results; + if (!oldResults) { + first = true; + // XXX maybe use OrderedDict instead? + oldResults = self._ordered ? [] : new LocalCollection._IdMap; + } + + self._testOnlyPollCallback && self._testOnlyPollCallback(); + + // Save the list of pending writes which this round will commit. + var writesForCycle = self._pendingWrites; + self._pendingWrites = []; + + // Get the new query results. (This yields.) + try { + newResults = self._cursor.getRawObjects(self._ordered); + } catch (e) { + if (first && typeof(e.code) === 'number') { + // This is an error document sent to us by mongod, not a connection + // error generated by the client. And we've never seen this query work + // successfully. Probably it's a bad selector or something, so we should + // NOT retry. Instead, we should halt the observe (which ends up calling + // `stop` on us). + self._multiplexer.queryError( + new Error( + "Exception while polling query " + + JSON.stringify(self._cursorDescription) + ": " + e.message)); + return; + } + + // getRawObjects can throw if we're having trouble talking to the + // database. That's fine --- we will repoll later anyway. But we should + // make sure not to lose track of this cycle's writes. + // (It also can throw if there's just something invalid about this query; + // unfortunately the ObserveDriver API doesn't provide a good way to + // "cancel" the observe from the inside in this case. + Array.prototype.push.apply(self._pendingWrites, writesForCycle); + Meteor._debug("Exception while polling query " + + JSON.stringify(self._cursorDescription), e); + return; + } + + // Run diffs. + if (!self._stopped) { + LocalCollection._diffQueryChanges( + self._ordered, oldResults, newResults, self._multiplexer); + } + + // Signals the multiplexer to allow all observeChanges calls that share this + // multiplexer to return. (This happens asynchronously, via the + // multiplexer's queue.) + if (first) + self._multiplexer.ready(); + + // Replace self._results atomically. (This assignment is what makes `first` + // stay through on the next cycle, so we've waited until after we've + // committed to ready-ing the multiplexer.) + self._results = newResults; + + // Once the ObserveMultiplexer has processed everything we've done in this + // round, mark all the writes which existed before this call as + // commmitted. (If new writes have shown up in the meantime, there'll + // already be another _pollMongo task scheduled.) + self._multiplexer.onFlush(function () { + _.each(writesForCycle, function (w) { + w.committed(); + }); + }); + }, + + async _pollMongoNoFibers() { + var self = this; + --self._pollsScheduledButNotStarted; + + if (self._stopped) + return; + + var first = false; + var newResults; + var oldResults = self._results; + if (!oldResults) { + first = true; + // XXX maybe use OrderedDict instead? + oldResults = self._ordered ? [] : new LocalCollection._IdMap; + } + + self._testOnlyPollCallback && self._testOnlyPollCallback(); + + // Save the list of pending writes which this round will commit. + var writesForCycle = self._pendingWrites; + self._pendingWrites = []; + + // Get the new query results. (This yields.) + try { + newResults = await self._cursor.getRawObjects(self._ordered); + } catch (e) { + if (first && typeof(e.code) === 'number') { + // This is an error document sent to us by mongod, not a connection + // error generated by the client. And we've never seen this query work + // successfully. Probably it's a bad selector or something, so we should + // NOT retry. Instead, we should halt the observe (which ends up calling + // `stop` on us). + self._multiplexer.queryError( + new Error( + "Exception while polling query " + + JSON.stringify(self._cursorDescription) + ": " + e.message)); + return; + } + + // getRawObjects can throw if we're having trouble talking to the + // database. That's fine --- we will repoll later anyway. But we should + // make sure not to lose track of this cycle's writes. + // (It also can throw if there's just something invalid about this query; + // unfortunately the ObserveDriver API doesn't provide a good way to + // "cancel" the observe from the inside in this case. + Array.prototype.push.apply(self._pendingWrites, writesForCycle); + Meteor._debug("Exception while polling query " + + JSON.stringify(self._cursorDescription), e); + return; + } + + // Run diffs. + if (!self._stopped) { + LocalCollection._diffQueryChanges( + self._ordered, oldResults, newResults, self._multiplexer); + } + + // Signals the multiplexer to allow all observeChanges calls that share this + // multiplexer to return. (This happens asynchronously, via the + // multiplexer's queue.) + if (first) + self._multiplexer.ready(); + + // Replace self._results atomically. (This assignment is what makes `first` + // stay through on the next cycle, so we've waited until after we've + // committed to ready-ing the multiplexer.) + self._results = newResults; + + // Once the ObserveMultiplexer has processed everything we've done in this + // round, mark all the writes which existed before this call as + // commmitted. (If new writes have shown up in the meantime, there'll + // already be another _pollMongo task scheduled.) + self._multiplexer.onFlush(function () { + _.each(writesForCycle, function (w) { + w.committed(); + }); + }); + }, + + _pollMongo: function () { + return Meteor._isFibersEnabled ? this._pollMongoFibers() : this._pollMongoNoFibers(); + }, + + stop: function () { + var self = this; + self._stopped = true; + const stopCallbacksCallerFibers = function (c) { + c(); + }; + const stopCallbacksCallerNoFibers = async function(c) { + await c(); + }; + + _.each(self._stopCallbacks, Meteor._isFibersEnabled ? stopCallbacksCallerFibers : stopCallbacksCallerNoFibers); + // Release any write fences that are waiting on us. + _.each(self._pendingWrites, function (w) { + w.committed(); + }); + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", -1); + } +}); diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo/remote_collection_driver.js new file mode 100644 index 0000000000..a7b654135c --- /dev/null +++ b/packages/mongo/remote_collection_driver.js @@ -0,0 +1,48 @@ +MongoInternals.RemoteCollectionDriver = function ( + mongo_url, options) { + var self = this; + self.mongo = new MongoConnection(mongo_url, options); +}; + +Object.assign(MongoInternals.RemoteCollectionDriver.prototype, { + open: function (name) { + var self = this; + var ret = {}; + ['find', 'findOne', 'insert', 'update', 'upsert', + 'remove', '_ensureIndex', 'createIndex', '_dropIndex', '_createCappedCollection', + 'dropCollection', 'rawCollection'].forEach( + function (m) { + ret[m] = _.bind(self.mongo[m], self.mongo, name); + }); + return ret; + } +}); + +// Create the singleton RemoteCollectionDriver only on demand, so we +// only require Mongo configuration if it's actually used (eg, not if +// you're only trying to receive data from a remote DDP server.) +MongoInternals.defaultRemoteCollectionDriver = _.once(function () { + var connectionOptions = {}; + + var mongoUrl = process.env.MONGO_URL; + + if (process.env.MONGO_OPLOG_URL) { + connectionOptions.oplogUrl = process.env.MONGO_OPLOG_URL; + } + + if (! mongoUrl) + throw new Error("MONGO_URL must be set in environment"); + + const driver = new MongoInternals.RemoteCollectionDriver(mongoUrl, connectionOptions); + + // As many deployment tools, including Meteor Up, send requests to the app in + // order to confirm that the deployment finished successfully, it's required + // to know about a database connection problem before the app starts. Doing so + // in a `Meteor.startup` is fine, as the `WebApp` handles requests only after + // all are finished. + Meteor.startup(async () => { + await driver.mongo.client.connect(); + }); + + return driver; +}); diff --git a/packages/mongo/upsert_compatibility_test.js b/packages/mongo/upsert_compatibility_test.js new file mode 100644 index 0000000000..dab3c6b3d3 --- /dev/null +++ b/packages/mongo/upsert_compatibility_test.js @@ -0,0 +1,151 @@ +Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS update', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); + + coll.insert({foo: 1}); + var result = coll.upsert({foo: 1}, {$set: {foo:2}}); + var updated = coll.findOne({foo: 2}); + + test.equal(result.insertedId, undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(updated._id instanceof Mongo.ObjectID); + + delete updated['_id']; + test.equal(EJSON.equals(updated, {foo: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS insert', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); + + var result = coll.upsert({foo: 1}, {$set: {bar:2}}); + var inserted = coll.findOne({foo: 1}); + + test.isTrue(result.insertedId !== undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(inserted._id instanceof Mongo.ObjectID); + test.equal(inserted._id, result.insertedId); + + delete inserted['_id']; + test.equal(EJSON.equals(inserted, {foo: 1, bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT update', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); + + coll.insert({foo: 1, baz: 42}); + var result = coll.upsert({foo: 1}, {bar:2}); + var updated = coll.findOne({bar: 2}); + + test.isTrue(result.insertedId === undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(updated._id instanceof Mongo.ObjectID); + + delete updated['_id']; + test.equal(EJSON.equals(updated, {bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT insert', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); + + var result = coll.upsert({foo: 1}, {bar:2}); + var inserted = coll.findOne({bar: 2}); + + test.isTrue(result.insertedId !== undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(inserted._id instanceof Mongo.ObjectID); + test.isTrue(result.insertedId instanceof Mongo.ObjectID); + test.equal(inserted._id, result.insertedId); + + delete inserted['_id']; + test.equal(EJSON.equals(inserted, {bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS update', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); + + coll.insert({foo: 1}); + var result = coll.upsert({foo: 1}, {$set: {foo:2}}); + var updated = coll.findOne({foo: 2}); + + test.equal(result.insertedId, undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(typeof updated._id === 'string'); + + delete updated['_id']; + test.equal(EJSON.equals(updated, {foo: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS insert', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); + + var result = coll.upsert({foo: 1}, {$set: {bar:2}}); + var inserted = coll.findOne({foo: 1}); + + test.isTrue(result.insertedId !== undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(typeof inserted._id === 'string'); + test.equal(inserted._id, result.insertedId); + + delete inserted['_id']; + test.equal(EJSON.equals(inserted, {foo: 1, bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT update', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); + + coll.insert({foo: 1, baz: 42}); + var result = coll.upsert({foo: 1}, {bar:2}); + var updated = coll.findOne({bar: 2}); + + test.isTrue(result.insertedId === undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(typeof updated._id === 'string'); + + delete updated['_id']; + test.equal(EJSON.equals(updated, {bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT insert', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); + + var result = coll.upsert({foo: 1}, {bar:2}); + var inserted = coll.findOne({bar: 2}); + + test.isTrue(result.insertedId !== undefined); + test.equal(result.numberAffected, 1); + + test.isTrue(typeof inserted._id === 'string'); + test.equal(inserted._id, result.insertedId); + + delete inserted['_id']; + test.equal(EJSON.equals(inserted, {bar: 2}), true); +}); + +Tinytest.add('mongo livedata - native upsert - MONGO passing id insert', function (test) { + var collName = Random.id(); + var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); + + var result = coll.upsert({foo: 1}, {_id: 'meu id'}); + var inserted = coll.findOne({_id: 'meu id'}); + + test.equal(result.insertedId, 'meu id'); + test.equal(result.numberAffected, 1); + + test.isTrue(typeof inserted._id === 'string'); + + test.equal(EJSON.equals(inserted, {_id: 'meu id'}), true); +}); From 6e8e4ff6c9819ec63858c565c16968d38f6284f9 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 4 Nov 2022 16:41:06 -0300 Subject: [PATCH 0038/1965] Revert changes to mongo with fibers package --- packages/mongo/collection.js | 183 ++------ packages/mongo/doc_fetcher.js | 12 +- packages/mongo/mongo_driver.js | 499 ++++----------------- packages/mongo/observe_multiplex.js | 260 +++++------ packages/mongo/oplog_observe_driver.js | 131 +++--- packages/mongo/oplog_tailing.js | 113 +++-- packages/mongo/package.js | 2 +- packages/mongo/polling_observe_driver.js | 123 +---- packages/mongo/remote_collection_driver.js | 4 +- 9 files changed, 396 insertions(+), 931 deletions(-) diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 98141d83c7..3dcc12dc96 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -412,22 +412,22 @@ Object.assign(Mongo.Collection.prototype, { }); Object.assign(Mongo.Collection, { - _publishCursorFibers(cursor, sub, collection) { + _publishCursor(cursor, sub, collection) { var observeHandle = cursor.observeChanges( - { - added: function(id, fields) { - sub.added(collection, id, fields); - }, - changed: function(id, fields) { - sub.changed(collection, id, fields); - }, - removed: function(id) { - sub.removed(collection, id); - }, + { + added: function(id, fields) { + sub.added(collection, id, fields); }, - // Publications don't mutate the documents - // This is tested by the `livedata - publish callbacks clone` test - { nonMutatingCallbacks: true } + changed: function(id, fields) { + sub.changed(collection, id, fields); + }, + removed: function(id) { + sub.removed(collection, id); + }, + }, + // Publications don't mutate the documents + // This is tested by the `livedata - publish callbacks clone` test + { nonMutatingCallbacks: true } ); // We don't call sub.ready() here: it gets called in livedata_server, after @@ -442,42 +442,6 @@ Object.assign(Mongo.Collection, { return observeHandle; }, - async _publishCursorNoFibers(cursor, sub, collection) { - var observeHandle = await cursor.observeChanges( - { - added: function(id, fields) { - sub.added(collection, id, fields); - }, - changed: function(id, fields) { - sub.changed(collection, id, fields); - }, - removed: function(id) { - sub.removed(collection, id); - }, - }, - // Publications don't mutate the documents - // This is tested by the `livedata - publish callbacks clone` test - { nonMutatingCallbacks: true } - ); - - // 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() { - return observeHandle.stop(); - }); - - // return the observeHandle in case it needs to be stopped early - return observeHandle; - }, - - _publishCursor(cursor, sub, collection) { - return Meteor._isFibersEnabled - ? this._publishCursorFibers(cursor, sub, collection) - : this._publishCursorNoFibers(cursor, sub, collection); - }, - // protect against dangerous selectors. falsey and {_id: falsey} are both // likely programmer error, and not what you want, particularly for destructive // operations. If a falsey _id is sent in, a new string _id will be @@ -533,7 +497,16 @@ Object.assign(Mongo.Collection.prototype, { // them. In the future maybe we should provide a flag to turn this // off. - _insertSync(doc, callback) { + /** + * @summary Insert a document in the collection. Returns its unique _id. + * @locus Anywhere + * @method insert + * @memberof Mongo.Collection + * @instance + * @param {Object} doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. + * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. + */ + insert(doc, callback) { // Make sure we were passed a document to insert if (!doc) { throw new Error('insert requires an argument'); @@ -541,17 +514,17 @@ Object.assign(Mongo.Collection.prototype, { // Make a shallow clone of the document, preserving its prototype. doc = Object.create( - Object.getPrototypeOf(doc), - Object.getOwnPropertyDescriptors(doc) + Object.getPrototypeOf(doc), + Object.getOwnPropertyDescriptors(doc) ); if ('_id' in doc) { if ( - !doc._id || - !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) + !doc._id || + !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) ) { throw new Error( - 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' + 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' ); } } else { @@ -588,8 +561,8 @@ Object.assign(Mongo.Collection.prototype, { }; const wrappedCallback = wrapCallback( - callback, - chooseReturnValueFromCollectionResult + callback, + chooseReturnValueFromCollectionResult ); if (this._isRemoteCollection()) { @@ -614,100 +587,6 @@ Object.assign(Mongo.Collection.prototype, { } }, - async _insertAsync(doc, callback) { - // Make sure we were passed a document to insert - if (!doc) { - throw new Error('insert requires an argument'); - } - - // Make a shallow clone of the document, preserving its prototype. - doc = Object.create( - Object.getPrototypeOf(doc), - Object.getOwnPropertyDescriptors(doc) - ); - - if ('_id' in doc) { - if ( - !doc._id || - !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) - ) { - throw new Error( - 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' - ); - } - } else { - let generateId = true; - - // Don't generate the id if we're the client and the 'outermost' call - // This optimization saves us passing both the randomSeed and the id - // Passing both is redundant. - if (this._isRemoteCollection()) { - const enclosing = DDP._CurrentMethodInvocation.get(); - if (!enclosing) { - generateId = false; - } - } - - if (generateId) { - doc._id = this._makeNewID(); - } - } - - // On inserts, always return the id that we generated; on all other - // operations, just return the result from the collection. - var chooseReturnValueFromCollectionResult = function(result) { - if (doc._id) { - return doc._id; - } - - // XXX what is this for?? - // It's some iteraction between the callback to _callMutatorMethod and - // the return value conversion - doc._id = result; - - return result; - }; - - const wrappedCallback = wrapCallback( - callback, - chooseReturnValueFromCollectionResult - ); - - if (this._isRemoteCollection()) { - const result = this._callMutatorMethod('insert', [doc], wrappedCallback); - return chooseReturnValueFromCollectionResult(result); - } - - // it's my collection. descend into the collection object - // and propagate any exception. - try { - // If the user provided a callback and the collection implements this - // operation asynchronously, then queryRet will be undefined, and the - // result will be returned through the callback instead. - const result = await this._collection.insert(doc, wrappedCallback); - return chooseReturnValueFromCollectionResult(result); - } catch (e) { - if (callback) { - callback(e); - return null; - } - throw e; - } - }, - - /** - * @summary Insert a document in the collection. Returns its unique _id. - * @locus Anywhere - * @method insert - * @memberof Mongo.Collection - * @instance - * @param {Object} doc The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. - * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. - */ - insert(doc, callback) { - return Meteor._isFibersEnabled ? this._insertSync(doc, callback) : this._insertAsync(doc, callback); - }, - /** * @summary Modify one or more documents in the collection. Returns the number of matched documents. * @locus Anywhere diff --git a/packages/mongo/doc_fetcher.js b/packages/mongo/doc_fetcher.js index 3d4740bf34..2b3412d39c 100644 --- a/packages/mongo/doc_fetcher.js +++ b/packages/mongo/doc_fetcher.js @@ -1,3 +1,5 @@ +var Fiber = Npm.require('fibers'); + export class DocFetcher { constructor(mongoConnection) { this._mongoConnection = mongoConnection; @@ -30,9 +32,9 @@ export class DocFetcher { const callbacks = [callback]; self._callbacksForOp.set(op, callbacks); - Meteor._runAsync(async function () { + Fiber(function () { try { - var doc = await self._mongoConnection.findOne( + var doc = self._mongoConnection.findOne( collectionName, {_id: id}) || null; // Return doc to all relevant callbacks. Note that this array can // continue to grow during callback excecution. @@ -41,17 +43,17 @@ export class DocFetcher { // objects that are intertwingled with each other. Clone before // popping the future, so that if clone throws, the error gets passed // to the next callback. - await callbacks.pop()(null, EJSON.clone(doc)); + callbacks.pop()(null, EJSON.clone(doc)); } } catch (e) { while (callbacks.length > 0) { - await callbacks.pop()(e); + callbacks.pop()(e); } } finally { // XXX consider keeping the doc around for a period of time before // removing from the cache self._callbacksForOp.delete(op); } - }); + }).run(); } } diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 8c8cf093eb..5d653636a8 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -212,7 +212,7 @@ MongoConnection = function (url, options) { } }; -MongoConnection.prototype._close = async function() { +MongoConnection.prototype.close = function() { var self = this; if (! self.db) @@ -222,16 +222,12 @@ MongoConnection.prototype._close = async function() { var oplogHandle = self._oplogHandle; self._oplogHandle = null; if (oplogHandle) - await oplogHandle.stop(); + oplogHandle.stop(); // Use Future.wrap so that errors get thrown. This happens to // work even outside a fiber since the 'close' method is not // actually asynchronous. - await self.client.close(); -}; - -MongoConnection.prototype.close = function () { - return Meteor._isFibersEnabled ? Promise.await(this._close()) : this._close(); + Future.wrap(_.bind(self.client.close, self.client))(true).wait(); }; // Returns the Mongo Collection object; may yield. @@ -490,27 +486,14 @@ MongoConnection.prototype._update = function (collection_name, selector, mod, // non-object modifier in that they don't crash, they are not // meaningful operations and do not do anything. Defensively throw an // error here. - if (!mod || typeof mod !== 'object') { - const error = new Error("Invalid modifier. Modifier must be an object."); - - if (callback) { - return callback(error); - } else { - throw error; - } - } + if (!mod || typeof mod !== 'object') + throw new Error("Invalid modifier. Modifier must be an object."); if (!(LocalCollection._isPlainObject(mod) && !EJSON._isCustomType(mod))) { - const error = new Error( - "Only plain objects may be used as replacement" + + throw new Error( + "Only plain objects may be used as replacement" + " documents in MongoDB"); - - if (callback) { - return callback(error); - } else { - throw error; - } } if (!options) options = {}; @@ -786,7 +769,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, _.each(["insert", "update", "remove", "dropCollection", "dropDatabase"], function (method) { MongoConnection.prototype[method] = function (/* arguments */) { var self = this; - return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : Meteor.promisify(self[`_${method}`]).apply(self, arguments); + return Meteor.wrapAsync(self["_" + method]).apply(self, arguments); }; }); @@ -818,7 +801,8 @@ MongoConnection.prototype.find = function (collectionName, selector, options) { self, new CursorDescription(collectionName, selector, options)); }; -MongoConnection.prototype._findOneFibers = function (collection_name, selector, options) { +MongoConnection.prototype.findOne = function (collection_name, selector, + options) { var self = this; if (arguments.length === 1) selector = {}; @@ -828,24 +812,6 @@ MongoConnection.prototype._findOneFibers = function (collection_name, selector, return self.find(collection_name, selector, options).fetch()[0]; }; -MongoConnection.prototype._findOneNoFibers = async function (collection_name, selector, options) { - var self = this; - if (arguments.length === 1) { - selector = {}; - } - - options = options || {}; - options.limit = 1; - - const results = await self.find(collection_name, selector, options).fetch(); - - return results[0]; -}; - -MongoConnection.prototype.findOne = function (...args) { - return Meteor._isFibersEnabled ? this._findOneFibers(...args) : this._findOneNoFibers(...args); -}; - // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. MongoConnection.prototype.createIndex = function (collectionName, index, @@ -1060,156 +1026,9 @@ MongoConnection.prototype._createSynchronousCursor = function( dbCursor = dbCursor.hint(cursorOptions.hint); } - return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options, collection) : new AsynchronousCursor(dbCursor, cursorDescription, options, collection); + return new SynchronousCursor(dbCursor, cursorDescription, options, collection); }; -/** - * This is just a light wrapper for the cursor. The goal here is to ensure compatibility even if - * there are breaking changes on the MongoDB driver. - * - * @constructor - */ -class AsynchronousCursor { - constructor(dbCursor, cursorDescription, options) { - this._cursor = dbCursor; - this._cursorDescription = cursorDescription - - this._selfForIteration = options.selfForIteration || this; - if (options.useTransform && cursorDescription.options.transform) { - this._transform = LocalCollection.wrapTransform( - cursorDescription.options.transform); - } else { - this._transform = null; - } - - this._visitedIds = new LocalCollection._IdMap; - } - - [Symbol.iterator]() { - return this._cursor[Symbol.iterator](); - } - - // Returns a Promise for the next object from the underlying cursor (before - // the Mongo->Meteor type replacement). - async _rawNextObjectPromise() { - try { - return this._cursor.next(); - } catch (e) { - console.error(e); - } - } - - // Returns a Promise for the next object from the cursor, skipping those whose - // IDs we've already seen and replacing Mongo atoms with Meteor atoms. - async _nextObjectPromise () { - while (true) { - var doc = await this._rawNextObjectPromise(); - - if (!doc) return null; - doc = replaceTypes(doc, replaceMongoAtomWithMeteor); - - if (!this._cursorDescription.options.tailable && _.has(doc, '_id')) { - // Did Mongo give us duplicate documents in the same cursor? If so, - // ignore this one. (Do this before the transform, since transform might - // return some unrelated value.) We don't do this for tailable cursors, - // because we want to maintain O(1) memory usage. And if there isn't _id - // for some reason (maybe it's the oplog), then we don't do this either. - // (Be careful to do this for falsey but existing _id, though.) - if (this._visitedIds.has(doc._id)) continue; - this._visitedIds.set(doc._id, true); - } - - if (this._transform) - doc = this._transform(doc); - - return doc; - } - } - - // Returns a promise which is resolved with the next object (like with - // _nextObjectPromise) or rejected if the cursor doesn't return within - // timeoutMS ms. - _nextObjectPromiseWithTimeout(timeoutMS) { - if (!timeoutMS) { - return this._nextObjectPromise(); - } - const nextObjectPromise = this._nextObjectPromise(); - const timeoutErr = new Error('Client-side timeout waiting for next object'); - const timeoutPromise = new Promise((resolve, reject) => { - setTimeout(() => { - reject(timeoutErr); - }, timeoutMS); - }); - return Promise.race([nextObjectPromise, timeoutPromise]) - .catch((err) => { - if (err === timeoutErr) { - this.close(); - } - throw err; - }); - } - - async forEach(callback, thisArg) { - // Get back to the beginning. - this._rewind(); - - let idx = 0; - while (true) { - const doc = await this._nextObjectPromise(); - if (!doc) return; - await callback.call(thisArg, doc, idx++, this._selfForIteration); - } - } - - async map(callback, thisArg) { - const results = []; - await this.forEach(async (doc, index) => { - results.push(await callback.call(thisArg, doc, index, this._selfForIteration)); - }); - - return results; - } - - _rewind() { - // known to be synchronous - this._cursor.rewind(); - - this._visitedIds = new LocalCollection._IdMap; - } - - // Mostly usable for tailable cursors. - close() { - this._cursor.close(); - } - - fetch() { - return this.map(_.identity); - } - - /** - * FIXME: (node:34680) [MONGODB DRIVER] Warning: cursor.count is deprecated and will be - * removed in the next major version, please use `collection.estimatedDocumentCount` or - * `collection.countDocuments` instead. - */ - count() { - return this._cursor.count(); - } - - // This method is NOT wrapped in Cursor. - async getRawObjects(ordered) { - var self = this; - if (ordered) { - return self.fetch(); - } else { - var results = new LocalCollection._IdMap; - await self.forEach(function (doc) { - results.set(doc._id, doc); - }); - return results; - } - } -} - var SynchronousCursor = function (dbCursor, cursorDescription, options, collection) { var self = this; options = _.pick(options || {}, 'selfForIteration', 'useTransform'); @@ -1420,14 +1239,13 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo var stopped = false; var lastTS; - - Meteor.defer(async function loop() { + var loop = function () { var doc = null; while (true) { if (stopped) return; try { - doc = await cursor._nextObjectPromiseWithTimeout(timeoutMS); + doc = cursor._nextObjectPromiseWithTimeout(timeoutMS).await(); } catch (err) { // There's no good way to figure out if this was actually an error from // Mongo, or just client-side (including our own timeout error). Ah @@ -1458,11 +1276,13 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo // Mongo failover takes many seconds. Retry in a bit. (Without this // setTimeout, we peg the CPU at 100% and never notice the actual // failover. - setTimeout(loop, 100); + Meteor.setTimeout(loop, 100); break; } } - }); + }; + + Meteor.defer(loop); return { stop: function () { @@ -1472,40 +1292,33 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo }; }; -Object.assign(MongoConnection.prototype, { - _observeChanges: function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - return Meteor._isFibersEnabled - ? this._observeChangesFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks) - : this._observeChangesNoFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks); - }, +MongoConnection.prototype._observeChanges = function ( + cursorDescription, ordered, callbacks, nonMutatingCallbacks) { + var self = this; - _observeChangesNoFibers: async function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - var self = this; + if (cursorDescription.options.tailable) { + return self._observeChangesTailable(cursorDescription, ordered, callbacks); + } - if (cursorDescription.options.tailable) { - return self._observeChangesTailable(cursorDescription, ordered, callbacks); - } + // You may not filter out _id when observing changes, because the id is a core + // part of the observeChanges API. + const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; + if (fieldsOptions && + (fieldsOptions._id === 0 || + fieldsOptions._id === false)) { + throw Error("You may not observe a cursor with {fields: {_id: 0}}"); + } - // You may not filter out _id when observing changes, because the id is a core - // part of the observeChanges API. - const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; - if (fieldsOptions && - (fieldsOptions._id === 0 || - fieldsOptions._id === false)) { - throw Error("You may not observe a cursor with {fields: {_id: 0}}"); - } + var observeKey = EJSON.stringify( + _.extend({ordered: ordered}, cursorDescription)); - var observeKey = EJSON.stringify( - _.extend({ordered: ordered}, cursorDescription)); + var multiplexer, observeDriver; + var firstHandle = false; - var multiplexer, observeDriver; - var firstHandle = false; - - // Find a matching ObserveMultiplexer, or create a new one. This next block is - // guaranteed to not yield (and it doesn't call anything that can observe a - // new query), so no other calls to this function can interleave with it. + // Find a matching ObserveMultiplexer, or create a new one. This next block is + // guaranteed to not yield (and it doesn't call anything that can observe a + // new query), so no other calls to this function can interleave with it. + Meteor._noYieldsAllowed(function () { if (_.has(self._observeMultiplexers, observeKey)) { multiplexer = self._observeMultiplexers[observeKey]; } else { @@ -1515,192 +1328,76 @@ Object.assign(MongoConnection.prototype, { ordered: ordered, onStop: function () { delete self._observeMultiplexers[observeKey]; - return observeDriver.stop(); + observeDriver.stop(); } }); self._observeMultiplexers[observeKey] = multiplexer; } + }); - var observeHandle = new ObserveHandle(multiplexer, - callbacks, - nonMutatingCallbacks, - ); + var observeHandle = new ObserveHandle(multiplexer, + callbacks, + nonMutatingCallbacks, + ); - if (firstHandle) { - var matcher, sorter; - var canUseOplog = _.all([ - function () { - // At a bare minimum, using the oplog requires us to have an oplog, to - // want unordered callbacks, and to not want a callback on the polls - // that won't happen. - return self._oplogHandle && !ordered && - !callbacks._testOnlyPollCallback; - }, function () { - // We need to be able to compile the selector. Fall back to polling for - // some newfangled $selector that minimongo doesn't support yet. - try { - matcher = new Minimongo.Matcher(cursorDescription.selector); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }, function () { - // ... and the selector itself needs to support oplog. - return OplogObserveDriver.cursorSupported(cursorDescription, matcher); - }, function () { - // And we need to be able to compile the sort, if any. eg, can't be - // {$natural: 1}. - if (!cursorDescription.options.sort) - return true; - try { - sorter = new Minimongo.Sorter(cursorDescription.options.sort); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }], function (f) { return f(); }); // invoke each function + if (firstHandle) { + var matcher, sorter; + var canUseOplog = _.all([ + function () { + // At a bare minimum, using the oplog requires us to have an oplog, to + // want unordered callbacks, and to not want a callback on the polls + // that won't happen. + return self._oplogHandle && !ordered && + !callbacks._testOnlyPollCallback; + }, function () { + // We need to be able to compile the selector. Fall back to polling for + // some newfangled $selector that minimongo doesn't support yet. + try { + matcher = new Minimongo.Matcher(cursorDescription.selector); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }, function () { + // ... and the selector itself needs to support oplog. + return OplogObserveDriver.cursorSupported(cursorDescription, matcher); + }, function () { + // And we need to be able to compile the sort, if any. eg, can't be + // {$natural: 1}. + if (!cursorDescription.options.sort) + return true; + try { + sorter = new Minimongo.Sorter(cursorDescription.options.sort); + return true; + } catch (e) { + // XXX make all compilation errors MinimongoError or something + // so that this doesn't ignore unrelated exceptions + return false; + } + }], function (f) { return f(); }); // invoke each function - var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; - observeDriver = new driverClass({ - cursorDescription: cursorDescription, - mongoHandle: self, - multiplexer: multiplexer, - ordered: ordered, - matcher: matcher, // ignored by polling - sorter: sorter, // ignored by polling - _testOnlyPollCallback: callbacks._testOnlyPollCallback - }); - - if (observeDriver._init) { - await observeDriver._init(); - } - - // This field is only set for use in tests. - multiplexer._observeDriver = observeDriver; - } - - // Blocks until the initial adds have been sent. - await multiplexer.addHandleAndSendInitialAdds(observeHandle); - - return observeHandle; - }, - - _observeChangesFibers: function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - var self = this; - - if (cursorDescription.options.tailable) { - return self._observeChangesTailable(cursorDescription, ordered, callbacks); - } - - // You may not filter out _id when observing changes, because the id is a core - // part of the observeChanges API. - const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; - if (fieldsOptions && - (fieldsOptions._id === 0 || - fieldsOptions._id === false)) { - throw Error("You may not observe a cursor with {fields: {_id: 0}}"); - } - - var observeKey = EJSON.stringify( - _.extend({ordered: ordered}, cursorDescription)); - - var multiplexer, observeDriver; - var firstHandle = false; - - // Find a matching ObserveMultiplexer, or create a new one. This next block is - // guaranteed to not yield (and it doesn't call anything that can observe a - // new query), so no other calls to this function can interleave with it. - Meteor._noYieldsAllowed(function () { - if (_.has(self._observeMultiplexers, observeKey)) { - multiplexer = self._observeMultiplexers[observeKey]; - } else { - firstHandle = true; - // Create a new ObserveMultiplexer. - multiplexer = new ObserveMultiplexer({ - ordered: ordered, - onStop: function () { - delete self._observeMultiplexers[observeKey]; - observeDriver.stop(); - } - }); - self._observeMultiplexers[observeKey] = multiplexer; - } + var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; + observeDriver = new driverClass({ + cursorDescription: cursorDescription, + mongoHandle: self, + multiplexer: multiplexer, + ordered: ordered, + matcher: matcher, // ignored by polling + sorter: sorter, // ignored by polling + _testOnlyPollCallback: callbacks._testOnlyPollCallback }); - var observeHandle = new ObserveHandle(multiplexer, - callbacks, - nonMutatingCallbacks, - ); + // This field is only set for use in tests. + multiplexer._observeDriver = observeDriver; + } - if (firstHandle) { - var matcher, sorter; - var canUseOplog = _.all([ - function () { - // At a bare minimum, using the oplog requires us to have an oplog, to - // want unordered callbacks, and to not want a callback on the polls - // that won't happen. - return self._oplogHandle && !ordered && - !callbacks._testOnlyPollCallback; - }, function () { - // We need to be able to compile the selector. Fall back to polling for - // some newfangled $selector that minimongo doesn't support yet. - try { - matcher = new Minimongo.Matcher(cursorDescription.selector); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }, function () { - // ... and the selector itself needs to support oplog. - return OplogObserveDriver.cursorSupported(cursorDescription, matcher); - }, function () { - // And we need to be able to compile the sort, if any. eg, can't be - // {$natural: 1}. - if (!cursorDescription.options.sort) - return true; - try { - sorter = new Minimongo.Sorter(cursorDescription.options.sort); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }], function (f) { return f(); }); // invoke each function - - var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; - observeDriver = new driverClass({ - cursorDescription: cursorDescription, - mongoHandle: self, - multiplexer: multiplexer, - ordered: ordered, - matcher: matcher, // ignored by polling - sorter: sorter, // ignored by polling - _testOnlyPollCallback: callbacks._testOnlyPollCallback - }); - - if (observeDriver._init) { - observeDriver._init(); - } - - // This field is only set for use in tests. - multiplexer._observeDriver = observeDriver; - } - - // Blocks until the initial adds have been sent. - multiplexer.addHandleAndSendInitialAdds(observeHandle); - - return observeHandle; - }, -}); + // Blocks until the initial adds have been sent. + multiplexer.addHandleAndSendInitialAdds(observeHandle); + return observeHandle; +}; // Listen for the invalidation messages that will trigger us to poll the // database for changes. If this selector specifies specific IDs, specify them diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index 4b6b64245c..6e8f9349f6 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -1,53 +1,58 @@ -let nextObserveHandleId = 1; +var Future = Npm.require('fibers/future'); -ObserveMultiplexer = class { - constructor({ ordered, onStop = () => {} } = {}) { - if (ordered === undefined) throw Error("must specify ordered"); +ObserveMultiplexer = function (options) { + var self = this; + + if (!options || !_.has(options, 'ordered')) + throw Error("must specified ordered"); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-multiplexers", 1); + + self._ordered = options.ordered; + self._onStop = options.onStop || function () {}; + self._queue = new Meteor._SynchronousQueue(); + self._handles = {}; + self._readyFuture = new Future; + self._cache = new LocalCollection._CachingChangeObserver({ + ordered: options.ordered}); + // Number of addHandleAndSendInitialAdds tasks scheduled but not yet + // running. removeHandle uses this to know if it's time to call the onStop + // callback. + self._addHandleTasksScheduledButNotPerformed = 0; + + _.each(self.callbackNames(), function (callbackName) { + self[callbackName] = function (/* ... */) { + self._applyCallback(callbackName, _.toArray(arguments)); + }; + }); +}; + +_.extend(ObserveMultiplexer.prototype, { + addHandleAndSendInitialAdds: function (handle) { + var self = this; + + // Check this before calling runTask (even though runTask does the same + // check) so that we don't leak an ObserveMultiplexer on error by + // incrementing _addHandleTasksScheduledButNotPerformed and never + // decrementing it. + if (!self._queue.safeToRunTask()) + throw new Error("Can't call observeChanges from an observe callback on the same query"); + ++self._addHandleTasksScheduledButNotPerformed; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-multiplexers", 1); + "mongo-livedata", "observe-handles", 1); - this._ordered = ordered; - this._onStop = onStop; - this._queue = new Meteor._AsynchronousQueue(); - this._handles = {}; - this._resolver = null; - this._readyPromise = new Promise(r => this._resolver = r).then(() => this._isReady = true); - this._cache = new LocalCollection._CachingChangeObserver({ - ordered}); - // Number of addHandleAndSendInitialAdds tasks scheduled but not yet - // running. removeHandle uses this to know if it's time to call the onStop - // callback. - this._addHandleTasksScheduledButNotPerformed = 0; - - const self = this; - this.callbackNames().forEach(callbackName => { - this[callbackName] = function(/* ... */) { - self._applyCallback(callbackName, _.toArray(arguments)); - }; - }); - } - - addHandleAndSendInitialAdds(handle) { - return Meteor._isFibersEnabled ? Promise.await(this._addHandleAndSendInitialAdds(handle)) : this._addHandleAndSendInitialAdds(handle); - } - - async _addHandleAndSendInitialAdds(handle) { - ++this._addHandleTasksScheduledButNotPerformed; - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-handles", 1); - - const self = this; - await this._queue.runTask(function () { + self._queue.runTask(function () { self._handles[handle._id] = handle; - // Send out whatever adds we have so far (whether the + // Send out whatever adds we have so far (whether or not we the // multiplexer is ready). self._sendAdds(handle); --self._addHandleTasksScheduledButNotPerformed; }); - await this._readyPromise; - } + // *outside* the task, since otherwise we'd deadlock + self._readyFuture.wait(); + }, // Remove an observe handle. If it was the last observe handle, call the // onStop callback; you cannot add any more observe handles after this. @@ -55,58 +60,55 @@ ObserveMultiplexer = class { // This is not synchronized with polls and handle additions: this means that // you can safely call it from within an observe callback, but it also means // that we have to be careful when we iterate over _handles. - async removeHandle(id) { + removeHandle: function (id) { + var self = this; + // This should not be possible: you can only call removeHandle by having // access to the ObserveHandle, which isn't returned to user code until the // multiplex is ready. - if (!this._ready()) + if (!self._ready()) throw new Error("Can't remove handles until the multiplex is ready"); - delete this._handles[id]; + delete self._handles[id]; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-handles", -1); + "mongo-livedata", "observe-handles", -1); - if (_.isEmpty(this._handles) && - this._addHandleTasksScheduledButNotPerformed === 0) { - await this._stop(); + if (_.isEmpty(self._handles) && + self._addHandleTasksScheduledButNotPerformed === 0) { + self._stop(); } - } - async _stop(options) { + }, + _stop: function (options) { + var self = this; options = options || {}; // It shouldn't be possible for us to stop when all our handles still // haven't been returned from observeChanges! - if (! this._ready() && ! options.fromQueryError) + if (! self._ready() && ! options.fromQueryError) throw Error("surprising _stop: not ready"); // Call stop callback (which kills the underlying process which sends us // callbacks and removes us from the connection's dictionary). - await this._onStop(); + self._onStop(); Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-multiplexers", -1); + "mongo-livedata", "observe-multiplexers", -1); // Cause future addHandleAndSendInitialAdds calls to throw (but the onStop // callback should make our connection forget about us). - this._handles = null; - } + self._handles = null; + }, // Allows all addHandleAndSendInitialAdds calls to return, once all preceding // adds have been processed. Does not block. - ready() { - const self = this; - this._queue.queueTask(function () { + ready: function () { + var self = this; + self._queue.queueTask(function () { if (self._ready()) throw Error("can't make ObserveMultiplex ready twice!"); - - if (!self._resolver) { - throw new Error("Missing resolver"); - } - - self._resolver(); - self._isReady = true; + self._readyFuture.return(); }); - } + }, // If trying to execute the query results in an error, call this. This is // intended for permanent errors, not transient network errors that could be @@ -114,45 +116,47 @@ ObserveMultiplexer = class { // that meant that you managed to run the query once. It will stop this // ObserveMultiplex and cause addHandleAndSendInitialAdds calls (and thus // observeChanges calls) to throw the error. - async queryError(err) { + queryError: function (err) { var self = this; - await this._queue.runTask(function () { + self._queue.runTask(function () { if (self._ready()) throw Error("can't claim query has an error after it worked!"); self._stop({fromQueryError: true}); - throw err; + self._readyFuture.throw(err); }); - } + }, // Calls "cb" once the effects of all "ready", "addHandleAndSendInitialAdds" // and observe callbacks which came before this call have been propagated to // all handles. "ready" must have already been called on this multiplexer. - onFlush(cb) { + onFlush: function (cb) { var self = this; - return this._queue.queueTask(async function () { + self._queue.queueTask(function () { if (!self._ready()) throw Error("only call onFlush on a multiplexer that will be ready"); - await cb(); + cb(); }); - } - callbackNames() { - if (this._ordered) + }, + callbackNames: function () { + var self = this; + if (self._ordered) return ["addedBefore", "changed", "movedBefore", "removed"]; else return ["added", "changed", "removed"]; - } - _ready() { - return !!this._isReady; - } - _applyCallback(callbackName, args) { - const self = this; - this._queue.queueTask(async function () { + }, + _ready: function () { + return this._readyFuture.isResolved(); + }, + _applyCallback: function (callbackName, args) { + var self = this; + self._queue.queueTask(function () { // If we stopped in the meantime, do nothing. if (!self._handles) return; // First, apply the change to the cache. - await self._cache.applyChange[callbackName].apply(null, args); + self._cache.applyChange[callbackName].apply(null, args); + // If we haven't finished the initial adds, then we should only be getting // adds. if (!self._ready() && @@ -165,67 +169,73 @@ ObserveMultiplexer = class { // can continue until these are done. (But we do have to be careful to not // use a handle that got removed, because removeHandle does not use the // queue; thus, we iterate over an array of keys that we control.) - const toAwait = Object.keys(self._handles).map(async (handleId) => { + _.each(_.keys(self._handles), function (handleId) { var handle = self._handles && self._handles[handleId]; if (!handle) return; var callback = handle['_' + callbackName]; // clone arguments so that callbacks can mutate their arguments - callback && await callback.apply(null, - handle.nonMutatingCallbacks ? args : EJSON.clone(args)); + callback && callback.apply(null, + handle.nonMutatingCallbacks ? args : EJSON.clone(args)); }); - - await Promise.all(toAwait); }); - } + }, // Sends initial adds to a handle. It should only be called from within a task // (the task that is processing the addHandleAndSendInitialAdds call). It // synchronously invokes the handle's added or addedBefore; there's no need to // flush the queue afterwards to ensure that the callbacks get out. - async _sendAdds(handle) { - var add = this._ordered ? handle._addedBefore : handle._added; + _sendAdds: function (handle) { + var self = this; + if (self._queue.safeToRunTask()) + throw Error("_sendAdds may only be called from within a task!"); + var add = self._ordered ? handle._addedBefore : handle._added; if (!add) return; // note: docs may be an _IdMap or an OrderedDict - await this._cache.docs.forEachAsync(async (doc, id) => { - if (!_.has(this._handles, handle._id)) + self._cache.docs.forEach(function (doc, id) { + if (!_.has(self._handles, handle._id)) throw Error("handle got removed before sending initial adds!"); const { _id, ...fields } = handle.nonMutatingCallbacks ? doc - : EJSON.clone(doc); - if (this._ordered) - await add(id, fields, null); // we're going in order, so add at end + : EJSON.clone(doc); + if (self._ordered) + add(id, fields, null); // we're going in order, so add at end else - await add(id, fields); + add(id, fields); }); } -}; +}); + + +var nextObserveHandleId = 1; // When the callbacks do not mutate the arguments, we can skip a lot of data clones -ObserveHandle = class { - constructor(multiplexer, callbacks, nonMutatingCallbacks = false) { - this._multiplexer = multiplexer; - multiplexer.callbackNames().forEach((name) => { - if (callbacks[name]) { - this['_' + name] = callbacks[name]; - } else if (name === "addedBefore" && callbacks.added) { - // Special case: if you specify "added" and "movedBefore", you get an - // ordered observe where for some reason you don't get ordering data on - // the adds. I dunno, we wrote tests for it, there must have been a - // reason. - this._addedBefore = function (id, fields, before) { - callbacks.added(id, fields); - }; - } - }); - this._stopped = false; - this._id = nextObserveHandleId++; - this.nonMutatingCallbacks = nonMutatingCallbacks; - } - - async stop() { - if (this._stopped) return; - this._stopped = true; - await this._multiplexer.removeHandle(this._id); - } +ObserveHandle = function (multiplexer, callbacks, nonMutatingCallbacks = false) { + var self = this; + // The end user is only supposed to call stop(). The other fields are + // accessible to the multiplexer, though. + self._multiplexer = multiplexer; + _.each(multiplexer.callbackNames(), function (name) { + if (callbacks[name]) { + self['_' + name] = callbacks[name]; + } else if (name === "addedBefore" && callbacks.added) { + // Special case: if you specify "added" and "movedBefore", you get an + // ordered observe where for some reason you don't get ordering data on + // the adds. I dunno, we wrote tests for it, there must have been a + // reason. + self._addedBefore = function (id, fields, before) { + callbacks.added(id, fields); + }; + } + }); + self._stopped = false; + self._id = nextObserveHandleId++; + self.nonMutatingCallbacks = nonMutatingCallbacks; +}; +ObserveHandle.prototype.stop = function () { + var self = this; + if (self._stopped) + return; + self._stopped = true; + self._multiplexer.removeHandle(self._id); }; diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 9edfd2703b..773e7e3feb 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -1,5 +1,7 @@ import { oplogV2V1Converter } from "./oplog_v2_converter"; +var Future = Npm.require('fibers/future'); + var PHASE = { QUERYING: "QUERYING", FETCHING: "FETCHING", @@ -10,9 +12,9 @@ var PHASE = { // enclosing call to finishIfNeedToPollQuery. var SwitchedToQuery = function () {}; var finishIfNeedToPollQuery = function (f) { - return async function () { + return function () { try { - await f.apply(this, arguments); + f.apply(this, arguments); } catch (e) { if (!(e instanceof SwitchedToQuery)) throw e; @@ -109,7 +111,7 @@ OplogObserveDriver = function (options) { // behind, say), re-poll. self._stopHandles.push(self._mongoHandle._oplogHandle.onSkippedEntries( finishIfNeedToPollQuery(function () { - return self._needToPollQuery(); + self._needToPollQuery(); }) )); @@ -122,13 +124,13 @@ OplogObserveDriver = function (options) { // Note: this call is not allowed to block on anything (especially // on waiting for oplog entries to catch up) because that will block // onOplogEntry! - return self._needToPollQuery(); + self._needToPollQuery(); } else { // All other operators should be handled depending on phase if (self._phase === PHASE.QUERYING) { - return self._handleOplogEntryQuerying(op); + self._handleOplogEntryQuerying(op); } else { - return self._handleOplogEntrySteadyOrFetching(op); + self._handleOplogEntrySteadyOrFetching(op); } } })); @@ -138,7 +140,7 @@ OplogObserveDriver = function (options) { // XXX ordering w.r.t. everything else? self._stopHandles.push(listenAll( - self._cursorDescription, function () { + self._cursorDescription, function (notification) { // If we're not in a pre-fire write fence, we don't have to do anything. var fence = DDPServer._CurrentWriteFence.get(); if (!fence || fence.fired) @@ -152,15 +154,15 @@ OplogObserveDriver = function (options) { fence._oplogObserveDrivers = {}; fence._oplogObserveDrivers[self._id] = self; - fence.onBeforeFire(async function () { + fence.onBeforeFire(function () { var drivers = fence._oplogObserveDrivers; delete fence._oplogObserveDrivers; // This fence cannot fire until we've caught up to "this point" in the // oplog, and all observers made it back to the steady state. - await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); + self._mongoHandle._oplogHandle.waitUntilCaughtUp(); - for (const driver of Object.values(drivers)) { + _.each(drivers, function (driver) { if (driver._stopped) return; @@ -169,11 +171,13 @@ OplogObserveDriver = function (options) { // Make sure that all of the callbacks have made it through the // multiplexer and been delivered to ObserveHandles before committing // writes. - await driver._multiplexer.onFlush(write.committed); + driver._multiplexer.onFlush(function () { + write.committed(); + }); } else { driver._writesToCommitWhenWeReachSteady.push(write); } - } + }); }); } )); @@ -182,17 +186,17 @@ OplogObserveDriver = function (options) { // oplog entry that got rolled back. self._stopHandles.push(self._mongoHandle._onFailover(finishIfNeedToPollQuery( function () { - return self._needToPollQuery(); + self._needToPollQuery(); }))); + + // Give _observeChanges a chance to add the new ObserveHandle to our + // multiplexer, so that the added calls get streamed. + Meteor.defer(finishIfNeedToPollQuery(function () { + self._runInitialQuery(); + })); }; _.extend(OplogObserveDriver.prototype, { - _init: function() { - const self = this; - // Give _observeChanges a chance to add the new ObserveHandle to our - // multiplexer, so that the added calls get streamed. - return self._runInitialQuery(); - }, _addPublished: function (id, doc) { var self = this; Meteor._noYieldsAllowed(function () { @@ -484,7 +488,7 @@ _.extend(OplogObserveDriver.prototype, { self._registerPhaseChange(PHASE.FETCHING); // Defer, because nothing called from the oplog entry handler may yield, // but fetch() yields. - Meteor.defer(finishIfNeedToPollQuery(async function () { + Meteor.defer(finishIfNeedToPollQuery(function () { while (!self._stopped && !self._needToFetch.empty()) { if (self._phase === PHASE.QUERYING) { // While fetching, we decided to go into QUERYING mode, and then we @@ -501,9 +505,7 @@ _.extend(OplogObserveDriver.prototype, { var thisGeneration = ++self._fetchGeneration; self._needToFetch = new LocalCollection._IdMap; var waiting = 0; - - let promiseResolver = null; - const awaitablePromise = new Promise(r => promiseResolver = r); + var fut = new Future; // This loop is safe, because _currentlyFetching will not be updated // during this loop (in fact, it is never mutated). self._currentlyFetching.forEach(function (op, id) { @@ -536,11 +538,11 @@ _.extend(OplogObserveDriver.prototype, { // this is safe (ie, we won't call fut.return() before the // forEach is done). if (waiting === 0) - promiseResolver(); + fut.return(); } })); }); - await awaitablePromise; + fut.wait(); // Exit now if we've had a _pollQuery call (here or in another fiber). if (self._phase === PHASE.QUERYING) return; @@ -549,20 +551,20 @@ _.extend(OplogObserveDriver.prototype, { // We're done fetching, so we can be steady, unless we've had a // _pollQuery call (here or in another fiber). if (self._phase !== PHASE.QUERYING) - await self._beSteady(); + self._beSteady(); })); }); }, - _beSteady: async function () { + _beSteady: function () { var self = this; - await Meteor._noYieldsAllowed(async function () { + Meteor._noYieldsAllowed(function () { self._registerPhaseChange(PHASE.STEADY); var writes = self._writesToCommitWhenWeReachSteady; self._writesToCommitWhenWeReachSteady = []; - await self._multiplexer.onFlush(async function () { - for (const w of writes) { - await w.committed(); - } + self._multiplexer.onFlush(function () { + _.each(writes, function (w) { + w.committed(); + }); }); }); }, @@ -656,27 +658,22 @@ _.extend(OplogObserveDriver.prototype, { } }); }, - - async _runInitialQueryAsync() { + // Yields! + _runInitialQuery: function () { var self = this; if (self._stopped) throw new Error("oplog stopped surprisingly early"); - await self._runQuery({initial: true}); // yields + self._runQuery({initial: true}); // yields if (self._stopped) return; // can happen on queryError // Allow observeChanges calls to return. (After this, it's possible for // stop() to be called.) - await self._multiplexer.ready(); + self._multiplexer.ready(); - await self._doneQuerying(); // yields - }, - - // Yields! - _runInitialQuery: function () { - return Meteor._isFibersEnabled ? Promise.await(this._runInitialQueryAsync()) : this._runInitialQueryAsync(); + self._doneQuerying(); // yields }, // In various circumstances, we may just want to stop processing the oplog and @@ -707,15 +704,15 @@ _.extend(OplogObserveDriver.prototype, { // Defer so that we don't yield. We don't need finishIfNeedToPollQuery // here because SwitchedToQuery is not thrown in QUERYING mode. - Meteor.defer(async function () { - await self._runQuery(); - await self._doneQuerying(); + Meteor.defer(function () { + self._runQuery(); + self._doneQuerying(); }); }); }, // Yields! - async _runQueryAsync(options) { + _runQuery: function (options) { var self = this; options = options || {}; var newResults, newBuffer; @@ -738,7 +735,7 @@ _.extend(OplogObserveDriver.prototype, { // buffer if such is needed. var cursor = self._cursorForQuery({ limit: self._limit * 2 }); try { - await cursor.forEach(function (doc, i) { // yields + cursor.forEach(function (doc, i) { // yields if (!self._limit || i < self._limit) { newResults.set(doc._id, doc); } else { @@ -753,14 +750,14 @@ _.extend(OplogObserveDriver.prototype, { // successfully. Probably it's a bad selector or something, so we // should NOT retry. Instead, we should halt the observe (which ends // up calling `stop` on us). - await self._multiplexer.queryError(e); + self._multiplexer.queryError(e); return; } // During failover (eg) if we get an exception we should log and retry // instead of crashing. Meteor._debug("Got exception while polling query", e); - await Meteor._sleepForMs(100); + Meteor._sleepForMs(100); } } @@ -770,11 +767,6 @@ _.extend(OplogObserveDriver.prototype, { self._publishNewResults(newResults, newBuffer); }, - // Yields! - _runQuery: function (options) { - return Meteor._isFibersEnabled ? Promise.await(this._runQueryAsync(options)) : this._runQueryAsync(options); - }, - // Transitions to QUERYING and runs another query, or (if already in QUERYING) // ensures that we will query again later. // @@ -807,25 +799,23 @@ _.extend(OplogObserveDriver.prototype, { }, // Yields! - _doneQuerying: async function () { + _doneQuerying: function () { var self = this; if (self._stopped) return; - - await self._mongoHandle._oplogHandle.waitUntilCaughtUp(); - + self._mongoHandle._oplogHandle.waitUntilCaughtUp(); // yields if (self._stopped) return; if (self._phase !== PHASE.QUERYING) throw Error("Phase unexpectedly " + self._phase); - await Meteor._noYieldsAllowed(async function () { + Meteor._noYieldsAllowed(function () { if (self._requeryWhenDoneThisQuery) { self._requeryWhenDoneThisQuery = false; self._pollQuery(); } else if (self._needToFetch.empty()) { - await self._beSteady(); + self._beSteady(); } else { self._fetchModifiedDocuments(); } @@ -926,20 +916,23 @@ _.extend(OplogObserveDriver.prototype, { // // It's important to check self._stopped after every call in this file that // can yield! - _stop: async function() { + stop: function () { var self = this; if (self._stopped) return; self._stopped = true; + _.each(self._stopHandles, function (handle) { + handle.stop(); + }); // Note: we *don't* use multiplexer.onFlush here because this stop // callback is actually invoked by the multiplexer itself when it has // determined that there are no handles left. So nothing is actually going // to get flushed (and it's probably not valid to call methods on the // dying multiplexer). - for (const w of self._writesToCommitWhenWeReachSteady) { - await w.committed(); - } + _.each(self._writesToCommitWhenWeReachSteady, function (w) { + w.committed(); // maybe yields? + }); self._writesToCommitWhenWeReachSteady = null; // Proactively drop references to potentially big things. @@ -951,15 +944,7 @@ _.extend(OplogObserveDriver.prototype, { self._listenersHandle = null; Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-oplog", -1); - - for await (const handle of self._stopHandles) { - await handle.stop(); - } - }, - stop: function() { - const self = this; - return Meteor._isFibersEnabled ? Promise.await(self._stop()) : self._stop(); + "mongo-livedata", "observe-drivers-oplog", -1); }, _registerPhaseChange: function (phase) { diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index a50e3f5a1e..fc702318db 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -1,3 +1,5 @@ +var Future = Npm.require('fibers/future'); + import { NpmModuleMongodb } from "meteor/npm-mongo"; const { Long } = NpmModuleMongodb; @@ -6,6 +8,10 @@ OPLOG_COLLECTION = 'oplog.rs'; var TOO_FAR_BEHIND = process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000; var TAIL_TIMEOUT = +process.env.METEOR_OPLOG_TAIL_TIMEOUT || 30000; +var showTS = function (ts) { + return "Timestamp(" + ts.getHighBits() + ", " + ts.getLowBits() + ")"; +}; + idForOp = function (op) { if (op.op === 'd') return op.o._id; @@ -29,8 +35,7 @@ OplogHandle = function (oplogUrl, dbName) { self._oplogTailConnection = null; self._stopped = false; self._tailHandle = null; - self._readyPromiseResolver = null; - self._readyPromise = new Promise(r => self._readyPromiseResolver = r); + self._readyFuture = new Future(); self._crossbar = new DDPServer._Crossbar({ factPackage: "mongo-livedata", factName: "oplog-watchers" }); @@ -67,7 +72,7 @@ OplogHandle = function (oplogUrl, dbName) { // incremented to be past its timestamp by the worker fiber. // // XXX use a priority queue or something else that's faster than an array - self._catchingUpResolvers = []; + self._catchingUpFutures = []; self._lastProcessedTS = null; self._onSkippedEntriesHook = new Hook({ @@ -77,10 +82,7 @@ OplogHandle = function (oplogUrl, dbName) { self._entryQueue = new Meteor._DoubleEndedQueue(); self._workerActive = false; - const shouldAwait = self._startTailing(); - if (Meteor._isFibersEnabled) { - Promise.await(shouldAwait); - } + self._startTailing(); }; Object.assign(OplogHandle.prototype, { @@ -93,13 +95,13 @@ Object.assign(OplogHandle.prototype, { self._tailHandle.stop(); // XXX should close connections too }, - _onOplogEntry: async function(trigger, callback) { + onOplogEntry: function (trigger, callback) { var self = this; if (self._stopped) throw new Error("Called onOplogEntry on stopped handle!"); // Calling onOplogEntry requires us to wait for the tailing to be ready. - await self._readyPromise; + self._readyFuture.wait(); var originalCallback = callback; callback = Meteor.bindEnvironment(function (notification) { @@ -114,9 +116,6 @@ Object.assign(OplogHandle.prototype, { } }; }, - onOplogEntry: function (trigger, callback) { - return Meteor._isFibersEnabled ? Promise.await(this._onOplogEntry(trigger, callback)) : this._onOplogEntry(trigger, callback); - }, // Register a callback to be invoked any time we skip oplog entries (eg, // because we are too far behind). onSkippedEntries: function (callback) { @@ -125,15 +124,19 @@ Object.assign(OplogHandle.prototype, { throw new Error("Called onSkippedEntries on stopped handle!"); return self._onSkippedEntriesHook.register(callback); }, - - async _waitUntilCaughtUp() { + // Calls `callback` once the oplog has been processed up to a point that is + // roughly "now": specifically, once we've processed all ops that are + // currently visible. + // XXX become convinced that this is actually safe even if oplogConnection + // is some kind of pool + waitUntilCaughtUp: function () { var self = this; if (self._stopped) throw new Error("Called waitUntilCaughtUp on stopped handle!"); // Calling waitUntilCaughtUp requries us to wait for the oplog connection to // be ready. - await self._readyPromise; + self._readyFuture.wait(); var lastEntry; while (!self._stopped) { @@ -141,15 +144,15 @@ Object.assign(OplogHandle.prototype, { // tailing selector (ie, we need to specify the DB name) or else we might // find a TS that won't show up in the actual tail stream. try { - lastEntry = await self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, self._baseOplogSelector, - {fields: {ts: 1}, sort: {$natural: -1}}); + lastEntry = self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, self._baseOplogSelector, + {fields: {ts: 1}, sort: {$natural: -1}}); break; } catch (e) { // During failover (eg) if we get an exception we should log and retry // instead of crashing. Meteor._debug("Got exception while reading last entry", e); - await Meteor._sleepForMs(100); + Meteor._sleepForMs(100); } } @@ -174,32 +177,21 @@ Object.assign(OplogHandle.prototype, { // Insert the future into our list. Almost always, this will be at the end, // but it's conceivable that if we fail over from one primary to another, // the oplog entries we see will go backwards. - var insertAfter = self._catchingUpResolvers.length; - while (insertAfter - 1 > 0 && self._catchingUpResolvers[insertAfter - 1].ts.greaterThan(ts)) { + var insertAfter = self._catchingUpFutures.length; + while (insertAfter - 1 > 0 && self._catchingUpFutures[insertAfter - 1].ts.greaterThan(ts)) { insertAfter--; } - let promiseResolver = null; - const promiseToAwait = new Promise(r => promiseResolver = r); - self._catchingUpResolvers.splice(insertAfter, 0, {ts: ts, resolver: promiseResolver}); - await promiseToAwait; + var f = new Future; + self._catchingUpFutures.splice(insertAfter, 0, {ts: ts, future: f}); + f.wait(); }, - - // Calls `callback` once the oplog has been processed up to a point that is - // roughly "now": specifically, once we've processed all ops that are - // currently visible. - // XXX become convinced that this is actually safe even if oplogConnection - // is some kind of pool - waitUntilCaughtUp: function () { - return Meteor._isFibersEnabled ? Promise.await(this._waitUntilCaughtUp()) : this._waitUntilCaughtUp(); - }, - - _startTailing: async function () { + _startTailing: function () { var self = this; // First, make sure that we're talking to the local database. var mongodbUri = Npm.require('mongodb-uri'); if (mongodbUri.parse(self._oplogUrl).database !== 'local') { throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + - "a Mongo replica set"); + "a Mongo replica set"); } // We make two separate connections to Mongo. The Node Mongo driver @@ -214,28 +206,32 @@ Object.assign(OplogHandle.prototype, { // The tail connection will only ever be running a single tail command, so // it only needs to make one underlying TCP connection. self._oplogTailConnection = new MongoConnection( - self._oplogUrl, {maxPoolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); // XXX better docs, but: it's to get monotonic results // XXX is it safe to say "if there's an in flight query, just use its // results"? I don't think so but should consider that self._oplogLastEntryConnection = new MongoConnection( - self._oplogUrl, {maxPoolSize: 1}); + self._oplogUrl, {maxPoolSize: 1}); - - const isMasterDoc = await Meteor.promisify((cb) => { - self._oplogLastEntryConnection.db.admin().command({ismaster: 1}, cb); - })(); + // Now, make sure that there actually is a repl set here. If not, oplog + // tailing won't ever find anything! + // More on the isMasterDoc + // https://docs.mongodb.com/manual/reference/command/isMaster/ + var f = new Future; + self._oplogLastEntryConnection.db.admin().command( + { ismaster: 1 }, f.resolver()); + var isMasterDoc = f.wait(); if (!(isMasterDoc && isMasterDoc.setName)) { throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " + - "a Mongo replica set"); + "a Mongo replica set"); } // Find the last oplog entry. - var lastOplogEntry = await self._oplogLastEntryConnection.findOne( - OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); + var lastOplogEntry = self._oplogLastEntryConnection.findOne( + OPLOG_COLLECTION, {}, {sort: {$natural: -1}, fields: {ts: 1}}); - var oplogSelector = Object.assign({}, self._baseOplogSelector); + var oplogSelector = _.clone(self._baseOplogSelector); if (lastOplogEntry) { // Start after the last entry that currently exists. oplogSelector.ts = {$gt: lastOplogEntry.ts}; @@ -246,7 +242,7 @@ Object.assign(OplogHandle.prototype, { } var cursorDescription = new CursorDescription( - OPLOG_COLLECTION, oplogSelector, {tailable: true}); + OPLOG_COLLECTION, oplogSelector, {tailable: true}); // Start tailing the oplog. // @@ -255,15 +251,14 @@ Object.assign(OplogHandle.prototype, { // one bug that can lead to query callbacks never getting called (even with // an error) when leadership failover occur. self._tailHandle = self._oplogTailConnection.tail( - cursorDescription, - function (doc) { - self._entryQueue.push(doc); - self._maybeStartWorker(); - }, - TAIL_TIMEOUT + cursorDescription, + function (doc) { + self._entryQueue.push(doc); + self._maybeStartWorker(); + }, + TAIL_TIMEOUT ); - - self._readyPromiseResolver(); + self._readyFuture.return(); }, _maybeStartWorker: function () { @@ -367,9 +362,9 @@ Object.assign(OplogHandle.prototype, { _setLastProcessedTS: function (ts) { var self = this; self._lastProcessedTS = ts; - while (!_.isEmpty(self._catchingUpResolvers) && self._catchingUpResolvers[0].ts.lessThanOrEqual(self._lastProcessedTS)) { - var sequencer = self._catchingUpResolvers.shift(); - sequencer.resolver(); + while (!_.isEmpty(self._catchingUpFutures) && self._catchingUpFutures[0].ts.lessThanOrEqual(self._lastProcessedTS)) { + var sequencer = self._catchingUpFutures.shift(); + sequencer.future.return(); } }, diff --git a/packages/mongo/package.js b/packages/mongo/package.js index f31b7efe27..0ec175d219 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.16.0' + version: '1.16.1-beta.0' }); Npm.depends({ diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js index 32744abeba..f378d28c43 100644 --- a/packages/mongo/polling_observe_driver.js +++ b/packages/mongo/polling_observe_driver.js @@ -11,7 +11,7 @@ PollingObserveDriver = function (options) { self._stopCallbacks = []; self._stopped = false; - self._cursor = self._mongoHandle._createSynchronousCursor( + self._synchronousCursor = self._mongoHandle._createSynchronousCursor( self._cursorDescription); // previous results snapshot. on each poll cycle, diffs against @@ -74,28 +74,15 @@ PollingObserveDriver = function (options) { Meteor.clearInterval(intervalHandle); }); } + + // Make sure we actually poll soon! + self._unthrottledEnsurePollIsScheduled(); + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "mongo-livedata", "observe-drivers-polling", 1); }; _.extend(PollingObserveDriver.prototype, { - _initAsync: async function () { - // Make sure we actually poll soon! - await this._unthrottledEnsurePollIsScheduled(); - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-polling", 1); - }, - _init() { - if (!Meteor._isFibersEnabled) { - return this._initAsync(); - } - - var self = this; - // Make sure we actually poll soon! - self._unthrottledEnsurePollIsScheduled(); - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-polling", 1); - }, // This is always called through _.throttle (except once at startup). _unthrottledEnsurePollIsScheduled: function () { var self = this; @@ -142,7 +129,7 @@ _.extend(PollingObserveDriver.prototype, { }); }, - _pollMongoFibers: function () { + _pollMongo: function () { var self = this; --self._pollsScheduledButNotStarted; @@ -166,7 +153,7 @@ _.extend(PollingObserveDriver.prototype, { // Get the new query results. (This yields.) try { - newResults = self._cursor.getRawObjects(self._ordered); + newResults = self._synchronousCursor.getRawObjects(self._ordered); } catch (e) { if (first && typeof(e.code) === 'number') { // This is an error document sent to us by mongod, not a connection @@ -221,100 +208,10 @@ _.extend(PollingObserveDriver.prototype, { }); }, - async _pollMongoNoFibers() { - var self = this; - --self._pollsScheduledButNotStarted; - - if (self._stopped) - return; - - var first = false; - var newResults; - var oldResults = self._results; - if (!oldResults) { - first = true; - // XXX maybe use OrderedDict instead? - oldResults = self._ordered ? [] : new LocalCollection._IdMap; - } - - self._testOnlyPollCallback && self._testOnlyPollCallback(); - - // Save the list of pending writes which this round will commit. - var writesForCycle = self._pendingWrites; - self._pendingWrites = []; - - // Get the new query results. (This yields.) - try { - newResults = await self._cursor.getRawObjects(self._ordered); - } catch (e) { - if (first && typeof(e.code) === 'number') { - // This is an error document sent to us by mongod, not a connection - // error generated by the client. And we've never seen this query work - // successfully. Probably it's a bad selector or something, so we should - // NOT retry. Instead, we should halt the observe (which ends up calling - // `stop` on us). - self._multiplexer.queryError( - new Error( - "Exception while polling query " + - JSON.stringify(self._cursorDescription) + ": " + e.message)); - return; - } - - // getRawObjects can throw if we're having trouble talking to the - // database. That's fine --- we will repoll later anyway. But we should - // make sure not to lose track of this cycle's writes. - // (It also can throw if there's just something invalid about this query; - // unfortunately the ObserveDriver API doesn't provide a good way to - // "cancel" the observe from the inside in this case. - Array.prototype.push.apply(self._pendingWrites, writesForCycle); - Meteor._debug("Exception while polling query " + - JSON.stringify(self._cursorDescription), e); - return; - } - - // Run diffs. - if (!self._stopped) { - LocalCollection._diffQueryChanges( - self._ordered, oldResults, newResults, self._multiplexer); - } - - // Signals the multiplexer to allow all observeChanges calls that share this - // multiplexer to return. (This happens asynchronously, via the - // multiplexer's queue.) - if (first) - self._multiplexer.ready(); - - // Replace self._results atomically. (This assignment is what makes `first` - // stay through on the next cycle, so we've waited until after we've - // committed to ready-ing the multiplexer.) - self._results = newResults; - - // Once the ObserveMultiplexer has processed everything we've done in this - // round, mark all the writes which existed before this call as - // commmitted. (If new writes have shown up in the meantime, there'll - // already be another _pollMongo task scheduled.) - self._multiplexer.onFlush(function () { - _.each(writesForCycle, function (w) { - w.committed(); - }); - }); - }, - - _pollMongo: function () { - return Meteor._isFibersEnabled ? this._pollMongoFibers() : this._pollMongoNoFibers(); - }, - stop: function () { var self = this; self._stopped = true; - const stopCallbacksCallerFibers = function (c) { - c(); - }; - const stopCallbacksCallerNoFibers = async function(c) { - await c(); - }; - - _.each(self._stopCallbacks, Meteor._isFibersEnabled ? stopCallbacksCallerFibers : stopCallbacksCallerNoFibers); + _.each(self._stopCallbacks, function (c) { c(); }); // Release any write fences that are waiting on us. _.each(self._pendingWrites, function (w) { w.committed(); diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo/remote_collection_driver.js index a7b654135c..f237879de0 100644 --- a/packages/mongo/remote_collection_driver.js +++ b/packages/mongo/remote_collection_driver.js @@ -40,8 +40,8 @@ MongoInternals.defaultRemoteCollectionDriver = _.once(function () { // to know about a database connection problem before the app starts. Doing so // in a `Meteor.startup` is fine, as the `WebApp` handles requests only after // all are finished. - Meteor.startup(async () => { - await driver.mongo.client.connect(); + Meteor.startup(() => { + Promise.await(driver.mongo.client.connect()); }); return driver; From 04320b4409559fa1f873e9a4f1743192436f5951 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 4 Nov 2022 17:08:24 -0300 Subject: [PATCH 0039/1965] Separate mongo packages into two packages - packages/mongo: current Fibers implementation; - Packages/mongo-async: New version without Fibers (Work in progress) - Change usages to import correct package based on flag DISABLE_FIBERS --- packages/accounts-base/package.js | 7 +++++-- packages/ddp-client/package.js | 6 +++++- packages/ddp-server/package.js | 6 +++++- packages/deprecated/facts/package.js | 8 ++++++-- packages/deprecated/meteor-platform/package.js | 8 ++++++-- packages/ejson/package.js | 7 ++++++- packages/facts-ui/package.js | 7 +++++-- packages/mongo-async/package.js | 2 +- packages/mongo-livedata/package.js | 6 +++++- packages/non-core/mongo-decimal/package.js | 6 +++++- packages/oauth/package.js | 8 ++++++-- packages/oauth1/package.js | 6 +++++- packages/reactive-dict/package.js | 7 ++++++- packages/service-configuration/package.js | 6 +++++- packages/session/package.js | 6 +++++- packages/tinytest/package.js | 6 +++++- 16 files changed, 81 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 32a6df946c..755421c580 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -24,8 +24,11 @@ Package.onUse(api => { // need this because of the Meteor.users collection but in the future // we'd probably want to abstract this away - api.use('mongo', ['client', 'server']); - + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', ['client', 'server']); + } else { + api.use('mongo-async', ['client', 'server']); + } // If the 'blaze' package is loaded, we'll define some helpers like // {{currentUser}}. If not, no biggie. api.use('blaze@2.5.0', 'client', { weak: true }); diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 0cdc77a953..b60d545947 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -40,7 +40,6 @@ Package.onUse((api) => { Package.onTest((api) => { api.use([ 'livedata', - 'mongo', 'test-helpers', 'ecmascript', 'underscore', @@ -54,6 +53,11 @@ Package.onTest((api) => { 'ddp-common', 'check' ]); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', ['client', 'server']); + } else { + api.use('mongo-async', ['client', 'server']); + } api.addFiles('test/stub_stream.js'); api.addFiles('test/livedata_connection_tests.js'); diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js index da413690b0..af3d53f069 100644 --- a/packages/ddp-server/package.js +++ b/packages/ddp-server/package.js @@ -50,7 +50,11 @@ Package.onUse(function (api) { Package.onTest(function (api) { api.use('ecmascript', ['client', 'server']); api.use('livedata', ['client', 'server']); - api.use('mongo', ['client', 'server']); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', ['client', 'server']); + } else { + api.use('mongo-async', ['client', 'server']); + } api.use('test-helpers', ['client', 'server']); api.use(['underscore', 'tinytest', 'random', 'tracker', 'minimongo', 'reactive-var']); diff --git a/packages/deprecated/facts/package.js b/packages/deprecated/facts/package.js index 0279df5748..82995487cd 100644 --- a/packages/deprecated/facts/package.js +++ b/packages/deprecated/facts/package.js @@ -7,8 +7,12 @@ Package.describe({ Package.onUse(function (api) { api.use(['underscore'], ['client', 'server']); - api.use(['templating@1.2.13', 'mongo', 'ddp'], ['client']); - + api.use(['templating@1.2.13', 'ddp'], ['client']); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', ['client', 'server']); + } else { + api.use('mongo-async', ['client', 'server']); + } // Detect whether autopublish is used. api.use('autopublish', 'server', {weak: true}); diff --git a/packages/deprecated/meteor-platform/package.js b/packages/deprecated/meteor-platform/package.js index 3f2371dfc6..f851ac8e96 100644 --- a/packages/deprecated/meteor-platform/package.js +++ b/packages/deprecated/meteor-platform/package.js @@ -30,8 +30,6 @@ Package.onUse(function(api) { // DDP: Meteor's client/server protocol. 'ddp', 'livedata', // XXX COMPAT WITH PACKAGES BUILT FOR 0.9.0. - // You want to keep your data somewhere? How about MongoDB? - 'mongo', // Blaze: Reactive DOM! 'blaze', 'ui', // XXX COMPAT WITH PACKAGES BUILT FOR 0.9.0. @@ -50,6 +48,12 @@ Package.onUse(function(api) { // People like being able to clone objects. 'ejson' ]); + // You want to keep your data somewhere? How about MongoDB? + if (!process.env.DISABLE_FIBERS) { + api.imply(['mongo']); + } else { + api.imply(['mongo-async']); + } // These are useful too! But you don't have to see their exports // unless you want to. diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 654f16c568..7ec8b9aee2 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -11,7 +11,12 @@ Package.onUse(function onUse(api) { }); Package.onTest(function onTest(api) { - api.use(['ecmascript', 'tinytest', 'mongo']); + api.use(['ecmascript', 'tinytest']); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo'); + } else { + api.use('mongo-async'); + } api.use('ejson'); api.mainModule('ejson_tests.js'); }); diff --git a/packages/facts-ui/package.js b/packages/facts-ui/package.js index 5f367eb09d..2d8bbb0aa8 100644 --- a/packages/facts-ui/package.js +++ b/packages/facts-ui/package.js @@ -7,10 +7,13 @@ Package.onUse(function (api) { api.use([ 'ecmascript', 'facts-base', - 'mongo', 'templating@1.2.13' ], 'client'); - + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', 'client'); + } else { + api.use('mongo-async', 'client'); + } api.imply('facts-base'); api.addFiles('facts_ui.html', 'client'); diff --git a/packages/mongo-async/package.js b/packages/mongo-async/package.js index f31b7efe27..ce39022e90 100644 --- a/packages/mongo-async/package.js +++ b/packages/mongo-async/package.js @@ -85,7 +85,7 @@ Package.onUse(function (api) { }); Package.onTest(function (api) { - api.use('mongo'); + api.use('mongo-async'); api.use('check'); api.use('ecmascript'); api.use('npm-mongo', 'server'); diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 2792c6b030..279ff74c1c 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -4,5 +4,9 @@ Package.describe({ }); Package.onUse(function (api) { - api.imply("mongo"); + if (!process.env.DISABLE_FIBERS) { + api.imply('mongo'); + } else { + api.imply('mongo-async'); + } }); diff --git a/packages/non-core/mongo-decimal/package.js b/packages/non-core/mongo-decimal/package.js index 601d2cd0b7..59478167e2 100644 --- a/packages/non-core/mongo-decimal/package.js +++ b/packages/non-core/mongo-decimal/package.js @@ -15,7 +15,11 @@ Package.onUse(function (api) { }); Package.onTest(function (api) { - api.use('mongo'); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo'); + } else { + api.use('mongo-async'); + } api.use('mongo-decimal'); api.use('insecure'); api.use(['tinytest']); diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 421be1d506..5fc50df840 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -5,8 +5,12 @@ Package.describe({ Package.onUse(api => { api.use(['check', 'ecmascript', 'localstorage', 'url']); - - api.use(['routepolicy', 'webapp', 'mongo', 'service-configuration', 'logging'], 'server'); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', 'server'); + } else { + api.use('mongo-async', 'server'); + } + api.use(['routepolicy', 'webapp', 'service-configuration', 'logging'], 'server'); api.use(['reload', 'base64'], 'client'); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index bb07c66774..8505da26ff 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -10,7 +10,11 @@ Package.onUse(api => { api.use('oauth', ['client', 'server']); api.use('check', 'server'); - api.use('mongo'); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo'); + } else { + api.use('mongo-async'); + } api.export('OAuth1Binding', 'server'); api.export('OAuth1Test', 'server', {testOnly: true}); diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index ee7d4e4e9f..0d856e6085 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -6,7 +6,12 @@ Package.describe({ Package.onUse(function (api) { api.use(['tracker', 'ejson', 'ecmascript']); // If we are loading mongo-livedata, let you store ObjectIDs in it. - api.use(['mongo', 'reload'], { weak: true }); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', { weak: true }); + } else { + api.use('mongo-async', { weak: true }); + } + api.use(['reload'], { weak: true }); api.mainModule('migration.js'); api.export('ReactiveDict'); }); diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index ec496a1fff..7313ce21de 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -5,7 +5,11 @@ Package.describe({ Package.onUse(function(api) { api.use('accounts-base', ['client', 'server']); - api.use('mongo', ['client', 'server']); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo', ['client', 'server']); + } else { + api.use('mongo-async', ['client', 'server']); + } api.use('ecmascript', ['client', 'server']); api.export('ServiceConfiguration'); api.addFiles('service_configuration_common.js', ['client', 'server']); diff --git a/packages/session/package.js b/packages/session/package.js index 5515510f25..5a8bff9712 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -20,6 +20,10 @@ Package.onTest(function (api) { api.use('tinytest'); api.use('session', 'client'); api.use('tracker'); - api.use('mongo'); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo'); + } else { + api.use('mongo-async'); + } api.addFiles('session_tests.js', 'client'); }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index 862749494b..a01ea79f71 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -10,9 +10,13 @@ Package.onUse(function (api) { 'underscore', 'random', 'ddp', - 'mongo', 'check' ]); + if (!process.env.DISABLE_FIBERS) { + api.use('mongo'); + } else { + api.use('mongo-async'); + } api.mainModule('tinytest_client.js', 'client'); api.mainModule('tinytest_server.js', 'server'); From ced273487662c675f9cce0ee9c7192475baa47dd Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 4 Nov 2022 17:31:22 -0300 Subject: [PATCH 0040/1965] Separate mongo packages into two packages - packages/mongo: current Fibers implementation; - Packages/mongo-async: New version without Fibers (Work in progress) - Change usages to import correct package based on flag DISABLE_FIBERS --- packages/mongo-async/collection.js | 40 +---- packages/mongo-async/doc_fetcher_tests.js | 2 - packages/mongo-async/mongo_driver.js | 141 +----------------- packages/mongo-async/observe_multiplex.js | 2 +- packages/mongo-async/oplog_observe_driver.js | 6 +- packages/mongo-async/oplog_tailing.js | 8 +- .../mongo-async/polling_observe_driver.js | 106 +------------ 7 files changed, 18 insertions(+), 287 deletions(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 98141d83c7..0209ff29ef 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -412,37 +412,7 @@ Object.assign(Mongo.Collection.prototype, { }); Object.assign(Mongo.Collection, { - _publishCursorFibers(cursor, sub, collection) { - var observeHandle = cursor.observeChanges( - { - added: function(id, fields) { - sub.added(collection, id, fields); - }, - changed: function(id, fields) { - sub.changed(collection, id, fields); - }, - removed: function(id) { - sub.removed(collection, id); - }, - }, - // Publications don't mutate the documents - // This is tested by the `livedata - publish callbacks clone` test - { nonMutatingCallbacks: true } - ); - - // 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(); - }); - - // return the observeHandle in case it needs to be stopped early - return observeHandle; - }, - - async _publishCursorNoFibers(cursor, sub, collection) { + async _publishCursor(cursor, sub, collection) { var observeHandle = await cursor.observeChanges( { added: function(id, fields) { @@ -472,12 +442,6 @@ Object.assign(Mongo.Collection, { return observeHandle; }, - _publishCursor(cursor, sub, collection) { - return Meteor._isFibersEnabled - ? this._publishCursorFibers(cursor, sub, collection) - : this._publishCursorNoFibers(cursor, sub, collection); - }, - // protect against dangerous selectors. falsey and {_id: falsey} are both // likely programmer error, and not what you want, particularly for destructive // operations. If a falsey _id is sent in, a new string _id will be @@ -705,7 +669,7 @@ Object.assign(Mongo.Collection.prototype, { * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. */ insert(doc, callback) { - return Meteor._isFibersEnabled ? this._insertSync(doc, callback) : this._insertAsync(doc, callback); + return this._insertAsync(doc, callback); }, /** diff --git a/packages/mongo-async/doc_fetcher_tests.js b/packages/mongo-async/doc_fetcher_tests.js index 484b5f6d03..9460ed8612 100644 --- a/packages/mongo-async/doc_fetcher_tests.js +++ b/packages/mongo-async/doc_fetcher_tests.js @@ -1,5 +1,3 @@ -var Fiber = Npm.require('fibers'); -var Future = Npm.require('fibers/future'); import { DocFetcher } from "./doc_fetcher.js"; testAsyncMulti("mongo-livedata - doc fetcher", [ diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index 8c8cf093eb..0c1a9cfd1c 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -231,7 +231,7 @@ MongoConnection.prototype._close = async function() { }; MongoConnection.prototype.close = function () { - return Meteor._isFibersEnabled ? Promise.await(this._close()) : this._close(); + return this._close(); }; // Returns the Mongo Collection object; may yield. @@ -786,7 +786,7 @@ var simulateUpsertWithInsertedId = function (collection, selector, mod, _.each(["insert", "update", "remove", "dropCollection", "dropDatabase"], function (method) { MongoConnection.prototype[method] = function (/* arguments */) { var self = this; - return Meteor._isFibersEnabled ? Meteor.wrapAsync(self["_" + method]).apply(self, arguments) : Meteor.promisify(self[`_${method}`]).apply(self, arguments); + return Meteor.promisify(self[`_${method}`]).apply(self, arguments); }; }); @@ -818,17 +818,7 @@ MongoConnection.prototype.find = function (collectionName, selector, options) { self, new CursorDescription(collectionName, selector, options)); }; -MongoConnection.prototype._findOneFibers = function (collection_name, selector, options) { - var self = this; - if (arguments.length === 1) - selector = {}; - - options = options || {}; - options.limit = 1; - return self.find(collection_name, selector, options).fetch()[0]; -}; - -MongoConnection.prototype._findOneNoFibers = async function (collection_name, selector, options) { +MongoConnection.prototype.findOne = async function (collection_name, selector, options) { var self = this; if (arguments.length === 1) { selector = {}; @@ -842,10 +832,6 @@ MongoConnection.prototype._findOneNoFibers = async function (collection_name, se return results[0]; }; -MongoConnection.prototype.findOne = function (...args) { - return Meteor._isFibersEnabled ? this._findOneFibers(...args) : this._findOneNoFibers(...args); -}; - // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. MongoConnection.prototype.createIndex = function (collectionName, index, @@ -1060,7 +1046,7 @@ MongoConnection.prototype._createSynchronousCursor = function( dbCursor = dbCursor.hint(cursorOptions.hint); } - return Meteor._isFibersEnabled ? new SynchronousCursor(dbCursor, cursorDescription, options, collection) : new AsynchronousCursor(dbCursor, cursorDescription, options, collection); + return new AsynchronousCursor(dbCursor, cursorDescription, options, collection); }; /** @@ -1473,14 +1459,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo }; Object.assign(MongoConnection.prototype, { - _observeChanges: function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - return Meteor._isFibersEnabled - ? this._observeChangesFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks) - : this._observeChangesNoFibers(cursorDescription, ordered, callbacks, nonMutatingCallbacks); - }, - - _observeChangesNoFibers: async function ( + _observeChanges: async function ( cursorDescription, ordered, callbacks, nonMutatingCallbacks) { var self = this; @@ -1589,116 +1568,6 @@ Object.assign(MongoConnection.prototype, { return observeHandle; }, - _observeChangesFibers: function ( - cursorDescription, ordered, callbacks, nonMutatingCallbacks) { - var self = this; - - if (cursorDescription.options.tailable) { - return self._observeChangesTailable(cursorDescription, ordered, callbacks); - } - - // You may not filter out _id when observing changes, because the id is a core - // part of the observeChanges API. - const fieldsOptions = cursorDescription.options.projection || cursorDescription.options.fields; - if (fieldsOptions && - (fieldsOptions._id === 0 || - fieldsOptions._id === false)) { - throw Error("You may not observe a cursor with {fields: {_id: 0}}"); - } - - var observeKey = EJSON.stringify( - _.extend({ordered: ordered}, cursorDescription)); - - var multiplexer, observeDriver; - var firstHandle = false; - - // Find a matching ObserveMultiplexer, or create a new one. This next block is - // guaranteed to not yield (and it doesn't call anything that can observe a - // new query), so no other calls to this function can interleave with it. - Meteor._noYieldsAllowed(function () { - if (_.has(self._observeMultiplexers, observeKey)) { - multiplexer = self._observeMultiplexers[observeKey]; - } else { - firstHandle = true; - // Create a new ObserveMultiplexer. - multiplexer = new ObserveMultiplexer({ - ordered: ordered, - onStop: function () { - delete self._observeMultiplexers[observeKey]; - observeDriver.stop(); - } - }); - self._observeMultiplexers[observeKey] = multiplexer; - } - }); - - var observeHandle = new ObserveHandle(multiplexer, - callbacks, - nonMutatingCallbacks, - ); - - if (firstHandle) { - var matcher, sorter; - var canUseOplog = _.all([ - function () { - // At a bare minimum, using the oplog requires us to have an oplog, to - // want unordered callbacks, and to not want a callback on the polls - // that won't happen. - return self._oplogHandle && !ordered && - !callbacks._testOnlyPollCallback; - }, function () { - // We need to be able to compile the selector. Fall back to polling for - // some newfangled $selector that minimongo doesn't support yet. - try { - matcher = new Minimongo.Matcher(cursorDescription.selector); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }, function () { - // ... and the selector itself needs to support oplog. - return OplogObserveDriver.cursorSupported(cursorDescription, matcher); - }, function () { - // And we need to be able to compile the sort, if any. eg, can't be - // {$natural: 1}. - if (!cursorDescription.options.sort) - return true; - try { - sorter = new Minimongo.Sorter(cursorDescription.options.sort); - return true; - } catch (e) { - // XXX make all compilation errors MinimongoError or something - // so that this doesn't ignore unrelated exceptions - return false; - } - }], function (f) { return f(); }); // invoke each function - - var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver; - observeDriver = new driverClass({ - cursorDescription: cursorDescription, - mongoHandle: self, - multiplexer: multiplexer, - ordered: ordered, - matcher: matcher, // ignored by polling - sorter: sorter, // ignored by polling - _testOnlyPollCallback: callbacks._testOnlyPollCallback - }); - - if (observeDriver._init) { - observeDriver._init(); - } - - // This field is only set for use in tests. - multiplexer._observeDriver = observeDriver; - } - - // Blocks until the initial adds have been sent. - multiplexer.addHandleAndSendInitialAdds(observeHandle); - - return observeHandle; - }, }); diff --git a/packages/mongo-async/observe_multiplex.js b/packages/mongo-async/observe_multiplex.js index 4b6b64245c..50ecba5d5f 100644 --- a/packages/mongo-async/observe_multiplex.js +++ b/packages/mongo-async/observe_multiplex.js @@ -29,7 +29,7 @@ ObserveMultiplexer = class { } addHandleAndSendInitialAdds(handle) { - return Meteor._isFibersEnabled ? Promise.await(this._addHandleAndSendInitialAdds(handle)) : this._addHandleAndSendInitialAdds(handle); + return this._addHandleAndSendInitialAdds(handle); } async _addHandleAndSendInitialAdds(handle) { diff --git a/packages/mongo-async/oplog_observe_driver.js b/packages/mongo-async/oplog_observe_driver.js index 9edfd2703b..d24728484f 100644 --- a/packages/mongo-async/oplog_observe_driver.js +++ b/packages/mongo-async/oplog_observe_driver.js @@ -676,7 +676,7 @@ _.extend(OplogObserveDriver.prototype, { // Yields! _runInitialQuery: function () { - return Meteor._isFibersEnabled ? Promise.await(this._runInitialQueryAsync()) : this._runInitialQueryAsync(); + return this._runInitialQueryAsync(); }, // In various circumstances, we may just want to stop processing the oplog and @@ -772,7 +772,7 @@ _.extend(OplogObserveDriver.prototype, { // Yields! _runQuery: function (options) { - return Meteor._isFibersEnabled ? Promise.await(this._runQueryAsync(options)) : this._runQueryAsync(options); + return this._runQueryAsync(options); }, // Transitions to QUERYING and runs another query, or (if already in QUERYING) @@ -959,7 +959,7 @@ _.extend(OplogObserveDriver.prototype, { }, stop: function() { const self = this; - return Meteor._isFibersEnabled ? Promise.await(self._stop()) : self._stop(); + return self._stop(); }, _registerPhaseChange: function (phase) { diff --git a/packages/mongo-async/oplog_tailing.js b/packages/mongo-async/oplog_tailing.js index a50e3f5a1e..330c43c2cf 100644 --- a/packages/mongo-async/oplog_tailing.js +++ b/packages/mongo-async/oplog_tailing.js @@ -78,9 +78,7 @@ OplogHandle = function (oplogUrl, dbName) { self._workerActive = false; const shouldAwait = self._startTailing(); - if (Meteor._isFibersEnabled) { - Promise.await(shouldAwait); - } + //TODO Why wait? }; Object.assign(OplogHandle.prototype, { @@ -115,7 +113,7 @@ Object.assign(OplogHandle.prototype, { }; }, onOplogEntry: function (trigger, callback) { - return Meteor._isFibersEnabled ? Promise.await(this._onOplogEntry(trigger, callback)) : this._onOplogEntry(trigger, callback); + return this._onOplogEntry(trigger, callback); }, // Register a callback to be invoked any time we skip oplog entries (eg, // because we are too far behind). @@ -190,7 +188,7 @@ Object.assign(OplogHandle.prototype, { // XXX become convinced that this is actually safe even if oplogConnection // is some kind of pool waitUntilCaughtUp: function () { - return Meteor._isFibersEnabled ? Promise.await(this._waitUntilCaughtUp()) : this._waitUntilCaughtUp(); + return this._waitUntilCaughtUp(); }, _startTailing: async function () { diff --git a/packages/mongo-async/polling_observe_driver.js b/packages/mongo-async/polling_observe_driver.js index 32744abeba..5df4d5f964 100644 --- a/packages/mongo-async/polling_observe_driver.js +++ b/packages/mongo-async/polling_observe_driver.js @@ -77,25 +77,13 @@ PollingObserveDriver = function (options) { }; _.extend(PollingObserveDriver.prototype, { - _initAsync: async function () { + _init: async function () { // Make sure we actually poll soon! await this._unthrottledEnsurePollIsScheduled(); Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( "mongo-livedata", "observe-drivers-polling", 1); }, - _init() { - if (!Meteor._isFibersEnabled) { - return this._initAsync(); - } - - var self = this; - // Make sure we actually poll soon! - self._unthrottledEnsurePollIsScheduled(); - - Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( - "mongo-livedata", "observe-drivers-polling", 1); - }, // This is always called through _.throttle (except once at startup). _unthrottledEnsurePollIsScheduled: function () { var self = this; @@ -142,86 +130,7 @@ _.extend(PollingObserveDriver.prototype, { }); }, - _pollMongoFibers: function () { - var self = this; - --self._pollsScheduledButNotStarted; - - if (self._stopped) - return; - - var first = false; - var newResults; - var oldResults = self._results; - if (!oldResults) { - first = true; - // XXX maybe use OrderedDict instead? - oldResults = self._ordered ? [] : new LocalCollection._IdMap; - } - - self._testOnlyPollCallback && self._testOnlyPollCallback(); - - // Save the list of pending writes which this round will commit. - var writesForCycle = self._pendingWrites; - self._pendingWrites = []; - - // Get the new query results. (This yields.) - try { - newResults = self._cursor.getRawObjects(self._ordered); - } catch (e) { - if (first && typeof(e.code) === 'number') { - // This is an error document sent to us by mongod, not a connection - // error generated by the client. And we've never seen this query work - // successfully. Probably it's a bad selector or something, so we should - // NOT retry. Instead, we should halt the observe (which ends up calling - // `stop` on us). - self._multiplexer.queryError( - new Error( - "Exception while polling query " + - JSON.stringify(self._cursorDescription) + ": " + e.message)); - return; - } - - // getRawObjects can throw if we're having trouble talking to the - // database. That's fine --- we will repoll later anyway. But we should - // make sure not to lose track of this cycle's writes. - // (It also can throw if there's just something invalid about this query; - // unfortunately the ObserveDriver API doesn't provide a good way to - // "cancel" the observe from the inside in this case. - Array.prototype.push.apply(self._pendingWrites, writesForCycle); - Meteor._debug("Exception while polling query " + - JSON.stringify(self._cursorDescription), e); - return; - } - - // Run diffs. - if (!self._stopped) { - LocalCollection._diffQueryChanges( - self._ordered, oldResults, newResults, self._multiplexer); - } - - // Signals the multiplexer to allow all observeChanges calls that share this - // multiplexer to return. (This happens asynchronously, via the - // multiplexer's queue.) - if (first) - self._multiplexer.ready(); - - // Replace self._results atomically. (This assignment is what makes `first` - // stay through on the next cycle, so we've waited until after we've - // committed to ready-ing the multiplexer.) - self._results = newResults; - - // Once the ObserveMultiplexer has processed everything we've done in this - // round, mark all the writes which existed before this call as - // commmitted. (If new writes have shown up in the meantime, there'll - // already be another _pollMongo task scheduled.) - self._multiplexer.onFlush(function () { - _.each(writesForCycle, function (w) { - w.committed(); - }); - }); - }, - - async _pollMongoNoFibers() { + async _pollMongo() { var self = this; --self._pollsScheduledButNotStarted; @@ -300,21 +209,14 @@ _.extend(PollingObserveDriver.prototype, { }); }, - _pollMongo: function () { - return Meteor._isFibersEnabled ? this._pollMongoFibers() : this._pollMongoNoFibers(); - }, - stop: function () { var self = this; self._stopped = true; - const stopCallbacksCallerFibers = function (c) { - c(); - }; - const stopCallbacksCallerNoFibers = async function(c) { + const stopCallbacksCaller = async function(c) { await c(); }; - _.each(self._stopCallbacks, Meteor._isFibersEnabled ? stopCallbacksCallerFibers : stopCallbacksCallerNoFibers); + _.each(self._stopCallbacks, stopCallbacksCaller); // Release any write fences that are waiting on us. _.each(self._pendingWrites, function (w) { w.committed(); From 97813d63fe3dcfcb67cb7750e46844aa07a9b977 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Tue, 8 Nov 2022 10:19:38 -0300 Subject: [PATCH 0041/1965] Separate mongo packages into two packages - packages/mongo: current Fibers implementation; - Packages/mongo-async: New version without Fibers (Work in progress) - Change usages to import correct package based on flag DISABLE_FIBERS --- packages/accounts-base/package.js | 7 ++----- packages/ddp-client/package.js | 6 +----- packages/ddp-server/package.js | 6 +----- packages/deprecated/facts/package.js | 8 ++------ packages/deprecated/meteor-platform/package.js | 8 ++------ packages/ejson/package.js | 7 +------ packages/facts-ui/package.js | 7 ++----- packages/mongo-async/collection.js | 2 +- packages/mongo-async/package.js | 2 +- packages/mongo-livedata/package.js | 6 +----- packages/mongo/collection.js | 1 + packages/mongo/package.js | 6 ++++++ packages/non-core/mongo-decimal/package.js | 6 +----- packages/oauth/package.js | 8 ++------ packages/oauth1/package.js | 6 +----- packages/reactive-dict/package.js | 7 +------ packages/service-configuration/package.js | 6 +----- packages/session/package.js | 6 +----- packages/tinytest/package.js | 6 +----- 19 files changed, 29 insertions(+), 82 deletions(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 755421c580..32a6df946c 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -24,11 +24,8 @@ Package.onUse(api => { // need this because of the Meteor.users collection but in the future // we'd probably want to abstract this away - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', ['client', 'server']); - } else { - api.use('mongo-async', ['client', 'server']); - } + api.use('mongo', ['client', 'server']); + // If the 'blaze' package is loaded, we'll define some helpers like // {{currentUser}}. If not, no biggie. api.use('blaze@2.5.0', 'client', { weak: true }); diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index b60d545947..0cdc77a953 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -40,6 +40,7 @@ Package.onUse((api) => { Package.onTest((api) => { api.use([ 'livedata', + 'mongo', 'test-helpers', 'ecmascript', 'underscore', @@ -53,11 +54,6 @@ Package.onTest((api) => { 'ddp-common', 'check' ]); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', ['client', 'server']); - } else { - api.use('mongo-async', ['client', 'server']); - } api.addFiles('test/stub_stream.js'); api.addFiles('test/livedata_connection_tests.js'); diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js index af3d53f069..da413690b0 100644 --- a/packages/ddp-server/package.js +++ b/packages/ddp-server/package.js @@ -50,11 +50,7 @@ Package.onUse(function (api) { Package.onTest(function (api) { api.use('ecmascript', ['client', 'server']); api.use('livedata', ['client', 'server']); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', ['client', 'server']); - } else { - api.use('mongo-async', ['client', 'server']); - } + api.use('mongo', ['client', 'server']); api.use('test-helpers', ['client', 'server']); api.use(['underscore', 'tinytest', 'random', 'tracker', 'minimongo', 'reactive-var']); diff --git a/packages/deprecated/facts/package.js b/packages/deprecated/facts/package.js index 82995487cd..0279df5748 100644 --- a/packages/deprecated/facts/package.js +++ b/packages/deprecated/facts/package.js @@ -7,12 +7,8 @@ Package.describe({ Package.onUse(function (api) { api.use(['underscore'], ['client', 'server']); - api.use(['templating@1.2.13', 'ddp'], ['client']); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', ['client', 'server']); - } else { - api.use('mongo-async', ['client', 'server']); - } + api.use(['templating@1.2.13', 'mongo', 'ddp'], ['client']); + // Detect whether autopublish is used. api.use('autopublish', 'server', {weak: true}); diff --git a/packages/deprecated/meteor-platform/package.js b/packages/deprecated/meteor-platform/package.js index f851ac8e96..3f2371dfc6 100644 --- a/packages/deprecated/meteor-platform/package.js +++ b/packages/deprecated/meteor-platform/package.js @@ -30,6 +30,8 @@ Package.onUse(function(api) { // DDP: Meteor's client/server protocol. 'ddp', 'livedata', // XXX COMPAT WITH PACKAGES BUILT FOR 0.9.0. + // You want to keep your data somewhere? How about MongoDB? + 'mongo', // Blaze: Reactive DOM! 'blaze', 'ui', // XXX COMPAT WITH PACKAGES BUILT FOR 0.9.0. @@ -48,12 +50,6 @@ Package.onUse(function(api) { // People like being able to clone objects. 'ejson' ]); - // You want to keep your data somewhere? How about MongoDB? - if (!process.env.DISABLE_FIBERS) { - api.imply(['mongo']); - } else { - api.imply(['mongo-async']); - } // These are useful too! But you don't have to see their exports // unless you want to. diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 7ec8b9aee2..654f16c568 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -11,12 +11,7 @@ Package.onUse(function onUse(api) { }); Package.onTest(function onTest(api) { - api.use(['ecmascript', 'tinytest']); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo'); - } else { - api.use('mongo-async'); - } + api.use(['ecmascript', 'tinytest', 'mongo']); api.use('ejson'); api.mainModule('ejson_tests.js'); }); diff --git a/packages/facts-ui/package.js b/packages/facts-ui/package.js index 2d8bbb0aa8..5f367eb09d 100644 --- a/packages/facts-ui/package.js +++ b/packages/facts-ui/package.js @@ -7,13 +7,10 @@ Package.onUse(function (api) { api.use([ 'ecmascript', 'facts-base', + 'mongo', 'templating@1.2.13' ], 'client'); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', 'client'); - } else { - api.use('mongo-async', 'client'); - } + api.imply('facts-base'); api.addFiles('facts_ui.html', 'client'); diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 0209ff29ef..0f31c33a00 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -12,7 +12,7 @@ import { normalizeProjection } from "./mongo_utils"; * @namespace */ Mongo = {}; - +console.log('Using package: mongo-async'); /** * @summary Constructor for a Collection * @locus Anywhere diff --git a/packages/mongo-async/package.js b/packages/mongo-async/package.js index ce39022e90..f31b7efe27 100644 --- a/packages/mongo-async/package.js +++ b/packages/mongo-async/package.js @@ -85,7 +85,7 @@ Package.onUse(function (api) { }); Package.onTest(function (api) { - api.use('mongo-async'); + api.use('mongo'); api.use('check'); api.use('ecmascript'); api.use('npm-mongo', 'server'); diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 279ff74c1c..2792c6b030 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -4,9 +4,5 @@ Package.describe({ }); Package.onUse(function (api) { - if (!process.env.DISABLE_FIBERS) { - api.imply('mongo'); - } else { - api.imply('mongo-async'); - } + api.imply("mongo"); }); diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 3dcc12dc96..bdc9333e70 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -13,6 +13,7 @@ import { normalizeProjection } from "./mongo_utils"; */ Mongo = {}; +console.log('Using package: mongo'); /** * @summary Constructor for a Collection * @locus Anywhere diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 0ec175d219..d4316d1603 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -21,6 +21,12 @@ Npm.strip({ }); Package.onUse(function (api) { + if (process.env.DISABLE_FIBERS) { + api.use('mongo-async', ['server', 'client']); + Mongo = Package["mongo-async"]; + api.export("Mongo"); + return; + } api.use('npm-mongo', 'server'); api.use('allow-deny'); diff --git a/packages/non-core/mongo-decimal/package.js b/packages/non-core/mongo-decimal/package.js index 59478167e2..601d2cd0b7 100644 --- a/packages/non-core/mongo-decimal/package.js +++ b/packages/non-core/mongo-decimal/package.js @@ -15,11 +15,7 @@ Package.onUse(function (api) { }); Package.onTest(function (api) { - if (!process.env.DISABLE_FIBERS) { - api.use('mongo'); - } else { - api.use('mongo-async'); - } + api.use('mongo'); api.use('mongo-decimal'); api.use('insecure'); api.use(['tinytest']); diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 5fc50df840..421be1d506 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -5,12 +5,8 @@ Package.describe({ Package.onUse(api => { api.use(['check', 'ecmascript', 'localstorage', 'url']); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', 'server'); - } else { - api.use('mongo-async', 'server'); - } - api.use(['routepolicy', 'webapp', 'service-configuration', 'logging'], 'server'); + + api.use(['routepolicy', 'webapp', 'mongo', 'service-configuration', 'logging'], 'server'); api.use(['reload', 'base64'], 'client'); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 8505da26ff..bb07c66774 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -10,11 +10,7 @@ Package.onUse(api => { api.use('oauth', ['client', 'server']); api.use('check', 'server'); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo'); - } else { - api.use('mongo-async'); - } + api.use('mongo'); api.export('OAuth1Binding', 'server'); api.export('OAuth1Test', 'server', {testOnly: true}); diff --git a/packages/reactive-dict/package.js b/packages/reactive-dict/package.js index 0d856e6085..ee7d4e4e9f 100644 --- a/packages/reactive-dict/package.js +++ b/packages/reactive-dict/package.js @@ -6,12 +6,7 @@ Package.describe({ Package.onUse(function (api) { api.use(['tracker', 'ejson', 'ecmascript']); // If we are loading mongo-livedata, let you store ObjectIDs in it. - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', { weak: true }); - } else { - api.use('mongo-async', { weak: true }); - } - api.use(['reload'], { weak: true }); + api.use(['mongo', 'reload'], { weak: true }); api.mainModule('migration.js'); api.export('ReactiveDict'); }); diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index 7313ce21de..ec496a1fff 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -5,11 +5,7 @@ Package.describe({ Package.onUse(function(api) { api.use('accounts-base', ['client', 'server']); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo', ['client', 'server']); - } else { - api.use('mongo-async', ['client', 'server']); - } + api.use('mongo', ['client', 'server']); api.use('ecmascript', ['client', 'server']); api.export('ServiceConfiguration'); api.addFiles('service_configuration_common.js', ['client', 'server']); diff --git a/packages/session/package.js b/packages/session/package.js index 5a8bff9712..5515510f25 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -20,10 +20,6 @@ Package.onTest(function (api) { api.use('tinytest'); api.use('session', 'client'); api.use('tracker'); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo'); - } else { - api.use('mongo-async'); - } + api.use('mongo'); api.addFiles('session_tests.js', 'client'); }); diff --git a/packages/tinytest/package.js b/packages/tinytest/package.js index a01ea79f71..862749494b 100644 --- a/packages/tinytest/package.js +++ b/packages/tinytest/package.js @@ -10,13 +10,9 @@ Package.onUse(function (api) { 'underscore', 'random', 'ddp', + 'mongo', 'check' ]); - if (!process.env.DISABLE_FIBERS) { - api.use('mongo'); - } else { - api.use('mongo-async'); - } api.mainModule('tinytest_client.js', 'client'); api.mainModule('tinytest_server.js', 'server'); From c5735365ae4648b28b6d02763e4d3dbcc2f120a4 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 09:34:16 -0300 Subject: [PATCH 0042/1965] Separate mongo packages into two packages - packages/mongo: current Fibers implementation; - Packages/mongo-async: New version without Fibers (Work in progress) - Change usages to import correct package based on flag DISABLE_FIBERS --- packages/ddp-server/livedata_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index fcdbc29003..533e01b701 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -778,7 +778,7 @@ Object.assign(Session.prototype, { const isThenable = resultOrThenable && typeof resultOrThenable.then === 'function'; if (isThenable) { - result = Promise.await(resultOrThenable); + result = Meteor._isFibersEnabled ? Promise.await(resultOrThenable) : resultOrThenable; } else { result = resultOrThenable; } From 87f84d9c9b7353a12a4423f559010852f605e548 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 15:20:03 -0300 Subject: [PATCH 0043/1965] Separate mongo packages into two packages - packages/mongo: current Fibers implementation; - Packages/mongo-async: New version without Fibers (Work in progress) - Change usages to import correct package based on flag DISABLE_FIBERS --- packages/meteor/async_helpers.js | 132 ++++++++++++++ packages/meteor/fiber_helpers.js | 253 ++++++++++++++------------- packages/meteor/package.js | 6 +- packages/mongo-async/mongo_driver.js | 26 ++- packages/mongo/mongo_driver.js | 3 + packages/mongo/package.js | 3 +- 6 files changed, 279 insertions(+), 144 deletions(-) create mode 100644 packages/meteor/async_helpers.js diff --git a/packages/meteor/async_helpers.js b/packages/meteor/async_helpers.js new file mode 100644 index 0000000000..e326265c03 --- /dev/null +++ b/packages/meteor/async_helpers.js @@ -0,0 +1,132 @@ +var Fiber = Npm.require('fibers'); +var Future = Npm.require('fibers/future'); + +Meteor._noYieldsAllowed = function (f) { + return f(); +}; + +Meteor._DoubleEndedQueue = Npm.require('double-ended-queue'); + +// Meteor._SynchronousQueue is a queue which runs task functions serially. +// Tasks are assumed to be synchronous: ie, it's assumed that they are +// done when they return. +// +// It has two methods: +// - queueTask queues a task to be run, and returns immediately. +// - runTask queues a task to be run, and then yields. It returns +// when the task finishes running. +// +// It's safe to call queueTask from within a task, but not runTask (unless +// you're calling runTask from a nested Fiber). +// +// Somewhat inspired by async.queue, but specific to blocking tasks. +// XXX break this out into an NPM module? +// XXX could maybe use the npm 'schlock' module instead, which would +// also support multiple concurrent "read" tasks +// +class AsynchronousQueue { + constructor() { + this._taskHandles = new Meteor._DoubleEndedQueue(); + this._runningOrRunScheduled = false; + // This is true if we're currently draining. While we're draining, a further + // drain is a noop, to prevent infinite loops. "drain" is a heuristic type + // operation, that has a meaning like unto "what a naive person would expect + // when modifying a table from an observe" + this._draining = false; + } + + queueTask(task) { + this._taskHandles.push({ + task: task, + name: task.name + }); + return this._scheduleRun(); + } + + _scheduleRun() { + // Already running or scheduled? Do nothing. + if (this._runningOrRunScheduled) + return; + + this._runningOrRunScheduled = true; + + let resolver; + const returnValue = new Promise(r => resolver = r); + setImmediate(() => { + Meteor._runAsync(async () => { + await this._run(); + + if (!resolver) { + throw new Error("Resolver not found for task"); + } + + resolver(); + }); + }); + + return returnValue; + } + + async _run() { + if (!this._runningOrRunScheduled) + throw new Error("expected to be _runningOrRunScheduled"); + + if (this._taskHandles.isEmpty()) { + // Done running tasks! Don't immediately schedule another run, but + // allow future tasks to do so. + this._runningOrRunScheduled = false; + return; + } + const taskHandle = this._taskHandles.shift(); + + // Run the task. + try { + await taskHandle.task(); + } catch (err) { + Meteor._debug("Exception in queued task", err); + } + + // Soon, run the next task, if there is any. + this._runningOrRunScheduled = false; + await this._scheduleRun(); + } + + runTask(task) { + const handle = { + task: Meteor.bindEnvironment(task, function(e) { + Meteor._debug('Exception from task', e); + throw e; + }), + name: task.name + }; + this._taskHandles.push(handle); + return this._scheduleRun(); + } + + flush() { + return this.runTask(() => {}); + } + + async drain() { + if (this._draining) + return; + + this._draining = true; + while (!this._taskHandles.isEmpty()) { + await this.flush(); + } + this._draining = false; + } +} + +Meteor._AsynchronousQueue = AsynchronousQueue; +Meteor._SynchronousQueue = AsynchronousQueue; + + +// Sleep. Mostly used for debugging (eg, inserting latency into server +// methods). +// +const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +Meteor._sleepForMs = function (ms) { + return _sleep(ms); +}; diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 431f3543d7..fd56ee29b7 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -2,10 +2,6 @@ var Fiber = Npm.require('fibers'); var Future = Npm.require('fibers/future'); Meteor._noYieldsAllowed = function (f) { - if (!Meteor._isFibersEnabled) { - return f(); - } - var savedYield = Fiber.yield; Fiber.yield = function () { throw new Error("Can't call yield in a noYieldsAllowed block!"); @@ -36,147 +32,152 @@ Meteor._DoubleEndedQueue = Npm.require('double-ended-queue'); // XXX could maybe use the npm 'schlock' module instead, which would // also support multiple concurrent "read" tasks // -class AsynchronousQueue { - constructor() { - this._taskHandles = new Meteor._DoubleEndedQueue(); - this._runningOrRunScheduled = false; - // This is true if we're currently draining. While we're draining, a further - // drain is a noop, to prevent infinite loops. "drain" is a heuristic type - // operation, that has a meaning like unto "what a naive person would expect - // when modifying a table from an observe" - this._draining = false; +Meteor._SynchronousQueue = function () { + var self = this; + // List of tasks to run (not including a currently-running task if any). Each + // is an object with field 'task' (the task function to run) and 'future' (the + // Future associated with the blocking runTask call that queued it, or null if + // called from queueTask). + self._taskHandles = new Meteor._DoubleEndedQueue(); + // This is true if self._run() is either currently executing or scheduled to + // do so soon. + self._runningOrRunScheduled = false; + // During the execution of a task, this is set to the fiber used to execute + // that task. We use this to throw an error rather than deadlocking if the + // user calls runTask from within a task on the same fiber. + self._currentTaskFiber = undefined; + // This is true if we're currently draining. While we're draining, a further + // drain is a noop, to prevent infinite loops. "drain" is a heuristic type + // operation, that has a meaning like unto "what a naive person would expect + // when modifying a table from an observe" + self._draining = false; +}; + +var SQp = Meteor._SynchronousQueue.prototype; + +SQp.runTask = function (task) { + var self = this; + + 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"); } - queueTask(task) { - this._taskHandles.push({ - task: task, - name: task.name - }); - return this._scheduleRun(); + var fut = new Future; + var handle = { + task: Meteor.bindEnvironment(task, function (e) { + Meteor._debug("Exception from task", e); + throw e; + }), + future: fut, + name: task.name + }; + self._taskHandles.push(handle); + self._scheduleRun(); + // Yield. We'll get back here after the task is run (and will throw if the + // task throws). + fut.wait(); +}; + +SQp.queueTask = function (task) { + var self = this; + self._taskHandles.push({ + task: task, + name: task.name + }); + self._scheduleRun(); + // No need to block. +}; + +SQp.flush = function () { + var self = this; + self.runTask(function () {}); +}; + +SQp.safeToRunTask = function () { + var self = this; + return Fiber.current && self._currentTaskFiber !== Fiber.current; +}; + +SQp.drain = function () { + var self = this; + if (self._draining) + return; + if (!self.safeToRunTask()) + return; + self._draining = true; + while (! self._taskHandles.isEmpty()) { + self.flush(); } + self._draining = false; +}; - _scheduleRun() { - // Already running or scheduled? Do nothing. - if (this._runningOrRunScheduled) - return; +SQp._scheduleRun = function () { + var self = this; + // Already running or scheduled? Do nothing. + if (self._runningOrRunScheduled) + return; - this._runningOrRunScheduled = true; + self._runningOrRunScheduled = true; + setImmediate(function () { + Fiber(function () { + self._run(); + }).run(); + }); +}; - let resolver; - const returnValue = new Promise(r => resolver = r); - setImmediate(() => { - Meteor._runAsync(async () => { - await this._run(); +SQp._run = function () { + var self = this; - if (!resolver) { - throw new Error("Resolver not found for task"); - } + if (!self._runningOrRunScheduled) + throw new Error("expected to be _runningOrRunScheduled"); - resolver(); - }); - }); - - return returnValue; + if (self._taskHandles.isEmpty()) { + // Done running tasks! Don't immediately schedule another run, but + // allow future tasks to do so. + self._runningOrRunScheduled = false; + return; } + var taskHandle = self._taskHandles.shift(); - async _run() { - if (!this._runningOrRunScheduled) - throw new Error("expected to be _runningOrRunScheduled"); - - if (this._taskHandles.isEmpty()) { - // Done running tasks! Don't immediately schedule another run, but - // allow future tasks to do so. - this._runningOrRunScheduled = false; - return; + // Run the task. + self._currentTaskFiber = Fiber.current; + var exception = undefined; + try { + taskHandle.task(); + } catch (err) { + if (taskHandle.future) { + // We'll throw this exception through runTask. + exception = err; + } else { + Meteor._debug("Exception in queued task", err); } - const taskHandle = this._taskHandles.shift(); - - // Run the task. - try { - await taskHandle.task(); - } catch (err) { - Meteor._debug("Exception in queued task", err); - } - - // Soon, run the next task, if there is any. - this._runningOrRunScheduled = false; - await this._scheduleRun(); } + self._currentTaskFiber = undefined; - runTask(task) { - const handle = { - task: Meteor.bindEnvironment(task, function(e) { - Meteor._debug('Exception from task', e); - throw e; - }), - name: task.name - }; - this._taskHandles.push(handle); - return this._scheduleRun(); - } + // Soon, run the next task, if there is any. + self._runningOrRunScheduled = false; + self._scheduleRun(); - flush() { - return this.runTask(() => {}); - } - - async drain() { - if (this._draining) - return; - - this._draining = true; - while (!this._taskHandles.isEmpty()) { - await this.flush(); - } - this._draining = false; - } -} - -Meteor._AsynchronousQueue = AsynchronousQueue; - -Meteor._SynchronousQueue = class extends AsynchronousQueue { - constructor() { - super(); - // During the execution of a task, this is set to the fiber used to execute - // that task. We use this to throw an error rather than deadlocking if the - // user calls runTask from within a task on the same fiber. - this._currentTaskFiber = undefined; - } - - runWithFibers(fn, args) { - if (!Meteor._isFibersEnabled) { - return fn.apply(this, args); - } - - return Promise.await(fn.apply(this, args)); - } - - runTask(task) { - this.runWithFibers(super.runTask, [task]); - } - - flush() { - this.runWithFibers(super.flush); - } - - safeToRunTask() { - return Fiber.current && this._currentTaskFiber !== Fiber.current; - } - - drain() { - this.runWithFibers(super.drain); - } - - _run() { - this.runWithFibers(super._run); + // If this was queued with runTask, let the runTask call return (throwing if + // the task threw). + if (taskHandle.future) { + if (exception) + taskHandle.future['throw'](exception); + else + taskHandle.future['return'](); } }; // Sleep. Mostly used for debugging (eg, inserting latency into server // methods). // -const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); Meteor._sleepForMs = function (ms) { - if (!Meteor._isFibersEnabled) return _sleep(ms); - - Promise.await(_sleep(ms)); + var fiber = Fiber.current; + setTimeout(function() { + fiber.run(); + }, ms); + Fiber.yield(); }; diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 504a2231e7..968c2051c7 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -34,7 +34,11 @@ Package.onUse(function (api) { api.addFiles('timers.js', ['client', 'server']); api.addFiles('errors.js', ['client', 'server']); api.addFiles('asl-helpers.js', 'server'); - api.addFiles('fiber_helpers.js', 'server'); + if (process.env.DISABLE_FIBERS) { + api.addFiles('async_helpers.js', 'server'); + } else { + api.addFiles('fiber_helpers.js', 'server'); + } api.addFiles('fiber_stubs_client.js', 'client'); api.addFiles('startup_client.js', ['client']); api.addFiles('startup_server.js', ['server']); diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index 0c1a9cfd1c..5a5abe3bc7 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -14,7 +14,6 @@ const util = require("util"); /** @type {import('mongodb')} */ var MongoDB = NpmModuleMongodb; -var Future = Npm.require('fibers/future'); import { DocFetcher } from "./doc_fetcher.js"; import { ASYNC_CURSOR_METHODS, @@ -23,6 +22,9 @@ import { MongoInternals = {}; +// TODO remove after test +MongoInternals.__packageName = 'mongo-async'; + MongoInternals.NpmModules = { mongodb: { version: NpmModuleMongodbVersion, @@ -244,19 +246,15 @@ MongoConnection.prototype.rawCollection = function (collectionName) { return self.db.collection(collectionName); }; -MongoConnection.prototype._createCappedCollection = function ( +MongoConnection.prototype._createCappedCollection = async function ( collectionName, byteSize, maxDocuments) { var self = this; if (! self.db) throw Error("_createCappedCollection called before Connection created?"); - var future = new Future(); - self.db.createCollection( - collectionName, - { capped: true, size: byteSize, max: maxDocuments }, - future.resolver()); - future.wait(); + await self.db.createCollection(collectionName, + { capped: true, size: byteSize, max: maxDocuments }); }; // This should be called synchronously with a write, to create a @@ -834,29 +832,25 @@ MongoConnection.prototype.findOne = async function (collection_name, selector, o // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. -MongoConnection.prototype.createIndex = function (collectionName, index, +MongoConnection.prototype.createIndex = async function (collectionName, index, options) { var self = this; // We expect this function to be called at startup, not from within a method, // so we don't interact with the write fence. var collection = self.rawCollection(collectionName); - var future = new Future; - var indexName = collection.createIndex(index, options, future.resolver()); - future.wait(); + var indexName = await collection.createIndex(index, options); }; MongoConnection.prototype._ensureIndex = MongoConnection.prototype.createIndex; -MongoConnection.prototype._dropIndex = function (collectionName, index) { +MongoConnection.prototype._dropIndex = async function (collectionName, index) { var self = this; // This function is only used by test code, not within a method, so we don't // interact with the write fence. var collection = self.rawCollection(collectionName); - var future = new Future; - var indexName = collection.dropIndex(index, future.resolver()); - future.wait(); + var indexName = await collection.dropIndex(index); }; // CURSORS diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 5d653636a8..f54e67361e 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -23,6 +23,9 @@ import { MongoInternals = {}; +// TODO remove after test +MongoInternals.__packageName = 'mongo' + MongoInternals.NpmModules = { mongodb: { version: NpmModuleMongodbVersion, diff --git a/packages/mongo/package.js b/packages/mongo/package.js index d4316d1603..1718220dc2 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -23,8 +23,9 @@ Npm.strip({ Package.onUse(function (api) { if (process.env.DISABLE_FIBERS) { api.use('mongo-async', ['server', 'client']); - Mongo = Package["mongo-async"]; api.export("Mongo"); + api.export('MongoInternals', 'server'); + api.export('ObserveMultiplexer', 'server', {testOnly: true}); return; } api.use('npm-mongo', 'server'); From 650bc0360f733c26acd3ac3fc33312dfdd56626d Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 15:22:05 -0300 Subject: [PATCH 0044/1965] Separate ddp-server packages into two packages - packages/ddp-server: current Fibers implementation; - Packages/ddp-server-async: New version without Fibers (Work in progress) --- packages/{ddp-server => ddp-server-async}/.npm/package/.gitignore | 0 packages/{ddp-server => ddp-server-async}/.npm/package/README | 0 .../.npm/package/npm-shrinkwrap.json | 0 packages/{ddp-server => ddp-server-async}/README.md | 0 packages/{ddp-server => ddp-server-async}/crossbar.js | 0 packages/{ddp-server => ddp-server-async}/crossbar_tests.js | 0 packages/{ddp-server => ddp-server-async}/livedata_server.js | 0 .../livedata_server_async_tests.js | 0 .../{ddp-server => ddp-server-async}/livedata_server_tests.js | 0 packages/{ddp-server => ddp-server-async}/package.js | 0 packages/{ddp-server => ddp-server-async}/server_convenience.js | 0 packages/{ddp-server => ddp-server-async}/session_view_tests.js | 0 packages/{ddp-server => ddp-server-async}/stream_server.js | 0 packages/{ddp-server => ddp-server-async}/writefence.js | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename packages/{ddp-server => ddp-server-async}/.npm/package/.gitignore (100%) rename packages/{ddp-server => ddp-server-async}/.npm/package/README (100%) rename packages/{ddp-server => ddp-server-async}/.npm/package/npm-shrinkwrap.json (100%) rename packages/{ddp-server => ddp-server-async}/README.md (100%) rename packages/{ddp-server => ddp-server-async}/crossbar.js (100%) rename packages/{ddp-server => ddp-server-async}/crossbar_tests.js (100%) rename packages/{ddp-server => ddp-server-async}/livedata_server.js (100%) rename packages/{ddp-server => ddp-server-async}/livedata_server_async_tests.js (100%) rename packages/{ddp-server => ddp-server-async}/livedata_server_tests.js (100%) rename packages/{ddp-server => ddp-server-async}/package.js (100%) rename packages/{ddp-server => ddp-server-async}/server_convenience.js (100%) rename packages/{ddp-server => ddp-server-async}/session_view_tests.js (100%) rename packages/{ddp-server => ddp-server-async}/stream_server.js (100%) rename packages/{ddp-server => ddp-server-async}/writefence.js (100%) diff --git a/packages/ddp-server/.npm/package/.gitignore b/packages/ddp-server-async/.npm/package/.gitignore similarity index 100% rename from packages/ddp-server/.npm/package/.gitignore rename to packages/ddp-server-async/.npm/package/.gitignore diff --git a/packages/ddp-server/.npm/package/README b/packages/ddp-server-async/.npm/package/README similarity index 100% rename from packages/ddp-server/.npm/package/README rename to packages/ddp-server-async/.npm/package/README diff --git a/packages/ddp-server/.npm/package/npm-shrinkwrap.json b/packages/ddp-server-async/.npm/package/npm-shrinkwrap.json similarity index 100% rename from packages/ddp-server/.npm/package/npm-shrinkwrap.json rename to packages/ddp-server-async/.npm/package/npm-shrinkwrap.json diff --git a/packages/ddp-server/README.md b/packages/ddp-server-async/README.md similarity index 100% rename from packages/ddp-server/README.md rename to packages/ddp-server-async/README.md diff --git a/packages/ddp-server/crossbar.js b/packages/ddp-server-async/crossbar.js similarity index 100% rename from packages/ddp-server/crossbar.js rename to packages/ddp-server-async/crossbar.js diff --git a/packages/ddp-server/crossbar_tests.js b/packages/ddp-server-async/crossbar_tests.js similarity index 100% rename from packages/ddp-server/crossbar_tests.js rename to packages/ddp-server-async/crossbar_tests.js diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server-async/livedata_server.js similarity index 100% rename from packages/ddp-server/livedata_server.js rename to packages/ddp-server-async/livedata_server.js diff --git a/packages/ddp-server/livedata_server_async_tests.js b/packages/ddp-server-async/livedata_server_async_tests.js similarity index 100% rename from packages/ddp-server/livedata_server_async_tests.js rename to packages/ddp-server-async/livedata_server_async_tests.js diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server-async/livedata_server_tests.js similarity index 100% rename from packages/ddp-server/livedata_server_tests.js rename to packages/ddp-server-async/livedata_server_tests.js diff --git a/packages/ddp-server/package.js b/packages/ddp-server-async/package.js similarity index 100% rename from packages/ddp-server/package.js rename to packages/ddp-server-async/package.js diff --git a/packages/ddp-server/server_convenience.js b/packages/ddp-server-async/server_convenience.js similarity index 100% rename from packages/ddp-server/server_convenience.js rename to packages/ddp-server-async/server_convenience.js diff --git a/packages/ddp-server/session_view_tests.js b/packages/ddp-server-async/session_view_tests.js similarity index 100% rename from packages/ddp-server/session_view_tests.js rename to packages/ddp-server-async/session_view_tests.js diff --git a/packages/ddp-server/stream_server.js b/packages/ddp-server-async/stream_server.js similarity index 100% rename from packages/ddp-server/stream_server.js rename to packages/ddp-server-async/stream_server.js diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server-async/writefence.js similarity index 100% rename from packages/ddp-server/writefence.js rename to packages/ddp-server-async/writefence.js From e6f7679aef747b48eb81b9f692bd2f3928c4899f Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 15:22:53 -0300 Subject: [PATCH 0045/1965] Separate ddp-server packages into two packages - packages/ddp-server: current Fibers implementation; - Packages/ddp-server-async: New version without Fibers (Work in progress) Restore ddp-server files --- packages/ddp-server/.npm/package/.gitignore | 1 + packages/ddp-server/.npm/package/README | 7 + .../.npm/package/npm-shrinkwrap.json | 45 + packages/ddp-server/README.md | 4 + packages/ddp-server/crossbar.js | 167 ++ packages/ddp-server/crossbar_tests.js | 49 + packages/ddp-server/livedata_server.js | 1917 +++++++++++++++++ .../ddp-server/livedata_server_async_tests.js | 192 ++ packages/ddp-server/livedata_server_tests.js | 411 ++++ packages/ddp-server/package.js | 61 + packages/ddp-server/server_convenience.js | 28 + packages/ddp-server/session_view_tests.js | 393 ++++ packages/ddp-server/stream_server.js | 190 ++ packages/ddp-server/writefence.js | 125 ++ 14 files changed, 3590 insertions(+) create mode 100644 packages/ddp-server/.npm/package/.gitignore create mode 100644 packages/ddp-server/.npm/package/README create mode 100644 packages/ddp-server/.npm/package/npm-shrinkwrap.json create mode 100644 packages/ddp-server/README.md create mode 100644 packages/ddp-server/crossbar.js create mode 100644 packages/ddp-server/crossbar_tests.js create mode 100644 packages/ddp-server/livedata_server.js create mode 100644 packages/ddp-server/livedata_server_async_tests.js create mode 100644 packages/ddp-server/livedata_server_tests.js create mode 100644 packages/ddp-server/package.js create mode 100755 packages/ddp-server/server_convenience.js create mode 100644 packages/ddp-server/session_view_tests.js create mode 100644 packages/ddp-server/stream_server.js create mode 100644 packages/ddp-server/writefence.js diff --git a/packages/ddp-server/.npm/package/.gitignore b/packages/ddp-server/.npm/package/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/ddp-server/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/ddp-server/.npm/package/README b/packages/ddp-server/.npm/package/README new file mode 100644 index 0000000000..3d492553a4 --- /dev/null +++ b/packages/ddp-server/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/ddp-server/.npm/package/npm-shrinkwrap.json b/packages/ddp-server/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000..b9b024674c --- /dev/null +++ b/packages/ddp-server/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,45 @@ +{ + "lockfileVersion": 1, + "dependencies": { + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==" + }, + "http-parser-js": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + }, + "permessage-deflate": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.7.tgz", + "integrity": "sha512-EUNi/RIsyJ1P1u9QHFwMOUWMYetqlE22ZgGbad7YP856WF4BFF0B7DuNy6vEGsgNNud6c/SkdWzkne71hH8MjA==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "sockjs": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", + "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==" + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + } + } +} diff --git a/packages/ddp-server/README.md b/packages/ddp-server/README.md new file mode 100644 index 0000000000..21d2ac4c84 --- /dev/null +++ b/packages/ddp-server/README.md @@ -0,0 +1,4 @@ +# ddp-server +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/ddp-server) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/ddp-server) +*** + diff --git a/packages/ddp-server/crossbar.js b/packages/ddp-server/crossbar.js new file mode 100644 index 0000000000..6672059f99 --- /dev/null +++ b/packages/ddp-server/crossbar.js @@ -0,0 +1,167 @@ +// A "crossbar" is a class that provides structured notification registration. +// See _match for the definition of how a notification matches a trigger. +// All notifications and triggers must have a string key named 'collection'. + +DDPServer._Crossbar = function (options) { + var self = this; + options = options || {}; + + self.nextId = 1; + // map from collection name (string) -> listener id -> object. each object has + // keys 'trigger', 'callback'. As a hack, the empty string means "no + // collection". + self.listenersByCollection = {}; + self.listenersByCollectionCount = {}; + self.factPackage = options.factPackage || "livedata"; + self.factName = options.factName || null; +}; + +_.extend(DDPServer._Crossbar.prototype, { + // msg is a trigger or a notification + _collectionForMessage: function (msg) { + var self = this; + if (! _.has(msg, 'collection')) { + return ''; + } else if (typeof(msg.collection) === 'string') { + if (msg.collection === '') + throw Error("Message has empty collection!"); + return msg.collection; + } else { + throw Error("Message has non-string collection!"); + } + }, + + // Listen for notification that match 'trigger'. A notification + // matches if it has the key-value pairs in trigger as a + // subset. When a notification matches, call 'callback', passing + // the actual notification. + // + // Returns a listen handle, which is an object with a method + // stop(). Call stop() to stop listening. + // + // XXX It should be legal to call fire() from inside a listen() + // callback? + listen: function (trigger, callback) { + var self = this; + var id = self.nextId++; + + var collection = self._collectionForMessage(trigger); + var record = {trigger: EJSON.clone(trigger), callback: callback}; + if (! _.has(self.listenersByCollection, collection)) { + self.listenersByCollection[collection] = {}; + self.listenersByCollectionCount[collection] = 0; + } + self.listenersByCollection[collection][id] = record; + self.listenersByCollectionCount[collection]++; + + if (self.factName && Package['facts-base']) { + Package['facts-base'].Facts.incrementServerFact( + self.factPackage, self.factName, 1); + } + + return { + stop: function () { + if (self.factName && Package['facts-base']) { + Package['facts-base'].Facts.incrementServerFact( + self.factPackage, self.factName, -1); + } + delete self.listenersByCollection[collection][id]; + self.listenersByCollectionCount[collection]--; + if (self.listenersByCollectionCount[collection] === 0) { + delete self.listenersByCollection[collection]; + delete self.listenersByCollectionCount[collection]; + } + } + }; + }, + + // Fire the provided 'notification' (an object whose attribute + // values are all JSON-compatibile) -- inform all matching listeners + // (registered with listen()). + // + // If fire() is called inside a write fence, then each of the + // listener callbacks will be called inside the write fence as well. + // + // The listeners may be invoked in parallel, rather than serially. + fire: function (notification) { + var self = this; + + var collection = self._collectionForMessage(notification); + + if (! _.has(self.listenersByCollection, collection)) { + return; + } + + var listenersForCollection = self.listenersByCollection[collection]; + var callbackIds = []; + _.each(listenersForCollection, function (l, id) { + if (self._matches(notification, l.trigger)) { + callbackIds.push(id); + } + }); + + // Listener callbacks can yield, so we need to first find all the ones that + // match in a single iteration over self.listenersByCollection (which can't + // be mutated during this iteration), and then invoke the matching + // callbacks, checking before each call to ensure they haven't stopped. + // Note that we don't have to check that + // self.listenersByCollection[collection] still === listenersForCollection, + // because the only way that stops being true is if listenersForCollection + // first gets reduced down to the empty object (and then never gets + // increased again). + _.each(callbackIds, function (id) { + if (_.has(listenersForCollection, id)) { + listenersForCollection[id].callback(notification); + } + }); + }, + + // A notification matches a trigger if all keys that exist in both are equal. + // + // Examples: + // N:{collection: "C"} matches T:{collection: "C"} + // (a non-targeted write to a collection matches a + // non-targeted query) + // N:{collection: "C", id: "X"} matches T:{collection: "C"} + // (a targeted write to a collection matches a non-targeted query) + // N:{collection: "C"} matches T:{collection: "C", id: "X"} + // (a non-targeted write to a collection matches a + // targeted query) + // N:{collection: "C", id: "X"} matches T:{collection: "C", id: "X"} + // (a targeted write to a collection matches a targeted query targeted + // at the same document) + // N:{collection: "C", id: "X"} does not match T:{collection: "C", id: "Y"} + // (a targeted write to a collection does not match a targeted query + // targeted at a different document) + _matches: function (notification, trigger) { + // Most notifications that use the crossbar have a string `collection` and + // maybe an `id` that is a string or ObjectID. We're already dividing up + // triggers by collection, but let's fast-track "nope, different ID" (and + // avoid the overly generic EJSON.equals). This makes a noticeable + // performance difference; see https://github.com/meteor/meteor/pull/3697 + if (typeof(notification.id) === 'string' && + typeof(trigger.id) === 'string' && + notification.id !== trigger.id) { + return false; + } + if (notification.id instanceof MongoID.ObjectID && + trigger.id instanceof MongoID.ObjectID && + ! notification.id.equals(trigger.id)) { + return false; + } + + return _.all(trigger, function (triggerValue, key) { + return !_.has(notification, key) || + EJSON.equals(triggerValue, notification[key]); + }); + } +}); + +// The "invalidation crossbar" is a specific instance used by the DDP server to +// implement write fence notifications. Listener callbacks on this crossbar +// should call beginWrite on the current write fence before they return, if they +// want to delay the write fence from firing (ie, the DDP method-data-updated +// message from being sent). +DDPServer._InvalidationCrossbar = new DDPServer._Crossbar({ + factName: "invalidation-crossbar-listeners" +}); diff --git a/packages/ddp-server/crossbar_tests.js b/packages/ddp-server/crossbar_tests.js new file mode 100644 index 0000000000..cf42351798 --- /dev/null +++ b/packages/ddp-server/crossbar_tests.js @@ -0,0 +1,49 @@ +// White box tests of invalidation crossbar matching function. +// Note: the current crossbar match function is designed specifically +// to ensure that a modification that targets a specific ID does not +// notify a query that is watching a different specific ID. (And to +// keep separate collections separate.) Other than that, there's no +// deep meaning to the matching function, and it could be changed later +// as long as it preserves that property. +Tinytest.add('livedata - crossbar', function (test) { + var crossbar = new DDPServer._Crossbar; + test.isTrue(crossbar._matches({collection: "C"}, + {collection: "C"})); + test.isTrue(crossbar._matches({collection: "C", id: "X"}, + {collection: "C"})); + test.isTrue(crossbar._matches({collection: "C"}, + {collection: "C", id: "X"})); + test.isTrue(crossbar._matches({collection: "C", id: "X"}, + {collection: "C"})); + + test.isFalse(crossbar._matches({collection: "C", id: "X"}, + {collection: "C", id: "Y"})); + + // Test that stopped listens definitely don't fire. + var calledFirst = false; + var calledSecond = false; + var trigger = {collection: "C"}; + var secondHandle; + crossbar.listen(trigger, function (notification) { + // This test assumes that listeners will be called in the order + // registered. It's not wrong for the crossbar to do something different, + // but the test won't be valid in that case, so make it fail so we notice. + calledFirst = true; + if (calledSecond) { + test.fail({ + type: "test_assumption_failed", + message: "test assumed that listeners would be called in the order registered" + }); + } else { + secondHandle.stop(); + } + }); + secondHandle = crossbar.listen(trigger, function (notification) { + // This should not get invoked, because it should be stopped by the other + // listener! + calledSecond = true; + }); + crossbar.fire(trigger); + test.isTrue(calledFirst); + test.isFalse(calledSecond); +}); diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js new file mode 100644 index 0000000000..533e01b701 --- /dev/null +++ b/packages/ddp-server/livedata_server.js @@ -0,0 +1,1917 @@ +DDPServer = {}; + +// Publication strategies define how we handle data from published cursors at the collection level +// This allows someone to: +// - Choose a trade-off between client-server bandwidth and server memory usage +// - Implement special (non-mongo) collections like volatile message queues +const publicationStrategies = { + // SERVER_MERGE is the default strategy. + // When using this strategy, the server maintains a copy of all data a connection is subscribed to. + // This allows us to only send deltas over multiple publications. + SERVER_MERGE: { + useCollectionView: true, + doAccountingForCollection: true, + }, + // The NO_MERGE_NO_HISTORY strategy results in the server sending all publication data + // directly to the client. It does not remember what it has previously sent + // to it will not trigger removed messages when a subscription is stopped. + // This should only be chosen for special use cases like send-and-forget queues. + NO_MERGE_NO_HISTORY: { + useCollectionView: false, + doAccountingForCollection: false, + }, + // NO_MERGE is similar to NO_MERGE_NO_HISTORY but the server will remember the IDs it has + // sent to the client so it can remove them when a subscription is stopped. + // This strategy can be used when a collection is only used in a single publication. + NO_MERGE: { + useCollectionView: false, + doAccountingForCollection: true, + } +}; + +DDPServer.publicationStrategies = publicationStrategies; + +// This file contains classes: +// * Session - The server's connection to a single DDP client +// * Subscription - A single subscription for a single client +// * Server - An entire server that may talk to > 1 client. A DDP endpoint. +// +// Session and Subscription are file scope. For now, until we freeze +// the interface, Server is package scope (in the future it should be +// exported). + +// Represents a single document in a SessionCollectionView +var SessionDocumentView = function () { + var self = this; + self.existsIn = new Set(); // set of subscriptionHandle + self.dataByKey = new Map(); // key-> [ {subscriptionHandle, value} by precedence] +}; + +DDPServer._SessionDocumentView = SessionDocumentView; + + +_.extend(SessionDocumentView.prototype, { + + getFields: function () { + var self = this; + var ret = {}; + self.dataByKey.forEach(function (precedenceList, key) { + ret[key] = precedenceList[0].value; + }); + return ret; + }, + + clearField: function (subscriptionHandle, key, changeCollector) { + var self = this; + // Publish API ignores _id if present in fields + if (key === "_id") + return; + var precedenceList = self.dataByKey.get(key); + + // It's okay to clear fields that didn't exist. No need to throw + // an error. + if (!precedenceList) + return; + + var removedValue = undefined; + for (var i = 0; i < precedenceList.length; i++) { + var precedence = precedenceList[i]; + if (precedence.subscriptionHandle === subscriptionHandle) { + // The view's value can only change if this subscription is the one that + // used to have precedence. + if (i === 0) + removedValue = precedence.value; + precedenceList.splice(i, 1); + break; + } + } + if (precedenceList.length === 0) { + self.dataByKey.delete(key); + changeCollector[key] = undefined; + } else if (removedValue !== undefined && + !EJSON.equals(removedValue, precedenceList[0].value)) { + changeCollector[key] = precedenceList[0].value; + } + }, + + changeField: function (subscriptionHandle, key, value, + changeCollector, isAdd) { + var self = this; + // Publish API ignores _id if present in fields + if (key === "_id") + return; + + // Don't share state with the data passed in by the user. + value = EJSON.clone(value); + + if (!self.dataByKey.has(key)) { + self.dataByKey.set(key, [{subscriptionHandle: subscriptionHandle, + value: value}]); + changeCollector[key] = value; + return; + } + var precedenceList = self.dataByKey.get(key); + var elt; + if (!isAdd) { + elt = precedenceList.find(function (precedence) { + return precedence.subscriptionHandle === subscriptionHandle; + }); + } + + if (elt) { + if (elt === precedenceList[0] && !EJSON.equals(value, elt.value)) { + // this subscription is changing the value of this field. + changeCollector[key] = value; + } + elt.value = value; + } else { + // this subscription is newly caring about this field + precedenceList.push({subscriptionHandle: subscriptionHandle, value: value}); + } + + } +}); + +/** + * Represents a client's view of a single collection + * @param {String} collectionName Name of the collection it represents + * @param {Object.} sessionCallbacks The callbacks for added, changed, removed + * @class SessionCollectionView + */ +var SessionCollectionView = function (collectionName, sessionCallbacks) { + var self = this; + self.collectionName = collectionName; + self.documents = new Map(); + self.callbacks = sessionCallbacks; +}; + +DDPServer._SessionCollectionView = SessionCollectionView; + + +Object.assign(SessionCollectionView.prototype, { + + isEmpty: function () { + var self = this; + return self.documents.size === 0; + }, + + diff: function (previous) { + var self = this; + DiffSequence.diffMaps(previous.documents, self.documents, { + both: _.bind(self.diffDocument, self), + + rightOnly: function (id, nowDV) { + self.callbacks.added(self.collectionName, id, nowDV.getFields()); + }, + + leftOnly: function (id, prevDV) { + self.callbacks.removed(self.collectionName, id); + } + }); + }, + + diffDocument: function (id, prevDV, nowDV) { + var self = this; + var fields = {}; + DiffSequence.diffObjects(prevDV.getFields(), nowDV.getFields(), { + both: function (key, prev, now) { + if (!EJSON.equals(prev, now)) + fields[key] = now; + }, + rightOnly: function (key, now) { + fields[key] = now; + }, + leftOnly: function(key, prev) { + fields[key] = undefined; + } + }); + self.callbacks.changed(self.collectionName, id, fields); + }, + + added: function (subscriptionHandle, id, fields) { + var self = this; + var docView = self.documents.get(id); + var added = false; + if (!docView) { + added = true; + docView = new SessionDocumentView(); + self.documents.set(id, docView); + } + docView.existsIn.add(subscriptionHandle); + var changeCollector = {}; + _.each(fields, function (value, key) { + docView.changeField( + subscriptionHandle, key, value, changeCollector, true); + }); + if (added) + self.callbacks.added(self.collectionName, id, changeCollector); + else + self.callbacks.changed(self.collectionName, id, changeCollector); + }, + + changed: function (subscriptionHandle, id, changed) { + var self = this; + var changedResult = {}; + var docView = self.documents.get(id); + if (!docView) + throw new Error("Could not find element with id " + id + " to change"); + _.each(changed, function (value, key) { + if (value === undefined) + docView.clearField(subscriptionHandle, key, changedResult); + else + docView.changeField(subscriptionHandle, key, value, changedResult); + }); + self.callbacks.changed(self.collectionName, id, changedResult); + }, + + removed: function (subscriptionHandle, id) { + var self = this; + var docView = self.documents.get(id); + if (!docView) { + var err = new Error("Removed nonexistent document " + id); + throw err; + } + docView.existsIn.delete(subscriptionHandle); + if (docView.existsIn.size === 0) { + // it is gone from everyone + self.callbacks.removed(self.collectionName, id); + self.documents.delete(id); + } else { + var changed = {}; + // remove this subscription from every precedence list + // and record the changes + docView.dataByKey.forEach(function (precedenceList, key) { + docView.clearField(subscriptionHandle, key, changed); + }); + + self.callbacks.changed(self.collectionName, id, changed); + } + } +}); + +/******************************************************************************/ +/* Session */ +/******************************************************************************/ + +var Session = function (server, version, socket, options) { + var self = this; + self.id = Random.id(); + + self.server = server; + self.version = version; + + self.initialized = false; + self.socket = socket; + + // Set to null when the session is destroyed. Multiple places below + // use this to determine if the session is alive or not. + self.inQueue = new Meteor._DoubleEndedQueue(); + + self.blocked = false; + self.workerRunning = false; + + self.cachedUnblock = null; + + // Sub objects for active subscriptions + self._namedSubs = new Map(); + self._universalSubs = []; + + self.userId = null; + + self.collectionViews = new Map(); + + // Set this to false to not send messages when collectionViews are + // modified. This is done when rerunning subs in _setUserId and those messages + // are calculated via a diff instead. + self._isSending = true; + + // If this is true, don't start a newly-created universal publisher on this + // session. The session will take care of starting it when appropriate. + self._dontStartNewUniversalSubs = false; + + // When we are rerunning subscriptions, any ready messages + // we want to buffer up for when we are done rerunning subscriptions + self._pendingReady = []; + + // List of callbacks to call when this connection is closed. + self._closeCallbacks = []; + + + // XXX HACK: If a sockjs connection, save off the URL. This is + // temporary and will go away in the near future. + self._socketUrl = socket.url; + + // Allow tests to disable responding to pings. + self._respondToPings = options.respondToPings; + + // This object is the public interface to the session. In the public + // API, it is called the `connection` object. Internally we call it + // a `connectionHandle` to avoid ambiguity. + self.connectionHandle = { + id: self.id, + close: function () { + self.close(); + }, + onClose: function (fn) { + var cb = Meteor.bindEnvironment(fn, "connection onClose callback"); + if (self.inQueue) { + self._closeCallbacks.push(cb); + } else { + // if we're already closed, call the callback. + Meteor.defer(cb); + } + }, + clientAddress: self._clientAddress(), + httpHeaders: self.socket.headers + }; + + self.send({ msg: 'connected', session: self.id }); + + // On initial connect, spin up all the universal publishers. + Meteor._runAsync(function() { + self.startUniversalSubs(); + }); + + if (version !== 'pre1' && options.heartbeatInterval !== 0) { + // We no longer need the low level timeout because we have heartbeats. + socket.setWebsocketTimeout(0); + + self.heartbeat = new DDPCommon.Heartbeat({ + heartbeatInterval: options.heartbeatInterval, + heartbeatTimeout: options.heartbeatTimeout, + onTimeout: function () { + self.close(); + }, + sendPing: function () { + self.send({msg: 'ping'}); + } + }); + self.heartbeat.start(); + } + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "livedata", "sessions", 1); +}; + +Object.assign(Session.prototype, { + + sendReady: function (subscriptionIds) { + var self = this; + if (self._isSending) + self.send({msg: "ready", subs: subscriptionIds}); + else { + _.each(subscriptionIds, function (subscriptionId) { + self._pendingReady.push(subscriptionId); + }); + } + }, + + _canSend(collectionName) { + return this._isSending || !this.server.getPublicationStrategy(collectionName).useCollectionView; + }, + + + sendAdded(collectionName, id, fields) { + if (this._canSend(collectionName)) + this.send({msg: "added", collection: collectionName, id, fields}); + }, + + sendChanged(collectionName, id, fields) { + if (_.isEmpty(fields)) + return; + + if (this._canSend(collectionName)) { + this.send({ + msg: "changed", + collection: collectionName, + id, + fields + }); + } + }, + + sendRemoved(collectionName, id) { + if (this._canSend(collectionName)) + this.send({msg: "removed", collection: collectionName, id}); + }, + + getSendCallbacks: function () { + var self = this; + return { + added: _.bind(self.sendAdded, self), + changed: _.bind(self.sendChanged, self), + removed: _.bind(self.sendRemoved, self) + }; + }, + + getCollectionView: function (collectionName) { + var self = this; + var ret = self.collectionViews.get(collectionName); + if (!ret) { + ret = new SessionCollectionView(collectionName, + self.getSendCallbacks()); + self.collectionViews.set(collectionName, ret); + } + return ret; + }, + + added(subscriptionHandle, collectionName, id, fields) { + if (this.server.getPublicationStrategy(collectionName).useCollectionView) { + const view = this.getCollectionView(collectionName); + view.added(subscriptionHandle, id, fields); + } else { + this.sendAdded(collectionName, id, fields); + } + }, + + removed(subscriptionHandle, collectionName, id) { + if (this.server.getPublicationStrategy(collectionName).useCollectionView) { + const view = this.getCollectionView(collectionName); + view.removed(subscriptionHandle, id); + if (view.isEmpty()) { + this.collectionViews.delete(collectionName); + } + } else { + this.sendRemoved(collectionName, id); + } + }, + + changed(subscriptionHandle, collectionName, id, fields) { + if (this.server.getPublicationStrategy(collectionName).useCollectionView) { + const view = this.getCollectionView(collectionName); + view.changed(subscriptionHandle, id, fields); + } else { + this.sendChanged(collectionName, id, fields); + } + }, + + startUniversalSubs: function () { + var self = this; + // Make a shallow copy of the set of universal handlers and start them. If + // additional universal publishers start while we're running them (due to + // yielding), they will run separately as part of Server.publish. + var handlers = _.clone(self.server.universal_publish_handlers); + _.each(handlers, function (handler) { + self._startSubscription(handler); + }); + }, + + // Destroy this session and unregister it at the server. + close: function () { + var self = this; + + // Destroy this session, even if it's not registered at the + // server. Stop all processing and tear everything down. If a socket + // was attached, close it. + + // Already destroyed. + if (! self.inQueue) + return; + + // Drop the merge box data immediately. + self.inQueue = null; + self.collectionViews = new Map(); + + if (self.heartbeat) { + self.heartbeat.stop(); + self.heartbeat = null; + } + + if (self.socket) { + self.socket.close(); + self.socket._meteorSession = null; + } + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "livedata", "sessions", -1); + + Meteor.defer(function () { + // Stop callbacks can yield, so we defer this on close. + // sub._isDeactivated() detects that we set inQueue to null and + // treats it as semi-deactivated (it will ignore incoming callbacks, etc). + self._deactivateAllSubscriptions(); + + // Defer calling the close callbacks, so that the caller closing + // the session isn't waiting for all the callbacks to complete. + _.each(self._closeCallbacks, function (callback) { + callback(); + }); + }); + + // Unregister the session. + self.server._removeSession(self); + }, + + // Send a message (doing nothing if no socket is connected right now). + // It should be a JSON object (it will be stringified). + send: function (msg) { + var self = this; + if (self.socket) { + if (Meteor._printSentDDP) + Meteor._debug("Sent DDP", DDPCommon.stringifyDDP(msg)); + self.socket.send(DDPCommon.stringifyDDP(msg)); + } + }, + + // Send a connection error. + sendError: function (reason, offendingMessage) { + var self = this; + var msg = {msg: 'error', reason: reason}; + if (offendingMessage) + msg.offendingMessage = offendingMessage; + self.send(msg); + }, + + // Process 'msg' as an incoming message. As a guard against + // race conditions during reconnection, ignore the message if + // 'socket' is not the currently connected socket. + // + // We run the messages from the client one at a time, in the order + // given by the client. The message handler is passed an idempotent + // function 'unblock' which it may call to allow other messages to + // begin running in parallel in another fiber (for example, a method + // that wants to yield). Otherwise, it is automatically unblocked + // when it returns. + // + // Actually, we don't have to 'totally order' the messages in this + // way, but it's the easiest thing that's correct. (unsub needs to + // be ordered against sub, methods need to be ordered against each + // other). + processMessage: function (msg_in) { + var self = this; + if (!self.inQueue) // we have been destroyed. + return; + + // Respond to ping and pong messages immediately without queuing. + // If the negotiated DDP version is "pre1" which didn't support + // pings, preserve the "pre1" behavior of responding with a "bad + // request" for the unknown messages. + // + // Fibers are needed because heartbeats use Meteor.setTimeout, which + // needs a Fiber. We could actually use regular setTimeout and avoid + // these new fibers, but it is easier to just make everything use + // Meteor.setTimeout and not think too hard. + // + // Any message counts as receiving a pong, as it demonstrates that + // the client is still alive. + if (self.heartbeat) { + Meteor._runAsync(function() { + self.heartbeat.messageReceived(); + }); + }; + + if (self.version !== 'pre1' && msg_in.msg === 'ping') { + if (self._respondToPings) + self.send({msg: "pong", id: msg_in.id}); + return; + } + if (self.version !== 'pre1' && msg_in.msg === 'pong') { + // Since everything is a pong, there is nothing to do + return; + } + + self.inQueue.push(msg_in); + if (self.workerRunning) + return; + self.workerRunning = true; + + var processNext = function () { + var msg = self.inQueue && self.inQueue.shift(); + if (!msg) { + self.workerRunning = false; + return; + } + + function runHandlers() { + var blocked = true; + + var unblock = function () { + if (!blocked) + return; // idempotent + blocked = false; + processNext(); + }; + + self.server.onMessageHook.each(function (callback) { + callback(msg, self); + return true; + }); + + if (_.has(self.protocol_handlers, msg.msg)) + self.protocol_handlers[msg.msg].call(self, msg, unblock); + else + self.sendError('Bad request', msg); + unblock(); // in case the handler didn't already do it + } + + Meteor._runAsync(runHandlers); + }; + + processNext(); + }, + + protocol_handlers: { + sub: function (msg, unblock) { + var self = this; + + // cacheUnblock temporarly, so we can capture it later + // we will use unblock in current eventLoop, so this is safe + self.cachedUnblock = unblock; + + // reject malformed messages + if (typeof (msg.id) !== "string" || + typeof (msg.name) !== "string" || + (('params' in msg) && !(msg.params instanceof Array))) { + self.sendError("Malformed subscription", msg); + return; + } + + if (!self.server.publish_handlers[msg.name]) { + self.send({ + msg: 'nosub', id: msg.id, + error: new Meteor.Error(404, `Subscription '${msg.name}' not found`)}); + return; + } + + if (self._namedSubs.has(msg.id)) + // subs are idempotent, or rather, they are ignored if a sub + // with that id already exists. this is important during + // reconnect. + return; + + // XXX It'd be much better if we had generic hooks where any package can + // hook into subscription handling, but in the mean while we special case + // ddp-rate-limiter package. This is also done for weak requirements to + // add the ddp-rate-limiter package in case we don't have Accounts. A + // user trying to use the ddp-rate-limiter must explicitly require it. + if (Package['ddp-rate-limiter']) { + var DDPRateLimiter = Package['ddp-rate-limiter'].DDPRateLimiter; + var rateLimiterInput = { + userId: self.userId, + clientAddress: self.connectionHandle.clientAddress, + type: "subscription", + name: msg.name, + connectionId: self.id + }; + + DDPRateLimiter._increment(rateLimiterInput); + var rateLimitResult = DDPRateLimiter._check(rateLimiterInput); + if (!rateLimitResult.allowed) { + self.send({ + msg: 'nosub', id: msg.id, + error: new Meteor.Error( + 'too-many-requests', + DDPRateLimiter.getErrorMessage(rateLimitResult), + {timeToReset: rateLimitResult.timeToReset}) + }); + return; + } + } + + var handler = self.server.publish_handlers[msg.name]; + + self._startSubscription(handler, msg.id, msg.params, msg.name); + + // cleaning cached unblock + self.cachedUnblock = null; + }, + + unsub: function (msg) { + var self = this; + + self._stopSubscription(msg.id); + }, + + method: function (msg, unblock) { + var self = this; + + // Reject malformed messages. + // For now, we silently ignore unknown attributes, + // for forwards compatibility. + if (typeof (msg.id) !== "string" || + typeof (msg.method) !== "string" || + (('params' in msg) && !(msg.params instanceof Array)) || + (('randomSeed' in msg) && (typeof msg.randomSeed !== "string"))) { + self.sendError("Malformed method invocation", msg); + return; + } + + var randomSeed = msg.randomSeed || null; + + // Set up to mark the method as satisfied once all observers + // (and subscriptions) have reacted to any writes that were + // done. + var fence = new DDPServer._WriteFence; + fence.onAllCommitted(function () { + // Retire the fence so that future writes are allowed. + // This means that callbacks like timers are free to use + // the fence, and if they fire before it's armed (for + // example, because the method waits for them) their + // writes will be included in the fence. + fence.retire(); + self.send({ + msg: 'updated', methods: [msg.id]}); + }); + + // Find the handler + var handler = self.server.method_handlers[msg.method]; + if (!handler) { + self.send({ + msg: 'result', id: msg.id, + error: new Meteor.Error(404, `Method '${msg.method}' not found`)}); + fence.arm(); + return; + } + + var setUserId = function(userId) { + self._setUserId(userId); + }; + + var invocation = new DDPCommon.MethodInvocation({ + isSimulation: false, + userId: self.userId, + setUserId: setUserId, + unblock: unblock, + connection: self.connectionHandle, + randomSeed: randomSeed + }); + + const promise = new Promise((resolve, reject) => { + // XXX It'd be better if we could hook into method handlers better but + // for now, we need to check if the ddp-rate-limiter exists since we + // have a weak requirement for the ddp-rate-limiter package to be added + // to our application. + if (Package['ddp-rate-limiter']) { + var DDPRateLimiter = Package['ddp-rate-limiter'].DDPRateLimiter; + var rateLimiterInput = { + userId: self.userId, + clientAddress: self.connectionHandle.clientAddress, + type: "method", + name: msg.method, + connectionId: self.id + }; + DDPRateLimiter._increment(rateLimiterInput); + var rateLimitResult = DDPRateLimiter._check(rateLimiterInput) + if (!rateLimitResult.allowed) { + reject(new Meteor.Error( + "too-many-requests", + DDPRateLimiter.getErrorMessage(rateLimitResult), + {timeToReset: rateLimitResult.timeToReset} + )); + return; + } + } + + const getCurrentMethodInvocationResult = () => { + const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( + invocation + ); + + try { + let result; + const resultOrThenable = maybeAuditArgumentChecks( + handler, + invocation, + msg.params, + "call to '" + msg.method + "'" + ); + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + result = Meteor._isFibersEnabled ? Promise.await(resultOrThenable) : resultOrThenable; + } else { + result = resultOrThenable; + } + return result; + } finally { + DDP._CurrentMethodInvocation._set(currentContext); + } + }; + + resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult)); + }); + + function finish() { + fence.arm(); + unblock(); + } + + const payload = { + msg: "result", + id: msg.id + }; + + promise.then(result => { + finish(); + if (result !== undefined) { + payload.result = result; + } + self.send(payload); + }, (exception) => { + finish(); + payload.error = wrapInternalException( + exception, + `while invoking method '${msg.method}'` + ); + self.send(payload); + }); + } + }, + + _eachSub: function (f) { + var self = this; + self._namedSubs.forEach(f); + self._universalSubs.forEach(f); + }, + + _diffCollectionViews: function (beforeCVs) { + var self = this; + DiffSequence.diffMaps(beforeCVs, self.collectionViews, { + both: function (collectionName, leftValue, rightValue) { + rightValue.diff(leftValue); + }, + rightOnly: function (collectionName, rightValue) { + rightValue.documents.forEach(function (docView, id) { + self.sendAdded(collectionName, id, docView.getFields()); + }); + }, + leftOnly: function (collectionName, leftValue) { + leftValue.documents.forEach(function (doc, id) { + self.sendRemoved(collectionName, id); + }); + } + }); + }, + + // Sets the current user id in all appropriate contexts and reruns + // all subscriptions + _setUserId: function(userId) { + var self = this; + + if (userId !== null && typeof userId !== "string") + throw new Error("setUserId must be called on string or null, not " + + typeof userId); + + // Prevent newly-created universal subscriptions from being added to our + // session. They will be found below when we call startUniversalSubs. + // + // (We don't have to worry about named subscriptions, because we only add + // them when we process a 'sub' message. We are currently processing a + // 'method' message, and the method did not unblock, because it is illegal + // to call setUserId after unblock. Thus we cannot be concurrently adding a + // new named subscription). + self._dontStartNewUniversalSubs = true; + + // Prevent current subs from updating our collectionViews and call their + // stop callbacks. This may yield. + self._eachSub(function (sub) { + sub._deactivate(); + }); + + // All subs should now be deactivated. Stop sending messages to the client, + // save the state of the published collections, reset to an empty view, and + // update the userId. + self._isSending = false; + var beforeCVs = self.collectionViews; + self.collectionViews = new Map(); + self.userId = userId; + + // _setUserId is normally called from a Meteor method with + // DDP._CurrentMethodInvocation set. But DDP._CurrentMethodInvocation is not + // expected to be set inside a publish function, so we temporary unset it. + // Inside a publish function DDP._CurrentPublicationInvocation is set. + DDP._CurrentMethodInvocation.withValue(undefined, function () { + // Save the old named subs, and reset to having no subscriptions. + var oldNamedSubs = self._namedSubs; + self._namedSubs = new Map(); + self._universalSubs = []; + + oldNamedSubs.forEach(function (sub, subscriptionId) { + var newSub = sub._recreate(); + self._namedSubs.set(subscriptionId, newSub); + // nb: if the handler throws or calls this.error(), it will in fact + // immediately send its 'nosub'. This is OK, though. + newSub._runHandler(); + }); + + // Allow newly-created universal subs to be started on our connection in + // parallel with the ones we're spinning up here, and spin up universal + // subs. + self._dontStartNewUniversalSubs = false; + self.startUniversalSubs(); + }); + + // Start sending messages again, beginning with the diff from the previous + // state of the world to the current state. No yields are allowed during + // this diff, so that other changes cannot interleave. + Meteor._noYieldsAllowed(function () { + self._isSending = true; + self._diffCollectionViews(beforeCVs); + if (!_.isEmpty(self._pendingReady)) { + self.sendReady(self._pendingReady); + self._pendingReady = []; + } + }); + }, + + _startSubscription: function (handler, subId, params, name) { + var self = this; + + var sub = new Subscription( + self, handler, subId, params, name); + + let unblockHander = self.cachedUnblock; + // _startSubscription may call from a lot places + // so cachedUnblock might be null in somecases + // assign the cachedUnblock + sub.unblock = unblockHander || (() => {}); + + if (subId) + self._namedSubs.set(subId, sub); + else + self._universalSubs.push(sub); + + sub._runHandler(); + }, + + // Tear down specified subscription + _stopSubscription: function (subId, error) { + var self = this; + + var subName = null; + if (subId) { + var maybeSub = self._namedSubs.get(subId); + if (maybeSub) { + subName = maybeSub._name; + maybeSub._removeAllDocuments(); + maybeSub._deactivate(); + self._namedSubs.delete(subId); + } + } + + var response = {msg: 'nosub', id: subId}; + + if (error) { + response.error = wrapInternalException( + error, + subName ? ("from sub " + subName + " id " + subId) + : ("from sub id " + subId)); + } + + self.send(response); + }, + + // Tear down all subscriptions. Note that this does NOT send removed or nosub + // messages, since we assume the client is gone. + _deactivateAllSubscriptions: function () { + var self = this; + + self._namedSubs.forEach(function (sub, id) { + sub._deactivate(); + }); + self._namedSubs = new Map(); + + self._universalSubs.forEach(function (sub) { + sub._deactivate(); + }); + self._universalSubs = []; + }, + + // Determine the remote client's IP address, based on the + // HTTP_FORWARDED_COUNT environment variable representing how many + // proxies the server is behind. + _clientAddress: function () { + var self = this; + + // For the reported client address for a connection to be correct, + // the developer must set the HTTP_FORWARDED_COUNT environment + // variable to an integer representing the number of hops they + // expect in the `x-forwarded-for` header. E.g., set to "1" if the + // server is behind one proxy. + // + // This could be computed once at startup instead of every time. + var httpForwardedCount = parseInt(process.env['HTTP_FORWARDED_COUNT']) || 0; + + if (httpForwardedCount === 0) + return self.socket.remoteAddress; + + var forwardedFor = self.socket.headers["x-forwarded-for"]; + if (! _.isString(forwardedFor)) + return null; + forwardedFor = forwardedFor.trim().split(/\s*,\s*/); + + // Typically the first value in the `x-forwarded-for` header is + // the original IP address of the client connecting to the first + // proxy. However, the end user can easily spoof the header, in + // which case the first value(s) will be the fake IP address from + // the user pretending to be a proxy reporting the original IP + // address value. By counting HTTP_FORWARDED_COUNT back from the + // end of the list, we ensure that we get the IP address being + // reported by *our* first proxy. + + if (httpForwardedCount < 0 || httpForwardedCount > forwardedFor.length) + return null; + + return forwardedFor[forwardedFor.length - httpForwardedCount]; + } +}); + +/******************************************************************************/ +/* Subscription */ +/******************************************************************************/ + +// Ctor for a sub handle: the input to each publish function + +// Instance name is this because it's usually referred to as this inside a +// publish +/** + * @summary The server's side of a subscription + * @class Subscription + * @instanceName this + * @showInstanceName true + */ +var Subscription = function ( + session, handler, subscriptionId, params, name) { + var self = this; + self._session = session; // type is Session + + /** + * @summary Access inside the publish function. The incoming [connection](#meteor_onconnection) for this subscription. + * @locus Server + * @name connection + * @memberOf Subscription + * @instance + */ + self.connection = session.connectionHandle; // public API object + + self._handler = handler; + + // My subscription ID (generated by client, undefined for universal subs). + self._subscriptionId = subscriptionId; + // Undefined for universal subs + self._name = name; + + self._params = params || []; + + // Only named subscriptions have IDs, but we need some sort of string + // internally to keep track of all subscriptions inside + // SessionDocumentViews. We use this subscriptionHandle for that. + if (self._subscriptionId) { + self._subscriptionHandle = 'N' + self._subscriptionId; + } else { + self._subscriptionHandle = 'U' + Random.id(); + } + + // Has _deactivate been called? + self._deactivated = false; + + // Stop callbacks to g/c this sub. called w/ zero arguments. + self._stopCallbacks = []; + + // The set of (collection, documentid) that this subscription has + // an opinion about. + self._documents = new Map(); + + // Remember if we are ready. + self._ready = false; + + // Part of the public API: the user of this sub. + + /** + * @summary Access inside the publish function. The id of the logged-in user, or `null` if no user is logged in. + * @locus Server + * @memberOf Subscription + * @name userId + * @instance + */ + self.userId = session.userId; + + // For now, the id filter is going to default to + // the to/from DDP methods on MongoID, to + // specifically deal with mongo/minimongo ObjectIds. + + // Later, you will be able to make this be "raw" + // if you want to publish a collection that you know + // just has strings for keys and no funny business, to + // a DDP consumer that isn't minimongo. + + self._idFilter = { + idStringify: MongoID.idStringify, + idParse: MongoID.idParse + }; + + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "livedata", "subscriptions", 1); +}; + +Object.assign(Subscription.prototype, { + _runHandler: function() { + // XXX should we unblock() here? Either before running the publish + // function, or before running _publishCursor. + // + // Right now, each publish function blocks all future publishes and + // methods waiting on data from Mongo (or whatever else the function + // blocks on). This probably slows page load in common cases. + + if (!this.unblock) { + this.unblock = () => {}; + } + + const self = this; + let resultOrThenable = null; + try { + resultOrThenable = DDP._CurrentPublicationInvocation.withValue(self, () => + maybeAuditArgumentChecks( + self._handler, + self, + EJSON.clone(self._params), + // It's OK that this would look weird for universal subscriptions, + // because they have no arguments so there can never be an + // audit-argument-checks failure. + "publisher '" + self._name + "'" + ) + ); + } catch (e) { + self.error(e); + return; + } + + // Did the handler call this.error or this.stop? + if (self._isDeactivated()) return; + + // Both conventional and async publish handler functions are supported. + // If an object is returned with a then() function, it is either a promise + // or thenable and will be resolved asynchronously. + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + Promise.resolve(resultOrThenable).then( + (...args) => self._publishHandlerResult.bind(self)(...args), + e => self.error(e) + ); + } else { + self._publishHandlerResult(resultOrThenable); + } + }, + + _publishHandlerResult: function (res) { + // 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 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 + // users to implement it themselves? Arguably, it's unnecessary; users can + // already write their own functions like + // var publishMyReactiveThingy = function (name, handler) { + // Meteor.publish(name, function () { + // var reactiveThingy = handler(); + // reactiveThingy.publishMe(); + // }); + // }; + + var self = this; + var isCursor = function (c) { + return c && c._publishCursor; + }; + if (isCursor(res)) { + if (Meteor._isFibersEnabled) { + try { + res._publishCursor(self); + } catch (e) { + self.error(e); + return; + } + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else { + res._publishCursor(self).then(() => { + self.ready(); + }).catch((e) => self.error(e)); + } + } 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; + }; + + if (Meteor._isFibersEnabled) { + try { + _.each(res, function (cur) { + cur._publishCursor(self); + }); + } catch (e) { + self.error(e); + return; + } + self.ready(); + } else { + Promise.all(res.map((c) => c._publishCursor(self))).then(() => { + self.ready(); + }).catch((e) => self.error(e)); + } + } else if (res) { + // Truthy values other than cursors or arrays are probably a + // user mistake (possible returning a Mongo document via, say, + // `coll.findOne()`). + self.error(new Error("Publish function can only return a Cursor or " + + "an array of Cursors")); + } + }, + + // This calls all stop callbacks and prevents the handler from updating any + // SessionCollectionViews further. It's used when the user unsubscribes or + // disconnects, as well as during setUserId re-runs. It does *NOT* send + // removed messages for the published objects; if that is necessary, call + // _removeAllDocuments first. + _deactivate: function() { + var self = this; + if (self._deactivated) + return; + self._deactivated = true; + self._callStopCallbacks(); + Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( + "livedata", "subscriptions", -1); + }, + + _callStopCallbacks: function () { + var self = this; + // Tell listeners, so they can clean up + var callbacks = self._stopCallbacks; + self._stopCallbacks = []; + _.each(callbacks, function (callback) { + callback(); + }); + }, + + // Send remove messages for every document. + _removeAllDocuments: function () { + var self = this; + Meteor._noYieldsAllowed(function () { + self._documents.forEach(function (collectionDocs, collectionName) { + collectionDocs.forEach(function (strId) { + self.removed(collectionName, self._idFilter.idParse(strId)); + }); + }); + }); + }, + + // Returns a new Subscription for the same session with the same + // initial creation parameters. This isn't a clone: it doesn't have + // the same _documents cache, stopped state or callbacks; may have a + // different _subscriptionHandle, and gets its userId from the + // session, not from this object. + _recreate: function () { + var self = this; + return new Subscription( + self._session, self._handler, self._subscriptionId, self._params, + self._name); + }, + + /** + * @summary Call inside the publish function. Stops this client's subscription, triggering a call on the client to the `onStop` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any. If `error` is not a [`Meteor.Error`](#meteor_error), it will be [sanitized](#meteor_error). + * @locus Server + * @param {Error} error The error to pass to the client. + * @instance + * @memberOf Subscription + */ + error: function (error) { + var self = this; + if (self._isDeactivated()) + return; + self._session._stopSubscription(self._subscriptionId, error); + }, + + // Note that while our DDP client will notice that you've called stop() on the + // server (and clean up its _subscriptions table) we don't actually provide a + // mechanism for an app to notice this (the subscribe onError callback only + // triggers if there is an error). + + /** + * @summary Call inside the publish function. Stops this client's subscription and invokes the client's `onStop` callback with no error. + * @locus Server + * @instance + * @memberOf Subscription + */ + stop: function () { + var self = this; + if (self._isDeactivated()) + return; + self._session._stopSubscription(self._subscriptionId); + }, + + /** + * @summary Call inside the publish function. Registers a callback function to run when the subscription is stopped. + * @locus Server + * @memberOf Subscription + * @instance + * @param {Function} func The callback function + */ + onStop: function (callback) { + var self = this; + callback = Meteor.bindEnvironment(callback, 'onStop callback', self); + if (self._isDeactivated()) + callback(); + else + self._stopCallbacks.push(callback); + }, + + // This returns true if the sub has been deactivated, *OR* if the session was + // destroyed but the deferred call to _deactivateAllSubscriptions hasn't + // happened yet. + _isDeactivated: function () { + var self = this; + return self._deactivated || self._session.inQueue === null; + }, + + /** + * @summary Call inside the publish function. Informs the subscriber that a document has been added to the record set. + * @locus Server + * @memberOf Subscription + * @instance + * @param {String} collection The name of the collection that contains the new document. + * @param {String} id The new document's ID. + * @param {Object} fields The fields in the new document. If `_id` is present it is ignored. + */ + added (collectionName, id, fields) { + if (this._isDeactivated()) + return; + id = this._idFilter.idStringify(id); + + if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) { + let ids = this._documents.get(collectionName); + if (ids == null) { + ids = new Set(); + this._documents.set(collectionName, ids); + } + ids.add(id); + } + + this._session.added(this._subscriptionHandle, collectionName, id, fields); + }, + + /** + * @summary Call inside the publish function. Informs the subscriber that a document in the record set has been modified. + * @locus Server + * @memberOf Subscription + * @instance + * @param {String} collection The name of the collection that contains the changed document. + * @param {String} id The changed document's ID. + * @param {Object} fields The fields in the document that have changed, together with their new values. If a field is not present in `fields` it was left unchanged; if it is present in `fields` and has a value of `undefined` it was removed from the document. If `_id` is present it is ignored. + */ + changed (collectionName, id, fields) { + if (this._isDeactivated()) + return; + id = this._idFilter.idStringify(id); + this._session.changed(this._subscriptionHandle, collectionName, id, fields); + }, + + /** + * @summary Call inside the publish function. Informs the subscriber that a document has been removed from the record set. + * @locus Server + * @memberOf Subscription + * @instance + * @param {String} collection The name of the collection that the document has been removed from. + * @param {String} id The ID of the document that has been removed. + */ + removed (collectionName, id) { + if (this._isDeactivated()) + return; + id = this._idFilter.idStringify(id); + + if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) { + // We don't bother to delete sets of things in a collection if the + // collection is empty. It could break _removeAllDocuments. + this._documents.get(collectionName).delete(id); + } + + this._session.removed(this._subscriptionHandle, collectionName, id); + }, + + /** + * @summary Call inside the publish function. Informs the subscriber that an initial, complete snapshot of the record set has been sent. This will trigger a call on the client to the `onReady` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any. + * @locus Server + * @memberOf Subscription + * @instance + */ + ready: function () { + var self = this; + if (self._isDeactivated()) + return; + if (!self._subscriptionId) + return; // Unnecessary but ignored for universal sub + if (!self._ready) { + self._session.sendReady([self._subscriptionId]); + self._ready = true; + } + } +}); + +/******************************************************************************/ +/* Server */ +/******************************************************************************/ + +Server = function (options = {}) { + var self = this; + + // The default heartbeat interval is 30 seconds on the server and 35 + // seconds on the client. Since the client doesn't need to send a + // ping as long as it is receiving pings, this means that pings + // normally go from the server to the client. + // + // Note: Troposphere depends on the ability to mutate + // Meteor.server.options.heartbeatTimeout! This is a hack, but it's life. + self.options = { + heartbeatInterval: 15000, + heartbeatTimeout: 15000, + // For testing, allow responding to pings to be disabled. + respondToPings: true, + defaultPublicationStrategy: publicationStrategies.SERVER_MERGE, + ...options, + }; + + // Map of callbacks to call when a new connection comes in to the + // server and completes DDP version negotiation. Use an object instead + // of an array so we can safely remove one from the list while + // iterating over it. + self.onConnectionHook = new Hook({ + debugPrintExceptions: "onConnection callback" + }); + + // Map of callbacks to call when a new message comes in. + self.onMessageHook = new Hook({ + debugPrintExceptions: "onMessage callback" + }); + + self.publish_handlers = {}; + self.universal_publish_handlers = []; + + self.method_handlers = {}; + + self._publicationStrategies = {}; + + self.sessions = new Map(); // map from id to session + + self.stream_server = new StreamServer; + + self.stream_server.register(function (socket) { + // socket implements the SockJSConnection interface + socket._meteorSession = null; + + var sendError = function (reason, offendingMessage) { + var msg = {msg: 'error', reason: reason}; + if (offendingMessage) + msg.offendingMessage = offendingMessage; + socket.send(DDPCommon.stringifyDDP(msg)); + }; + + socket.on('data', function (raw_msg) { + if (Meteor._printReceivedDDP) { + Meteor._debug("Received DDP", raw_msg); + } + try { + try { + var msg = DDPCommon.parseDDP(raw_msg); + } catch (err) { + sendError('Parse error'); + return; + } + if (msg === null || !msg.msg) { + sendError('Bad request', msg); + return; + } + + if (msg.msg === 'connect') { + if (socket._meteorSession) { + sendError("Already connected", msg); + return; + } + + Meteor._runAsync(function() { + self._handleConnect(socket, msg); + }) + + return; + } + + if (!socket._meteorSession) { + sendError('Must connect first', msg); + return; + } + socket._meteorSession.processMessage(msg); + } catch (e) { + // XXX print stack nicely + Meteor._debug("Internal exception while processing message", msg, e); + } + }); + + socket.on('close', function () { + if (socket._meteorSession) { + Meteor._runAsync(function() { + socket._meteorSession.close(); + }); + } + }); + }); +}; + +Object.assign(Server.prototype, { + + /** + * @summary Register a callback to be called when a new DDP connection is made to the server. + * @locus Server + * @param {function} callback The function to call when a new DDP connection is established. + * @memberOf Meteor + * @importFromPackage meteor + */ + onConnection: function (fn) { + var self = this; + return self.onConnectionHook.register(fn); + }, + + /** + * @summary Set publication strategy for the given collection. Publications strategies are available from `DDPServer.publicationStrategies`. You call this method from `Meteor.server`, like `Meteor.server.setPublicationStrategy()` + * @locus Server + * @alias setPublicationStrategy + * @param collectionName {String} + * @param strategy {{useCollectionView: boolean, doAccountingForCollection: boolean}} + * @memberOf Meteor.server + * @importFromPackage meteor + */ + setPublicationStrategy(collectionName, strategy) { + if (!Object.values(publicationStrategies).includes(strategy)) { + throw new Error(`Invalid merge strategy: ${strategy} + for collection ${collectionName}`); + } + this._publicationStrategies[collectionName] = strategy; + }, + + /** + * @summary Gets the publication strategy for the requested collection. You call this method from `Meteor.server`, like `Meteor.server.getPublicationStrategy()` + * @locus Server + * @alias getPublicationStrategy + * @param collectionName {String} + * @memberOf Meteor.server + * @importFromPackage meteor + * @return {{useCollectionView: boolean, doAccountingForCollection: boolean}} + */ + getPublicationStrategy(collectionName) { + return this._publicationStrategies[collectionName] + || this.options.defaultPublicationStrategy; + }, + + /** + * @summary Register a callback to be called when a new DDP message is received. + * @locus Server + * @param {function} callback The function to call when a new DDP message is received. + * @memberOf Meteor + * @importFromPackage meteor + */ + onMessage: function (fn) { + var self = this; + return self.onMessageHook.register(fn); + }, + + _handleConnect: function (socket, msg) { + var self = this; + + // The connect message must specify a version and an array of supported + // versions, and it must claim to support what it is proposing. + if (!(typeof (msg.version) === 'string' && + _.isArray(msg.support) && + _.all(msg.support, _.isString) && + _.contains(msg.support, msg.version))) { + socket.send(DDPCommon.stringifyDDP({msg: 'failed', + version: DDPCommon.SUPPORTED_DDP_VERSIONS[0]})); + socket.close(); + return; + } + + // In the future, handle session resumption: something like: + // socket._meteorSession = self.sessions[msg.session] + var version = calculateVersion(msg.support, DDPCommon.SUPPORTED_DDP_VERSIONS); + + if (msg.version !== version) { + // The best version to use (according to the client's stated preferences) + // is not the one the client is trying to use. Inform them about the best + // version to use. + socket.send(DDPCommon.stringifyDDP({msg: 'failed', version: version})); + socket.close(); + return; + } + + // Yay, version matches! Create a new session. + // Note: Troposphere depends on the ability to mutate + // Meteor.server.options.heartbeatTimeout! This is a hack, but it's life. + socket._meteorSession = new Session(self, version, socket, self.options); + self.sessions.set(socket._meteorSession.id, socket._meteorSession); + self.onConnectionHook.each(function (callback) { + if (socket._meteorSession) + callback(socket._meteorSession.connectionHandle); + return true; + }); + }, + /** + * Register a publish handler function. + * + * @param name {String} identifier for query + * @param handler {Function} publish handler + * @param options {Object} + * + * Server will call handler function on each new subscription, + * either when receiving DDP sub message for a named subscription, or on + * DDP connect for a universal subscription. + * + * If name is null, this will be a subscription that is + * automatically established and permanently on for all connected + * client, instead of a subscription that can be turned on and off + * with subscribe(). + * + * options to contain: + * - (mostly internal) is_auto: true if generated automatically + * from an autopublish hook. this is for cosmetic purposes only + * (it lets us determine whether to print a warning suggesting + * that you turn off autopublish). + */ + + /** + * @summary Publish a record set. + * @memberOf Meteor + * @importFromPackage meteor + * @locus Server + * @param {String|Object} name If String, name of the record set. If Object, publications Dictionary of publish functions by name. If `null`, the set has no name, and the record set is automatically sent to all connected clients. + * @param {Function} func Function called on the server each time a client subscribes. Inside the function, `this` is the publish handler object, described below. If the client passed arguments to `subscribe`, the function is called with the same arguments. + */ + publish: function (name, handler, options) { + var self = this; + + if (! _.isObject(name)) { + options = options || {}; + + if (name && name in self.publish_handlers) { + Meteor._debug("Ignoring duplicate publish named '" + name + "'"); + return; + } + + if (Package.autopublish && !options.is_auto) { + // They have autopublish on, yet they're trying to manually + // pick stuff to publish. They probably should turn off + // autopublish. (This check isn't perfect -- if you create a + // publish before you turn on autopublish, it won't catch + // it, but this will definitely handle the simple case where + // you've added the autopublish package to your app, and are + // calling publish from your app code). + if (!self.warned_about_autopublish) { + self.warned_about_autopublish = true; + Meteor._debug( + "** You've set up some data subscriptions with Meteor.publish(), but\n" + + "** you still have autopublish turned on. Because autopublish is still\n" + + "** on, your Meteor.publish() calls won't have much effect. All data\n" + + "** will still be sent to all clients.\n" + + "**\n" + + "** Turn off autopublish by removing the autopublish package:\n" + + "**\n" + + "** $ meteor remove autopublish\n" + + "**\n" + + "** .. and make sure you have Meteor.publish() and Meteor.subscribe() calls\n" + + "** for each collection that you want clients to see.\n"); + } + } + + if (name) + self.publish_handlers[name] = handler; + else { + self.universal_publish_handlers.push(handler); + // Spin up the new publisher on any existing session too. Run each + // session's subscription in a new Fiber, so that there's no change for + // self.sessions to change while we're running this loop. + self.sessions.forEach(function (session) { + if (!session._dontStartNewUniversalSubs) { + Meteor._runAsync(function() { + session._startSubscription(handler); + }); + } + }); + } + } + else{ + _.each(name, function(value, key) { + self.publish(key, value, {}); + }); + } + }, + + _removeSession: function (session) { + var self = this; + self.sessions.delete(session.id); + }, + + /** + * @summary Defines functions that can be invoked over the network by clients. + * @locus Anywhere + * @param {Object} methods Dictionary whose keys are method names and values are functions. + * @memberOf Meteor + * @importFromPackage meteor + */ + methods: function (methods) { + var self = this; + _.each(methods, function (func, name) { + if (typeof func !== 'function') + throw new Error("Method '" + name + "' must be a function"); + if (self.method_handlers[name]) + throw new Error("A method named '" + name + "' is already defined"); + self.method_handlers[name] = func; + }); + }, + + call: function (name, ...args) { + if (args.length && typeof args[args.length - 1] === "function") { + // If it's a function, the last argument is the result callback, not + // a parameter to the remote method. + var callback = args.pop(); + } + + return this.apply(name, args, callback); + }, + + // A version of the call method that always returns a Promise. + callAsync: function (name, ...args) { + return this.applyAsync(name, args); + }, + + apply: function (name, args, options, callback) { + // We were passed 3 arguments. They may be either (name, args, options) + // or (name, args, callback) + if (! callback && typeof options === 'function') { + callback = options; + options = {}; + } else { + options = options || {}; + } + + const promise = this.applyAsync(name, args, options); + + // Return the result in whichever way the caller asked for it. Note that we + // do NOT block on the write fence in an analogous way to how the client + // blocks on the relevant data being visible, so you are NOT guaranteed that + // cursor observe callbacks have fired when your callback is invoked. (We + // can change this if there's a real use case). + if (callback) { + promise.then( + result => callback(undefined, result), + exception => callback(exception) + ); + } else { + return promise.await(); + } + }, + + // @param options {Optional Object} + applyAsync: function (name, args, options) { + // Run the handler + var handler = this.method_handlers[name]; + if (! handler) { + return Promise.reject( + new Meteor.Error(404, `Method '${name}' not found`) + ); + } + + // If this is a method call from within another method or publish function, + // get the user state from the outer method or publish function, otherwise + // don't allow setUserId to be called + var userId = null; + var setUserId = function() { + throw new Error("Can't call setUserId on a server initiated method call"); + }; + var connection = null; + var currentMethodInvocation = DDP._CurrentMethodInvocation.get(); + var currentPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + var randomSeed = null; + if (currentMethodInvocation) { + userId = currentMethodInvocation.userId; + setUserId = function(userId) { + currentMethodInvocation.setUserId(userId); + }; + connection = currentMethodInvocation.connection; + randomSeed = DDPCommon.makeRpcSeed(currentMethodInvocation, name); + } else if (currentPublicationInvocation) { + userId = currentPublicationInvocation.userId; + setUserId = function(userId) { + currentPublicationInvocation._session._setUserId(userId); + }; + connection = currentPublicationInvocation.connection; + } + + var invocation = new DDPCommon.MethodInvocation({ + isSimulation: false, + userId, + setUserId, + connection, + randomSeed + }); + + return new Promise(resolve => resolve( + DDP._CurrentMethodInvocation.withValue( + invocation, + () => maybeAuditArgumentChecks( + handler, invocation, EJSON.clone(args), + "internal call to '" + name + "'" + ) + ) + )).then(EJSON.clone); + }, + + _urlForSession: function (sessionId) { + var self = this; + var session = self.sessions.get(sessionId); + if (session) + return session._socketUrl; + else + return null; + } +}); + +var calculateVersion = function (clientSupportedVersions, + serverSupportedVersions) { + var correctVersion = _.find(clientSupportedVersions, function (version) { + return _.contains(serverSupportedVersions, version); + }); + if (!correctVersion) { + correctVersion = serverSupportedVersions[0]; + } + return correctVersion; +}; + +DDPServer._calculateVersion = calculateVersion; + + +// "blind" exceptions other than those that were deliberately thrown to signal +// errors to the client +var wrapInternalException = function (exception, context) { + if (!exception) return exception; + + // To allow packages to throw errors intended for the client but not have to + // depend on the Meteor.Error class, `isClientSafe` can be set to true on any + // error before it is thrown. + if (exception.isClientSafe) { + if (!(exception instanceof Meteor.Error)) { + const originalMessage = exception.message; + exception = new Meteor.Error(exception.error, exception.reason, exception.details); + exception.message = originalMessage; + } + return exception; + } + + // Tests can set the '_expectedByTest' flag on an exception so it won't go to + // the server log. + if (!exception._expectedByTest) { + Meteor._debug("Exception " + context, exception.stack); + if (exception.sanitizedError) { + Meteor._debug("Sanitized and reported to the client as:", exception.sanitizedError); + Meteor._debug(); + } + } + + // Did the error contain more details that could have been useful if caught in + // server code (or if thrown from non-client-originated code), but also + // provided a "sanitized" version with more context than 500 Internal server + // error? Use that. + if (exception.sanitizedError) { + if (exception.sanitizedError.isClientSafe) + return exception.sanitizedError; + Meteor._debug("Exception " + context + " provides a sanitizedError that " + + "does not have isClientSafe property set; ignoring"); + } + + return new Meteor.Error(500, "Internal server error"); +}; + + +// Audit argument checks, if the audit-argument-checks package exists (it is a +// weak dependency of this package). +var maybeAuditArgumentChecks = function (f, context, args, description) { + args = args || []; + if (Package['audit-argument-checks']) { + return Match._failIfArgumentsAreNotAllChecked( + f, context, args, description); + } + return f.apply(context, args); +}; diff --git a/packages/ddp-server/livedata_server_async_tests.js b/packages/ddp-server/livedata_server_async_tests.js new file mode 100644 index 0000000000..d145aeee92 --- /dev/null +++ b/packages/ddp-server/livedata_server_async_tests.js @@ -0,0 +1,192 @@ +var Fiber = Npm.require('fibers'); + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// connectionId -> callback +var onSubscription = {}; + +Meteor.publish('livedata_server_test_sub_async', async function(connectionId) { + await sleep(50); + var callback = onSubscription[connectionId]; + if (callback) callback(this); + this.stop(); +}); + +Meteor.publish('livedata_server_test_sub_context_async', async function( + connectionId, + userId +) { + await sleep(50); + var callback = onSubscription[connectionId]; + var methodInvocation = DDP._CurrentMethodInvocation.get(); + var publicationInvocation = DDP._CurrentPublicationInvocation.get(); + + // Check the publish function's environment variables and context. + if (callback) { + callback.call(this, methodInvocation, publicationInvocation); + } + + // Check that onStop callback is have the same context as the publish function + // and that it runs with the same environment variables as this publish function. + this.onStop(function() { + var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + callback.call( + this, + onStopMethodInvocation, + onStopPublicationInvocation, + true + ); + }); + + if (this.userId) { + this.stop(); + } else { + this.ready(); + Meteor.call('livedata_server_test_setuserid', userId); + } +}); + +Tinytest.addAsync( + 'livedata server - connection in async publish function', + function(test, onComplete) { + makeTestConnection(test, function(clientConn, serverConn) { + onSubscription[serverConn.id] = function(subscription) { + delete onSubscription[serverConn.id]; + test.equal(subscription.connection.id, serverConn.id); + clientConn.disconnect(); + onComplete(); + }; + clientConn.subscribe('livedata_server_test_sub_async', serverConn.id); + }); + } +); + +Tinytest.addAsync( + 'livedata server - verify context in async publish function', + function(test, onComplete) { + makeTestConnection(test, function(clientConn, serverConn) { + var userId = 'someUserId'; + onSubscription[serverConn.id] = function( + methodInvocation, + publicationInvocation, + fromOnStop + ) { + // DDP._CurrentMethodInvocation should be undefined in a publish function + test.isUndefined(methodInvocation, 'Should have been undefined'); + // DDP._CurrentPublicationInvocation should be set in a publish function + test.isNotUndefined(publicationInvocation, 'Should have been defined'); + if (this.userId === userId && fromOnStop) { + delete onSubscription[serverConn.id]; + clientConn.disconnect(); + onComplete(); + } + }; + clientConn.subscribe( + 'livedata_server_test_sub_context_async', + serverConn.id, + userId + ); + }); + } +); + +let onSubscriptions = {}; + +Meteor.publish({ + async publicationObjectAsync() { + await sleep(50); + let callback = onSubscriptions; + if (callback) callback(); + this.stop(); + }, +}); + +Meteor.publish({ + publication_object_async: async function() { + await sleep(50); + let callback = onSubscriptions; + if (callback) callback(); + this.stop(); + }, +}); + +Meteor.publish('publication_compatibility_async', async function() { + await sleep(50); + let callback = onSubscriptions; + if (callback) callback(); + this.stop(); +}); + +Tinytest.addAsync('livedata server - async publish object', function( + test, + onComplete +) { + makeTestConnection(test, function(clientConn, serverConn) { + let testsLength = 0; + + onSubscriptions = function(subscription) { + delete onSubscriptions; + clientConn.disconnect(); + testsLength++; + if (testsLength == 3) { + onComplete(); + } + }; + clientConn.subscribe('publicationObjectAsync'); + clientConn.subscribe('publication_object_async'); + clientConn.subscribe('publication_compatibility_async'); + }); +}); +const collection = new Mongo.Collection('names'); + +async function getAllNames(shouldThrow = false) { + const count = await collection.rawCollection().count(); + if (shouldThrow) { + throw new Meteor.Error('Expected error'); + } + if (count <= 0) { + collection.insert({ name: 'async' }); + } +} +Meteor.publish('asyncPublishCursor', async function() { + await getAllNames(); + return collection.find(); +}); + +Tinytest.addAsync('livedata server - async publish cursor', function( + test, + onComplete +) { + makeTestConnection(test, (clientConn, serverConn) => { + const remoteCollection = new Mongo.Collection('names', { + connection: clientConn, + }); + clientConn.subscribe('asyncPublishCursor', () => { + const actual = remoteCollection.find().fetch(); + test.equal(actual[0].name, 'async'); + onComplete(); + }); + }); +}); + +Meteor.publish('asyncPublishErrorCursor', async function() { + await getAllNames(true); + return collection.find(); +}); + +Tinytest.addAsync('livedata server - async publish test error thrown', function( + test, + onComplete +) { + makeTestConnection(test, (clientConn, serverConn) => { + clientConn.subscribe('asyncPublishErrorCursor', { + onStop: e => { + test.equal(e.error, 'Expected error'); + onComplete(); + }, + }); + }); +}); diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js new file mode 100644 index 0000000000..cde56b6196 --- /dev/null +++ b/packages/ddp-server/livedata_server_tests.js @@ -0,0 +1,411 @@ +var Fiber = Npm.require('fibers'); + + +Tinytest.addAsync( + "livedata server - connectionHandle.onClose()", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + // On the server side, wait for the connection to be closed. + serverConn.onClose(function () { + test.isTrue(true); + // Add a new onClose after the connection is already + // closed. See that it fires. + serverConn.onClose(function () { + onComplete(); + }); + }); + // Close the connection from the client. + clientConn.disconnect(); + }, + onComplete + ); + } +); + +Tinytest.addAsync( + "livedata server - connectionHandle.close()", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + // Wait for the connection to be closed from the server side. + simplePoll( + function () { + return ! clientConn.status().connected; + }, + onComplete, + function () { + test.fail("timeout waiting for the connection to be closed on the server side"); + onComplete(); + } + ); + + // Close the connection from the server. + serverConn.close(); + }, + onComplete + ); + } +); + + +testAsyncMulti( + "livedata server - onConnection doesn't get callback after stop.", + [function (test, expect) { + var afterStop = false; + var expectStop1 = expect(); + var stopHandle1 = Meteor.onConnection(function (conn) { + stopHandle2.stop(); + stopHandle1.stop(); + afterStop = true; + // yield to the event loop for a moment to see that no other calls + // to listener2 are called. + Meteor.setTimeout(expectStop1, 10); + }); + var stopHandle2 = Meteor.onConnection(function (conn) { + test.isFalse(afterStop); + }); + + // trigger a connection + var expectConnection = expect(); + makeTestConnection( + test, + function (clientConn, serverConn) { + // Close the connection from the client. + clientConn.disconnect(); + expectConnection(); + }, + expectConnection + ); + }] +); + +Meteor.methods({ + livedata_server_test_inner: function () { + return this.connection && this.connection.id; + }, + + livedata_server_test_outer: function () { + return Meteor.call('livedata_server_test_inner'); + }, + + livedata_server_test_setuserid: function (userId) { + this.setUserId(userId); + } +}); + + +Tinytest.addAsync( + "livedata server - onMessage hook", + function (test, onComplete) { + + var cb = Meteor.onMessage(function (msg, session) { + test.equal(msg.method, 'livedata_server_test_inner'); + cb.stop(); + onComplete(); + }); + + makeTestConnection( + test, + function (clientConn, serverConn) { + clientConn.call('livedata_server_test_inner'); + clientConn.disconnect(); + }, + onComplete + ); + } +); + + +Tinytest.addAsync( + "livedata server - connection in method invocation", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + var res = clientConn.call('livedata_server_test_inner'); + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }, + onComplete + ); + } +); + + +Tinytest.addAsync( + "livedata server - connection in nested method invocation", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + var res = clientConn.call('livedata_server_test_outer'); + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }, + onComplete + ); + } +); + + +// connectionId -> callback +var onSubscription = {}; + +Meteor.publish("livedata_server_test_sub", function (connectionId) { + var callback = onSubscription[connectionId]; + if (callback) + callback(this); + this.stop(); +}); + +Meteor.publish("livedata_server_test_sub_method", function (connectionId) { + var callback = onSubscription[connectionId]; + if (callback) { + var id = Meteor.call('livedata_server_test_inner'); + callback(id); + } + this.stop(); +}); + +Meteor.publish("livedata_server_test_sub_context", function (connectionId, userId) { + var callback = onSubscription[connectionId]; + var methodInvocation = DDP._CurrentMethodInvocation.get(); + var publicationInvocation = DDP._CurrentPublicationInvocation.get(); + + // Check the publish function's environment variables and context. + if (callback) { + callback.call(this, methodInvocation, publicationInvocation); + } + + // Check that onStop callback is have the same context as the publish function + // and that it runs with the same environment variables as this publish function. + this.onStop(function () { + var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + callback.call(this, onStopMethodInvocation, onStopPublicationInvocation, true); + }); + + if (this.userId) { + this.stop(); + } else { + this.ready(); + Meteor.call('livedata_server_test_setuserid', userId); + } +}); + + +Tinytest.addAsync( + "livedata server - connection in publish function", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + onSubscription[serverConn.id] = function (subscription) { + delete onSubscription[serverConn.id]; + test.equal(subscription.connection.id, serverConn.id); + clientConn.disconnect(); + onComplete(); + }; + clientConn.subscribe("livedata_server_test_sub", serverConn.id); + } + ); + } +); + +Tinytest.addAsync( + "livedata server - connection in method called from publish function", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + onSubscription[serverConn.id] = function (id) { + delete onSubscription[serverConn.id]; + test.equal(id, serverConn.id); + clientConn.disconnect(); + onComplete(); + }; + clientConn.subscribe("livedata_server_test_sub_method", serverConn.id); + } + ); + } +); + +Tinytest.addAsync( + "livedata server - verify context in publish function", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + var userId = 'someUserId'; + onSubscription[serverConn.id] = function (methodInvocation, publicationInvocation, fromOnStop) { + // DDP._CurrentMethodInvocation should be undefined in a publish function + test.isUndefined(methodInvocation, 'Should have been undefined'); + // DDP._CurrentPublicationInvocation should be set in a publish function + test.isNotUndefined(publicationInvocation, 'Should have been defined'); + if (this.userId === userId && fromOnStop) { + delete onSubscription[serverConn.id]; + clientConn.disconnect(); + onComplete(); + } + } + clientConn.subscribe("livedata_server_test_sub_context", serverConn.id, userId); + } + ); + } +); + +let onSubscriptions = {}; + +Meteor.publish({ + publicationObject () { + let callback = onSubscriptions; + if (callback) + callback(); + this.stop(); + } +}); + +Meteor.publish({ + "publication_object": function () { + let callback = onSubscriptions; + if (callback) + callback(); + this.stop(); + } +}); + +Meteor.publish("publication_compatibility", function () { + let callback = onSubscriptions; + if (callback) + callback(); + this.stop(); +}); + +Tinytest.addAsync( + "livedata server - publish object", + function (test, onComplete) { + makeTestConnection( + test, + function (clientConn, serverConn) { + let testsLength = 0; + + onSubscriptions = function (subscription) { + delete onSubscriptions; + clientConn.disconnect(); + testsLength++; + if(testsLength == 3){ + onComplete(); + } + }; + clientConn.subscribe("publicationObject"); + clientConn.subscribe("publication_object"); + clientConn.subscribe("publication_compatibility"); + } + ); + } +); + +Meteor.methods({ + testResolvedPromise(arg) { + const invocation1 = DDP._CurrentMethodInvocation.get(); + return Promise.resolve(arg).then(result => { + const invocation2 = DDP._CurrentMethodInvocation.get(); + // This equality holds because Promise callbacks are bound to the + // dynamic environment where .then was called. + if (invocation1 !== invocation2) { + throw new Meteor.Error("invocation mismatch"); + } + return result + " after waiting"; + }); + }, + + testRejectedPromise(arg) { + return Promise.resolve(arg).then(result => { + throw new Meteor.Error(result + " raised Meteor.Error"); + }); + }, + + testRejectedPromiseWithGenericError(arg) { + return Promise.resolve(arg).then(result => { + const error = new Error('MESSAGE'); + error.error = 'ERROR'; + error.reason = 'REASON'; + error.details = { foo: 'bar' }; + error.isClientSafe = true; + throw error; + }); + } +}); + +Tinytest.addAsync( + "livedata server - waiting for Promise", + (test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => { + test.equal( + clientConn.call("testResolvedPromise", "clientConn.call"), + "clientConn.call after waiting" + ); + + const clientCallPromise = new Promise( + (resolve, reject) => clientConn.call( + "testResolvedPromise", + "clientConn.call with callback", + (error, result) => error ? reject(error) : resolve(result) + ) + ); + + const serverCallAsyncPromise = Meteor.server.callAsync( + "testResolvedPromise", + "Meteor.server.callAsync" + ); + + const serverApplyAsyncPromise = Meteor.server.applyAsync( + "testResolvedPromise", + ["Meteor.server.applyAsync"] + ); + + const clientCallRejectedPromise = new Promise(resolve => { + clientConn.call( + "testRejectedPromise", + "with callback", + (error, result) => resolve(error.message) + ); + }); + + const clientCallRejectedPromiseWithGenericError = new Promise(resolve => { + clientConn.call( + "testRejectedPromiseWithGenericError", + (error, result) => resolve({ + message: error.message, + error: error.error, + reason: error.reason, + details: error.details, + }) + ); + }); + + Promise.all([ + clientCallPromise, + clientCallRejectedPromise, + clientCallRejectedPromiseWithGenericError, + serverCallAsyncPromise, + serverApplyAsyncPromise + ]).then(results => test.equal(results, [ + "clientConn.call with callback after waiting", + "[with callback raised Meteor.Error]", + { + message: 'REASON [ERROR]', + error: 'ERROR', + reason: 'REASON', + details: { foo: 'bar' }, + }, + "Meteor.server.callAsync after waiting", + "Meteor.server.applyAsync after waiting" + ]), error => test.fail(error)) + .then(onComplete); + }) +); diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js new file mode 100644 index 0000000000..da413690b0 --- /dev/null +++ b/packages/ddp-server/package.js @@ -0,0 +1,61 @@ +Package.describe({ + summary: "Meteor's latency-compensated distributed data server", + version: '2.6.0', + documentation: null +}); + +Npm.depends({ + "permessage-deflate": "0.1.7", + sockjs: "0.3.21" +}); + +Package.onUse(function (api) { + api.use(['check', 'random', 'ejson', 'underscore', + 'retry', 'mongo-id', 'diff-sequence', 'ecmascript'], + 'server'); + + // common functionality + api.use('ddp-common', 'server'); // heartbeat + api.use('ddp-rate-limiter', 'server', {weak: true}); + // Transport + api.use('ddp-client', 'server'); + api.imply('ddp-client'); + + api.use(['webapp', 'routepolicy'], 'server'); + + // Detect whether or not the user wants us to audit argument checks. + api.use(['audit-argument-checks'], 'server', {weak: true}); + + // Allow us to detect 'autopublish', so we can print a warning if the user + // runs Meteor.publish while it's loaded. + api.use('autopublish', 'server', {weak: true}); + + // If the facts package is loaded, publish some statistics. + api.use('facts-base', 'server', {weak: true}); + + api.use('callback-hook', 'server'); + api.export('DDPServer', 'server'); + + api.addFiles('stream_server.js', 'server'); + + api.addFiles('livedata_server.js', 'server'); + api.addFiles('writefence.js', 'server'); + api.addFiles('crossbar.js', 'server'); + + api.addFiles('server_convenience.js', 'server'); +}); + + + +Package.onTest(function (api) { + api.use('ecmascript', ['client', 'server']); + api.use('livedata', ['client', 'server']); + api.use('mongo', ['client', 'server']); + api.use('test-helpers', ['client', 'server']); + api.use(['underscore', 'tinytest', 'random', 'tracker', 'minimongo', 'reactive-var']); + + api.addFiles('livedata_server_tests.js', 'server'); + api.addFiles('livedata_server_async_tests.js', 'server'); + api.addFiles('session_view_tests.js', ['server']); + api.addFiles('crossbar_tests.js', ['server']); +}); diff --git a/packages/ddp-server/server_convenience.js b/packages/ddp-server/server_convenience.js new file mode 100755 index 0000000000..063bbe6385 --- /dev/null +++ b/packages/ddp-server/server_convenience.js @@ -0,0 +1,28 @@ +if (process.env.DDP_DEFAULT_CONNECTION_URL) { + __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = + process.env.DDP_DEFAULT_CONNECTION_URL; +} + +Meteor.server = new Server; + +Meteor.refresh = function (notification) { + DDPServer._InvalidationCrossbar.fire(notification); +}; + +// Proxy the public methods of Meteor.server so they can +// be called directly on Meteor. +_.each( + [ + 'publish', + 'methods', + 'call', + 'callAsync', + 'apply', + 'applyAsync', + 'onConnection', + 'onMessage', + ], + function(name) { + Meteor[name] = _.bind(Meteor.server[name], Meteor.server); + } +); diff --git a/packages/ddp-server/session_view_tests.js b/packages/ddp-server/session_view_tests.js new file mode 100644 index 0000000000..0459a45107 --- /dev/null +++ b/packages/ddp-server/session_view_tests.js @@ -0,0 +1,393 @@ +var newView = function(test) { + var results = []; + var view = new DDPServer._SessionCollectionView('test', { + added: function (collection, id, fields) { + results.push({fun: 'added', id: id, fields: fields}); + }, + changed: function (collection, id, changed) { + if (_.isEmpty(changed)) + return; + results.push({fun: 'changed', id: id, changed: changed}); + }, + removed: function (collection, id) { + results.push({fun: 'removed', id: id}); + } + }); + var v = { + view: view, + results: results + }; + _.each(["added", "changed", "removed"], function (it) { + v[it] = _.bind(view[it], view); + }); + v.expectResult = function (result) { + test.equal(results.shift(), result); + }; + v.expectNoResult = function () { + test.equal(results, []); + }; + v.drain = function() { + var ret = results; + results = []; + return ret; + }; + return v; +}; + +Tinytest.add('livedata - sessionview - exists reveal', function (test) { + var v = newView(test); + + v.added("A", "A1", {}); + v.expectResult({fun: 'added', id: "A1", fields: {}}); + v.expectNoResult(); + + v.added("B", "A1", {}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - added a second field in another sub', function (test) { + var v = newView(test); + + v.added("A", "A1", {a: "foo"}); + v.expectResult({fun: 'added', id: "A1", fields: {a: "foo"}}); + v.expectNoResult(); + + v.added("B", "A1", {a: "foo", b: "bar"}); + v.expectResult({fun: 'changed', 'id': "A1", changed: {b: "bar"}}); + + v.removed("A", "A1"); + v.expectNoResult(); + + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + + +Tinytest.add('livedata - sessionview - field reveal', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.added("B", "A1", {foo: "baz"}); + v.removed("A", "A1"); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}}); + v.expectNoResult(); + // Somewhere in here we must have changed foo to baz. Legal either on the + // added or on the removed, but only once. + + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - field change', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.changed("A", "A1", {foo: "baz"}, []); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - field clear', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.changed("A", "A1", {foo: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: undefined}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - change makes a new field', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.changed("A", "A1", {baz:"quux"}); + v.expectResult({fun: 'changed', id: "A1", changed: {baz: "quux"}}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - add, remove, add', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + +}); + +Tinytest.add('livedata - sessionview - field clear reveal', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + + v.added("B", "A1", {foo: "baz"}); + v.changed("A", "A1", {foo: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - change to canonical value produces no change', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + + v.added("B", "A1", {foo: "baz"}); + var canon = "bar"; + var maybeResults = v.drain(); + if (!_.isEmpty(maybeResults)) { + // if something happened, it was a change message to baz. + // if nothing did, canon is still bar. + test.length(maybeResults, 1); + test.equal(maybeResults[0], {fun: 'changed', id: "A1", changed: {foo: "baz"}}); + canon = "baz"; + } + v.changed("B", "A1", {foo: canon}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - new field of canonical value produces no change', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + + v.added("B", "A1", {}); + + v.changed("B", "A1", {foo: "bar"}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - clear all clears only once', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.added("B", "A1", {foo: "bar"}); + v.added("C", "A1", {foo: "bar"}); + v.changed("A", "A1", {foo: undefined}); + v.changed("B", "A1", {foo: undefined}); + v.changed("C", "A1", {foo: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: undefined}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - change all changes only once', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar"}}); + v.expectNoResult(); + + v.added("B", "A1", {foo: "bar"}); + v.added("C", "A1", {foo: "bar"}); + v.changed("B", "A1", {foo: "baz"}); + v.changed("A", "A1", {foo: "baz"}); + v.changed("C", "A1", {foo: "baz"}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - multiple operations at once in a change', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar", baz: "quux"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar", baz: "quux"}}); + v.expectNoResult(); + + + v.added("B", "A1", {foo: "baz"}); + v.changed("A", "A1", {thing: "stuff", foo: undefined, baz: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz", thing: "stuff", baz: undefined}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'changed', id: "A1", changed: {thing: undefined}}); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - more than one document', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar", baz: "quux"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar", baz: "quux"}}); + v.expectNoResult(); + + + v.added("A", "A2", {foo: "baz"}); + v.expectResult({fun: 'added', id: "A2", fields: {foo: "baz"}}); + v.changed("A", "A1", {thing: "stuff", foo: undefined, baz: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {thing: "stuff", foo: undefined, baz: undefined}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); + v.removed("A", "A2"); + v.expectResult({fun: 'removed', id: "A2"}); + v.expectNoResult(); + +}); + +Tinytest.add('livedata - sessionview - multiple docs removed', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar", baz: "quux"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar", baz: "quux"}}); + v.expectNoResult(); + + + v.added("A", "A2", {foo: "baz"}); + v.expectResult({fun: 'added', id: "A2", fields: {foo: "baz"}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.removed("A", "A2"); + v.expectResult({fun: 'removed', id: "A2"}); + v.expectNoResult(); +}); + + +Tinytest.add('livedata - sessionview - complicated sequence', function (test) { + var v = newView(test); + + v.added("A", "A1", {foo: "bar", baz: "quux"}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: "bar", baz: "quux"}}); + v.expectNoResult(); + + v.added("A", "A2", {foo: "eats"}); + v.expectResult({fun: 'added', id: "A2", fields: {foo: "eats"}}); + + v.added("B", "A1", {foo: "baz"}); + v.changed("A", "A1", {thing: "stuff", foo: undefined, baz: undefined}); + v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz", thing: "stuff", baz: undefined}}); + v.expectNoResult(); + + v.removed("A", "A1"); + v.removed("A", "A2"); + v.expectResult({fun: 'changed', id: "A1", changed: {thing: undefined}}); + v.expectResult({fun: 'removed', id: "A2"}); + v.expectNoResult(); + v.removed("B", "A1"); + v.expectResult({fun: 'removed', id: "A1"}); + v.expectNoResult(); +}); + +Tinytest.add('livedata - sessionview - added becomes changed', function (test) { + var v = newView(test); + + v.added('A', "A1", {foo: 'bar'}); + v.expectResult({fun: 'added', id: "A1", fields: {foo: 'bar'}}); + + v.added('B', "A1", {hi: 'there'}); + v.expectResult({fun: 'changed', id: 'A1', changed: {hi: 'there'}}); + + v.removed('A', 'A1'); + v.expectResult({fun: 'changed', id: 'A1', changed: {foo: undefined}}); + + v.removed('B', 'A1'); + v.expectResult({fun: 'removed', id: 'A1'}); +}); + +Tinytest.add('livedata - sessionview - weird key names', function (test) { + var v = newView(test); + + v.added('A', "A1", {}); + v.expectResult({fun: 'added', id: "A1", fields: {}}); + + v.changed('A', "A1", {constructor: 'bla'}); + v.expectResult({fun: 'changed', id: 'A1', changed: {constructor: 'bla'}}); +}); + +Tinytest.add('livedata - sessionview - clear undefined value', function (test) { + var v = newView(test); + + v.added("A", "A1", {field: "value"}); + v.expectResult({fun: 'added', id: "A1", fields: {field: "value"}}); + v.expectNoResult(); + + v.changed("A", "A1", {field: undefined}); + v.expectResult({fun: 'changed', id: 'A1', changed: {field: undefined}}); + v.expectNoResult(); + + v.changed("A", "A1", {field: undefined}); + v.expectNoResult(); + +}); diff --git a/packages/ddp-server/stream_server.js b/packages/ddp-server/stream_server.js new file mode 100644 index 0000000000..49c0f1385d --- /dev/null +++ b/packages/ddp-server/stream_server.js @@ -0,0 +1,190 @@ +// By default, we use the permessage-deflate extension with default +// configuration. If $SERVER_WEBSOCKET_COMPRESSION is set, then it must be valid +// JSON. If it represents a falsey value, then we do not use permessage-deflate +// at all; otherwise, the JSON value is used as an argument to deflate's +// configure method; see +// https://github.com/faye/permessage-deflate-node/blob/master/README.md +// +// (We do this in an _.once instead of at startup, because we don't want to +// crash the tool during isopacket load if your JSON doesn't parse. This is only +// a problem because the tool has to load the DDP server code just in order to +// be a DDP client; see https://github.com/meteor/meteor/issues/3452 .) +var websocketExtensions = _.once(function () { + var extensions = []; + + var websocketCompressionConfig = process.env.SERVER_WEBSOCKET_COMPRESSION + ? JSON.parse(process.env.SERVER_WEBSOCKET_COMPRESSION) : {}; + if (websocketCompressionConfig) { + extensions.push(Npm.require('permessage-deflate').configure( + websocketCompressionConfig + )); + } + + return extensions; +}); + +var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; + +StreamServer = function () { + var self = this; + self.registration_callbacks = []; + self.open_sockets = []; + + // Because we are installing directly onto WebApp.httpServer instead of using + // WebApp.app, we have to process the path prefix ourselves. + self.prefix = pathPrefix + '/sockjs'; + RoutePolicy.declare(self.prefix + '/', 'network'); + + // set up sockjs + var sockjs = Npm.require('sockjs'); + var serverOptions = { + prefix: self.prefix, + log: function() {}, + // this is the default, but we code it explicitly because we depend + // on it in stream_client:HEARTBEAT_TIMEOUT + heartbeat_delay: 45000, + // The default disconnect_delay is 5 seconds, but if the server ends up CPU + // bound for that much time, SockJS might not notice that the user has + // reconnected because the timer (of disconnect_delay ms) can fire before + // SockJS processes the new connection. Eventually we'll fix this by not + // combining CPU-heavy processing with SockJS termination (eg a proxy which + // converts to Unix sockets) but for now, raise the delay. + disconnect_delay: 60 * 1000, + // Set the USE_JSESSIONID environment variable to enable setting the + // JSESSIONID cookie. This is useful for setting up proxies with + // session affinity. + jsessionid: !!process.env.USE_JSESSIONID + }; + + // If you know your server environment (eg, proxies) will prevent websockets + // from ever working, set $DISABLE_WEBSOCKETS and SockJS clients (ie, + // browsers) will not waste time attempting to use them. + // (Your server will still have a /websocket endpoint.) + if (process.env.DISABLE_WEBSOCKETS) { + serverOptions.websocket = false; + } else { + serverOptions.faye_server_options = { + extensions: websocketExtensions() + }; + } + + self.server = sockjs.createServer(serverOptions); + + // Install the sockjs handlers, but we want to keep around our own particular + // request handler that adjusts idle timeouts while we have an outstanding + // request. This compensates for the fact that sockjs removes all listeners + // for "request" to add its own. + WebApp.httpServer.removeListener( + 'request', WebApp._timeoutAdjustmentRequestCallback); + self.server.installHandlers(WebApp.httpServer); + WebApp.httpServer.addListener( + 'request', WebApp._timeoutAdjustmentRequestCallback); + + // Support the /websocket endpoint + self._redirectWebsocketEndpoint(); + + self.server.on('connection', function (socket) { + // sockjs sometimes passes us null instead of a socket object + // so we need to guard against that. see: + // https://github.com/sockjs/sockjs-node/issues/121 + // https://github.com/meteor/meteor/issues/10468 + if (!socket) return; + + // We want to make sure that if a client connects to us and does the initial + // Websocket handshake but never gets to the DDP handshake, that we + // eventually kill the socket. Once the DDP handshake happens, DDP + // heartbeating will work. And before the Websocket handshake, the timeouts + // we set at the server level in webapp_server.js will work. But + // faye-websocket calls setTimeout(0) on any socket it takes over, so there + // is an "in between" state where this doesn't happen. We work around this + // by explicitly setting the socket timeout to a relatively large time here, + // and setting it back to zero when we set up the heartbeat in + // livedata_server.js. + socket.setWebsocketTimeout = function (timeout) { + if ((socket.protocol === 'websocket' || + socket.protocol === 'websocket-raw') + && socket._session.recv) { + socket._session.recv.connection.setTimeout(timeout); + } + }; + socket.setWebsocketTimeout(45 * 1000); + + socket.send = function (data) { + socket.write(data); + }; + socket.on('close', function () { + self.open_sockets = _.without(self.open_sockets, socket); + }); + self.open_sockets.push(socket); + + // only to send a message after connection on tests, useful for + // socket-stream-client/server-tests.js + if (process.env.TEST_METADATA && process.env.TEST_METADATA !== "{}") { + socket.send(JSON.stringify({ testMessageOnConnect: true })); + } + + // call all our callbacks when we get a new socket. they will do the + // work of setting up handlers and such for specific messages. + _.each(self.registration_callbacks, function (callback) { + callback(socket); + }); + }); + +}; + +Object.assign(StreamServer.prototype, { + // call my callback when a new socket connects. + // also call it for all current connections. + register: function (callback) { + var self = this; + self.registration_callbacks.push(callback); + _.each(self.all_sockets(), function (socket) { + callback(socket); + }); + }, + + // get a list of all sockets + all_sockets: function () { + var self = this; + return _.values(self.open_sockets); + }, + + // Redirect /websocket to /sockjs/websocket in order to not expose + // sockjs to clients that want to use raw websockets + _redirectWebsocketEndpoint: function() { + var self = this; + // Unfortunately we can't use a connect middleware here since + // sockjs installs itself prior to all existing listeners + // (meaning prior to any connect middlewares) so we need to take + // an approach similar to overshadowListeners in + // https://github.com/sockjs/sockjs-node/blob/cf820c55af6a9953e16558555a31decea554f70e/src/utils.coffee + ['request', 'upgrade'].forEach((event) => { + var httpServer = WebApp.httpServer; + var oldHttpServerListeners = httpServer.listeners(event).slice(0); + httpServer.removeAllListeners(event); + + // request and upgrade have different arguments passed but + // we only care about the first one which is always request + var newListener = function(request /*, moreArguments */) { + // Store arguments for use within the closure below + var args = arguments; + + // TODO replace with url package + var url = Npm.require('url'); + + // Rewrite /websocket and /websocket/ urls to /sockjs/websocket while + // preserving query string. + var parsedUrl = url.parse(request.url); + if (parsedUrl.pathname === pathPrefix + '/websocket' || + parsedUrl.pathname === pathPrefix + '/websocket/') { + parsedUrl.pathname = self.prefix + '/websocket'; + request.url = url.format(parsedUrl); + } + _.each(oldHttpServerListeners, function(oldListener) { + oldListener.apply(httpServer, args); + }); + }; + httpServer.addListener(event, newListener); + }); + } +}); diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js new file mode 100644 index 0000000000..d85f028ff8 --- /dev/null +++ b/packages/ddp-server/writefence.js @@ -0,0 +1,125 @@ +// A write fence collects a group of writes, and provides a callback +// when all of the writes are fully committed and propagated (all +// observers have been notified of the write and acknowledged it.) +// +DDPServer._WriteFence = class { + constructor() { + this.armed = false; + this.fired = false; + this.retired = false; + this.outstanding_writes = 0; + this.before_fire_callbacks = []; + this.completion_callbacks = []; + } + + // Start tracking a write, and return an object to represent it. The + // object has a single method, committed(). This method should be + // called when the write is fully committed and propagated. You can + // continue to add writes to the WriteFence up until it is triggered + // (calls its callbacks because all writes have committed.) + beginWrite() { + if (this.retired) + return { committed: function () {} }; + + if (this.fired) + throw new Error("fence has already activated -- too late to add writes"); + + this.outstanding_writes++; + let committed = false; + const _committedFn = async () => { + if (committed) + throw new Error("committed called twice on the same write"); + committed = true; + this.outstanding_writes--; + await this._maybeFire(); + }; + + const self = this; + return { + committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, + }; + } + + // Arm the fence. Once the fence is armed, and there are no more + // uncommitted writes, it will activate. + arm() { + if (this === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); + this.armed = true; + return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); + } + + // Register a function to be called once before firing the fence. + // Callback function can add new writes to the fence, in which case + // it won't fire until those writes are done as well. + onBeforeFire(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.before_fire_callbacks.push(func); + } + + // Register a function to be called when the fence fires. + onAllCommitted(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.completion_callbacks.push(func); + } + + _armAndWait() { + let resolver; + const returnValue = new Promise(r => resolver = r); + this.onAllCommitted(resolver); + this.arm(); + + return returnValue; + } + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait() { + return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait(); + } + + async _maybeFire() { + if (this.fired) + throw new Error("write fence already activated?"); + if (this.armed && !this.outstanding_writes) { + const invokeCallback = async (func) => { + try { + await func(this); + } catch (err) { + Meteor._debug("exception in write fence callback:", err); + } + }; + + this.outstanding_writes++; + while (this.before_fire_callbacks.length > 0) { + const cb = this.before_fire_callbacks.shift(); + await invokeCallback(cb); + } + this.outstanding_writes--; + + if (!this.outstanding_writes) { + this.fired = true; + while (this.completion_callbacks.length > 0) { + const cb = this.completion_callbacks.shift(); + await invokeCallback(cb); + } + } + } + } + + // Deactivate this fence so that adding more writes has no effect. + // The fence must have already fired. + retire() { + if (!this.fired) + throw new Error("Can't retire a fence that hasn't fired."); + this.retired = true; + } +}; + +// The current write fence. When there is a current write fence, code +// that writes to databases should register their writes with it using +// beginWrite(). +// +DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; From f737084a5626490894978553b8ae35cb9b71d467 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 15:24:45 -0300 Subject: [PATCH 0046/1965] Rollback changes to fibers implementation --- packages/ddp-server/livedata_server.js | 78 ++++----- packages/ddp-server/writefence.js | 232 +++++++++++++------------ 2 files changed, 151 insertions(+), 159 deletions(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 533e01b701..3b4853c39e 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1,5 +1,7 @@ DDPServer = {}; +var Fiber = Npm.require('fibers'); + // Publication strategies define how we handle data from published cursors at the collection level // This allows someone to: // - Choose a trade-off between client-server bandwidth and server memory usage @@ -328,9 +330,9 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - Meteor._runAsync(function() { + Fiber(function () { self.startUniversalSubs(); - }); + }).run(); if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -555,10 +557,10 @@ Object.assign(Session.prototype, { // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. if (self.heartbeat) { - Meteor._runAsync(function() { + Fiber(function () { self.heartbeat.messageReceived(); - }); - }; + }).run(); + } if (self.version !== 'pre1' && msg_in.msg === 'ping') { if (self._respondToPings) @@ -582,7 +584,7 @@ Object.assign(Session.prototype, { return; } - function runHandlers() { + Fiber(function () { var blocked = true; var unblock = function () { @@ -602,9 +604,7 @@ Object.assign(Session.prototype, { else self.sendError('Bad request', msg); unblock(); // in case the handler didn't already do it - } - - Meteor._runAsync(runHandlers); + }).run(); }; processNext(); @@ -778,7 +778,7 @@ Object.assign(Session.prototype, { const isThenable = resultOrThenable && typeof resultOrThenable.then === 'function'; if (isThenable) { - result = Meteor._isFibersEnabled ? Promise.await(resultOrThenable) : resultOrThenable; + result = Promise.await(resultOrThenable); } else { result = resultOrThenable; } @@ -1177,21 +1177,15 @@ Object.assign(Subscription.prototype, { return c && c._publishCursor; }; if (isCursor(res)) { - if (Meteor._isFibersEnabled) { - try { - res._publishCursor(self); - } catch (e) { - self.error(e); - return; - } - // _publishCursor only returns after the initial added callbacks have run. - // mark subscription as ready. - self.ready(); - } else { - res._publishCursor(self).then(() => { - self.ready(); - }).catch((e) => self.error(e)); + try { + res._publishCursor(self); + } catch (e) { + self.error(e); + return; } + // _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)) { @@ -1213,21 +1207,15 @@ Object.assign(Subscription.prototype, { collectionNames[collectionName] = true; }; - if (Meteor._isFibersEnabled) { - try { - _.each(res, function (cur) { - cur._publishCursor(self); - }); - } catch (e) { - self.error(e); - return; - } - self.ready(); - } else { - Promise.all(res.map((c) => c._publishCursor(self))).then(() => { - self.ready(); - }).catch((e) => self.error(e)); + try { + _.each(res, function (cur) { + cur._publishCursor(self); + }); + } catch (e) { + self.error(e); + return; } + self.ready(); } else if (res) { // Truthy values other than cursors or arrays are probably a // user mistake (possible returning a Mongo document via, say, @@ -1504,11 +1492,9 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - - Meteor._runAsync(function() { + Fiber(function () { self._handleConnect(socket, msg); - }) - + }).run(); return; } @@ -1525,9 +1511,9 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - Meteor._runAsync(function() { + Fiber(function () { socket._meteorSession.close(); - }); + }).run(); } }); }); @@ -1705,9 +1691,9 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - Meteor._runAsync(function() { + Fiber(function() { session._startSubscription(handler); - }); + }).run(); } }); } diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index d85f028ff8..e9310c9f7f 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -1,121 +1,18 @@ +var Future = Npm.require('fibers/future'); + // A write fence collects a group of writes, and provides a callback // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) // -DDPServer._WriteFence = class { - constructor() { - this.armed = false; - this.fired = false; - this.retired = false; - this.outstanding_writes = 0; - this.before_fire_callbacks = []; - this.completion_callbacks = []; - } +DDPServer._WriteFence = function () { + var self = this; - // Start tracking a write, and return an object to represent it. The - // object has a single method, committed(). This method should be - // called when the write is fully committed and propagated. You can - // continue to add writes to the WriteFence up until it is triggered - // (calls its callbacks because all writes have committed.) - beginWrite() { - if (this.retired) - return { committed: function () {} }; - - if (this.fired) - throw new Error("fence has already activated -- too late to add writes"); - - this.outstanding_writes++; - let committed = false; - const _committedFn = async () => { - if (committed) - throw new Error("committed called twice on the same write"); - committed = true; - this.outstanding_writes--; - await this._maybeFire(); - }; - - const self = this; - return { - committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, - }; - } - - // Arm the fence. Once the fence is armed, and there are no more - // uncommitted writes, it will activate. - arm() { - if (this === DDPServer._CurrentWriteFence.get()) - throw Error("Can't arm the current fence"); - this.armed = true; - return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); - } - - // Register a function to be called once before firing the fence. - // Callback function can add new writes to the fence, in which case - // it won't fire until those writes are done as well. - onBeforeFire(func) { - if (this.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - this.before_fire_callbacks.push(func); - } - - // Register a function to be called when the fence fires. - onAllCommitted(func) { - if (this.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - this.completion_callbacks.push(func); - } - - _armAndWait() { - let resolver; - const returnValue = new Promise(r => resolver = r); - this.onAllCommitted(resolver); - this.arm(); - - return returnValue; - } - // Convenience function. Arms the fence, then blocks until it fires. - armAndWait() { - return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait(); - } - - async _maybeFire() { - if (this.fired) - throw new Error("write fence already activated?"); - if (this.armed && !this.outstanding_writes) { - const invokeCallback = async (func) => { - try { - await func(this); - } catch (err) { - Meteor._debug("exception in write fence callback:", err); - } - }; - - this.outstanding_writes++; - while (this.before_fire_callbacks.length > 0) { - const cb = this.before_fire_callbacks.shift(); - await invokeCallback(cb); - } - this.outstanding_writes--; - - if (!this.outstanding_writes) { - this.fired = true; - while (this.completion_callbacks.length > 0) { - const cb = this.completion_callbacks.shift(); - await invokeCallback(cb); - } - } - } - } - - // Deactivate this fence so that adding more writes has no effect. - // The fence must have already fired. - retire() { - if (!this.fired) - throw new Error("Can't retire a fence that hasn't fired."); - this.retired = true; - } + self.armed = false; + self.fired = false; + self.retired = false; + self.outstanding_writes = 0; + self.before_fire_callbacks = []; + self.completion_callbacks = []; }; // The current write fence. When there is a current write fence, code @@ -123,3 +20,112 @@ DDPServer._WriteFence = class { // beginWrite(). // DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; + +_.extend(DDPServer._WriteFence.prototype, { + // Start tracking a write, and return an object to represent it. The + // object has a single method, committed(). This method should be + // called when the write is fully committed and propagated. You can + // continue to add writes to the WriteFence up until it is triggered + // (calls its callbacks because all writes have committed.) + beginWrite: function () { + var self = this; + + if (self.retired) + return { committed: function () {} }; + + if (self.fired) + throw new Error("fence has already activated -- too late to add writes"); + + self.outstanding_writes++; + var committed = false; + return { + committed: function () { + if (committed) + throw new Error("committed called twice on the same write"); + committed = true; + self.outstanding_writes--; + self._maybeFire(); + } + }; + }, + + // Arm the fence. Once the fence is armed, and there are no more + // uncommitted writes, it will activate. + arm: function () { + var self = this; + if (self === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); + self.armed = true; + self._maybeFire(); + }, + + // Register a function to be called once before firing the fence. + // Callback function can add new writes to the fence, in which case + // it won't fire until those writes are done as well. + onBeforeFire: function (func) { + var self = this; + if (self.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + self.before_fire_callbacks.push(func); + }, + + // Register a function to be called when the fence fires. + onAllCommitted: function (func) { + var self = this; + if (self.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + self.completion_callbacks.push(func); + }, + + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait: function () { + var self = this; + var future = new Future; + self.onAllCommitted(function () { + future['return'](); + }); + self.arm(); + future.wait(); + }, + + _maybeFire: function () { + var self = this; + if (self.fired) + throw new Error("write fence already activated?"); + if (self.armed && !self.outstanding_writes) { + function invokeCallback (func) { + try { + func(self); + } catch (err) { + Meteor._debug("exception in write fence callback", err); + } + } + + self.outstanding_writes++; + while (self.before_fire_callbacks.length > 0) { + var callbacks = self.before_fire_callbacks; + self.before_fire_callbacks = []; + _.each(callbacks, invokeCallback); + } + self.outstanding_writes--; + + if (!self.outstanding_writes) { + self.fired = true; + var callbacks = self.completion_callbacks; + self.completion_callbacks = []; + _.each(callbacks, invokeCallback); + } + } + }, + + // Deactivate this fence so that adding more writes has no effect. + // The fence must have already fired. + retire: function () { + var self = this; + if (! self.fired) + throw new Error("Can't retire a fence that hasn't fired."); + self.retired = true; + } +}); From 3a3dcda13e4e170c585c0da8f85fef955c27fd50 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 16:46:04 -0300 Subject: [PATCH 0047/1965] Rollback changes to fibers implementation --- packages/accounts-password/password_server.js | 3 ++- packages/ddp-server/package.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 812bf848d1..2e6d57c075 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -1025,7 +1025,8 @@ Accounts.createUserAsync = async (options, callback) => { // method calling Accounts.createUser could set? // -Accounts.createUser = (options, callback) => { +Accounts.createUser = !Meteor._isFibersEnabled ? Accounts.createUserAsync + : (options, callback) => { return Promise.await(Accounts.createUserAsync(options, callback)); }; diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js index da413690b0..4077518df0 100644 --- a/packages/ddp-server/package.js +++ b/packages/ddp-server/package.js @@ -10,6 +10,11 @@ Npm.depends({ }); Package.onUse(function (api) { + if (process.env.DISABLE_FIBERS) { + api.use('ddp-server-async', 'server'); + api.export('DDPServer', 'server'); + return; + } api.use(['check', 'random', 'ejson', 'underscore', 'retry', 'mongo-id', 'diff-sequence', 'ecmascript'], 'server'); From bb5da57704b83e9fbbaa8e46e8e5abad3587908d Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 16:47:17 -0300 Subject: [PATCH 0048/1965] Separate ddp-client packages into two packages - packages/ddp-client: current Fibers implementation; - Packages/ddp-client-async: New version without Fibers (Work in progress) --- packages/{ddp-client => ddp-client-async}/.npm/package/.gitignore | 0 packages/{ddp-client => ddp-client-async}/.npm/package/README | 0 .../.npm/package/npm-shrinkwrap.json | 0 packages/{ddp-client => ddp-client-async}/README.md | 0 packages/{ddp-client => ddp-client-async}/client/client.js | 0 .../{ddp-client => ddp-client-async}/client/client_convenience.js | 0 packages/{ddp-client => ddp-client-async}/common/MethodInvoker.js | 0 .../common/livedata_connection.js | 0 packages/{ddp-client => ddp-client-async}/common/namespace.js | 0 packages/{ddp-client => ddp-client-async}/package.js | 0 packages/{ddp-client => ddp-client-async}/server/server.js | 0 .../test/livedata_connection_tests.js | 0 .../test/livedata_test_service.js | 0 packages/{ddp-client => ddp-client-async}/test/livedata_tests.js | 0 .../{ddp-client => ddp-client-async}/test/random_stream_tests.js | 0 packages/{ddp-client => ddp-client-async}/test/stub_stream.js | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename packages/{ddp-client => ddp-client-async}/.npm/package/.gitignore (100%) rename packages/{ddp-client => ddp-client-async}/.npm/package/README (100%) rename packages/{ddp-client => ddp-client-async}/.npm/package/npm-shrinkwrap.json (100%) rename packages/{ddp-client => ddp-client-async}/README.md (100%) rename packages/{ddp-client => ddp-client-async}/client/client.js (100%) rename packages/{ddp-client => ddp-client-async}/client/client_convenience.js (100%) rename packages/{ddp-client => ddp-client-async}/common/MethodInvoker.js (100%) rename packages/{ddp-client => ddp-client-async}/common/livedata_connection.js (100%) rename packages/{ddp-client => ddp-client-async}/common/namespace.js (100%) rename packages/{ddp-client => ddp-client-async}/package.js (100%) rename packages/{ddp-client => ddp-client-async}/server/server.js (100%) rename packages/{ddp-client => ddp-client-async}/test/livedata_connection_tests.js (100%) rename packages/{ddp-client => ddp-client-async}/test/livedata_test_service.js (100%) rename packages/{ddp-client => ddp-client-async}/test/livedata_tests.js (100%) rename packages/{ddp-client => ddp-client-async}/test/random_stream_tests.js (100%) rename packages/{ddp-client => ddp-client-async}/test/stub_stream.js (100%) diff --git a/packages/ddp-client/.npm/package/.gitignore b/packages/ddp-client-async/.npm/package/.gitignore similarity index 100% rename from packages/ddp-client/.npm/package/.gitignore rename to packages/ddp-client-async/.npm/package/.gitignore diff --git a/packages/ddp-client/.npm/package/README b/packages/ddp-client-async/.npm/package/README similarity index 100% rename from packages/ddp-client/.npm/package/README rename to packages/ddp-client-async/.npm/package/README diff --git a/packages/ddp-client/.npm/package/npm-shrinkwrap.json b/packages/ddp-client-async/.npm/package/npm-shrinkwrap.json similarity index 100% rename from packages/ddp-client/.npm/package/npm-shrinkwrap.json rename to packages/ddp-client-async/.npm/package/npm-shrinkwrap.json diff --git a/packages/ddp-client/README.md b/packages/ddp-client-async/README.md similarity index 100% rename from packages/ddp-client/README.md rename to packages/ddp-client-async/README.md diff --git a/packages/ddp-client/client/client.js b/packages/ddp-client-async/client/client.js similarity index 100% rename from packages/ddp-client/client/client.js rename to packages/ddp-client-async/client/client.js diff --git a/packages/ddp-client/client/client_convenience.js b/packages/ddp-client-async/client/client_convenience.js similarity index 100% rename from packages/ddp-client/client/client_convenience.js rename to packages/ddp-client-async/client/client_convenience.js diff --git a/packages/ddp-client/common/MethodInvoker.js b/packages/ddp-client-async/common/MethodInvoker.js similarity index 100% rename from packages/ddp-client/common/MethodInvoker.js rename to packages/ddp-client-async/common/MethodInvoker.js diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client-async/common/livedata_connection.js similarity index 100% rename from packages/ddp-client/common/livedata_connection.js rename to packages/ddp-client-async/common/livedata_connection.js diff --git a/packages/ddp-client/common/namespace.js b/packages/ddp-client-async/common/namespace.js similarity index 100% rename from packages/ddp-client/common/namespace.js rename to packages/ddp-client-async/common/namespace.js diff --git a/packages/ddp-client/package.js b/packages/ddp-client-async/package.js similarity index 100% rename from packages/ddp-client/package.js rename to packages/ddp-client-async/package.js diff --git a/packages/ddp-client/server/server.js b/packages/ddp-client-async/server/server.js similarity index 100% rename from packages/ddp-client/server/server.js rename to packages/ddp-client-async/server/server.js diff --git a/packages/ddp-client/test/livedata_connection_tests.js b/packages/ddp-client-async/test/livedata_connection_tests.js similarity index 100% rename from packages/ddp-client/test/livedata_connection_tests.js rename to packages/ddp-client-async/test/livedata_connection_tests.js diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client-async/test/livedata_test_service.js similarity index 100% rename from packages/ddp-client/test/livedata_test_service.js rename to packages/ddp-client-async/test/livedata_test_service.js diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client-async/test/livedata_tests.js similarity index 100% rename from packages/ddp-client/test/livedata_tests.js rename to packages/ddp-client-async/test/livedata_tests.js diff --git a/packages/ddp-client/test/random_stream_tests.js b/packages/ddp-client-async/test/random_stream_tests.js similarity index 100% rename from packages/ddp-client/test/random_stream_tests.js rename to packages/ddp-client-async/test/random_stream_tests.js diff --git a/packages/ddp-client/test/stub_stream.js b/packages/ddp-client-async/test/stub_stream.js similarity index 100% rename from packages/ddp-client/test/stub_stream.js rename to packages/ddp-client-async/test/stub_stream.js From fd2f3b551ad6b285646ebfb41f5aaa3789e3e8b1 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 16:48:01 -0300 Subject: [PATCH 0049/1965] Separate ddp-client packages into two packages - packages/ddp-client: current Fibers implementation; - Packages/ddp-client-async: New version without Fibers (Work in progress) Restore ddp-client --- packages/ddp-client/.npm/package/.gitignore | 1 + packages/ddp-client/.npm/package/README | 7 + .../.npm/package/npm-shrinkwrap.json | 25 + packages/ddp-client/README.md | 4 + packages/ddp-client/client/client.js | 6 + .../ddp-client/client/client_convenience.js | 59 + packages/ddp-client/common/MethodInvoker.js | 85 + .../ddp-client/common/livedata_connection.js | 1899 +++++++++++++ packages/ddp-client/common/namespace.js | 91 + packages/ddp-client/package.js | 63 + packages/ddp-client/server/server.js | 1 + .../test/livedata_connection_tests.js | 2491 +++++++++++++++++ .../ddp-client/test/livedata_test_service.js | 376 +++ packages/ddp-client/test/livedata_tests.js | 1103 ++++++++ .../ddp-client/test/random_stream_tests.js | 44 + packages/ddp-client/test/stub_stream.js | 57 + 16 files changed, 6312 insertions(+) create mode 100644 packages/ddp-client/.npm/package/.gitignore create mode 100644 packages/ddp-client/.npm/package/README create mode 100644 packages/ddp-client/.npm/package/npm-shrinkwrap.json create mode 100644 packages/ddp-client/README.md create mode 100644 packages/ddp-client/client/client.js create mode 100644 packages/ddp-client/client/client_convenience.js create mode 100644 packages/ddp-client/common/MethodInvoker.js create mode 100644 packages/ddp-client/common/livedata_connection.js create mode 100644 packages/ddp-client/common/namespace.js create mode 100644 packages/ddp-client/package.js create mode 100644 packages/ddp-client/server/server.js create mode 100644 packages/ddp-client/test/livedata_connection_tests.js create mode 100644 packages/ddp-client/test/livedata_test_service.js create mode 100644 packages/ddp-client/test/livedata_tests.js create mode 100644 packages/ddp-client/test/random_stream_tests.js create mode 100644 packages/ddp-client/test/stub_stream.js diff --git a/packages/ddp-client/.npm/package/.gitignore b/packages/ddp-client/.npm/package/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/ddp-client/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/ddp-client/.npm/package/README b/packages/ddp-client/.npm/package/README new file mode 100644 index 0000000000..3d492553a4 --- /dev/null +++ b/packages/ddp-client/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/ddp-client/.npm/package/npm-shrinkwrap.json b/packages/ddp-client/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000..28feb731a6 --- /dev/null +++ b/packages/ddp-client/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,25 @@ +{ + "lockfileVersion": 1, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==" + }, + "@sinonjs/fake-timers": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", + "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + } + } +} diff --git a/packages/ddp-client/README.md b/packages/ddp-client/README.md new file mode 100644 index 0000000000..9daa0a6277 --- /dev/null +++ b/packages/ddp-client/README.md @@ -0,0 +1,4 @@ +# ddp-client +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/ddp-client) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/ddp-client) +*** + diff --git a/packages/ddp-client/client/client.js b/packages/ddp-client/client/client.js new file mode 100644 index 0000000000..fd9c746bbc --- /dev/null +++ b/packages/ddp-client/client/client.js @@ -0,0 +1,6 @@ +export { DDP } from '../common/namespace.js'; + +import '../common/livedata_connection'; + +// Initialize the default server connection and put it on Meteor.connection +import './client_convenience'; diff --git a/packages/ddp-client/client/client_convenience.js b/packages/ddp-client/client/client_convenience.js new file mode 100644 index 0000000000..7fc3222106 --- /dev/null +++ b/packages/ddp-client/client/client_convenience.js @@ -0,0 +1,59 @@ +import { DDP } from '../common/namespace.js'; +import { Meteor } from 'meteor/meteor'; + +// Meteor.refresh can be called on the client (if you're in common code) but it +// only has an effect on the server. +Meteor.refresh = () => {}; + +// By default, try to connect back to the same endpoint as the page +// was served from. +// +// XXX We should be doing this a different way. Right now we don't +// include ROOT_URL_PATH_PREFIX when computing ddpUrl. (We don't +// include it on the server when computing +// DDP_DEFAULT_CONNECTION_URL, and we don't include it in our +// default, '/'.) We get by with this because DDP.connect then +// forces the URL passed to it to be interpreted relative to the +// app's deploy path, even if it is absolute. Instead, we should +// make DDP_DEFAULT_CONNECTION_URL, if set, include the path prefix; +// make the default ddpUrl be '' rather that '/'; and make +// _translateUrl in stream_client_common.js not force absolute paths +// to be treated like relative paths. See also +// stream_client_common.js #RationalizingRelativeDDPURLs +const runtimeConfig = typeof __meteor_runtime_config__ !== 'undefined' ? __meteor_runtime_config__ : Object.create(null); +const ddpUrl = runtimeConfig.DDP_DEFAULT_CONNECTION_URL || '/'; + +const retry = new Retry(); + +function onDDPVersionNegotiationFailure(description) { + Meteor._debug(description); + if (Package.reload) { + const migrationData = Package.reload.Reload._migrationData('livedata') || Object.create(null); + let failures = migrationData.DDPVersionNegotiationFailures || 0; + ++failures; + Package.reload.Reload._onMigrate('livedata', () => [true, { DDPVersionNegotiationFailures: failures }]); + retry.retryLater(failures, () => { + Package.reload.Reload._reload({ immediateMigration: true }); + }); + } +} + +Meteor.connection = DDP.connect(ddpUrl, { + onDDPVersionNegotiationFailure: onDDPVersionNegotiationFailure +}); + +// Proxy the public methods of Meteor.connection so they can +// be called directly on Meteor. +[ + 'subscribe', + 'methods', + 'call', + 'callAsync', + 'apply', + 'applyAsync', + 'status', + 'reconnect', + 'disconnect' +].forEach(name => { + Meteor[name] = Meteor.connection[name].bind(Meteor.connection); +}); diff --git a/packages/ddp-client/common/MethodInvoker.js b/packages/ddp-client/common/MethodInvoker.js new file mode 100644 index 0000000000..f2490b92f8 --- /dev/null +++ b/packages/ddp-client/common/MethodInvoker.js @@ -0,0 +1,85 @@ +// A MethodInvoker manages sending a method to the server and calling the user's +// callbacks. On construction, it registers itself in the connection's +// _methodInvokers map; it removes itself once the method is fully finished and +// the callback is invoked. This occurs when it has both received a result, +// and the data written by it is fully visible. +export default class MethodInvoker { + constructor(options) { + // Public (within this file) fields. + this.methodId = options.methodId; + this.sentMessage = false; + + this._callback = options.callback; + this._connection = options.connection; + this._message = options.message; + this._onResultReceived = options.onResultReceived || (() => {}); + this._wait = options.wait; + this.noRetry = options.noRetry; + this._methodResult = null; + this._dataVisible = false; + + // Register with the connection. + this._connection._methodInvokers[this.methodId] = this; + } + // Sends the method message to the server. May be called additional times if + // we lose the connection and reconnect before receiving a result. + sendMessage() { + // This function is called before sending a method (including resending on + // reconnect). We should only (re)send methods where we don't already have a + // result! + if (this.gotResult()) + throw new Error('sendingMethod is called on method with result'); + + // If we're re-sending it, it doesn't matter if data was written the first + // time. + this._dataVisible = false; + this.sentMessage = true; + + // If this is a wait method, make all data messages be buffered until it is + // done. + if (this._wait) + this._connection._methodsBlockingQuiescence[this.methodId] = true; + + // Actually send the message. + this._connection._send(this._message); + } + // Invoke the callback, if we have both a result and know that all data has + // been written to the local cache. + _maybeInvokeCallback() { + if (this._methodResult && this._dataVisible) { + // Call the callback. (This won't throw: the callback was wrapped with + // bindEnvironment.) + this._callback(this._methodResult[0], this._methodResult[1]); + + // Forget about this method. + delete this._connection._methodInvokers[this.methodId]; + + // Let the connection know that this method is finished, so it can try to + // move on to the next block of methods. + this._connection._outstandingMethodFinished(); + } + } + // Call with the result of the method from the server. Only may be called + // once; once it is called, you should not call sendMessage again. + // If the user provided an onResultReceived callback, call it immediately. + // Then invoke the main callback if data is also visible. + receiveResult(err, result) { + if (this.gotResult()) + throw new Error('Methods should only receive results once'); + this._methodResult = [err, result]; + this._onResultReceived(err, result); + this._maybeInvokeCallback(); + } + // Call this when all data written by the method is visible. This means that + // the method has returns its "data is done" message *AND* all server + // documents that are buffered at that time have been written to the local + // cache. Invokes the main callback if the result has been received. + dataVisible() { + this._dataVisible = true; + this._maybeInvokeCallback(); + } + // True if receiveResult has been called. + gotResult() { + return !!this._methodResult; + } +} diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js new file mode 100644 index 0000000000..c30ff6f48d --- /dev/null +++ b/packages/ddp-client/common/livedata_connection.js @@ -0,0 +1,1899 @@ +import { Meteor } from 'meteor/meteor'; +import { DDPCommon } from 'meteor/ddp-common'; +import { Tracker } from 'meteor/tracker'; +import { EJSON } from 'meteor/ejson'; +import { Random } from 'meteor/random'; +import { Hook } from 'meteor/callback-hook'; +import { MongoID } from 'meteor/mongo-id'; +import { DDP } from './namespace.js'; +import MethodInvoker from './MethodInvoker.js'; +import { + hasOwn, + slice, + keys, + isEmpty, + last, +} from "meteor/ddp-common/utils.js"; + +let Fiber; +let Future; +if (Meteor.isServer) { + Fiber = Npm.require('fibers'); + Future = Npm.require('fibers/future'); +} + +class MongoIDMap extends IdMap { + constructor() { + super(MongoID.idStringify, MongoID.idParse); + } +} + +// @param url {String|Object} URL to Meteor app, +// or an object as a test hook (see code) +// Options: +// reloadWithOutstanding: is it OK to reload if there are outstanding methods? +// headers: extra headers to send on the websockets connection, for +// server-to-server DDP only +// _sockjsOptions: Specifies options to pass through to the sockjs client +// onDDPNegotiationVersionFailure: callback when version negotiation fails. +// +// XXX There should be a way to destroy a DDP connection, causing all +// outstanding method calls to fail. +// +// XXX Our current way of handling failure and reconnection is great +// for an app (where we want to tolerate being disconnected as an +// expect state, and keep trying forever to reconnect) but cumbersome +// for something like a command line tool that wants to make a +// connection, call a method, and print an error if connection +// fails. We should have better usability in the latter case (while +// still transparently reconnecting if it's just a transient failure +// or the server migrating us). +export class Connection { + constructor(url, options) { + const self = this; + + this.options = options = { + onConnected() {}, + onDDPVersionNegotiationFailure(description) { + Meteor._debug(description); + }, + heartbeatInterval: 17500, + heartbeatTimeout: 15000, + npmFayeOptions: Object.create(null), + // These options are only for testing. + reloadWithOutstanding: false, + supportedDDPVersions: DDPCommon.SUPPORTED_DDP_VERSIONS, + retry: true, + respondToPings: true, + // When updates are coming within this ms interval, batch them together. + bufferedWritesInterval: 5, + // Flush buffers immediately if writes are happening continuously for more than this many ms. + bufferedWritesMaxAge: 500, + + ...options + }; + + // If set, called when we reconnect, queuing method calls _before_ the + // existing outstanding ones. + // NOTE: This feature has been preserved for backwards compatibility. The + // preferred method of setting a callback on reconnect is to use + // DDP.onReconnect. + self.onReconnect = null; + + // as a test hook, allow passing a stream instead of a url. + if (typeof url === 'object') { + self._stream = url; + } else { + const { ClientStream } = require("meteor/socket-stream-client"); + self._stream = new ClientStream(url, { + retry: options.retry, + ConnectionError: DDP.ConnectionError, + headers: options.headers, + _sockjsOptions: options._sockjsOptions, + // Used to keep some tests quiet, or for other cases in which + // the right thing to do with connection errors is to silently + // fail (e.g. sending package usage stats). At some point we + // should have a real API for handling client-stream-level + // errors. + _dontPrintErrors: options._dontPrintErrors, + connectTimeoutMs: options.connectTimeoutMs, + npmFayeOptions: options.npmFayeOptions + }); + } + + self._lastSessionId = null; + self._versionSuggestion = null; // The last proposed DDP version. + self._version = null; // The DDP version agreed on by client and server. + self._stores = Object.create(null); // name -> object with methods + self._methodHandlers = Object.create(null); // name -> func + self._nextMethodId = 1; + self._supportedDDPVersions = options.supportedDDPVersions; + + self._heartbeatInterval = options.heartbeatInterval; + self._heartbeatTimeout = options.heartbeatTimeout; + + // Tracks methods which the user has tried to call but which have not yet + // called their user callback (ie, they are waiting on their result or for all + // of their writes to be written to the local cache). Map from method ID to + // MethodInvoker object. + self._methodInvokers = Object.create(null); + + // Tracks methods which the user has called but whose result messages have not + // arrived yet. + // + // _outstandingMethodBlocks is an array of blocks of methods. Each block + // represents a set of methods that can run at the same time. The first block + // represents the methods which are currently in flight; subsequent blocks + // must wait for previous blocks to be fully finished before they can be sent + // to the server. + // + // Each block is an object with the following fields: + // - methods: a list of MethodInvoker objects + // - wait: a boolean; if true, this block had a single method invoked with + // the "wait" option + // + // There will never be adjacent blocks with wait=false, because the only thing + // that makes methods need to be serialized is a wait method. + // + // Methods are removed from the first block when their "result" is + // received. The entire first block is only removed when all of the in-flight + // methods have received their results (so the "methods" list is empty) *AND* + // all of the data written by those methods are visible in the local cache. So + // it is possible for the first block's methods list to be empty, if we are + // still waiting for some objects to quiesce. + // + // Example: + // _outstandingMethodBlocks = [ + // {wait: false, methods: []}, + // {wait: true, methods: []}, + // {wait: false, methods: [, + // ]}] + // This means that there were some methods which were sent to the server and + // which have returned their results, but some of the data written by + // the methods may not be visible in the local cache. Once all that data is + // visible, we will send a 'login' method. Once the login method has returned + // and all the data is visible (including re-running subs if userId changes), + // we will send the 'foo' and 'bar' methods in parallel. + self._outstandingMethodBlocks = []; + + // method ID -> array of objects with keys 'collection' and 'id', listing + // documents written by a given method's stub. keys are associated with + // methods whose stub wrote at least one document, and whose data-done message + // has not yet been received. + self._documentsWrittenByStub = {}; + // collection -> IdMap of "server document" object. A "server document" has: + // - "document": the version of the document according the + // server (ie, the snapshot before a stub wrote it, amended by any changes + // received from the server) + // It is undefined if we think the document does not exist + // - "writtenByStubs": a set of method IDs whose stubs wrote to the document + // whose "data done" messages have not yet been processed + self._serverDocuments = {}; + + // Array of callbacks to be called after the next update of the local + // cache. Used for: + // - Calling methodInvoker.dataVisible and sub ready callbacks after + // the relevant data is flushed. + // - Invoking the callbacks of "half-finished" methods after reconnect + // quiescence. Specifically, methods whose result was received over the old + // connection (so we don't re-send it) but whose data had not been made + // visible. + self._afterUpdateCallbacks = []; + + // In two contexts, we buffer all incoming data messages and then process them + // all at once in a single update: + // - During reconnect, we buffer all data messages until all subs that had + // been ready before reconnect are ready again, and all methods that are + // active have returned their "data done message"; then + // - During the execution of a "wait" method, we buffer all data messages + // until the wait method gets its "data done" message. (If the wait method + // occurs during reconnect, it doesn't get any special handling.) + // all data messages are processed in one update. + // + // The following fields are used for this "quiescence" process. + + // This buffers the messages that aren't being processed yet. + self._messagesBufferedUntilQuiescence = []; + // Map from method ID -> true. Methods are removed from this when their + // "data done" message is received, and we will not quiesce until it is + // empty. + self._methodsBlockingQuiescence = {}; + // map from sub ID -> true for subs that were ready (ie, called the sub + // ready callback) before reconnect but haven't become ready again yet + self._subsBeingRevived = {}; // map from sub._id -> true + // if true, the next data update should reset all stores. (set during + // reconnect.) + self._resetStores = false; + + // name -> array of updates for (yet to be created) collections + self._updatesForUnknownStores = {}; + // if we're blocking a migration, the retry func + self._retryMigrate = null; + + self.__flushBufferedWrites = Meteor.bindEnvironment( + self._flushBufferedWrites, + 'flushing DDP buffered writes', + self + ); + // Collection name -> array of messages. + self._bufferedWrites = {}; + // When current buffer of updates must be flushed at, in ms timestamp. + self._bufferedWritesFlushAt = null; + // Timeout handle for the next processing of all pending writes + self._bufferedWritesFlushHandle = null; + + self._bufferedWritesInterval = options.bufferedWritesInterval; + self._bufferedWritesMaxAge = options.bufferedWritesMaxAge; + + // metadata for subscriptions. Map from sub ID to object with keys: + // - id + // - name + // - params + // - inactive (if true, will be cleaned up if not reused in re-run) + // - ready (has the 'ready' message been received?) + // - readyCallback (an optional callback to call when ready) + // - errorCallback (an optional callback to call if the sub terminates with + // an error, XXX COMPAT WITH 1.0.3.1) + // - stopCallback (an optional callback to call when the sub terminates + // for any reason, with an error argument if an error triggered the stop) + self._subscriptions = {}; + + // Reactive userId. + self._userId = null; + self._userIdDeps = new Tracker.Dependency(); + + // Block auto-reload while we're waiting for method responses. + if (Meteor.isClient && + Package.reload && + ! options.reloadWithOutstanding) { + Package.reload.Reload._onMigrate(retry => { + if (! self._readyToMigrate()) { + self._retryMigrate = retry; + return [false]; + } else { + return [true]; + } + }); + } + + const onDisconnect = () => { + if (self._heartbeat) { + self._heartbeat.stop(); + self._heartbeat = null; + } + }; + + if (Meteor.isServer) { + self._stream.on( + 'message', + Meteor.bindEnvironment( + this.onMessage.bind(this), + 'handling DDP message' + ) + ); + self._stream.on( + 'reset', + Meteor.bindEnvironment(this.onReset.bind(this), 'handling DDP reset') + ); + self._stream.on( + 'disconnect', + Meteor.bindEnvironment(onDisconnect, 'handling DDP disconnect') + ); + } else { + self._stream.on('message', this.onMessage.bind(this)); + self._stream.on('reset', this.onReset.bind(this)); + self._stream.on('disconnect', onDisconnect); + } + } + + // 'name' is the name of the data on the wire that should go in the + // store. 'wrappedStore' should be an object with methods beginUpdate, update, + // endUpdate, saveOriginals, retrieveOriginals. see Collection for an example. + registerStore(name, wrappedStore) { + const self = this; + + if (name in self._stores) return false; + + // Wrap the input object in an object which makes any store method not + // implemented by 'store' into a no-op. + const store = Object.create(null); + const keysOfStore = [ + 'update', + 'beginUpdate', + 'endUpdate', + 'saveOriginals', + 'retrieveOriginals', + 'getDoc', + '_getCollection' + ]; + keysOfStore.forEach((method) => { + store[method] = (...args) => { + if (wrappedStore[method]) { + return wrappedStore[method](...args); + } + }; + }); + self._stores[name] = store; + + const queued = self._updatesForUnknownStores[name]; + if (Array.isArray(queued)) { + store.beginUpdate(queued.length, false); + queued.forEach(msg => { + store.update(msg); + }); + store.endUpdate(); + delete self._updatesForUnknownStores[name]; + } + + return true; + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.subscribe + * @summary Subscribe to a record set. Returns a handle that provides + * `stop()` and `ready()` methods. + * @locus Client + * @param {String} name Name of the subscription. Matches the name of the + * server's `publish()` call. + * @param {EJSONable} [arg1,arg2...] Optional arguments passed to publisher + * function on server. + * @param {Function|Object} [callbacks] Optional. May include `onStop` + * and `onReady` callbacks. If there is an error, it is passed as an + * argument to `onStop`. If a function is passed instead of an object, it + * is interpreted as an `onReady` callback. + */ + subscribe(name /* .. [arguments] .. (callback|callbacks) */) { + const self = this; + + const params = slice.call(arguments, 1); + let callbacks = Object.create(null); + if (params.length) { + const lastParam = params[params.length - 1]; + if (typeof lastParam === 'function') { + callbacks.onReady = params.pop(); + } else if (lastParam && [ + lastParam.onReady, + // XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use + // onStop with an error callback instead. + lastParam.onError, + lastParam.onStop + ].some(f => typeof f === "function")) { + callbacks = params.pop(); + } + } + + // Is there an existing sub with the same name and param, run in an + // invalidated Computation? This will happen if we are rerunning an + // existing computation. + // + // For example, consider a rerun of: + // + // Tracker.autorun(function () { + // Meteor.subscribe("foo", Session.get("foo")); + // Meteor.subscribe("bar", Session.get("bar")); + // }); + // + // If "foo" has changed but "bar" has not, we will match the "bar" + // subcribe to an existing inactive subscription in order to not + // unsub and resub the subscription unnecessarily. + // + // We only look for one such sub; if there are N apparently-identical subs + // being invalidated, we will require N matching subscribe calls to keep + // them all active. + const existing = Object.values(self._subscriptions).find( + sub => (sub.inactive && sub.name === name && EJSON.equals(sub.params, params)) + ); + + let id; + if (existing) { + id = existing.id; + existing.inactive = false; // reactivate + + if (callbacks.onReady) { + // If the sub is not already ready, replace any ready callback with the + // one provided now. (It's not really clear what users would expect for + // an onReady callback inside an autorun; the semantics we provide is + // that at the time the sub first becomes ready, we call the last + // onReady callback provided, if any.) + // If the sub is already ready, run the ready callback right away. + // It seems that users would expect an onReady callback inside an + // autorun to trigger once the the sub first becomes ready and also + // when re-subs happens. + if (existing.ready) { + callbacks.onReady(); + } else { + existing.readyCallback = callbacks.onReady; + } + } + + // XXX COMPAT WITH 1.0.3.1 we used to have onError but now we call + // onStop with an optional error argument + if (callbacks.onError) { + // Replace existing callback if any, so that errors aren't + // double-reported. + existing.errorCallback = callbacks.onError; + } + + if (callbacks.onStop) { + existing.stopCallback = callbacks.onStop; + } + } else { + // New sub! Generate an id, save it locally, and send message. + id = Random.id(); + self._subscriptions[id] = { + id: id, + name: name, + params: EJSON.clone(params), + inactive: false, + ready: false, + readyDeps: new Tracker.Dependency(), + readyCallback: callbacks.onReady, + // XXX COMPAT WITH 1.0.3.1 #errorCallback + errorCallback: callbacks.onError, + stopCallback: callbacks.onStop, + connection: self, + remove() { + delete this.connection._subscriptions[this.id]; + this.ready && this.readyDeps.changed(); + }, + stop() { + this.connection._send({ msg: 'unsub', id: id }); + this.remove(); + + if (callbacks.onStop) { + callbacks.onStop(); + } + } + }; + self._send({ msg: 'sub', id: id, name: name, params: params }); + } + + // return a handle to the application. + const handle = { + stop() { + if (! hasOwn.call(self._subscriptions, id)) { + return; + } + self._subscriptions[id].stop(); + }, + ready() { + // return false if we've unsubscribed. + if (!hasOwn.call(self._subscriptions, id)) { + return false; + } + const record = self._subscriptions[id]; + record.readyDeps.depend(); + return record.ready; + }, + subscriptionId: id + }; + + if (Tracker.active) { + // We're in a reactive computation, so we'd like to unsubscribe when the + // computation is invalidated... but not if the rerun just re-subscribes + // to the same subscription! When a rerun happens, we use onInvalidate + // as a change to mark the subscription "inactive" so that it can + // be reused from the rerun. If it isn't reused, it's killed from + // an afterFlush. + Tracker.onInvalidate((c) => { + if (hasOwn.call(self._subscriptions, id)) { + self._subscriptions[id].inactive = true; + } + + Tracker.afterFlush(() => { + if (hasOwn.call(self._subscriptions, id) && + self._subscriptions[id].inactive) { + handle.stop(); + } + }); + }); + } + + return handle; + } + + // options: + // - onLateError {Function(error)} called if an error was received after the ready event. + // (errors received before ready cause an error to be thrown) + _subscribeAndWait(name, args, options) { + const self = this; + const f = new Future(); + let ready = false; + args = args || []; + args.push({ + onReady() { + ready = true; + f['return'](); + }, + onError(e) { + if (!ready) f['throw'](e); + else options && options.onLateError && options.onLateError(e); + } + }); + + const handle = self.subscribe.apply(self, [name].concat(args)); + f.wait(); + return handle; + } + + methods(methods) { + Object.entries(methods).forEach(([name, func]) => { + if (typeof func !== 'function') { + throw new Error("Method '" + name + "' must be a function"); + } + if (this._methodHandlers[name]) { + throw new Error("A method named '" + name + "' is already defined"); + } + this._methodHandlers[name] = func; + }); + } + + _getIsSimulation({isFromCallAsync, alreadyInSimulation}) { + if (!isFromCallAsync) { + return alreadyInSimulation; + } + return alreadyInSimulation && DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.call + * @summary Invokes a method with a sync stub, passing any number of arguments. + * @locus Anywhere + * @param {String} name Name of method to invoke + * @param {EJSONable} [arg1,arg2...] Optional method arguments + * @param {Function} [asyncCallback] Optional callback, which is called asynchronously with the error or result after the method is complete. If not provided, the method runs synchronously if possible (see below). + */ + call(name /* .. [arguments] .. callback */) { + // if it's a function, the last argument is the result callback, + // not a parameter to the remote method. + const args = slice.call(arguments, 1); + let callback; + if (args.length && typeof args[args.length - 1] === 'function') { + callback = args.pop(); + } + return this.apply(name, args, callback); + } + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.callAsync + * @summary Invokes a method with an async stub, passing any number of arguments. + * @locus Anywhere + * @param {String} name Name of method to invoke + * @param {EJSONable} [arg1,arg2...] Optional method arguments + * @returns {Promise} + */ + async callAsync(name /* .. [arguments] .. */) { + const args = slice.call(arguments, 1); + if (args.length && typeof args[args.length - 1] === 'function') { + throw new Error( + "Meteor.callAsync() does not accept a callback. You should 'await' the result, or use .then()." + ); + } + /* + * This is necessary because when you call a Promise.then, you're actually calling a bound function by Meteor. + * + * This is done by this code https://github.com/meteor/meteor/blob/17673c66878d3f7b1d564a4215eb0633fa679017/npm-packages/meteor-promise/promise_client.js#L1-L16. (All the logic below can be removed in the future, when we stop overwriting the + * Promise.) + * + * When you call a ".then()", like "Meteor.callAsync().then()", the global context (inside currentValues) + * will be from the call of Meteor.callAsync(), and not the context after the promise is done. + * + * This means that without this code if you call a stub inside the ".then()", this stub will act as a simulation + * and won't reach the server. + * + * Inside the function _getIsSimulation(), if isFromCallAsync is false, we continue to consider just the + * alreadyInSimulation, otherwise, isFromCallAsync is true, we also check the value of callAsyncMethodRunning (by + * calling DDP._CurrentMethodInvocation._isCallAsyncMethodRunning()). + * + * With this, if a stub is running inside a ".then()", it'll know it's not a simulation, because callAsyncMethodRunning + * will be false. + * + * DDP._CurrentMethodInvocation._set() is important because without it, if you have a code like: + * + * Meteor.callAsync("m1").then(() => { + * Meteor.callAsync("m2") + * }) + * + * The call the method m2 will act as a simulation and won't reach the server. That's why we reset the context here + * before calling everything else. + * + * */ + DDP._CurrentMethodInvocation._set(); + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); + return new Promise((resolve, reject) => { + this.applyAsync(name, args, { isFromCallAsync: true }, (err, result) => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + if (err) { + reject(err); + return; + } + resolve(result); + }); + }); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.apply + * @summary Invoke a method passing an array of arguments. + * @locus Anywhere + * @param {String} name Name of method to invoke + * @param {EJSONable[]} args Method arguments + * @param {Object} [options] + * @param {Boolean} options.wait (Client only) If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed. + * @param {Function} options.onResultReceived (Client only) This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method. + * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. + * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. + * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. + * @param {Function} [asyncCallback] Optional callback; same semantics as in [`Meteor.call`](#meteor_call). + */ + apply(name, args, options, callback) { + const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args)); + + if (stubOptions.hasStub) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { + this._saveOriginals(); + } + try { + stubOptions.stubReturnValue = DDP._CurrentMethodInvocation + .withValue(invocation, stubInvocation); + } catch (e) { + stubOptions.exception = e; + } + } + return this._apply(name, stubOptions, args, options, callback); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.applyAsync + * @summary Invoke a method passing an array of arguments. + * @locus Anywhere + * @param {String} name Name of method to invoke + * @param {EJSONable[]} args Method arguments + * @param {Object} [options] + * @param {Boolean} options.wait (Client only) If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed. + * @param {Function} options.onResultReceived (Client only) This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method. + * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. + * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. + * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. + * @param {Function} [asyncCallback] Optional callback. + */ + async applyAsync(name, args, options, callback) { + const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); + if (stubOptions.hasStub) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { + this._saveOriginals(); + } + try { + /* + * The code below follows the same logic as the function withValues(). + * + * But as the Meteor package is not compiled by ecmascript, it is unable to use newer syntax in the browser, + * such as, the async/await. + * + * So, to keep supporting old browsers, like IE 11, we're creating the logic one level above. + */ + const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( + invocation + ); + try { + stubOptions.stubReturnValue = await stubInvocation(); + } finally { + DDP._CurrentMethodInvocation._set(currentContext); + } + } catch (e) { + stubOptions.exception = e; + } + } + return this._apply(name, stubOptions, args, options, callback); + } + + _apply(name, stubCallValue, args, options, callback) { + const self = this; + + // We were passed 3 arguments. They may be either (name, args, options) + // or (name, args, callback) + if (!callback && typeof options === 'function') { + callback = options; + options = Object.create(null); + } + options = options || Object.create(null); + + if (callback) { + // XXX would it be better form to do the binding in stream.on, + // or caller, instead of here? + // XXX improve error message (and how we report it) + callback = Meteor.bindEnvironment( + callback, + "delivering result of invoking '" + name + "'" + ); + } + + // Keep our args safe from mutation (eg if we don't send the message for a + // while because of a wait method). + args = EJSON.clone(args); + + const { hasStub, exception, stubReturnValue, alreadyInSimulation, randomSeed } = stubCallValue; + + // If we're in a simulation, stop and return the result we have, + // rather than going on to do an RPC. If there was no stub, + // we'll end up returning undefined. + if ( + this._getIsSimulation({ + alreadyInSimulation, + isFromCallAsync: stubCallValue.isFromCallAsync, + }) + ) { + if (callback) { + callback(exception, stubReturnValue); + return undefined; + } + if (exception) throw exception; + return stubReturnValue; + } + + // We only create the methodId here because we don't actually need one if + // we're already in a simulation + const methodId = '' + self._nextMethodId++; + if (hasStub) { + self._retrieveAndStoreOriginals(methodId); + } + + // Generate the DDP message for the method call. Note that on the client, + // it is important that the stub have finished before we send the RPC, so + // that we know we have a complete list of which local documents the stub + // wrote. + const message = { + msg: 'method', + id: methodId, + method: name, + params: args + }; + + // If an exception occurred in a stub, and we're ignoring it + // because we're doing an RPC and want to use what the server + // returns instead, log it so the developer knows + // (unless they explicitly ask to see the error). + // + // Tests can set the '_expectedByTest' flag on an exception so it won't + // go to log. + if (exception) { + if (options.throwStubExceptions) { + throw exception; + } else if (!exception._expectedByTest) { + Meteor._debug( + "Exception while simulating the effect of invoking '" + name + "'", + exception + ); + } + } + + // At this point we're definitely doing an RPC, and we're going to + // return the value of the RPC to the caller. + + // If the caller didn't give a callback, decide what to do. + let future; + if (!callback) { + if (Meteor.isClient) { + // On the client, we don't have fibers, so we can't block. The + // only thing we can do is to return undefined and discard the + // result of the RPC. If an error occurred then print the error + // to the console. + callback = err => { + err && Meteor._debug("Error invoking Method '" + name + "'", err); + }; + } else { + // On the server, make the function synchronous. Throw on + // errors, return on success. + future = new Future(); + callback = future.resolver(); + } + } + + // Send the randomSeed only if we used it + if (randomSeed.value !== null) { + message.randomSeed = randomSeed.value; + } + + const methodInvoker = new MethodInvoker({ + methodId, + callback: callback, + connection: self, + onResultReceived: options.onResultReceived, + wait: !!options.wait, + message: message, + noRetry: !!options.noRetry + }); + + if (options.wait) { + // It's a wait method! Wait methods go in their own block. + self._outstandingMethodBlocks.push({ + wait: true, + methods: [methodInvoker] + }); + } else { + // Not a wait method. Start a new block if the previous block was a wait + // block, and add it to the last block of methods. + if (isEmpty(self._outstandingMethodBlocks) || + last(self._outstandingMethodBlocks).wait) { + self._outstandingMethodBlocks.push({ + wait: false, + methods: [], + }); + } + + last(self._outstandingMethodBlocks).methods.push(methodInvoker); + } + + // If we added it to the first block, send it out now. + if (self._outstandingMethodBlocks.length === 1) methodInvoker.sendMessage(); + + // If we're using the default callback on the server, + // block waiting for the result. + if (future) { + return future.wait(); + } + return options.returnStubValue ? stubReturnValue : undefined; + } + + + _stubCall(name, args, options) { + // Run the stub, if we have one. The stub is supposed to make some + // temporary writes to the database to give the user a smooth experience + // until the actual result of executing the method comes back from the + // server (whereupon the temporary writes to the database will be reversed + // during the beginUpdate/endUpdate process.) + // + // Normally, we ignore the return value of the stub (even if it is an + // exception), in favor of the real return value from the server. The + // exception is if the *caller* is a stub. In that case, we're not going + // to do a RPC, so we use the return value of the stub as our return + // value. + const self = this; + const enclosing = DDP._CurrentMethodInvocation.get(); + const stub = self._methodHandlers[name]; + const alreadyInSimulation = enclosing?.isSimulation; + const isFromCallAsync = enclosing?._isFromCallAsync; + const randomSeed = { value: null}; + + const defaultReturn = { + alreadyInSimulation, randomSeed, isFromCallAsync + }; + if (!stub) { + return { ...defaultReturn, hasStub: false }; + } + + // Lazily generate a randomSeed, only if it is requested by the stub. + // The random streams only have utility if they're used on both the client + // and the server; if the client doesn't generate any 'random' values + // then we don't expect the server to generate any either. + // Less commonly, the server may perform different actions from the client, + // and may in fact generate values where the client did not, but we don't + // have any client-side values to match, so even here we may as well just + // use a random seed on the server. In that case, we don't pass the + // randomSeed to save bandwidth, and we don't even generate it to save a + // bit of CPU and to avoid consuming entropy. + + const randomSeedGenerator = () => { + if (randomSeed.value === null) { + randomSeed.value = DDPCommon.makeRpcSeed(enclosing, name); + } + return randomSeed.value; + }; + + const setUserId = userId => { + self.setUserId(userId); + }; + + const invocation = new DDPCommon.MethodInvocation({ + isSimulation: true, + userId: self.userId(), + isFromCallAsync: options?.isFromCallAsync, + setUserId: setUserId, + randomSeed() { + return randomSeedGenerator(); + } + }); + + // Note that unlike in the corresponding server code, we never audit + // that stubs check() their arguments. + const stubInvocation = () => { + if (Meteor.isServer) { + // Because saveOriginals and retrieveOriginals aren't reentrant, + // don't allow stubs to yield. + return Meteor._noYieldsAllowed(() => { + // re-clone, so that the stub can't affect our caller's values + return stub.apply(invocation, EJSON.clone(args)); + }); + } else { + return stub.apply(invocation, EJSON.clone(args)); + } + }; + return { ...defaultReturn, hasStub: true, stubInvocation, invocation }; + } + + // Before calling a method stub, prepare all stores to track changes and allow + // _retrieveAndStoreOriginals to get the original versions of changed + // documents. + _saveOriginals() { + if (! this._waitingForQuiescence()) { + this._flushBufferedWrites(); + } + + Object.values(this._stores).forEach((store) => { + store.saveOriginals(); + }); + } + + // Retrieves the original versions of all documents modified by the stub for + // method 'methodId' from all stores and saves them to _serverDocuments (keyed + // by document) and _documentsWrittenByStub (keyed by method ID). + _retrieveAndStoreOriginals(methodId) { + const self = this; + if (self._documentsWrittenByStub[methodId]) + throw new Error('Duplicate methodId in _retrieveAndStoreOriginals'); + + const docsWritten = []; + + Object.entries(self._stores).forEach(([collection, store]) => { + const originals = store.retrieveOriginals(); + // not all stores define retrieveOriginals + if (! originals) return; + originals.forEach((doc, id) => { + docsWritten.push({ collection, id }); + if (! hasOwn.call(self._serverDocuments, collection)) { + self._serverDocuments[collection] = new MongoIDMap(); + } + const serverDoc = self._serverDocuments[collection].setDefault( + id, + Object.create(null) + ); + if (serverDoc.writtenByStubs) { + // We're not the first stub to write this doc. Just add our method ID + // to the record. + serverDoc.writtenByStubs[methodId] = true; + } else { + // First stub! Save the original value and our method ID. + serverDoc.document = doc; + serverDoc.flushCallbacks = []; + serverDoc.writtenByStubs = Object.create(null); + serverDoc.writtenByStubs[methodId] = true; + } + }); + }); + if (! isEmpty(docsWritten)) { + self._documentsWrittenByStub[methodId] = docsWritten; + } + } + + // This is very much a private function we use to make the tests + // take up fewer server resources after they complete. + _unsubscribeAll() { + Object.values(this._subscriptions).forEach((sub) => { + // Avoid killing the autoupdate subscription so that developers + // still get hot code pushes when writing tests. + // + // XXX it's a hack to encode knowledge about autoupdate here, + // but it doesn't seem worth it yet to have a special API for + // subscriptions to preserve after unit tests. + if (sub.name !== 'meteor_autoupdate_clientVersions') { + sub.stop(); + } + }); + } + + // Sends the DDP stringification of the given message object + _send(obj) { + this._stream.send(DDPCommon.stringifyDDP(obj)); + } + + // We detected via DDP-level heartbeats that we've lost the + // connection. Unlike `disconnect` or `close`, a lost connection + // will be automatically retried. + _lostConnection(error) { + this._stream._lostConnection(error); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.status + * @summary Get the current connection status. A reactive data source. + * @locus Client + */ + status(...args) { + return this._stream.status(...args); + } + + /** + * @summary Force an immediate reconnection attempt if the client is not connected to the server. + + This method does nothing if the client is already connected. + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.reconnect + * @locus Client + */ + reconnect(...args) { + return this._stream.reconnect(...args); + } + + /** + * @memberOf Meteor + * @importFromPackage meteor + * @alias Meteor.disconnect + * @summary Disconnect the client from the server. + * @locus Client + */ + disconnect(...args) { + return this._stream.disconnect(...args); + } + + close() { + return this._stream.disconnect({ _permanent: true }); + } + + /// + /// Reactive user system + /// + userId() { + if (this._userIdDeps) this._userIdDeps.depend(); + return this._userId; + } + + setUserId(userId) { + // Avoid invalidating dependents if setUserId is called with current value. + if (this._userId === userId) return; + this._userId = userId; + if (this._userIdDeps) this._userIdDeps.changed(); + } + + // Returns true if we are in a state after reconnect of waiting for subs to be + // revived or early methods to finish their data, or we are waiting for a + // "wait" method to finish. + _waitingForQuiescence() { + return ( + ! isEmpty(this._subsBeingRevived) || + ! isEmpty(this._methodsBlockingQuiescence) + ); + } + + // Returns true if any method whose message has been sent to the server has + // not yet invoked its user callback. + _anyMethodsAreOutstanding() { + const invokers = this._methodInvokers; + return Object.values(invokers).some((invoker) => !!invoker.sentMessage); + } + + _livedata_connected(msg) { + const self = this; + + if (self._version !== 'pre1' && self._heartbeatInterval !== 0) { + self._heartbeat = new DDPCommon.Heartbeat({ + heartbeatInterval: self._heartbeatInterval, + heartbeatTimeout: self._heartbeatTimeout, + onTimeout() { + self._lostConnection( + new DDP.ConnectionError('DDP heartbeat timed out') + ); + }, + sendPing() { + self._send({ msg: 'ping' }); + } + }); + self._heartbeat.start(); + } + + // If this is a reconnect, we'll have to reset all stores. + if (self._lastSessionId) self._resetStores = true; + + let reconnectedToPreviousSession; + if (typeof msg.session === 'string') { + reconnectedToPreviousSession = self._lastSessionId === msg.session; + self._lastSessionId = msg.session; + } + + if (reconnectedToPreviousSession) { + // Successful reconnection -- pick up where we left off. Note that right + // now, this never happens: the server never connects us to a previous + // session, because DDP doesn't provide enough data for the server to know + // what messages the client has processed. We need to improve DDP to make + // this possible, at which point we'll probably need more code here. + return; + } + + // Server doesn't have our data any more. Re-sync a new session. + + // Forget about messages we were buffering for unknown collections. They'll + // be resent if still relevant. + self._updatesForUnknownStores = Object.create(null); + + if (self._resetStores) { + // Forget about the effects of stubs. We'll be resetting all collections + // anyway. + self._documentsWrittenByStub = Object.create(null); + self._serverDocuments = Object.create(null); + } + + // Clear _afterUpdateCallbacks. + self._afterUpdateCallbacks = []; + + // Mark all named subscriptions which are ready (ie, we already called the + // ready callback) as needing to be revived. + // XXX We should also block reconnect quiescence until unnamed subscriptions + // (eg, autopublish) are done re-publishing to avoid flicker! + self._subsBeingRevived = Object.create(null); + Object.entries(self._subscriptions).forEach(([id, sub]) => { + if (sub.ready) { + self._subsBeingRevived[id] = true; + } + }); + + // Arrange for "half-finished" methods to have their callbacks run, and + // track methods that were sent on this connection so that we don't + // quiesce until they are all done. + // + // Start by clearing _methodsBlockingQuiescence: methods sent before + // reconnect don't matter, and any "wait" methods sent on the new connection + // that we drop here will be restored by the loop below. + self._methodsBlockingQuiescence = Object.create(null); + if (self._resetStores) { + const invokers = self._methodInvokers; + keys(invokers).forEach(id => { + const invoker = invokers[id]; + if (invoker.gotResult()) { + // This method already got its result, but it didn't call its callback + // because its data didn't become visible. We did not resend the + // method RPC. We'll call its callback when we get a full quiesce, + // since that's as close as we'll get to "data must be visible". + self._afterUpdateCallbacks.push( + (...args) => invoker.dataVisible(...args) + ); + } else if (invoker.sentMessage) { + // This method has been sent on this connection (maybe as a resend + // from the last connection, maybe from onReconnect, maybe just very + // quickly before processing the connected message). + // + // We don't need to do anything special to ensure its callbacks get + // called, but we'll count it as a method which is preventing + // reconnect quiescence. (eg, it might be a login method that was run + // from onReconnect, and we don't want to see flicker by seeing a + // logged-out state.) + self._methodsBlockingQuiescence[invoker.methodId] = true; + } + }); + } + + self._messagesBufferedUntilQuiescence = []; + + // If we're not waiting on any methods or subs, we can reset the stores and + // call the callbacks immediately. + if (! self._waitingForQuiescence()) { + if (self._resetStores) { + Object.values(self._stores).forEach((store) => { + store.beginUpdate(0, true); + store.endUpdate(); + }); + self._resetStores = false; + } + self._runAfterUpdateCallbacks(); + } + } + + _processOneDataMessage(msg, updates) { + const messageType = msg.msg; + + // msg is one of ['added', 'changed', 'removed', 'ready', 'updated'] + if (messageType === 'added') { + this._process_added(msg, updates); + } else if (messageType === 'changed') { + this._process_changed(msg, updates); + } else if (messageType === 'removed') { + this._process_removed(msg, updates); + } else if (messageType === 'ready') { + this._process_ready(msg, updates); + } else if (messageType === 'updated') { + this._process_updated(msg, updates); + } else if (messageType === 'nosub') { + // ignore this + } else { + Meteor._debug('discarding unknown livedata data message type', msg); + } + } + + _livedata_data(msg) { + const self = this; + + if (self._waitingForQuiescence()) { + self._messagesBufferedUntilQuiescence.push(msg); + + if (msg.msg === 'nosub') { + delete self._subsBeingRevived[msg.id]; + } + + if (msg.subs) { + msg.subs.forEach(subId => { + delete self._subsBeingRevived[subId]; + }); + } + + if (msg.methods) { + msg.methods.forEach(methodId => { + delete self._methodsBlockingQuiescence[methodId]; + }); + } + + if (self._waitingForQuiescence()) { + return; + } + + // No methods or subs are blocking quiescence! + // We'll now process and all of our buffered messages, reset all stores, + // and apply them all at once. + + const bufferedMessages = self._messagesBufferedUntilQuiescence; + Object.values(bufferedMessages).forEach(bufferedMessage => { + self._processOneDataMessage( + bufferedMessage, + self._bufferedWrites + ); + }); + + self._messagesBufferedUntilQuiescence = []; + + } else { + self._processOneDataMessage(msg, self._bufferedWrites); + } + + // Immediately flush writes when: + // 1. Buffering is disabled. Or; + // 2. any non-(added/changed/removed) message arrives. + const standardWrite = + msg.msg === "added" || + msg.msg === "changed" || + msg.msg === "removed"; + + if (self._bufferedWritesInterval === 0 || ! standardWrite) { + self._flushBufferedWrites(); + return; + } + + if (self._bufferedWritesFlushAt === null) { + self._bufferedWritesFlushAt = + new Date().valueOf() + self._bufferedWritesMaxAge; + } else if (self._bufferedWritesFlushAt < new Date().valueOf()) { + self._flushBufferedWrites(); + return; + } + + if (self._bufferedWritesFlushHandle) { + clearTimeout(self._bufferedWritesFlushHandle); + } + self._bufferedWritesFlushHandle = setTimeout( + self.__flushBufferedWrites, + self._bufferedWritesInterval + ); + } + + _flushBufferedWrites() { + const self = this; + if (self._bufferedWritesFlushHandle) { + clearTimeout(self._bufferedWritesFlushHandle); + self._bufferedWritesFlushHandle = null; + } + + self._bufferedWritesFlushAt = null; + // We need to clear the buffer before passing it to + // performWrites. As there's no guarantee that it + // will exit cleanly. + const writes = self._bufferedWrites; + self._bufferedWrites = Object.create(null); + self._performWrites(writes); + } + + _performWrites(updates) { + const self = this; + + if (self._resetStores || ! isEmpty(updates)) { + // Begin a transactional update of each store. + + Object.entries(self._stores).forEach(([storeName, store]) => { + store.beginUpdate( + hasOwn.call(updates, storeName) + ? updates[storeName].length + : 0, + self._resetStores + ); + }); + + self._resetStores = false; + + Object.entries(updates).forEach(([storeName, updateMessages]) => { + const store = self._stores[storeName]; + if (store) { + updateMessages.forEach(updateMessage => { + store.update(updateMessage); + }); + } else { + // Nobody's listening for this data. Queue it up until + // someone wants it. + // XXX memory use will grow without bound if you forget to + // create a collection or just don't care about it... going + // to have to do something about that. + const updates = self._updatesForUnknownStores; + + if (! hasOwn.call(updates, storeName)) { + updates[storeName] = []; + } + + updates[storeName].push(...updateMessages); + } + }); + + // End update transaction. + Object.values(self._stores).forEach((store) => { + store.endUpdate(); + }); + } + + self._runAfterUpdateCallbacks(); + } + + // Call any callbacks deferred with _runWhenAllServerDocsAreFlushed whose + // relevant docs have been flushed, as well as dataVisible callbacks at + // reconnect-quiescence time. + _runAfterUpdateCallbacks() { + const self = this; + const callbacks = self._afterUpdateCallbacks; + self._afterUpdateCallbacks = []; + callbacks.forEach((c) => { + c(); + }); + } + + _pushUpdate(updates, collection, msg) { + if (! hasOwn.call(updates, collection)) { + updates[collection] = []; + } + updates[collection].push(msg); + } + + _getServerDoc(collection, id) { + const self = this; + if (! hasOwn.call(self._serverDocuments, collection)) { + return null; + } + const serverDocsForCollection = self._serverDocuments[collection]; + return serverDocsForCollection.get(id) || null; + } + + _process_added(msg, updates) { + const self = this; + const id = MongoID.idParse(msg.id); + const serverDoc = self._getServerDoc(msg.collection, id); + if (serverDoc) { + // Some outstanding stub wrote here. + const isExisting = serverDoc.document !== undefined; + + serverDoc.document = msg.fields || Object.create(null); + serverDoc.document._id = id; + + if (self._resetStores) { + // During reconnect the server is sending adds for existing ids. + // Always push an update so that document stays in the store after + // reset. Use current version of the document for this update, so + // that stub-written values are preserved. + const currentDoc = self._stores[msg.collection].getDoc(msg.id); + if (currentDoc !== undefined) msg.fields = currentDoc; + + self._pushUpdate(updates, msg.collection, msg); + } else if (isExisting) { + throw new Error('Server sent add for existing id: ' + msg.id); + } + } else { + self._pushUpdate(updates, msg.collection, msg); + } + } + + _process_changed(msg, updates) { + const self = this; + const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id)); + if (serverDoc) { + if (serverDoc.document === undefined) + throw new Error('Server sent changed for nonexisting id: ' + msg.id); + DiffSequence.applyChanges(serverDoc.document, msg.fields); + } else { + self._pushUpdate(updates, msg.collection, msg); + } + } + + _process_removed(msg, updates) { + const self = this; + const serverDoc = self._getServerDoc(msg.collection, MongoID.idParse(msg.id)); + if (serverDoc) { + // Some outstanding stub wrote here. + if (serverDoc.document === undefined) + throw new Error('Server sent removed for nonexisting id:' + msg.id); + serverDoc.document = undefined; + } else { + self._pushUpdate(updates, msg.collection, { + msg: 'removed', + collection: msg.collection, + id: msg.id + }); + } + } + + _process_updated(msg, updates) { + const self = this; + // Process "method done" messages. + + msg.methods.forEach((methodId) => { + const docs = self._documentsWrittenByStub[methodId] || {}; + Object.values(docs).forEach((written) => { + const serverDoc = self._getServerDoc(written.collection, written.id); + if (! serverDoc) { + throw new Error('Lost serverDoc for ' + JSON.stringify(written)); + } + if (! serverDoc.writtenByStubs[methodId]) { + throw new Error( + 'Doc ' + + JSON.stringify(written) + + ' not written by method ' + + methodId + ); + } + delete serverDoc.writtenByStubs[methodId]; + if (isEmpty(serverDoc.writtenByStubs)) { + // All methods whose stubs wrote this method have completed! We can + // now copy the saved document to the database (reverting the stub's + // change if the server did not write to this object, or applying the + // server's writes if it did). + + // This is a fake ddp 'replace' message. It's just for talking + // between livedata connections and minimongo. (We have to stringify + // the ID because it's supposed to look like a wire message.) + self._pushUpdate(updates, written.collection, { + msg: 'replace', + id: MongoID.idStringify(written.id), + replace: serverDoc.document + }); + // Call all flush callbacks. + + serverDoc.flushCallbacks.forEach((c) => { + c(); + }); + + // Delete this completed serverDocument. Don't bother to GC empty + // IdMaps inside self._serverDocuments, since there probably aren't + // many collections and they'll be written repeatedly. + self._serverDocuments[written.collection].remove(written.id); + } + }); + delete self._documentsWrittenByStub[methodId]; + + // We want to call the data-written callback, but we can't do so until all + // currently buffered messages are flushed. + const callbackInvoker = self._methodInvokers[methodId]; + if (! callbackInvoker) { + throw new Error('No callback invoker for method ' + methodId); + } + + self._runWhenAllServerDocsAreFlushed( + (...args) => callbackInvoker.dataVisible(...args) + ); + }); + } + + _process_ready(msg, updates) { + const self = this; + // Process "sub ready" messages. "sub ready" messages don't take effect + // until all current server documents have been flushed to the local + // database. We can use a write fence to implement this. + + msg.subs.forEach((subId) => { + self._runWhenAllServerDocsAreFlushed(() => { + const subRecord = self._subscriptions[subId]; + // Did we already unsubscribe? + if (!subRecord) return; + // Did we already receive a ready message? (Oops!) + if (subRecord.ready) return; + subRecord.ready = true; + subRecord.readyCallback && subRecord.readyCallback(); + subRecord.readyDeps.changed(); + }); + }); + } + + // Ensures that "f" will be called after all documents currently in + // _serverDocuments have been written to the local cache. f will not be called + // if the connection is lost before then! + _runWhenAllServerDocsAreFlushed(f) { + const self = this; + const runFAfterUpdates = () => { + self._afterUpdateCallbacks.push(f); + }; + let unflushedServerDocCount = 0; + const onServerDocFlush = () => { + --unflushedServerDocCount; + if (unflushedServerDocCount === 0) { + // This was the last doc to flush! Arrange to run f after the updates + // have been applied. + runFAfterUpdates(); + } + }; + + Object.values(self._serverDocuments).forEach((serverDocuments) => { + serverDocuments.forEach((serverDoc) => { + const writtenByStubForAMethodWithSentMessage = + keys(serverDoc.writtenByStubs).some(methodId => { + const invoker = self._methodInvokers[methodId]; + return invoker && invoker.sentMessage; + }); + + if (writtenByStubForAMethodWithSentMessage) { + ++unflushedServerDocCount; + serverDoc.flushCallbacks.push(onServerDocFlush); + } + }); + }); + if (unflushedServerDocCount === 0) { + // There aren't any buffered docs --- we can call f as soon as the current + // round of updates is applied! + runFAfterUpdates(); + } + } + + _livedata_nosub(msg) { + const self = this; + + // First pass it through _livedata_data, which only uses it to help get + // towards quiescence. + self._livedata_data(msg); + + // Do the rest of our processing immediately, with no + // buffering-until-quiescence. + + // we weren't subbed anyway, or we initiated the unsub. + if (! hasOwn.call(self._subscriptions, msg.id)) { + return; + } + + // XXX COMPAT WITH 1.0.3.1 #errorCallback + const errorCallback = self._subscriptions[msg.id].errorCallback; + const stopCallback = self._subscriptions[msg.id].stopCallback; + + self._subscriptions[msg.id].remove(); + + const meteorErrorFromMsg = msgArg => { + return ( + msgArg && + msgArg.error && + new Meteor.Error( + msgArg.error.error, + msgArg.error.reason, + msgArg.error.details + ) + ); + }; + + // XXX COMPAT WITH 1.0.3.1 #errorCallback + if (errorCallback && msg.error) { + errorCallback(meteorErrorFromMsg(msg)); + } + + if (stopCallback) { + stopCallback(meteorErrorFromMsg(msg)); + } + } + + _livedata_result(msg) { + // id, result or error. error has error (code), reason, details + + const self = this; + + // Lets make sure there are no buffered writes before returning result. + if (! isEmpty(self._bufferedWrites)) { + self._flushBufferedWrites(); + } + + // find the outstanding request + // should be O(1) in nearly all realistic use cases + if (isEmpty(self._outstandingMethodBlocks)) { + Meteor._debug('Received method result but no methods outstanding'); + return; + } + const currentMethodBlock = self._outstandingMethodBlocks[0].methods; + let i; + const m = currentMethodBlock.find((method, idx) => { + const found = method.methodId === msg.id; + if (found) i = idx; + return found; + }); + if (!m) { + Meteor._debug("Can't match method response to original method call", msg); + return; + } + + // Remove from current method block. This may leave the block empty, but we + // don't move on to the next block until the callback has been delivered, in + // _outstandingMethodFinished. + currentMethodBlock.splice(i, 1); + + if (hasOwn.call(msg, 'error')) { + m.receiveResult( + new Meteor.Error(msg.error.error, msg.error.reason, msg.error.details) + ); + } else { + // msg.result may be undefined if the method didn't return a + // value + m.receiveResult(undefined, msg.result); + } + } + + // Called by MethodInvoker after a method's callback is invoked. If this was + // the last outstanding method in the current block, runs the next block. If + // there are no more methods, consider accepting a hot code push. + _outstandingMethodFinished() { + const self = this; + if (self._anyMethodsAreOutstanding()) return; + + // No methods are outstanding. This should mean that the first block of + // methods is empty. (Or it might not exist, if this was a method that + // half-finished before disconnect/reconnect.) + if (! isEmpty(self._outstandingMethodBlocks)) { + const firstBlock = self._outstandingMethodBlocks.shift(); + if (! isEmpty(firstBlock.methods)) + throw new Error( + 'No methods outstanding but nonempty block: ' + + JSON.stringify(firstBlock) + ); + + // Send the outstanding methods now in the first block. + if (! isEmpty(self._outstandingMethodBlocks)) + self._sendOutstandingMethods(); + } + + // Maybe accept a hot code push. + self._maybeMigrate(); + } + + // Sends messages for all the methods in the first block in + // _outstandingMethodBlocks. + _sendOutstandingMethods() { + const self = this; + + if (isEmpty(self._outstandingMethodBlocks)) { + return; + } + + self._outstandingMethodBlocks[0].methods.forEach(m => { + m.sendMessage(); + }); + } + + _livedata_error(msg) { + Meteor._debug('Received error from server: ', msg.reason); + if (msg.offendingMessage) Meteor._debug('For: ', msg.offendingMessage); + } + + _callOnReconnectAndSendAppropriateOutstandingMethods() { + const self = this; + const oldOutstandingMethodBlocks = self._outstandingMethodBlocks; + self._outstandingMethodBlocks = []; + + self.onReconnect && self.onReconnect(); + DDP._reconnectHook.each(callback => { + callback(self); + return true; + }); + + if (isEmpty(oldOutstandingMethodBlocks)) return; + + // We have at least one block worth of old outstanding methods to try + // again. First: did onReconnect actually send anything? If not, we just + // restore all outstanding methods and run the first block. + if (isEmpty(self._outstandingMethodBlocks)) { + self._outstandingMethodBlocks = oldOutstandingMethodBlocks; + self._sendOutstandingMethods(); + return; + } + + // OK, there are blocks on both sides. Special case: merge the last block of + // the reconnect methods with the first block of the original methods, if + // neither of them are "wait" blocks. + if (! last(self._outstandingMethodBlocks).wait && + ! oldOutstandingMethodBlocks[0].wait) { + oldOutstandingMethodBlocks[0].methods.forEach(m => { + last(self._outstandingMethodBlocks).methods.push(m); + + // If this "last block" is also the first block, send the message. + if (self._outstandingMethodBlocks.length === 1) { + m.sendMessage(); + } + }); + + oldOutstandingMethodBlocks.shift(); + } + + // Now add the rest of the original blocks on. + self._outstandingMethodBlocks.push(...oldOutstandingMethodBlocks); + } + + // We can accept a hot code push if there are no methods in flight. + _readyToMigrate() { + return isEmpty(this._methodInvokers); + } + + // If we were blocking a migration, see if it's now possible to continue. + // Call whenever the set of outstanding/blocked methods shrinks. + _maybeMigrate() { + const self = this; + if (self._retryMigrate && self._readyToMigrate()) { + self._retryMigrate(); + self._retryMigrate = null; + } + } + + onMessage(raw_msg) { + let msg; + try { + msg = DDPCommon.parseDDP(raw_msg); + } catch (e) { + Meteor._debug('Exception while parsing DDP', e); + return; + } + + // Any message counts as receiving a pong, as it demonstrates that + // the server is still alive. + if (this._heartbeat) { + this._heartbeat.messageReceived(); + } + + if (msg === null || !msg.msg) { + if(!msg || !msg.testMessageOnConnect) { + if (Object.keys(msg).length === 1 && msg.server_id) return; + Meteor._debug('discarding invalid livedata message', msg); + } + return; + } + + if (msg.msg === 'connected') { + this._version = this._versionSuggestion; + this._livedata_connected(msg); + this.options.onConnected(); + } else if (msg.msg === 'failed') { + if (this._supportedDDPVersions.indexOf(msg.version) >= 0) { + this._versionSuggestion = msg.version; + this._stream.reconnect({ _force: true }); + } else { + const description = + 'DDP version negotiation failed; server requested version ' + + msg.version; + this._stream.disconnect({ _permanent: true, _error: description }); + this.options.onDDPVersionNegotiationFailure(description); + } + } else if (msg.msg === 'ping' && this.options.respondToPings) { + this._send({ msg: 'pong', id: msg.id }); + } else if (msg.msg === 'pong') { + // noop, as we assume everything's a pong + } else if ( + ['added', 'changed', 'removed', 'ready', 'updated'].includes(msg.msg) + ) { + this._livedata_data(msg); + } else if (msg.msg === 'nosub') { + this._livedata_nosub(msg); + } else if (msg.msg === 'result') { + this._livedata_result(msg); + } else if (msg.msg === 'error') { + this._livedata_error(msg); + } else { + Meteor._debug('discarding unknown livedata message type', msg); + } + } + + onReset() { + // Send a connect message at the beginning of the stream. + // NOTE: reset is called even on the first connection, so this is + // the only place we send this message. + const msg = { msg: 'connect' }; + if (this._lastSessionId) msg.session = this._lastSessionId; + msg.version = this._versionSuggestion || this._supportedDDPVersions[0]; + this._versionSuggestion = msg.version; + msg.support = this._supportedDDPVersions; + this._send(msg); + + // Mark non-retry calls as failed. This has to be done early as getting these methods out of the + // current block is pretty important to making sure that quiescence is properly calculated, as + // well as possibly moving on to another useful block. + + // Only bother testing if there is an outstandingMethodBlock (there might not be, especially if + // we are connecting for the first time. + if (this._outstandingMethodBlocks.length > 0) { + // If there is an outstanding method block, we only care about the first one as that is the + // one that could have already sent messages with no response, that are not allowed to retry. + const currentMethodBlock = this._outstandingMethodBlocks[0].methods; + this._outstandingMethodBlocks[0].methods = currentMethodBlock.filter( + methodInvoker => { + // Methods with 'noRetry' option set are not allowed to re-send after + // recovering dropped connection. + if (methodInvoker.sentMessage && methodInvoker.noRetry) { + // Make sure that the method is told that it failed. + methodInvoker.receiveResult( + new Meteor.Error( + 'invocation-failed', + 'Method invocation might have failed due to dropped connection. ' + + 'Failing because `noRetry` option was passed to Meteor.apply.' + ) + ); + } + + // Only keep a method if it wasn't sent or it's allowed to retry. + // This may leave the block empty, but we don't move on to the next + // block until the callback has been delivered, in _outstandingMethodFinished. + return !(methodInvoker.sentMessage && methodInvoker.noRetry); + } + ); + } + + // Now, to minimize setup latency, go ahead and blast out all of + // our pending methods ands subscriptions before we've even taken + // the necessary RTT to know if we successfully reconnected. (1) + // They're supposed to be idempotent, and where they are not, + // they can block retry in apply; (2) even if we did reconnect, + // we're not sure what messages might have gotten lost + // (in either direction) since we were disconnected (TCP being + // sloppy about that.) + + // If the current block of methods all got their results (but didn't all get + // their data visible), discard the empty block now. + if ( + this._outstandingMethodBlocks.length > 0 && + this._outstandingMethodBlocks[0].methods.length === 0 + ) { + this._outstandingMethodBlocks.shift(); + } + + // Mark all messages as unsent, they have not yet been sent on this + // connection. + keys(this._methodInvokers).forEach(id => { + this._methodInvokers[id].sentMessage = false; + }); + + // If an `onReconnect` handler is set, call it first. Go through + // some hoops to ensure that methods that are called from within + // `onReconnect` get executed _before_ ones that were originally + // outstanding (since `onReconnect` is used to re-establish auth + // certificates) + this._callOnReconnectAndSendAppropriateOutstandingMethods(); + + // add new subscriptions at the end. this way they take effect after + // the handlers and we don't see flicker. + Object.entries(this._subscriptions).forEach(([id, sub]) => { + this._send({ + msg: 'sub', + id: id, + name: sub.name, + params: sub.params + }); + }); + } +} diff --git a/packages/ddp-client/common/namespace.js b/packages/ddp-client/common/namespace.js new file mode 100644 index 0000000000..117ea27d6b --- /dev/null +++ b/packages/ddp-client/common/namespace.js @@ -0,0 +1,91 @@ +import { DDPCommon } from 'meteor/ddp-common'; +import { Meteor } from 'meteor/meteor'; + +import { Connection } from './livedata_connection.js'; + +// This array allows the `_allSubscriptionsReady` method below, which +// is used by the `spiderable` package, to keep track of whether all +// data is ready. +const allConnections = []; + +/** + * @namespace DDP + * @summary Namespace for DDP-related methods/classes. + */ +export const DDP = {}; + +// This is private but it's used in a few places. accounts-base uses +// it to get the current user. Meteor.setTimeout and friends clear +// it. We can probably find a better way to factor this. +DDP._CurrentMethodInvocation = new Meteor.EnvironmentVariable(); +DDP._CurrentPublicationInvocation = new Meteor.EnvironmentVariable(); + +// XXX: Keep DDP._CurrentInvocation for backwards-compatibility. +DDP._CurrentInvocation = DDP._CurrentMethodInvocation; + +// This is passed into a weird `makeErrorType` function that expects its thing +// to be a constructor +function connectionErrorConstructor(message) { + this.message = message; +} + +DDP.ConnectionError = Meteor.makeErrorType( + 'DDP.ConnectionError', + connectionErrorConstructor +); + +DDP.ForcedReconnectError = Meteor.makeErrorType( + 'DDP.ForcedReconnectError', + () => {} +); + +// Returns the named sequence of pseudo-random values. +// The scope will be DDP._CurrentMethodInvocation.get(), so the stream will produce +// consistent values for method calls on the client and server. +DDP.randomStream = name => { + const scope = DDP._CurrentMethodInvocation.get(); + return DDPCommon.RandomStream.get(scope, name); +}; + +// @param url {String} URL to Meteor app, +// e.g.: +// "subdomain.meteor.com", +// "http://subdomain.meteor.com", +// "/", +// "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" + +/** + * @summary Connect to the server of a different Meteor application to subscribe to its document sets and invoke its remote methods. + * @locus Anywhere + * @param {String} url The URL of another Meteor application. + * @param {Object} [options] + * @param {Boolean} options.reloadWithOutstanding is it OK to reload if there are outstanding methods? + * @param {Object} options.headers extra headers to send on the websockets connection, for server-to-server DDP only + * @param {Object} options._sockjsOptions Specifies options to pass through to the sockjs client + * @param {Function} options.onDDPNegotiationVersionFailure callback when version negotiation fails. + */ +DDP.connect = (url, options) => { + const ret = new Connection(url, options); + allConnections.push(ret); // hack. see below. + return ret; +}; + +DDP._reconnectHook = new Hook({ bindEnvironment: false }); + +/** + * @summary Register a function to call as the first step of + * reconnecting. This function can call methods which will be executed before + * any other outstanding methods. For example, this can be used to re-establish + * the appropriate authentication context on the connection. + * @locus Anywhere + * @param {Function} callback The function to call. It will be called with a + * single argument, the [connection object](#ddp_connect) that is reconnecting. + */ +DDP.onReconnect = callback => DDP._reconnectHook.register(callback); + +// Hack for `spiderable` package: a way to see if the page is done +// loading all the data it needs. +// +DDP._allSubscriptionsReady = () => allConnections.every( + conn => Object.values(conn._subscriptions).every(sub => sub.ready) +); diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js new file mode 100644 index 0000000000..0cdc77a953 --- /dev/null +++ b/packages/ddp-client/package.js @@ -0,0 +1,63 @@ +Package.describe({ + summary: "Meteor's latency-compensated distributed data client", + version: '2.6.0', + documentation: null +}); + +Npm.depends({ + '@sinonjs/fake-timers': '7.0.5' +}); + +Package.onUse((api) => { + api.use([ + 'check', + 'random', + 'ejson', + 'tracker', + 'retry', + 'id-map', + 'ecmascript', + 'callback-hook', + 'ddp-common', + 'reload', + 'socket-stream-client', + + // we depend on _diffObjects, _applyChanges, + 'diff-sequence', + + // _idParse, _idStringify. + 'mongo-id' + ], ['client', 'server']); + + api.use('reload', 'client', { weak: true }); + + // For backcompat where things use Package.ddp.DDP, etc + api.export('DDP'); + api.mainModule('client/client.js', 'client'); + api.mainModule('server/server.js', 'server'); +}); + +Package.onTest((api) => { + api.use([ + 'livedata', + 'mongo', + 'test-helpers', + 'ecmascript', + 'underscore', + 'tinytest', + 'random', + 'tracker', + 'reactive-var', + 'mongo-id', + 'diff-sequence', + 'ejson', + 'ddp-common', + 'check' + ]); + + api.addFiles('test/stub_stream.js'); + api.addFiles('test/livedata_connection_tests.js'); + api.addFiles('test/livedata_tests.js'); + api.addFiles('test/livedata_test_service.js'); + api.addFiles('test/random_stream_tests.js'); +}); diff --git a/packages/ddp-client/server/server.js b/packages/ddp-client/server/server.js new file mode 100644 index 0000000000..8566aba9c2 --- /dev/null +++ b/packages/ddp-client/server/server.js @@ -0,0 +1 @@ +export { DDP } from '../common/namespace.js'; diff --git a/packages/ddp-client/test/livedata_connection_tests.js b/packages/ddp-client/test/livedata_connection_tests.js new file mode 100644 index 0000000000..32e014ccbf --- /dev/null +++ b/packages/ddp-client/test/livedata_connection_tests.js @@ -0,0 +1,2491 @@ +import FakeTimers from '@sinonjs/fake-timers'; +import { DDP } from '../common/namespace.js'; +import { Connection } from '../common/livedata_connection.js'; + +const newConnection = function(stream, options) { + // Some of these tests leave outstanding methods with no result yet + // returned. This should not block us from re-running tests when sources + // change. + return new Connection( + stream, + _.extend( + { + reloadWithOutstanding: true, + bufferedWritesInterval: 0 + }, + options + ) + ); +}; + +const makeConnectMessage = function(session) { + const msg = { + msg: 'connect', + version: DDPCommon.SUPPORTED_DDP_VERSIONS[0], + support: DDPCommon.SUPPORTED_DDP_VERSIONS + }; + + if (session) msg.session = session; + return msg; +}; + +// Tests that stream got a message that matches expected. +// Expected is normally an object, and allows a wildcard value of '*', +// which will then match any value. +// Returns the message (parsed as a JSON object if expected is an object); +// which is particularly handy if you want to extract a value that was +// matched as a wildcard. +const testGotMessage = function(test, stream, expected) { + if (stream.sent.length === 0) { + test.fail({ error: 'no message received', expected: expected }); + return undefined; + } + + let got = stream.sent.shift(); + + if (typeof got === 'string' && typeof expected === 'object') + got = JSON.parse(got); + + // An expected value of '*' matches any value, and the matching value (or + // array of matching values, if there are multiple) is returned from this + // function. + if (typeof expected === 'object') { + const keysWithStarValues = []; + _.each(expected, function(v, k) { + if (v === '*') keysWithStarValues.push(k); + }); + _.each(keysWithStarValues, function(k) { + expected[k] = got[k]; + }); + } + + test.equal(got, expected); + return got; +}; + +const startAndConnect = function(test, stream) { + stream.reset(); // initial connection start. + + testGotMessage(test, stream, makeConnectMessage()); + test.length(stream.sent, 0); + + stream.receive({ msg: 'connected', session: SESSION_ID }); + test.length(stream.sent, 0); +}; + +const SESSION_ID = '17'; + +Tinytest.add('livedata stub - receive data', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + // data comes in for unknown collection. + const coll_name = Random.id(); + stream.receive({ + msg: 'added', + collection: coll_name, + id: '1234', + fields: { a: 1 } + }); + // break throught the black box and test internal state + test.length(conn._updatesForUnknownStores[coll_name], 1); + + // XXX: Test that the old signature of passing manager directly instead of in + // options works. + const coll = new Mongo.Collection(coll_name, conn); + + // queue has been emptied and doc is in db. + test.isUndefined(conn._updatesForUnknownStores[coll_name]); + test.equal(coll.find({}).fetch(), [{ _id: '1234', a: 1 }]); + + // second message. applied directly to the db. + stream.receive({ + msg: 'changed', + collection: coll_name, + id: '1234', + fields: { a: 2 } + }); + test.equal(coll.find({}).fetch(), [{ _id: '1234', a: 2 }]); + test.isUndefined(conn._updatesForUnknownStores[coll_name]); +}); + +Tinytest.add('livedata stub - buffering data', function(test) { + // Install special setTimeout that allows tick-by-tick control in tests using sinonjs 'lolex' + // This needs to be before the connection is instantiated. + const clock = FakeTimers.install(); + const tick = timeout => clock.tick(timeout); + + const stream = new StubStream(); + const conn = newConnection(stream, { + bufferedWritesInterval: 10, + bufferedWritesMaxAge: 40 + }); + + startAndConnect(test, stream); + + const coll_name = Random.id(); + const coll = new Mongo.Collection(coll_name, conn); + + const testDocCount = count => test.equal(coll.find({}).count(), count); + + const addDoc = () => { + stream.receive({ + msg: 'added', + collection: coll_name, + id: Random.id(), + fields: {} + }); + }; + + // Starting at 0 ticks. At this point we haven't advanced the fake clock at all. + + addDoc(); // 1st Doc + testDocCount(0); // No doc been recognized yet because it's buffered, waiting for more. + tick(6); // 6 total ticks + testDocCount(0); // Ensure that the doc still hasn't shown up, despite the clock moving forward. + tick(4); // 10 total ticks, 1st buffer interval + testDocCount(1); // No other docs have arrived, so we 'see' the 1st doc. + + addDoc(); // 2nd doc + tick(1); // 11 total ticks (1 since last flush) + testDocCount(1); // Again, second doc hasn't arrived because we're waiting for more... + tick(9); // 20 total ticks (10 ticks since last flush & the 2nd 10-tick interval) + testDocCount(2); // Now we're here and got the second document. + + // Add several docs, frequently enough that we buffer multiple times before the next flush. + addDoc(); // 3 docs + tick(6); // 26 ticks (6 since last flush) + addDoc(); // 4 docs + tick(6); // 32 ticks (12 since last flush) + addDoc(); // 5 docs + tick(6); // 38 ticks (18 since last flush) + addDoc(); // 6 docs + tick(6); // 44 ticks (24 since last flush) + addDoc(); // 7 docs + tick(9); // 53 ticks (33 since last flush) + addDoc(); // 8 docs + tick(9); // 62 ticks! (42 ticks since last flush, over max-age - next interval triggers flush) + testDocCount(2); // Still at 2 from before! (Just making sure) + tick(1); // Ok, 63 ticks (10 since last doc, so this should cause the flush of all the docs) + testDocCount(8); // See all the docs. + + // Put things back how they were. + clock.uninstall(); +}); + +Tinytest.add('livedata stub - subscribe', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + // subscribe + let callback_fired = false; + const sub = conn.subscribe('my_data', function() { + callback_fired = true; + }); + test.isFalse(callback_fired); + + test.length(stream.sent, 1); + let message = JSON.parse(stream.sent.shift()); + const id = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'my_data', params: [] }); + + let reactivelyReady = false; + const autorunHandle = Tracker.autorun(function() { + reactivelyReady = sub.ready(); + }); + test.isFalse(reactivelyReady); + + // get the sub satisfied. callback fires. + stream.receive({ msg: 'ready', subs: [id] }); + test.isTrue(callback_fired); + Tracker.flush(); + test.isTrue(reactivelyReady); + + // Unsubscribe. + sub.stop(); + test.length(stream.sent, 1); + message = JSON.parse(stream.sent.shift()); + test.equal(message, { msg: 'unsub', id: id }); + Tracker.flush(); + test.isFalse(reactivelyReady); + + // Resubscribe. + conn.subscribe('my_data'); + test.length(stream.sent, 1); + message = JSON.parse(stream.sent.shift()); + const id2 = message.id; + test.notEqual(id, id2); + delete message.id; + test.equal(message, { msg: 'sub', name: 'my_data', params: [] }); +}); + +Tinytest.add('livedata stub - reactive subscribe', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const rFoo = new ReactiveVar('foo1'); + const rBar = new ReactiveVar('bar1'); + + const onReadyCount = {}; + const onReady = function(tag) { + return function() { + if (_.has(onReadyCount, tag)) ++onReadyCount[tag]; + else onReadyCount[tag] = 1; + }; + }; + + // Subscribe to some subs. + let stopperHandle, completerHandle; + const autorunHandle = Tracker.autorun(function() { + conn.subscribe('foo', rFoo.get(), onReady(rFoo.get())); + conn.subscribe('bar', rBar.get(), onReady(rBar.get())); + completerHandle = conn.subscribe('completer', onReady('completer')); + stopperHandle = conn.subscribe('stopper', onReady('stopper')); + }); + + let completerReady; + const readyAutorunHandle = Tracker.autorun(function() { + completerReady = completerHandle.ready(); + }); + + // Check sub messages. (Assume they are sent in the order executed.) + test.length(stream.sent, 4); + let message = JSON.parse(stream.sent.shift()); + const idFoo1 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo', params: ['foo1'] }); + + message = JSON.parse(stream.sent.shift()); + const idBar1 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'bar', params: ['bar1'] }); + + message = JSON.parse(stream.sent.shift()); + const idCompleter = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'completer', params: [] }); + + message = JSON.parse(stream.sent.shift()); + const idStopper = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'stopper', params: [] }); + + // Haven't hit onReady yet. + test.equal(onReadyCount, {}); + Tracker.flush(); + test.isFalse(completerReady); + + // "completer" gets ready now. its callback should fire. + stream.receive({ msg: 'ready', subs: [idCompleter] }); + test.equal(onReadyCount, { completer: 1 }); + test.length(stream.sent, 0); + Tracker.flush(); + test.isTrue(completerReady); + + // Stop 'stopper'. + stopperHandle.stop(); + test.length(stream.sent, 1); + message = JSON.parse(stream.sent.shift()); + test.equal(message, { msg: 'unsub', id: idStopper }); + + test.equal(onReadyCount, { completer: 1 }); + Tracker.flush(); + test.isTrue(completerReady); + + // Change the foo subscription and flush. We should sub to the new foo + // subscription, re-sub to the stopper subscription, and then unsub from the old + // foo subscription. The bar subscription should be unaffected. The completer + // subscription should call its new onReady callback, because we always + // call onReady for a given reactively-saved subscription. + // The completerHandle should have been reestablished to the ready handle. + rFoo.set('foo2'); + Tracker.flush(); + test.length(stream.sent, 3); + + message = JSON.parse(stream.sent.shift()); + const idFoo2 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo', params: ['foo2'] }); + + message = JSON.parse(stream.sent.shift()); + const idStopperAgain = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'stopper', params: [] }); + + message = JSON.parse(stream.sent.shift()); + test.equal(message, { msg: 'unsub', id: idFoo1 }); + + test.equal(onReadyCount, { completer: 2 }); + test.isTrue(completerReady); + + // Ready the stopper and bar subs. Completing stopper should call only the + // onReady from the new subscription because they were separate subscriptions + // started at different times and the first one was explicitly torn down by + // the client; completing bar should call the onReady from the new + // subscription because we always call onReady for a given reactively-saved + // subscription. + stream.receive({ msg: 'ready', subs: [idStopperAgain, idBar1] }); + test.equal(onReadyCount, { completer: 2, bar1: 1, stopper: 1 }); + + // Shut down the autorun. This should unsub us from all current subs at flush + // time. + autorunHandle.stop(); + Tracker.flush(); + test.isFalse(completerReady); + readyAutorunHandle.stop(); + + test.length(stream.sent, 4); + // The order of unsubs here is not important. + const unsubMessages = _.map(stream.sent, JSON.parse); + stream.sent.length = 0; + test.equal(_.unique(_.pluck(unsubMessages, 'msg')), ['unsub']); + const actualIds = _.pluck(unsubMessages, 'id'); + const expectedIds = [idFoo2, idBar1, idCompleter, idStopperAgain]; + actualIds.sort(); + expectedIds.sort(); + test.equal(actualIds, expectedIds); +}); + +Tinytest.add('livedata stub - reactive subscribe handle correct', function( + test +) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const rFoo = new ReactiveVar('foo1'); + + // Subscribe to some subs. + let fooHandle, fooReady; + const autorunHandle = Tracker.autorun(function() { + fooHandle = conn.subscribe('foo', rFoo.get()); + Tracker.autorun(function() { + fooReady = fooHandle.ready(); + }); + }); + + let message = JSON.parse(stream.sent.shift()); + const idFoo1 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo', params: ['foo1'] }); + + // Not ready yet + Tracker.flush(); + test.isFalse(fooHandle.ready()); + test.isFalse(fooReady); + + // change the argument to foo. This will make a new handle, which isn't ready + // the ready autorun should invalidate, reading the new false value, and + // setting up a new dep which goes true soon + rFoo.set('foo2'); + Tracker.flush(); + test.length(stream.sent, 2); + + message = JSON.parse(stream.sent.shift()); + const idFoo2 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo', params: ['foo2'] }); + + message = JSON.parse(stream.sent.shift()); + test.equal(message, { msg: 'unsub', id: idFoo1 }); + + Tracker.flush(); + test.isFalse(fooHandle.ready()); + test.isFalse(fooReady); + + // "foo" gets ready now. The handle should be ready and the autorun rerun + stream.receive({ msg: 'ready', subs: [idFoo2] }); + test.length(stream.sent, 0); + Tracker.flush(); + test.isTrue(fooHandle.ready()); + test.isTrue(fooReady); + + // change the argument to foo. This will make a new handle, which isn't ready + // the ready autorun should invalidate, making fooReady false too + rFoo.set('foo3'); + Tracker.flush(); + test.length(stream.sent, 2); + + message = JSON.parse(stream.sent.shift()); + const idFoo3 = message.id; + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo', params: ['foo3'] }); + + message = JSON.parse(stream.sent.shift()); + test.equal(message, { msg: 'unsub', id: idFoo2 }); + + Tracker.flush(); + test.isFalse(fooHandle.ready()); + test.isFalse(fooReady); + + // "foo" gets ready again + stream.receive({ msg: 'ready', subs: [idFoo3] }); + test.length(stream.sent, 0); + Tracker.flush(); + test.isTrue(fooHandle.ready()); + test.isTrue(fooReady); + + autorunHandle.stop(); +}); + +Tinytest.add('livedata stub - this', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + conn.methods({ + test_this: function() { + test.isTrue(this.isSimulation); + this.unblock(); // should be a no-op + } + }); + + // should throw no exceptions + conn.call('test_this', _.identity); + // satisfy method, quiesce connection + let message = JSON.parse(stream.sent.shift()); + test.isUndefined(message.randomSeed); + test.equal(message, { + msg: 'method', + method: 'test_this', + params: [], + id: message.id + }); + test.length(stream.sent, 0); + + stream.receive({ msg: 'result', id: message.id, result: null }); + stream.receive({ msg: 'updated', methods: [message.id] }); +}); + +if (Meteor.isClient) { + Tinytest.add('livedata stub - methods', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + // setup method + conn.methods({ + do_something: function(x) { + coll.insert({ value: x }); + } + }); + + // setup observers + const counts = { added: 0, removed: 0, changed: 0, moved: 0 }; + const handle = coll.find({}).observe({ + addedAt: function() { + counts.added += 1; + }, + removedAt: function() { + counts.removed += 1; + }, + changedAt: function() { + counts.changed += 1; + }, + movedTo: function() { + counts.moved += 1; + } + }); + + // call method with results callback + let callback1Fired = false; + conn.call('do_something', 'friday!', function(err, res) { + test.isUndefined(err); + test.equal(res, '1234'); + callback1Fired = true; + }); + test.isFalse(callback1Fired); + + // observers saw the method run. + test.equal(counts, { added: 1, removed: 0, changed: 0, moved: 0 }); + + // get response from server + const message = testGotMessage(test, stream, { + msg: 'method', + method: 'do_something', + params: ['friday!'], + id: '*', + randomSeed: '*' + }); + + test.equal(coll.find({}).count(), 1); + test.equal(coll.find({ value: 'friday!' }).count(), 1); + const docId = coll.findOne({ value: 'friday!' })._id; + + // results does not yet result in callback, because data is not + // ready. + stream.receive({ msg: 'result', id: message.id, result: '1234' }); + test.isFalse(callback1Fired); + + // result message doesn't affect data + test.equal(coll.find({}).count(), 1); + test.equal(coll.find({ value: 'friday!' }).count(), 1); + test.equal(counts, { added: 1, removed: 0, changed: 0, moved: 0 }); + + // data methods do not show up (not quiescent yet) + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(docId), + fields: { value: 'tuesday' } + }); + test.equal(coll.find({}).count(), 1); + test.equal(coll.find({ value: 'friday!' }).count(), 1); + test.equal(counts, { added: 1, removed: 0, changed: 0, moved: 0 }); + + // send another methods (unknown on client) + let callback2Fired = false; + conn.call('do_something_else', 'monday', function(err, res) { + callback2Fired = true; + }); + test.isFalse(callback1Fired); + test.isFalse(callback2Fired); + + // test we still send a method request to server + const message2 = JSON.parse(stream.sent.shift()); + test.isUndefined(message2.randomSeed); + test.equal(message2, { + msg: 'method', + method: 'do_something_else', + params: ['monday'], + id: message2.id + }); + + // get the first data satisfied message. changes are applied to database even + // though another method is outstanding, because the other method didn't have + // a stub. and its callback is called. + stream.receive({ msg: 'updated', methods: [message.id] }); + test.isTrue(callback1Fired); + test.isFalse(callback2Fired); + + test.equal(coll.find({}).count(), 1); + test.equal(coll.find({ value: 'tuesday' }).count(), 1); + test.equal(counts, { added: 1, removed: 0, changed: 1, moved: 0 }); + + // second result + stream.receive({ msg: 'result', id: message2.id, result: 'bupkis' }); + test.isFalse(callback2Fired); + + // get second satisfied; no new changes are applied. + stream.receive({ msg: 'updated', methods: [message2.id] }); + test.isTrue(callback2Fired); + + test.equal(coll.find({}).count(), 1); + test.equal(coll.find({ value: 'tuesday', _id: docId }).count(), 1); + test.equal(counts, { added: 1, removed: 0, changed: 1, moved: 0 }); + + handle.stop(); + }); +} + +Tinytest.add('livedata stub - mutating method args', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + conn.methods({ + mutateArgs: function(arg) { + arg.foo = 42; + } + }); + + conn.call('mutateArgs', { foo: 50 }, _.identity); + + // Method should be called with original arg, not mutated arg. + let message = JSON.parse(stream.sent.shift()); + test.isUndefined(message.randomSeed); + test.equal(message, { + msg: 'method', + method: 'mutateArgs', + params: [{ foo: 50 }], + id: message.id + }); + test.length(stream.sent, 0); +}); + +const observeCursor = function(test, cursor) { + const counts = { added: 0, removed: 0, changed: 0, moved: 0 }; + const expectedCounts = _.clone(counts); + const handle = cursor.observe({ + addedAt: function() { + counts.added += 1; + }, + removedAt: function() { + counts.removed += 1; + }, + changedAt: function() { + counts.changed += 1; + }, + movedTo: function() { + counts.moved += 1; + } + }); + return { + stop: _.bind(handle.stop, handle), + expectCallbacks: function(delta) { + _.each(delta, function(mod, field) { + expectedCounts[field] += mod; + }); + test.equal(counts, expectedCounts); + } + }; +}; + +// method calls another method in simulation. see not sent. +if (Meteor.isClient) { + Tinytest.add('livedata stub - methods calling methods', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const coll_name = Random.id(); + const coll = new Mongo.Collection(coll_name, { connection: conn }); + + // setup methods + conn.methods({ + do_something: function() { + conn.call('do_something_else'); + }, + do_something_else: function() { + coll.insert({ a: 1 }); + } + }); + + const o = observeCursor(test, coll.find()); + + // call method. + conn.call('do_something', _.identity); + + // see we only send message for outer methods + const message = testGotMessage(test, stream, { + msg: 'method', + method: 'do_something', + params: [], + id: '*', + randomSeed: '*' + }); + test.length(stream.sent, 0); + + // but inner method runs locally. + o.expectCallbacks({ added: 1 }); + test.equal(coll.find().count(), 1); + const docId = coll.findOne()._id; + test.equal(coll.findOne(), { _id: docId, a: 1 }); + + // we get the results + stream.receive({ msg: 'result', id: message.id, result: '1234' }); + + // get data from the method. data from this doc does not show up yet, but data + // from another doc does. + stream.receive({ + msg: 'added', + collection: coll_name, + id: MongoID.idStringify(docId), + fields: { value: 'tuesday' } + }); + o.expectCallbacks(); + test.equal(coll.findOne(docId), { _id: docId, a: 1 }); + stream.receive({ + msg: 'added', + collection: coll_name, + id: 'monkey', + fields: { value: 'bla' } + }); + o.expectCallbacks({ added: 1 }); + test.equal(coll.findOne(docId), { _id: docId, a: 1 }); + const newDoc = coll.findOne({ value: 'bla' }); + test.isTrue(newDoc); + test.equal(newDoc, { _id: newDoc._id, value: 'bla' }); + + // get method satisfied. all data shows up. the 'a' field is reverted and + // 'value' field is set. + stream.receive({ msg: 'updated', methods: [message.id] }); + o.expectCallbacks({ changed: 1 }); + test.equal(coll.findOne(docId), { _id: docId, value: 'tuesday' }); + test.equal(coll.findOne(newDoc._id), { _id: newDoc._id, value: 'bla' }); + + o.stop(); + }); +} +Tinytest.add('livedata stub - method call before connect', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + const callbackOutput = []; + conn.call('someMethod', function(err, result) { + callbackOutput.push(result); + }); + test.equal(callbackOutput, []); + + // the real stream drops all output pre-connection + stream.sent.length = 0; + + // Now connect. + stream.reset(); + + testGotMessage(test, stream, makeConnectMessage()); + testGotMessage(test, stream, { + msg: 'method', + method: 'someMethod', + params: [], + id: '*' + }); +}); + +Tinytest.add('livedata stub - reconnect', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + const o = observeCursor(test, coll.find()); + + // subscribe + let subCallbackFired = false; + const sub = conn.subscribe('my_data', function() { + subCallbackFired = true; + }); + test.isFalse(subCallbackFired); + + let subMessage = JSON.parse(stream.sent.shift()); + test.equal(subMessage, { + msg: 'sub', + name: 'my_data', + params: [], + id: subMessage.id + }); + + // get some data. it shows up. + stream.receive({ + msg: 'added', + collection: collName, + id: '1234', + fields: { a: 1 } + }); + + test.equal(coll.find({}).count(), 1); + o.expectCallbacks({ added: 1 }); + test.isFalse(subCallbackFired); + + stream.receive({ + msg: 'changed', + collection: collName, + id: '1234', + fields: { b: 2 } + }); + stream.receive({ + msg: 'ready', + subs: [subMessage.id] // satisfy sub + }); + test.isTrue(subCallbackFired); + subCallbackFired = false; // re-arm for test that it doesn't fire again. + + test.equal(coll.find({ a: 1, b: 2 }).count(), 1); + o.expectCallbacks({ changed: 1 }); + + // call method. + let methodCallbackFired = false; + conn.call('do_something', function() { + methodCallbackFired = true; + }); + + conn.apply('do_something_else', [], { wait: true }, _.identity); + conn.apply('do_something_later', [], _.identity); + + test.isFalse(methodCallbackFired); + + // The non-wait method should send, but not the wait method. + let methodMessage = JSON.parse(stream.sent.shift()); + test.isUndefined(methodMessage.randomSeed); + test.equal(methodMessage, { + msg: 'method', + method: 'do_something', + params: [], + id: methodMessage.id + }); + test.equal(stream.sent.length, 0); + + // more data. shows up immediately because there was no relevant method stub. + stream.receive({ + msg: 'changed', + collection: collName, + id: '1234', + fields: { c: 3 } + }); + test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + o.expectCallbacks({ changed: 1 }); + + // stream reset. reconnect! we send a connect, our pending method, and our + // sub. The wait method still is blocked. + stream.reset(); + + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + testGotMessage(test, stream, methodMessage); + testGotMessage(test, stream, subMessage); + + // reconnect with different session id + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + + // resend data. doesn't show up: we're in reconnect quiescence. + stream.receive({ + msg: 'added', + collection: collName, + id: '1234', + fields: { a: 1, b: 2, c: 3, d: 4 } + }); + stream.receive({ + msg: 'added', + collection: collName, + id: '2345', + fields: { e: 5 } + }); + test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + test.isFalse(coll.findOne('2345')); + o.expectCallbacks(); + + // satisfy and return the method + stream.receive({ + msg: 'updated', + methods: [methodMessage.id] + }); + test.isFalse(methodCallbackFired); + stream.receive({ msg: 'result', id: methodMessage.id, result: 'bupkis' }); + // The callback still doesn't fire (and we don't send the wait method): we're + // still in global quiescence + test.isFalse(methodCallbackFired); + test.equal(stream.sent.length, 0); + + // still no update. + test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + test.isFalse(coll.findOne('2345')); + o.expectCallbacks(); + + // re-satisfy sub + stream.receive({ msg: 'ready', subs: [subMessage.id] }); + + // now the doc changes and method callback is called, and the wait method is + // sent. the sub callback isn't re-called. + test.isTrue(methodCallbackFired); + test.isFalse(subCallbackFired); + test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3, d: 4 }); + test.equal(coll.findOne('2345'), { _id: '2345', e: 5 }); + o.expectCallbacks({ added: 1, changed: 1 }); + + let waitMethodMessage = JSON.parse(stream.sent.shift()); + test.isUndefined(waitMethodMessage.randomSeed); + test.equal(waitMethodMessage, { + msg: 'method', + method: 'do_something_else', + params: [], + id: waitMethodMessage.id + }); + test.equal(stream.sent.length, 0); + stream.receive({ msg: 'result', id: waitMethodMessage.id, result: 'bupkis' }); + test.equal(stream.sent.length, 0); + stream.receive({ msg: 'updated', methods: [waitMethodMessage.id] }); + + // wait method done means we can send the third method + test.equal(stream.sent.length, 1); + let laterMethodMessage = JSON.parse(stream.sent.shift()); + test.isUndefined(laterMethodMessage.randomSeed); + test.equal(laterMethodMessage, { + msg: 'method', + method: 'do_something_later', + params: [], + id: laterMethodMessage.id + }); + + o.stop(); +}); + +if (Meteor.isClient) { + Tinytest.add('livedata stub - reconnect non-idempotent method', function( + test + ) { + // This test is for https://github.com/meteor/meteor/issues/6108 + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + let firstMethodCallbackFired = false; + let firstMethodCallbackErrored = false; + let secondMethodCallbackFired = false; + let secondMethodCallbackErrored = false; + + // call with noRetry true so that the method should fail to retry on reconnect. + conn.apply('do_something', [], { noRetry: true }, function(error) { + firstMethodCallbackFired = true; + // failure on reconnect should trigger an error. + if (error && error.error === 'invocation-failed') { + firstMethodCallbackErrored = true; + } + }); + conn.apply('do_something_else', [], { noRetry: true }, function(error) { + secondMethodCallbackFired = true; + // failure on reconnect should trigger an error. + if (error && error.error === 'invocation-failed') { + secondMethodCallbackErrored = true; + } + }); + + // The method has not succeeded yet + test.isFalse(firstMethodCallbackFired); + test.isFalse(secondMethodCallbackFired); + + // send the methods + stream.sent.shift(); + stream.sent.shift(); + // reconnect + stream.reset(); + + // verify that a reconnect message was sent. + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + // Make sure that the stream triggers connection. + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + + //The method callback should fire even though the stream has not sent a response. + //the callback should have been fired with an error. + test.isTrue(firstMethodCallbackFired); + test.isTrue(firstMethodCallbackErrored); + test.isTrue(secondMethodCallbackFired); + test.isTrue(secondMethodCallbackErrored); + + // verify that the method message was not sent. + test.isUndefined(stream.sent.shift()); + }); +} + +function addReconnectTests(name, testFunc) { + Tinytest.add(name + ' (deprecated)', function(test) { + function deprecatedSetOnReconnect(conn, handler) { + conn.onReconnect = handler; + } + testFunc.call(this, test, deprecatedSetOnReconnect); + }); + + Tinytest.add(name, function(test) { + let stopper; + function setOnReconnect(conn, handler) { + stopper && stopper.stop(); + stopper = DDP.onReconnect(function(reconnectingConn) { + if (reconnectingConn === conn) { + handler(); + } + }); + } + testFunc.call(this, test, setOnReconnect); + stopper && stopper.stop(); + }); +} + +if (Meteor.isClient) { + addReconnectTests( + 'livedata stub - reconnect method which only got result', + function(test, setOnReconnect) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + const o = observeCursor(test, coll.find()); + + conn.methods({ + writeSomething: function() { + // stub write + coll.insert({ foo: 'bar' }); + } + }); + + test.equal(coll.find({ foo: 'bar' }).count(), 0); + + // Call a method. We'll get the result but not data-done before reconnect. + const callbackOutput = []; + const onResultReceivedOutput = []; + conn.apply( + 'writeSomething', + [], + { + onResultReceived: function(err, result) { + onResultReceivedOutput.push(result); + } + }, + function(err, result) { + callbackOutput.push(result); + } + ); + // Stub write is visible. + test.equal(coll.find({ foo: 'bar' }).count(), 1); + const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + o.expectCallbacks({ added: 1 }); + // Callback not called. + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, []); + // Method sent. + const methodId = testGotMessage(test, stream, { + msg: 'method', + method: 'writeSomething', + params: [], + id: '*', + randomSeed: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Get some data. + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(stubWrittenId), + fields: { baz: 42 } + }); + // It doesn't show up yet. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar' + }); + o.expectCallbacks(); + + // Get the result. + stream.receive({ msg: 'result', id: methodId, result: 'bla' }); + // Data unaffected. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar' + }); + o.expectCallbacks(); + // Callback not called, but onResultReceived is. + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, ['bla']); + + // Reset stream. Method does NOT get resent, because its result is already + // in. Reconnect quiescence happens as soon as 'connected' is received because + // there are no pending methods or subs in need of revival. + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + // Still holding out hope for session resumption, so nothing updated yet. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar' + }); + o.expectCallbacks(); + test.equal(callbackOutput, []); + + // Receive 'connected': time for reconnect quiescence! Data gets updated + // locally (ie, data is reset) and callback gets called. + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + test.equal(coll.find().count(), 0); + o.expectCallbacks({ removed: 1 }); + test.equal(callbackOutput, ['bla']); + test.equal(onResultReceivedOutput, ['bla']); + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(stubWrittenId), + fields: { baz: 42 } + }); + test.equal(coll.findOne(stubWrittenId), { _id: stubWrittenId, baz: 42 }); + o.expectCallbacks({ added: 1 }); + + // Run method again. We're going to do the same thing this time, except we're + // also going to use an onReconnect to insert another method at reconnect + // time, which will delay reconnect quiescence. + conn.apply( + 'writeSomething', + [], + { + onResultReceived: function(err, result) { + onResultReceivedOutput.push(result); + } + }, + function(err, result) { + callbackOutput.push(result); + } + ); + // Stub write is visible. + test.equal(coll.find({ foo: 'bar' }).count(), 1); + const stubWrittenId2 = coll.findOne({ foo: 'bar' })._id; + o.expectCallbacks({ added: 1 }); + // Callback not called. + test.equal(callbackOutput, ['bla']); + test.equal(onResultReceivedOutput, ['bla']); + // Method sent. + const methodId2 = testGotMessage(test, stream, { + msg: 'method', + method: 'writeSomething', + params: [], + id: '*', + randomSeed: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Get some data. + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(stubWrittenId2), + fields: { baz: 42 } + }); + // It doesn't show up yet. + test.equal(coll.find().count(), 2); + test.equal(coll.findOne(stubWrittenId2), { + _id: stubWrittenId2, + foo: 'bar' + }); + o.expectCallbacks(); + + // Get the result. + stream.receive({ msg: 'result', id: methodId2, result: 'blab' }); + // Data unaffected. + test.equal(coll.find().count(), 2); + test.equal(coll.findOne(stubWrittenId2), { + _id: stubWrittenId2, + foo: 'bar' + }); + o.expectCallbacks(); + // Callback not called, but onResultReceived is. + test.equal(callbackOutput, ['bla']); + test.equal(onResultReceivedOutput, ['bla', 'blab']); + setOnReconnect(conn, function() { + conn.call('slowMethod', function(err, result) { + callbackOutput.push(result); + }); + }); + + // Reset stream. Method does NOT get resent, because its result is already in, + // but slowMethod gets called via onReconnect. Reconnect quiescence is now + // blocking on slowMethod. + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(SESSION_ID + 1)); + const slowMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'slowMethod', + params: [], + id: '*' + }).id; + // Still holding out hope for session resumption, so nothing updated yet. + test.equal(coll.find().count(), 2); + test.equal(coll.findOne(stubWrittenId2), { + _id: stubWrittenId2, + foo: 'bar' + }); + o.expectCallbacks(); + test.equal(callbackOutput, ['bla']); + + // Receive 'connected'... but no reconnect quiescence yet due to slowMethod. + stream.receive({ msg: 'connected', session: SESSION_ID + 2 }); + test.equal(coll.find().count(), 2); + test.equal(coll.findOne(stubWrittenId2), { + _id: stubWrittenId2, + foo: 'bar' + }); + o.expectCallbacks(); + test.equal(callbackOutput, ['bla']); + + // Receive data matching our stub. It doesn't take effect yet. + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(stubWrittenId2), + fields: { foo: 'bar' } + }); + o.expectCallbacks(); + + // slowMethod is done writing, so we get full reconnect quiescence (but no + // slowMethod callback)... ie, a reset followed by applying the data we just + // got, as well as calling the callback from the method that half-finished + // before reset. The net effect is deleting doc 'stubWrittenId'. + stream.receive({ msg: 'updated', methods: [slowMethodId] }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId2), { + _id: stubWrittenId2, + foo: 'bar' + }); + o.expectCallbacks({ removed: 1 }); + test.equal(callbackOutput, ['bla', 'blab']); + + // slowMethod returns a value now. + stream.receive({ msg: 'result', id: slowMethodId, result: 'slow' }); + o.expectCallbacks(); + test.equal(callbackOutput, ['bla', 'blab', 'slow']); + + o.stop(); + } + ); +} +Tinytest.add('livedata stub - reconnect method which only got data', function( + test +) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + const o = observeCursor(test, coll.find()); + + // Call a method. We'll get the data-done message but not the result before + // reconnect. + const callbackOutput = []; + const onResultReceivedOutput = []; + conn.apply( + 'doLittle', + [], + { + onResultReceived: function(err, result) { + onResultReceivedOutput.push(result); + } + }, + function(err, result) { + callbackOutput.push(result); + } + ); + // Callbacks not called. + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, []); + // Method sent. + const methodId = testGotMessage(test, stream, { + msg: 'method', + method: 'doLittle', + params: [], + id: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Get some data. + stream.receive({ + msg: 'added', + collection: collName, + id: 'photo', + fields: { baz: 42 } + }); + // It shows up instantly because the stub didn't write anything. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + o.expectCallbacks({ added: 1 }); + + // Get the data-done message. + stream.receive({ msg: 'updated', methods: [methodId] }); + // Data still here. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + o.expectCallbacks(); + // Method callback not called yet (no result yet). + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, []); + + // Reset stream. Method gets resent (with same ID), and blocks reconnect + // quiescence. + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + testGotMessage(test, stream, { + msg: 'method', + method: 'doLittle', + params: [], + id: methodId + }); + // Still holding out hope for session resumption, so nothing updated yet. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + o.expectCallbacks(); + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, []); + + // Receive 'connected'. Still blocking on reconnect quiescence. + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + o.expectCallbacks(); + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, []); + + // Receive method result. onResultReceived is called but the main callback + // isn't (ie, we don't get confused by the fact that we got data-done the + // *FIRST* time through). + stream.receive({ msg: 'result', id: methodId, result: 'res' }); + test.equal(callbackOutput, []); + test.equal(onResultReceivedOutput, ['res']); + + // Now we get data-done. Collection is reset and callback is called. + stream.receive({ msg: 'updated', methods: [methodId] }); + test.equal(coll.find().count(), 0); + o.expectCallbacks({ removed: 1 }); + test.equal(callbackOutput, ['res']); + test.equal(onResultReceivedOutput, ['res']); + + o.stop(); +}); +if (Meteor.isClient) { + Tinytest.add('livedata stub - multiple stubs same doc', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + const o = observeCursor(test, coll.find()); + + conn.methods({ + insertSomething: function() { + // stub write + coll.insert({ foo: 'bar' }); + }, + updateIt: function(id) { + coll.update(id, { $set: { baz: 42 } }); + } + }); + + test.equal(coll.find().count(), 0); + + // Call the insert method. + conn.call('insertSomething', _.identity); + // Stub write is visible. + test.equal(coll.find({ foo: 'bar' }).count(), 1); + const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + o.expectCallbacks({ added: 1 }); + // Method sent. + const insertMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'insertSomething', + params: [], + id: '*', + randomSeed: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Call update method. + conn.call('updateIt', stubWrittenId, _.identity); + // This stub write is visible too. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar', + baz: 42 + }); + o.expectCallbacks({ changed: 1 }); + // Method sent. + const updateMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'updateIt', + params: [stubWrittenId], + id: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Get some data... slightly different than what we wrote. + stream.receive({ + msg: 'added', + collection: collName, + id: MongoID.idStringify(stubWrittenId), + fields: { + foo: 'barb', + other: 'field', + other2: 'bla' + } + }); + // It doesn't show up yet. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar', + baz: 42 + }); + o.expectCallbacks(); + + // And get the first method-done. Still no updates to minimongo: we can't + // quiesce the doc until the second method is done. + stream.receive({ msg: 'updated', methods: [insertMethodId] }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar', + baz: 42 + }); + o.expectCallbacks(); + + // More data. Not quite what we wrote. Also ignored for now. + stream.receive({ + msg: 'changed', + collection: collName, + id: MongoID.idStringify(stubWrittenId), + fields: { baz: 43 }, + cleared: ['other'] + }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'bar', + baz: 42 + }); + o.expectCallbacks(); + + // Second data-ready. Now everything takes effect! + stream.receive({ msg: 'updated', methods: [updateMethodId] }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(stubWrittenId), { + _id: stubWrittenId, + foo: 'barb', + other2: 'bla', + baz: 43 + }); + o.expectCallbacks({ changed: 1 }); + + o.stop(); + }); +} + +if (Meteor.isClient) { + Tinytest.add( + "livedata stub - unsent methods don't block quiescence", + function(test) { + // This test is for https://github.com/meteor/meteor/issues/555 + + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + conn.methods({ + insertSomething: function() { + // stub write + coll.insert({ foo: 'bar' }); + } + }); + + test.equal(coll.find().count(), 0); + + // Call a random method (no-op) + conn.call('no-op', _.identity); + // Call a wait method + conn.apply('no-op', [], { wait: true }, _.identity); + // Call a method with a stub that writes. + conn.call('insertSomething', _.identity); + + // Stub write is visible. + test.equal(coll.find({ foo: 'bar' }).count(), 1); + const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + + // first method sent + const firstMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'no-op', + params: [], + id: '*' + }).id; + test.equal(stream.sent.length, 0); + + // ack the first method + stream.receive({ msg: 'updated', methods: [firstMethodId] }); + stream.receive({ msg: 'result', id: firstMethodId }); + + // Wait method sent. + const waitMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'no-op', + params: [], + id: '*' + }).id; + test.equal(stream.sent.length, 0); + + // ack the wait method + stream.receive({ msg: 'updated', methods: [waitMethodId] }); + stream.receive({ msg: 'result', id: waitMethodId }); + + // insert method sent. + const insertMethodId = testGotMessage(test, stream, { + msg: 'method', + method: 'insertSomething', + params: [], + id: '*', + randomSeed: '*' + }).id; + test.equal(stream.sent.length, 0); + + // ack the insert method + stream.receive({ msg: 'updated', methods: [insertMethodId] }); + stream.receive({ msg: 'result', id: insertMethodId }); + + // simulation reverted. + test.equal(coll.find({ foo: 'bar' }).count(), 0); + } + ); +} +Tinytest.add('livedata stub - reactive resub', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const readiedSubs = {}; + const markAllReady = function() { + // synthesize a "ready" message in response to any "sub" + // message with an id we haven't seen before + _.each(stream.sent, function(msg) { + msg = JSON.parse(msg); + if (msg.msg === 'sub' && !_.has(readiedSubs, msg.id)) { + stream.receive({ msg: 'ready', subs: [msg.id] }); + readiedSubs[msg.id] = true; + } + }); + }; + + const fooArg = new ReactiveVar('A'); + let fooReady = 0; + + let inner; + const outer = Tracker.autorun(function() { + inner = Tracker.autorun(function() { + conn.subscribe('foo-sub', fooArg.get(), function() { + fooReady++; + }); + }); + }); + + markAllReady(); + let message = JSON.parse(stream.sent.shift()); + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo-sub', params: ['A'] }); + test.equal(fooReady, 1); + + // Rerun the inner autorun with different subscription + // arguments. + fooArg.set('B'); + test.isTrue(inner.invalidated); + Tracker.flush(); + test.isFalse(inner.invalidated); + markAllReady(); + message = JSON.parse(stream.sent.shift()); + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo-sub', params: ['B'] }); + message = JSON.parse(stream.sent.shift()); + delete message.id; + test.equal(message, { msg: 'unsub' }); + test.equal(fooReady, 2); + + // Rerun inner again with same args; should be no re-sub. + inner.invalidate(); + test.isTrue(inner.invalidated); + Tracker.flush(); + test.isFalse(inner.invalidated); + markAllReady(); + test.isUndefined(stream.sent.shift()); + test.isUndefined(stream.sent.shift()); + test.equal(fooReady, 3); + + // Rerun outer! Should still be no re-sub even though + // the inner computation is stopped and a new one is + // started. + outer.invalidate(); + test.isTrue(inner.invalidated); + Tracker.flush(); + test.isFalse(inner.invalidated); + markAllReady(); + test.isUndefined(stream.sent.shift()); + test.equal(fooReady, 4); + + // Change the subscription. Now we should get an onReady. + fooArg.set('C'); + Tracker.flush(); + markAllReady(); + message = JSON.parse(stream.sent.shift()); + delete message.id; + test.equal(message, { msg: 'sub', name: 'foo-sub', params: ['C'] }); + message = JSON.parse(stream.sent.shift()); + delete message.id; + test.equal(message, { msg: 'unsub' }); + test.equal(fooReady, 5); +}); + +Tinytest.add('livedata connection - reactive userId', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + test.equal(conn.userId(), null); + conn.setUserId(1337); + test.equal(conn.userId(), 1337); +}); + +Tinytest.add('livedata connection - two wait methods', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + // setup method + conn.methods({ do_something: function(x) {} }); + + const responses = []; + conn.apply('do_something', ['one!'], function() { + responses.push('one'); + }); + let one_message = JSON.parse(stream.sent.shift()); + test.equal(one_message.params, ['one!']); + + conn.apply('do_something', ['two!'], { wait: true }, function() { + responses.push('two'); + }); + // 'two!' isn't sent yet, because it's a wait method. + test.equal(stream.sent.length, 0); + + conn.apply('do_something', ['three!'], function() { + responses.push('three'); + }); + conn.apply('do_something', ['four!'], function() { + responses.push('four'); + }); + + conn.apply('do_something', ['five!'], { wait: true }, function() { + responses.push('five'); + }); + + conn.apply('do_something', ['six!'], function() { + responses.push('six'); + }); + + // Verify that we did not send any more methods since we are still waiting on + // 'one!'. + test.equal(stream.sent.length, 0); + + // Receive some data. "one" is not a wait method and there are no stubs, so it + // gets applied immediately. + test.equal(coll.find().count(), 0); + stream.receive({ + msg: 'added', + collection: collName, + id: 'foo', + fields: { x: 1 } + }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('foo'), { _id: 'foo', x: 1 }); + + // Let "one!" finish. Both messages are required to fire the callback. + stream.receive({ msg: 'result', id: one_message.id }); + test.equal(responses, []); + stream.receive({ msg: 'updated', methods: [one_message.id] }); + test.equal(responses, ['one']); + + // Now we've send out "two!". + let two_message = JSON.parse(stream.sent.shift()); + test.equal(two_message.params, ['two!']); + + // But still haven't sent "three!". + test.equal(stream.sent.length, 0); + + // Receive more data. "two" is a wait method, so the data doesn't get applied + // yet. + stream.receive({ + msg: 'changed', + collection: collName, + id: 'foo', + fields: { y: 3 } + }); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('foo'), { _id: 'foo', x: 1 }); + + // Let "two!" finish, with its end messages in the opposite order to "one!". + stream.receive({ msg: 'updated', methods: [two_message.id] }); + test.equal(responses, ['one']); + test.equal(stream.sent.length, 0); + // data-done message is enough to allow data to be written. + test.equal(coll.find().count(), 1); + test.equal(coll.findOne('foo'), { _id: 'foo', x: 1, y: 3 }); + stream.receive({ msg: 'result', id: two_message.id }); + test.equal(responses, ['one', 'two']); + + // Verify that we just sent "three!" and "four!" now that we got + // responses for "one!" and "two!" + test.equal(stream.sent.length, 2); + let three_message = JSON.parse(stream.sent.shift()); + test.equal(three_message.params, ['three!']); + let four_message = JSON.parse(stream.sent.shift()); + test.equal(four_message.params, ['four!']); + + // Out of order response is OK for non-wait methods. + stream.receive({ msg: 'result', id: three_message.id }); + stream.receive({ msg: 'result', id: four_message.id }); + stream.receive({ msg: 'updated', methods: [four_message.id] }); + test.equal(responses, ['one', 'two', 'four']); + test.equal(stream.sent.length, 0); + + // Let three finish too. + stream.receive({ msg: 'updated', methods: [three_message.id] }); + test.equal(responses, ['one', 'two', 'four', 'three']); + + // Verify that we just sent "five!" (the next wait method). + test.equal(stream.sent.length, 1); + let five_message = JSON.parse(stream.sent.shift()); + test.equal(five_message.params, ['five!']); + test.equal(responses, ['one', 'two', 'four', 'three']); + + // Let five finish. + stream.receive({ msg: 'result', id: five_message.id }); + stream.receive({ msg: 'updated', methods: [five_message.id] }); + test.equal(responses, ['one', 'two', 'four', 'three', 'five']); + + let six_message = JSON.parse(stream.sent.shift()); + test.equal(six_message.params, ['six!']); +}); + +addReconnectTests( + 'livedata connection - onReconnect prepends messages correctly with a wait method', + function(test, setOnReconnect) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + // setup method + conn.methods({ do_something: function(x) {} }); + + setOnReconnect(conn, function() { + conn.apply('do_something', ['reconnect zero'], _.identity); + conn.apply('do_something', ['reconnect one'], _.identity); + conn.apply('do_something', ['reconnect two'], { wait: true }, _.identity); + conn.apply('do_something', ['reconnect three'], _.identity); + }); + + conn.apply('do_something', ['one'], _.identity); + conn.apply('do_something', ['two'], { wait: true }, _.identity); + conn.apply('do_something', ['three'], _.identity); + + // reconnect + stream.sent = []; + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); + + // Test that we sent what we expect to send, and we're blocked on + // what we expect to be blocked. The subsequent logic to correctly + // read the wait flag is tested separately. + test.equal( + _.map(stream.sent, function(msg) { + return JSON.parse(msg).params[0]; + }), + ['reconnect zero', 'reconnect one'] + ); + + // white-box test: + test.equal( + _.map(conn._outstandingMethodBlocks, function(block) { + return [ + block.wait, + _.map(block.methods, function(method) { + return method._message.params[0]; + }) + ]; + }), + [ + [false, ['reconnect zero', 'reconnect one']], + [true, ['reconnect two']], + [false, ['reconnect three', 'one']], + [true, ['two']], + [false, ['three']] + ] + ); + } +); + +Tinytest.add('livedata connection - ping without id', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + stream.receive({ msg: 'ping' }); + testGotMessage(test, stream, { msg: 'pong' }); +}); + +Tinytest.add('livedata connection - ping with id', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const id = Random.id(); + stream.receive({ msg: 'ping', id: id }); + testGotMessage(test, stream, { msg: 'pong', id: id }); +}); + +_.each(DDPCommon.SUPPORTED_DDP_VERSIONS, function(version) { + Tinytest.addAsync('livedata connection - ping from ' + version, function( + test, + onComplete + ) { + const connection = new Connection(getSelfConnectionUrl(), { + reloadWithOutstanding: true, + supportedDDPVersions: [version], + onDDPVersionNegotiationFailure: function() { + test.fail(); + onComplete(); + }, + onConnected: function() { + test.equal(connection._version, version); + // It's a little naughty to access _stream and _send, but it works... + connection._stream.on('message', function(json) { + let msg = JSON.parse(json); + let done = false; + if (msg.msg === 'pong') { + test.notEqual(version, 'pre1'); + done = true; + } else if (msg.msg === 'error') { + // Version pre1 does not play ping-pong + test.equal(version, 'pre1'); + done = true; + } else { + Meteor._debug('Got unexpected message: ' + json); + } + if (done) { + connection._stream.disconnect({ _permanent: true }); + onComplete(); + } + }); + connection._send({ msg: 'ping' }); + } + }); + }); +}); + +const getSelfConnectionUrl = function() { + if (Meteor.isClient) { + let ddpUrl = Meteor._relativeToSiteRootUrl('/'); + if (typeof __meteor_runtime_config__ !== 'undefined') { + if (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL) + ddpUrl = __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL; + } + return ddpUrl; + } else { + return Meteor.absoluteUrl(); + } +}; + +if (Meteor.isServer) { + Meteor.methods({ + reverse: function(arg) { + // Return something notably different from reverse.meteor.com. + return ( + arg + .split('') + .reverse() + .join('') + ' LOCAL' + ); + } + }); +} + +testAsyncMulti('livedata connection - reconnect to a different server', [ + function(test, expect) { + const self = this; + self.conn = DDP.connect('reverse.meteor.com'); + pollUntil( + expect, + function() { + return self.conn.status().connected; + }, + 5000, + 100, + false + ); + }, + function(test, expect) { + const self = this; + self.doTest = self.conn.status().connected; + if (self.doTest) { + self.conn.call( + 'reverse', + 'foo', + expect(function(err, res) { + test.equal(res, 'oof'); + }) + ); + } + }, + function(test, expect) { + const self = this; + if (self.doTest) { + self.conn.reconnect({ url: getSelfConnectionUrl() }); + self.conn.call( + 'reverse', + 'bar', + expect(function(err, res) { + test.equal(res, 'rab LOCAL'); + }) + ); + } + } +]); + +Tinytest.addAsync( + 'livedata connection - version negotiation requires renegotiating', + function(test, onComplete) { + const connection = new Connection(getSelfConnectionUrl(), { + reloadWithOutstanding: true, + supportedDDPVersions: ['garbled', DDPCommon.SUPPORTED_DDP_VERSIONS[0]], + onDDPVersionNegotiationFailure: function() { + test.fail(); + onComplete(); + }, + onConnected: function() { + test.equal(connection._version, DDPCommon.SUPPORTED_DDP_VERSIONS[0]); + connection._stream.disconnect({ _permanent: true }); + onComplete(); + } + }); + } +); + +Tinytest.addAsync('livedata connection - version negotiation error', function( + test, + onComplete +) { + const connection = new Connection(getSelfConnectionUrl(), { + reloadWithOutstanding: true, + supportedDDPVersions: ['garbled', 'more garbled'], + onDDPVersionNegotiationFailure: function() { + test.equal(connection.status().status, 'failed'); + test.matches( + connection.status().reason, + /DDP version negotiation failed/ + ); + test.isFalse(connection.status().connected); + onComplete(); + }, + onConnected: function() { + test.fail(); + onComplete(); + } + }); +}); + +addReconnectTests( + 'livedata connection - onReconnect prepends messages correctly without a wait method', + function(test, setOnReconnect) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + // setup method + conn.methods({ do_something: function(x) {} }); + + setOnReconnect(conn, function() { + conn.apply('do_something', ['reconnect one'], _.identity); + conn.apply('do_something', ['reconnect two'], _.identity); + conn.apply('do_something', ['reconnect three'], _.identity); + }); + + conn.apply('do_something', ['one'], _.identity); + conn.apply('do_something', ['two'], { wait: true }, _.identity); + conn.apply('do_something', ['three'], { wait: true }, _.identity); + conn.apply('do_something', ['four'], _.identity); + + // reconnect + stream.sent = []; + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); + + // Test that we sent what we expect to send, and we're blocked on + // what we expect to be blocked. The subsequent logic to correctly + // read the wait flag is tested separately. + test.equal( + _.map(stream.sent, function(msg) { + return JSON.parse(msg).params[0]; + }), + ['reconnect one', 'reconnect two', 'reconnect three', 'one'] + ); + + // white-box test: + test.equal( + _.map(conn._outstandingMethodBlocks, function(block) { + return [ + block.wait, + _.map(block.methods, function(method) { + return method._message.params[0]; + }) + ]; + }), + [ + [false, ['reconnect one', 'reconnect two', 'reconnect three', 'one']], + [true, ['two']], + [true, ['three']], + [false, ['four']] + ] + ); + } +); + +addReconnectTests( + 'livedata connection - onReconnect with sent messages', + function(test, setOnReconnect) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + // setup method + conn.methods({ do_something: function(x) {} }); + + setOnReconnect(conn, function() { + conn.apply('do_something', ['login'], { wait: true }, _.identity); + }); + + conn.apply('do_something', ['one'], _.identity); + + // initial connect + stream.sent = []; + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); + + // Test that we sent just the login message. + const loginId = testGotMessage(test, stream, { + msg: 'method', + method: 'do_something', + params: ['login'], + id: '*' + }).id; + + // we connect. + stream.receive({ msg: 'connected', session: Random.id() }); + test.length(stream.sent, 0); + + // login got result (but not yet data) + stream.receive({ msg: 'result', id: loginId, result: 'foo' }); + test.length(stream.sent, 0); + + // login got data. now we send next method. + stream.receive({ msg: 'updated', methods: [loginId] }); + + testGotMessage(test, stream, { + msg: 'method', + method: 'do_something', + params: ['one'], + id: '*' + }).id; + } +); + +addReconnectTests('livedata stub - reconnect double wait method', function( + test, + setOnReconnect +) { + const stream = new StubStream(); + const conn = newConnection(stream); + startAndConnect(test, stream); + + const output = []; + setOnReconnect(conn, function() { + conn.apply('reconnectMethod', [], { wait: true }, function(err, result) { + output.push('reconnect'); + }); + }); + + conn.apply('halfwayMethod', [], { wait: true }, function(err, result) { + output.push('halfway'); + }); + + test.equal(output, []); + // Method sent. + const halfwayId = testGotMessage(test, stream, { + msg: 'method', + method: 'halfwayMethod', + params: [], + id: '*' + }).id; + test.equal(stream.sent.length, 0); + + // Get the result. This means it will not be resent. + stream.receive({ msg: 'result', id: halfwayId, result: 'bla' }); + // Callback not called. + test.equal(output, []); + + // Reset stream. halfwayMethod does NOT get resent, but reconnectMethod does! + // Reconnect quiescence happens when reconnectMethod is done. + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + const reconnectId = testGotMessage(test, stream, { + msg: 'method', + method: 'reconnectMethod', + params: [], + id: '*' + }).id; + test.length(stream.sent, 0); + // Still holding out hope for session resumption, so no callbacks yet. + test.equal(output, []); + + // Receive 'connected', but reconnect quiescence is blocking on + // reconnectMethod. + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + test.equal(output, []); + + // Data-done for reconnectMethod. This gets us to reconnect quiescence, so + // halfwayMethod's callback fires. reconnectMethod's is still waiting on its + // result. + stream.receive({ msg: 'updated', methods: [reconnectId] }); + test.equal(output.shift(), 'halfway'); + test.equal(output, []); + + // Get result of reconnectMethod. Its callback fires. + stream.receive({ msg: 'result', id: reconnectId, result: 'foo' }); + test.equal(output.shift(), 'reconnect'); + test.equal(output, []); + + // Call another method. It should be delivered immediately. This is a + // regression test for a case where it never got delivered because there was + // an empty block in _outstandingMethodBlocks blocking it from being sent. + conn.call('lastMethod', _.identity); + testGotMessage(test, stream, { + msg: 'method', + method: 'lastMethod', + params: [], + id: '*' + }); +}); + +Tinytest.add('livedata stub - subscribe errors', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + // subscribe + let onReadyFired = false; + let subErrorInStopped = null; + let subErrorInError = null; + + conn.subscribe('unknownSub', { + onReady: function() { + onReadyFired = true; + }, + + // We now have two ways to get the error from a subscription: + // 1. onStop, which is called no matter what when the subscription is + // stopped (a lifecycle callback) + // 2. onError, which is deprecated and is called only if there is an + // error + onStop: function(error) { + subErrorInStopped = error; + }, + onError: function(error) { + subErrorInError = error; + } + }); + + test.isFalse(onReadyFired); + test.equal(subErrorInStopped, null); + + // XXX COMPAT WITH 1.0.3.1 #errorCallback + test.equal(subErrorInError, null); + + let subMessage = JSON.parse(stream.sent.shift()); + test.equal(subMessage, { + msg: 'sub', + name: 'unknownSub', + params: [], + id: subMessage.id + }); + + // Reject the sub. + stream.receive({ + msg: 'nosub', + id: subMessage.id, + error: new Meteor.Error(404, 'Subscription not found') + }); + test.isFalse(onReadyFired); + + // Check the error passed to the stopped callback was correct + test.instanceOf(subErrorInStopped, Meteor.Error); + test.equal(subErrorInStopped.error, 404); + test.equal(subErrorInStopped.reason, 'Subscription not found'); + + // Check the error passed to the error callback was correct + // XXX COMPAT WITH 1.0.3.1 #errorCallback + test.instanceOf(subErrorInError, Meteor.Error); + test.equal(subErrorInError.error, 404); + test.equal(subErrorInError.reason, 'Subscription not found'); + + // stream reset: reconnect! + stream.reset(); + // We send a connect. + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + // We should NOT re-sub to the sub, because we processed the error. + test.length(stream.sent, 0); + test.isFalse(onReadyFired); +}); + +Tinytest.add('livedata stub - subscribe stop', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + // subscribe + let onReadyFired = false; + let onStopFired = false; + let subErrorInStopped = null; + + const sub = conn.subscribe('my_data', { + onStop: function(error) { + onStopFired = true; + subErrorInStopped = error; + } + }); + + test.equal(subErrorInStopped, null); + + sub.stop(); + + test.isTrue(onStopFired); + test.equal(subErrorInStopped, undefined); +}); + +if (Meteor.isClient) { + Tinytest.add('livedata stub - stubs before connected', function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + // Start and send "connect", but DON'T get 'connected' quite yet. + stream.reset(); // initial connection start. + + testGotMessage(test, stream, makeConnectMessage()); + test.length(stream.sent, 0); + + // Insert a document. The stub updates "conn" directly. + coll.insert({ _id: 'foo', bar: 42 }, _.identity); + test.equal(coll.find().count(), 1); + test.equal(coll.findOne(), { _id: 'foo', bar: 42 }); + // It also sends the method message. + let methodMessage = JSON.parse(stream.sent.shift()); + test.isUndefined(methodMessage.randomSeed); + test.equal(methodMessage, { + msg: 'method', + method: '/' + collName + '/insert', + params: [{ _id: 'foo', bar: 42 }], + id: methodMessage.id + }); + test.length(stream.sent, 0); + + // Now receive a connected message. This should not clear the + // _documentsWrittenByStub state! + stream.receive({ msg: 'connected', session: SESSION_ID }); + test.length(stream.sent, 0); + test.equal(coll.find().count(), 1); + + // Now receive the "updated" message for the method. This should revert the + // insert. + stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + test.length(stream.sent, 0); + test.equal(coll.find().count(), 0); + }); +} + +if (Meteor.isClient) { + Tinytest.add( + 'livedata stub - method call between reset and quiescence', + function(test) { + const stream = new StubStream(); + const conn = newConnection(stream); + + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + conn.methods({ + update_value: function() { + coll.update('aaa', { value: 222 }); + } + }); + + // Set up test subscription. + const sub = conn.subscribe('test_data'); + let subMessage = JSON.parse(stream.sent.shift()); + test.equal(subMessage, { + msg: 'sub', + name: 'test_data', + params: [], + id: subMessage.id + }); + test.length(stream.sent, 0); + + const subDocMessage = { + msg: 'added', + collection: collName, + id: 'aaa', + fields: { value: 111 } + }; + + const subReadyMessage = { msg: 'ready', subs: [subMessage.id] }; + + stream.receive(subDocMessage); + stream.receive(subReadyMessage); + test.isTrue(coll.findOne('aaa').value == 111); + + // Initiate reconnect. + stream.reset(); + testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); + testGotMessage(test, stream, subMessage); + stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + + // Now in reconnect, can still see the document. + test.isTrue(coll.findOne('aaa').value == 111); + + conn.call('update_value'); + + // Observe the stub-written value. + test.isTrue(coll.findOne('aaa').value == 222); + + let methodMessage = JSON.parse(stream.sent.shift()); + test.equal(methodMessage, { + msg: 'method', + method: 'update_value', + params: [], + id: methodMessage.id + }); + test.length(stream.sent, 0); + + stream.receive(subDocMessage); + stream.receive(subReadyMessage); + + // By this point quiescence is reached and stores have been reset. + + // The stub-written value is still there. + test.isTrue(coll.findOne('aaa').value == 222); + + stream.receive({ + msg: 'changed', + collection: collName, + id: 'aaa', + fields: { value: 333 } + }); + stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + stream.receive({ msg: 'result', id: methodMessage.id, result: null }); + + // Server wrote a different value, make sure it's visible now. + test.isTrue(coll.findOne('aaa').value == 333); + } + ); + + Tinytest.add('livedata stub - buffering and methods interaction', function( + test + ) { + const stream = new StubStream(); + const conn = newConnection(stream, { + // A very high values so that all messages are effectively buffered. + bufferedWritesInterval: 10000, + bufferedWritesMaxAge: 10000 + }); + + startAndConnect(test, stream); + + const collName = Random.id(); + const coll = new Mongo.Collection(collName, { connection: conn }); + + conn.methods({ + update_value: function() { + const value = coll.findOne('aaa').subscription; + // Method should have access to the latest value of the collection. + coll.update('aaa', { $set: { method: value + 110 } }); + } + }); + + // Set up test subscription. + const sub = conn.subscribe('test_data'); + let subMessage = JSON.parse(stream.sent.shift()); + test.equal(subMessage, { + msg: 'sub', + name: 'test_data', + params: [], + id: subMessage.id + }); + test.length(stream.sent, 0); + + const subDocMessage = { + msg: 'added', + collection: collName, + id: 'aaa', + fields: { subscription: 111 } + }; + + const subReadyMessage = { msg: 'ready', subs: [subMessage.id] }; + + stream.receive(subDocMessage); + stream.receive(subReadyMessage); + test.equal(coll.findOne('aaa').subscription, 111); + + const subDocChangeMessage = { + msg: 'changed', + collection: collName, + id: 'aaa', + fields: { subscription: 112 } + }; + + stream.receive(subDocChangeMessage); + // Still 111 because buffer has not been flushed. + test.equal(coll.findOne('aaa').subscription, 111); + + // Call updates the stub. + conn.call('update_value'); + + // Observe the stub-written value. + test.equal(coll.findOne('aaa').method, 222); + // subscription field is updated to the latest value + // because of the method call. + test.equal(coll.findOne('aaa').subscription, 112); + + let methodMessage = JSON.parse(stream.sent.shift()); + test.equal(methodMessage, { + msg: 'method', + method: 'update_value', + params: [], + id: methodMessage.id + }); + test.length(stream.sent, 0); + + // "Server-side" change from the method arrives and method returns. + // With potentially fixed value for method field, if stub didn't + // use 112 as the subscription field value. + stream.receive({ + msg: 'changed', + collection: collName, + id: 'aaa', + fields: { method: 222 } + }); + stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + stream.receive({ msg: 'result', id: methodMessage.id, result: null }); + + test.equal(coll.findOne('aaa').method, 222); + test.equal(coll.findOne('aaa').subscription, 112); + + // Buffer should already be flushed because of a non-update message. + // And after a flush we really want subscription field to be 112. + conn._flushBufferedWrites(); + test.equal(coll.findOne('aaa').method, 222); + test.equal(coll.findOne('aaa').subscription, 112); + }); +} + +// XXX also test: +// - reconnect, with session resume. +// - restart on update flag +// - on_update event +// - reloading when the app changes, including session migration diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js new file mode 100644 index 0000000000..db32cf3cc4 --- /dev/null +++ b/packages/ddp-client/test/livedata_test_service.js @@ -0,0 +1,376 @@ +Meteor.methods({ + nothing: function() { + // No need to check if there are no arguments. + }, + echo: function(/* arguments */) { + check(arguments, [Match.Any]); + return _.toArray(arguments); + }, + echoOne: function(/*arguments*/) { + check(arguments, [Match.Any]); + return arguments[0]; + }, + exception: function(where, options) { + check(where, String); + check( + options, + Match.Optional({ + intended: Match.Optional(Boolean), + throwThroughFuture: Match.Optional(Boolean) + }) + ); + options = options || Object.create(null); + const shouldThrow = + (Meteor.isServer && where === 'server') || + (Meteor.isClient && where === 'client') || + where === 'both'; + + if (shouldThrow) { + let e; + if (options.intended) + e = new Meteor.Error(999, 'Client-visible test exception'); + else e = new Error('Test method throwing an exception'); + e._expectedByTest = true; + + // We used to improperly serialize errors that were thrown through a + // future first. + if (Meteor.isServer && options.throwThroughFuture) { + const Future = Npm.require('fibers/future'); + const f = new Future(); + f['throw'](e); + e = f.wait(); + } + throw e; + } + }, + setUserId: function(userId) { + check(userId, Match.OneOf(String, null)); + this.setUserId(userId); + } +}); + +// Methods to help test applying methods with `wait: true`: delayedTrue returns +// true 1s after being run unless makeDelayedTrueImmediatelyReturnFalse was run +// in the meanwhile. Increasing the timeout makes the "wait: true" test slower; +// decreasing the timeout makes the "wait: false" test flakier (ie, the timeout +// could fire before processing the second method). +if (Meteor.isServer) { + // Keys are random tokens, used to isolate multiple test invocations from each + // other. + const waiters = Object.create(null); + + const Future = Npm.require('fibers/future'); + + const returnThroughFuture = function(token, returnValue) { + // Make sure that when we call return, the fields are already cleared. + const record = waiters[token]; + if (!record) return; + delete waiters[token]; + record.future['return'](returnValue); + }; + + Meteor.methods({ + delayedTrue: function(token) { + check(token, String); + const record = (waiters[token] = { + future: new Future(), + timer: Meteor.setTimeout(function() { + returnThroughFuture(token, true); + }, 1000) + }); + + this.unblock(); + return record.future.wait(); + }, + makeDelayedTrueImmediatelyReturnFalse: function(token) { + check(token, String); + const record = waiters[token]; + if (!record) return; // since delayedTrue's timeout had already run + clearTimeout(record.timer); + returnThroughFuture(token, false); + } + }); +} + +/*****/ + +Ledger = new Mongo.Collection('ledger'); +Ledger.allow({ + insert: function() { + return true; + }, + update: function() { + return true; + }, + remove: function() { + return true; + }, + fetch: [] +}); + +Meteor.startup(function() { + if (Meteor.isServer) Ledger.remove({}); // XXX can this please be Ledger.remove()? +}); + +if (Meteor.isServer) + Meteor.publish('ledger', function(world) { + check(world, String); + return Ledger.find({ world: world }); + }); + +Meteor.methods({ + 'ledger/transfer': function(world, from_name, to_name, amount, cheat) { + check(world, String); + check(from_name, String); + check(to_name, String); + check(amount, Number); + check(cheat, Match.Optional(Boolean)); + const from = Ledger.findOne({ name: from_name, world: world }); + const to = Ledger.findOne({ name: to_name, world: world }); + + if (Meteor.isServer) cheat = false; + + if (!from) + throw new Meteor.Error( + 404, + 'No such account ' + from_name + ' in ' + world + ); + + if (!to) + throw new Meteor.Error( + 404, + 'No such account ' + to_name + ' in ' + world + ); + + if (from.balance < amount && !cheat) + throw new Meteor.Error(409, 'Insufficient funds'); + + Ledger.update(from._id, { $inc: { balance: -amount } }); + Ledger.update(to._id, { $inc: { balance: amount } }); + } +}); + +/*****/ + +/// Helpers for "livedata - changing userid reruns subscriptions..." + +objectsWithUsers = new Mongo.Collection('objectsWithUsers'); + +if (Meteor.isServer) { + objectsWithUsers.remove({}); + objectsWithUsers.insert({ name: 'owned by none', ownerUserIds: [null] }); + objectsWithUsers.insert({ name: 'owned by one - a', ownerUserIds: ['1'] }); + objectsWithUsers.insert({ + name: 'owned by one/two - a', + ownerUserIds: ['1', '2'] + }); + objectsWithUsers.insert({ + name: 'owned by one/two - b', + ownerUserIds: ['1', '2'] + }); + objectsWithUsers.insert({ name: 'owned by two - a', ownerUserIds: ['2'] }); + objectsWithUsers.insert({ name: 'owned by two - b', ownerUserIds: ['2'] }); + + Meteor.publish('objectsWithUsers', function() { + return objectsWithUsers.find( + { ownerUserIds: this.userId }, + { fields: { ownerUserIds: 0 } } + ); + }); + + (function() { + const userIdWhenStopped = Object.create(null); + Meteor.publish('recordUserIdOnStop', function(key) { + check(key, String); + const self = this; + self.onStop(function() { + userIdWhenStopped[key] = self.userId; + }); + }); + + Meteor.methods({ + userIdWhenStopped: function(key) { + check(key, String); + return userIdWhenStopped[key]; + } + }); + })(); +} + +/*****/ + +/// Helper for "livedata - setUserId fails when called on server" + +if (Meteor.isServer) { + Meteor.startup(function() { + errorThrownWhenCallingSetUserIdDirectlyOnServer = null; + try { + Meteor.call('setUserId', '1000'); + } catch (e) { + errorThrownWhenCallingSetUserIdDirectlyOnServer = e; + } + }); +} + +/// Helper for "livedata - no setUserId after unblock" + +if (Meteor.isServer) { + Meteor.methods({ + setUserIdAfterUnblock: function() { + this.unblock(); + let threw = false; + const originalUserId = this.userId; + try { + // Calling setUserId after unblock should throw an error (and not mutate + // userId). + this.setUserId(originalUserId + 'bla'); + } catch (e) { + threw = true; + } + return threw && this.userId === originalUserId; + } + }); +} + +/*****/ + +/// Helper for "livedata - overlapping universal subs" + +if (Meteor.isServer) { + (function() { + const collName = 'overlappingUniversalSubs'; + const universalSubscribers = [[], []]; + + _.each([0, 1], function(index) { + Meteor.publish(null, function() { + const sub = this; + universalSubscribers[index].push(sub); + sub.onStop(function() { + universalSubscribers[index] = _.without( + universalSubscribers[index], + sub + ); + }); + }); + }); + + Meteor.methods({ + testOverlappingSubs: function(token) { + check(token, String); + _.each(universalSubscribers[0], function(sub) { + sub.added(collName, token, {}); + }); + _.each(universalSubscribers[1], function(sub) { + sub.added(collName, token, {}); + }); + _.each(universalSubscribers[0], function(sub) { + sub.removed(collName, token); + }); + } + }); + })(); +} + +/// Helper for "livedata - runtime universal sub creation" + +if (Meteor.isServer) { + Meteor.methods({ + runtimeUniversalSubCreation: function(token) { + check(token, String); + Meteor.publish(null, function() { + this.added('runtimeSubCreation', token, {}); + }); + } + }); +} + +/// Helper for "livedata - publisher errors" + +if (Meteor.isServer) { + Meteor.publish('publisherErrors', function(collName, options) { + check(collName, String); + // See below to see what options are accepted. + check(options, Object); + const sub = this; + + // First add a random item, which should be cleaned up. We use ready/onReady + // to make sure that the second test block is only called after the added is + // processed, so that there's any chance of the coll.find().count() failing. + sub.added(collName, Random.id(), { foo: 42 }); + sub.ready(); + + if (options.stopInHandler) { + sub.stop(); + return; + } + + let error; + if (options.internalError) { + error = new Error('Egads!'); + error._expectedByTest = true; // don't log + } else { + error = new Meteor.Error(412, 'Explicit error'); + } + if (options.throwInHandler) { + throw error; + } else if (options.errorInHandler) { + sub.error(error); + } else if (options.throwWhenUserIdSet) { + if (sub.userId) throw error; + } else if (options.errorLater) { + Meteor.defer(function() { + sub.error(error); + }); + } + }); +} + +/*****/ + +/// Helpers for "livedata - publish multiple cursors" +One = new Mongo.Collection('collectionOne'); +Two = new Mongo.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) { + // See below to see what options are accepted. + check(options, Object); + 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'; + }); +} + +/// Helper for "livedata - result by value" +const resultByValueArrays = Object.create(null); +Meteor.methods({ + getArray: function(testId) { + if (!_.has(resultByValueArrays, testId)) resultByValueArrays[testId] = []; + return resultByValueArrays[testId]; + }, + pushToArray: function(testId, value) { + if (!_.has(resultByValueArrays, testId)) resultByValueArrays[testId] = []; + resultByValueArrays[testId].push(value); + } +}); diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js new file mode 100644 index 0000000000..a5fae51937 --- /dev/null +++ b/packages/ddp-client/test/livedata_tests.js @@ -0,0 +1,1103 @@ +import { DDP } from '../common/namespace.js'; +import { Connection } from '../common/livedata_connection.js'; + +// XXX should check error codes +const failure = function(test, code, reason) { + return function(error, result) { + test.equal(result, undefined); + test.isTrue(error && typeof error === 'object'); + if (error && typeof error === 'object') { + if (typeof code === 'number') { + test.instanceOf(error, Meteor.Error); + code && test.equal(error.error, code); + reason && test.equal(error.reason, reason); + // XXX should check that other keys aren't present.. should + // probably use something like the Matcher we used to have + } else { + // for normal Javascript errors + test.instanceOf(error, Error); + test.equal(error.message, code); + } + } + }; +}; + +const failureOnStopped = function(test, code, reason) { + const f = failure(test, code, reason); + + return function(error) { + if (error) { + f(error); + } + }; +}; + +Tinytest.add('livedata - Meteor.Error', function(test) { + const error = new Meteor.Error(123, 'kittens', 'puppies'); + test.instanceOf(error, Meteor.Error); + test.instanceOf(error, Error); + test.equal(error.error, 123); + test.equal(error.reason, 'kittens'); + test.equal(error.details, 'puppies'); +}); + +if (Meteor.isServer) { + Tinytest.add('livedata - version negotiation', function(test) { + const versionCheck = function(clientVersions, serverVersions, expected) { + test.equal( + DDPServer._calculateVersion(clientVersions, serverVersions), + expected + ); + }; + + versionCheck(['A', 'B', 'C'], ['A', 'B', 'C'], 'A'); + versionCheck(['B', 'C'], ['A', 'B', 'C'], 'B'); + versionCheck(['A', 'B', 'C'], ['B', 'C'], 'B'); + versionCheck(['foo', 'bar', 'baz'], ['A', 'B', 'C'], 'A'); + }); +} + +Tinytest.add('livedata - methods with colliding names', function(test) { + const x = Random.id(); + const m = {}; + m[x] = function() {}; + Meteor.methods(m); + + test.throws(function() { + Meteor.methods(m); + }); +}); + +Tinytest.add('livedata - non-function method', function(test) { + const x = Random.id(); + const m = {}; + m[x] = 'kitten'; + + test.throws(function() { + Meteor.methods(m); + }); +}); + +const echoTest = function(item) { + return function(test, expect) { + 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 + ); + }; +}; + +testAsyncMulti('livedata - basic method invocation', [ + // Unknown methods + function(test, expect) { + if (Meteor.isServer) { + // On server, with no callback, throws exception + let ret; + let threw; + try { + ret = Meteor.call('unknown method'); + } catch (e) { + test.equal(e.error, 404); + threw = true; + } + test.isTrue(threw); + test.equal(ret, undefined); + } + + if (Meteor.isClient) { + // On client, with no callback, just returns undefined + const ret = Meteor.call('unknown method'); + test.equal(ret, undefined); + } + + // On either, with a callback, calls the callback and does not throw + const ret = Meteor.call( + 'unknown method', + expect(failure(test, 404, "Method 'unknown method' not found")) + ); + test.equal(ret, undefined); + }, + + function(test, expect) { + // make sure 'undefined' is preserved as such, instead of turning + // into null (JSON does not have 'undefined' so there is special + // code for this) + if (Meteor.isServer) test.equal(Meteor.call('nothing'), undefined); + if (Meteor.isClient) test.equal(Meteor.call('nothing'), undefined); + + test.equal(Meteor.call('nothing', expect(undefined, undefined)), undefined); + }, + + function(test, expect) { + if (Meteor.isServer) test.equal(Meteor.call('echo'), []); + if (Meteor.isClient) test.equal(Meteor.call('echo'), undefined); + + test.equal(Meteor.call('echo', expect(undefined, [])), undefined); + }, + + echoTest(new Date()), + echoTest({ d: new Date(), s: 'foobarbaz' }), + echoTest([new Date(), 'foobarbaz']), + echoTest(new Mongo.ObjectID()), + echoTest({ o: new Mongo.ObjectID() }), + echoTest({ $date: 30 }), // literal + echoTest({ $literal: { $date: 30 } }), + echoTest(12), + echoTest(Infinity), + echoTest(-Infinity), + + function(test, expect) { + if (Meteor.isServer) + test.equal(Meteor.call('echo', 12, { x: 13 }), [12, { x: 13 }]); + if (Meteor.isClient) + test.equal(Meteor.call('echo', 12, { x: 13 }), undefined); + + test.equal( + Meteor.call('echo', 12, { x: 13 }, expect(undefined, [12, { x: 13 }])), + undefined + ); + }, + + // test that `wait: false` is respected + function(test, expect) { + if (Meteor.isClient) { + // For test isolation + const token = Random.id(); + Meteor.apply( + 'delayedTrue', + [token], + { wait: false }, + expect(function(err, res) { + test.equal(res, false); + }) + ); + Meteor.apply('makeDelayedTrueImmediatelyReturnFalse', [token]); + } + }, + + // test that `wait: true` is respected + function(test, expect) { + if (Meteor.isClient) { + const token = Random.id(); + Meteor.apply( + 'delayedTrue', + [token], + { wait: true }, + expect(function(err, res) { + test.equal(res, true); + }) + ); + Meteor.apply('makeDelayedTrueImmediatelyReturnFalse', [token]); + } + }, + + function(test, expect) { + // No callback + + if (Meteor.isServer) { + test.throws(function() { + Meteor.call('exception', 'both'); + }); + test.throws(function() { + Meteor.call('exception', 'server'); + }); + // No exception, because no code will run on the client + test.equal(Meteor.call('exception', 'client'), undefined); + } + + if (Meteor.isClient) { + // The client exception is thrown away because it's in the + // stub. The server exception is throw away because we didn't + // give a callback. + test.equal(Meteor.call('exception', 'both'), undefined); + test.equal(Meteor.call('exception', 'server'), undefined); + test.equal(Meteor.call('exception', 'client'), undefined); + + // If we pass throwStubExceptions then we *should* see thrown exceptions + // on the client + test.throws(function() { + Meteor.apply('exception', ['both'], { throwStubExceptions: true }); + }); + test.equal( + Meteor.apply('exception', ['server'], { throwStubExceptions: true }), + undefined + ); + test.throws(function() { + Meteor.apply('exception', ['client'], { throwStubExceptions: true }); + }); + } + + // With callback + + if (Meteor.isClient) { + test.equal( + Meteor.call( + 'exception', + 'both', + expect(failure(test, 500, 'Internal server error')) + ), + undefined + ); + test.equal( + Meteor.call( + 'exception', + 'server', + expect(failure(test, 500, 'Internal server error')) + ), + undefined + ); + test.equal(Meteor.call('exception', 'client'), undefined); + } + + if (Meteor.isServer) { + test.equal( + Meteor.call( + 'exception', + 'both', + expect(failure(test, 'Test method throwing an exception')) + ), + undefined + ); + test.equal( + Meteor.call( + 'exception', + 'server', + expect(failure(test, 'Test method throwing an exception')) + ), + undefined + ); + test.equal(Meteor.call('exception', 'client'), undefined); + } + }, + + function(test, expect) { + if (Meteor.isServer) { + let threw = false; + try { + Meteor.call('exception', 'both', { intended: true }); + } catch (e) { + threw = true; + test.equal(e.error, 999); + test.equal(e.reason, 'Client-visible test exception'); + } + test.isTrue(threw); + threw = false; + try { + Meteor.call('exception', 'both', { + intended: true, + throwThroughFuture: true + }); + } catch (e) { + threw = true; + test.equal(e.error, 999); + test.equal(e.reason, 'Client-visible test exception'); + } + test.isTrue(threw); + } + + if (Meteor.isClient) { + test.equal( + Meteor.call( + 'exception', + 'both', + { intended: true }, + expect(failure(test, 999, 'Client-visible test exception')) + ), + undefined + ); + test.equal( + Meteor.call( + 'exception', + 'server', + { intended: true }, + expect(failure(test, 999, 'Client-visible test exception')) + ), + undefined + ); + test.equal( + Meteor.call( + 'exception', + 'server', + { + intended: true, + throwThroughFuture: true + }, + expect(failure(test, 999, 'Client-visible test exception')) + ), + undefined + ); + } + } +]); + +const checkBalances = function(test, a, b) { + const alice = Ledger.findOne({ name: 'alice', world: test.runId() }); + const bob = Ledger.findOne({ name: 'bob', world: test.runId() }); + test.equal(alice.balance, a); + test.equal(bob.balance, b); +}; + +// would be nice to have a database-aware test harness of some kind -- +// this is a big hack (and XXX pollutes the global test namespace) +testAsyncMulti('livedata - compound methods', [ + function(test, expect) { + if (Meteor.isClient) Meteor.subscribe('ledger', test.runId(), expect()); + + Ledger.insert( + { name: 'alice', balance: 100, world: test.runId() }, + expect(function() {}) + ); + Ledger.insert( + { name: 'bob', balance: 50, world: test.runId() }, + expect(function() {}) + ); + }, + function(test, expect) { + Meteor.call( + 'ledger/transfer', + test.runId(), + 'alice', + 'bob', + 10, + expect(function(err, result) { + test.equal(err, undefined); + test.equal(result, undefined); + checkBalances(test, 90, 60); + }) + ); + checkBalances(test, 90, 60); + }, + function(test, expect) { + Meteor.call( + 'ledger/transfer', + test.runId(), + 'alice', + 'bob', + 100, + true, + expect(function(err, result) { + failure(test, 409)(err, result); + // Balances are reverted back to pre-stub values. + checkBalances(test, 90, 60); + }) + ); + + if (Meteor.isClient) + // client can fool itself by cheating, but only until the sync + // finishes + checkBalances(test, -10, 160); + else checkBalances(test, 90, 60); + } +]); + +// Replaces the Connection's `_livedata_data` method to push incoming +// messages on a given collection to an array. This can be used to +// verify that the right data is sent on the wire +// +// @param messages {Array} The array to which to append the messages +// @return {Function} A function to call to undo the eavesdropping +const eavesdropOnCollection = function( + livedata_connection, + collection_name, + messages +) { + const old_livedata_data = _.bind( + livedata_connection._livedata_data, + livedata_connection + ); + + // Kind of gross since all tests past this one will run with this + // hook set up. That's probably fine since we only check a specific + // collection but still... + // + // Should we consider having a separate connection per Tinytest or + // some similar scheme? + livedata_connection._livedata_data = function(msg) { + if (msg.collection && msg.collection === collection_name) { + messages.push(msg); + } + old_livedata_data(msg); + }; + + return function() { + livedata_connection._livedata_data = old_livedata_data; + }; +}; + +if (Meteor.isClient) { + testAsyncMulti( + 'livedata - changing userid reruns subscriptions without flapping data on the wire', + [ + function(test, expect) { + const messages = []; + const undoEavesdrop = eavesdropOnCollection( + Meteor.connection, + 'objectsWithUsers', + messages + ); + + // A helper for testing incoming set and unset messages + // XXX should this be extracted as a general helper together with + // eavesdropOnCollection? + const expectMessages = function( + expectedAddedMessageCount, + expectedRemovedMessageCount, + expectedNamesInCollection + ) { + let actualAddedMessageCount = 0; + let actualRemovedMessageCount = 0; + _.each(messages, function(msg) { + if (msg.msg === 'added') ++actualAddedMessageCount; + else if (msg.msg === 'removed') ++actualRemovedMessageCount; + else test.fail({ unexpected: JSON.stringify(msg) }); + }); + test.equal(actualAddedMessageCount, expectedAddedMessageCount); + test.equal(actualRemovedMessageCount, expectedRemovedMessageCount); + expectedNamesInCollection.sort(); + test.equal( + _.pluck( + objectsWithUsers.find({}, { sort: ['name'] }).fetch(), + 'name' + ), + expectedNamesInCollection + ); + messages.length = 0; // clear messages without creating a new object + }; + + // make sure we're not already logged in. can happen if accounts + // tests fail oddly. + Meteor.apply( + 'setUserId', + [null], + { wait: true }, + expect(function() {}) + ); + + let afterFirstSetUserId; + let afterSecondSetUserId; + let afterThirdSetUserId; + + Meteor.subscribe( + 'objectsWithUsers', + expect(function() { + expectMessages(1, 0, ['owned by none']); + Meteor.apply( + 'setUserId', + ['1'], + { wait: true }, + afterFirstSetUserId + ); + }) + ); + + afterFirstSetUserId = expect(function() { + expectMessages(3, 1, [ + 'owned by one - a', + 'owned by one/two - a', + 'owned by one/two - b' + ]); + Meteor.apply( + 'setUserId', + ['2'], + { wait: true }, + afterSecondSetUserId + ); + }); + + afterSecondSetUserId = expect(function() { + expectMessages(2, 1, [ + 'owned by one/two - a', + 'owned by one/two - b', + 'owned by two - a', + 'owned by two - b' + ]); + Meteor.apply('setUserId', ['2'], { wait: true }, afterThirdSetUserId); + }); + + afterThirdSetUserId = expect(function() { + // Nothing should have been sent since the results of the + // query are the same ("don't flap data on the wire") + expectMessages(0, 0, [ + 'owned by one/two - a', + 'owned by one/two - b', + 'owned by two - a', + 'owned by two - b' + ]); + undoEavesdrop(); + }); + }, + function(test, expect) { + const key = Random.id(); + Meteor.subscribe('recordUserIdOnStop', key); + Meteor.apply( + 'setUserId', + ['100'], + { wait: true }, + expect(function() {}) + ); + Meteor.apply( + 'setUserId', + ['101'], + { wait: true }, + expect(function() {}) + ); + Meteor.call( + 'userIdWhenStopped', + key, + expect(function(err, result) { + test.isFalse(err); + test.equal(result, '100'); + }) + ); + // clean up + Meteor.apply( + 'setUserId', + [null], + { wait: true }, + expect(function() {}) + ); + } + ] + ); +} + +Tinytest.add('livedata - setUserId error when called from server', function( + test +) { + if (Meteor.isServer) { + test.equal( + errorThrownWhenCallingSetUserIdDirectlyOnServer.message, + "Can't call setUserId on a server initiated method call" + ); + } +}); + +let pubHandles; +if (Meteor.isServer) { + pubHandles = {}; +} + +Meteor.methods({ + 'livedata/setup': function(id) { + check(id, String); + if (Meteor.isServer) { + pubHandles[id] = {}; + Meteor.publish('pub1' + id, function() { + pubHandles[id].pub1 = this; + this.ready(); + }); + Meteor.publish('pub2' + id, function() { + pubHandles[id].pub2 = this; + this.ready(); + }); + } + }, + 'livedata/pub1go': function(id) { + check(id, String); + if (Meteor.isServer) { + pubHandles[id].pub1.added('MultiPubCollection' + id, 'foo', { a: 'aa' }); + return 1; + } + return 0; + }, + 'livedata/pub2go': function(id) { + check(id, String); + if (Meteor.isServer) { + pubHandles[id].pub2.added('MultiPubCollection' + id, 'foo', { b: 'bb' }); + return 2; + } + return 0; + } +}); + +if (Meteor.isClient) { + (function() { + let MultiPub; + const id = Random.id(); + testAsyncMulti('livedata - added from two different subs', [ + function(test, expect) { + Meteor.call('livedata/setup', id, expect(function() {})); + }, + function(test, expect) { + MultiPub = new Mongo.Collection('MultiPubCollection' + id); + const sub1 = Meteor.subscribe('pub1' + id, expect(function() {})); + const sub2 = Meteor.subscribe('pub2' + id, expect(function() {})); + }, + function(test, expect) { + Meteor.call( + 'livedata/pub1go', + id, + expect(function(err, res) { + test.equal(res, 1); + }) + ); + }, + function(test, expect) { + test.equal(MultiPub.findOne('foo'), { _id: 'foo', a: 'aa' }); + }, + function(test, expect) { + Meteor.call( + 'livedata/pub2go', + id, + expect(function(err, res) { + test.equal(res, 2); + }) + ); + }, + function(test, expect) { + test.equal(MultiPub.findOne('foo'), { _id: 'foo', a: 'aa', b: 'bb' }); + } + ]); + })(); +} + +if (Meteor.isClient) { + testAsyncMulti('livedata - overlapping universal subs', [ + function(test, expect) { + const coll = new Mongo.Collection('overlappingUniversalSubs'); + const token = Random.id(); + test.isFalse(coll.findOne(token)); + Meteor.call( + 'testOverlappingSubs', + token, + expect(function(err) { + test.isFalse(err); + test.isTrue(coll.findOne(token)); + }) + ); + } + ]); + + testAsyncMulti('livedata - runtime universal sub creation', [ + function(test, expect) { + const coll = new Mongo.Collection('runtimeSubCreation'); + const token = Random.id(); + test.isFalse(coll.findOne(token)); + Meteor.call( + 'runtimeUniversalSubCreation', + token, + expect(function(err) { + test.isFalse(err); + test.isTrue(coll.findOne(token)); + }) + ); + } + ]); + + testAsyncMulti('livedata - no setUserId after unblock', [ + function(test, expect) { + Meteor.call( + 'setUserIdAfterUnblock', + expect(function(err, result) { + test.isFalse(err); + test.isTrue(result); + }) + ); + } + ]); + + testAsyncMulti( + 'livedata - publisher errors with onError callback', + (function() { + let conn, collName, coll; + let errorFromRerun; + let gotErrorFromStopper = false; + return [ + function(test, expect) { + // Use a separate connection so that we can safely check to see if + // conn._subscriptions is empty. + conn = new Connection('/', { + reloadWithOutstanding: true + }); + collName = Random.id(); + coll = new Mongo.Collection(collName, { connection: conn }); + + const testSubError = function(options) { + conn.subscribe('publisherErrors', collName, options, { + onReady: expect(), + onError: expect( + failure( + test, + options.internalError ? 500 : 412, + options.internalError + ? 'Internal server error' + : 'Explicit error' + ) + ) + }); + }; + testSubError({ throwInHandler: true }); + testSubError({ throwInHandler: true, internalError: true }); + testSubError({ errorInHandler: true }); + testSubError({ errorInHandler: true, internalError: true }); + testSubError({ errorLater: true }); + testSubError({ errorLater: true, internalError: true }); + }, + function(test, expect) { + test.equal(coll.find().count(), 0); + test.equal(_.size(conn._subscriptions), 0); // white-box test + + conn.subscribe( + 'publisherErrors', + collName, + { throwWhenUserIdSet: true }, + { + onReady: expect(), + onError: function(error) { + errorFromRerun = error; + } + } + ); + }, + function(test, expect) { + // Because the last subscription is ready, we should have a document. + test.equal(coll.find().count(), 1); + test.isFalse(errorFromRerun); + test.equal(_.size(conn._subscriptions), 1); // white-box test + conn.call('setUserId', 'bla', expect(function() {})); + }, + function(test, expect) { + // Now that we've re-run, we should have stopped the subscription, + // gotten a error, and lost the document. + test.equal(coll.find().count(), 0); + test.isTrue(errorFromRerun); + test.instanceOf(errorFromRerun, Meteor.Error); + test.equal(errorFromRerun.error, 412); + test.equal(errorFromRerun.reason, 'Explicit error'); + test.equal(_.size(conn._subscriptions), 0); // white-box test + + conn.subscribe( + 'publisherErrors', + collName, + { stopInHandler: true }, + { + onError: function() { + gotErrorFromStopper = true; + } + } + ); + // Call a method. This method won't be processed until the publisher's + // function returns, so blocking on it being done ensures that we've + // gotten the removed/nosub/etc. + conn.call('nothing', expect(function() {})); + }, + function(test, expect) { + test.equal(coll.find().count(), 0); + // sub.stop does NOT call onError. + test.isFalse(gotErrorFromStopper); + test.equal(_.size(conn._subscriptions), 0); // white-box test + conn._stream.disconnect({ _permanent: true }); + } + ]; + })() + ); + + testAsyncMulti( + 'livedata - publisher errors with onStop callback', + (function() { + let conn, collName, coll; + let errorFromRerun; + let gotErrorFromStopper = false; + return [ + function(test, expect) { + // Use a separate connection so that we can safely check to see if + // conn._subscriptions is empty. + conn = new Connection('/', { + reloadWithOutstanding: true + }); + collName = Random.id(); + coll = new Mongo.Collection(collName, { connection: conn }); + + const testSubError = function(options) { + conn.subscribe('publisherErrors', collName, options, { + onReady: expect(), + onStop: expect( + failureOnStopped( + test, + options.internalError ? 500 : 412, + options.internalError + ? 'Internal server error' + : 'Explicit error' + ) + ) + }); + }; + testSubError({ throwInHandler: true }); + testSubError({ throwInHandler: true, internalError: true }); + testSubError({ errorInHandler: true }); + testSubError({ errorInHandler: true, internalError: true }); + testSubError({ errorLater: true }); + testSubError({ errorLater: true, internalError: true }); + }, + function(test, expect) { + test.equal(coll.find().count(), 0); + test.equal(_.size(conn._subscriptions), 0); // white-box test + + conn.subscribe( + 'publisherErrors', + collName, + { throwWhenUserIdSet: true }, + { + onReady: expect(), + onStop: function(error) { + errorFromRerun = error; + } + } + ); + }, + function(test, expect) { + // Because the last subscription is ready, we should have a document. + test.equal(coll.find().count(), 1); + test.isFalse(errorFromRerun); + test.equal(_.size(conn._subscriptions), 1); // white-box test + conn.call('setUserId', 'bla', expect(function() {})); + }, + function(test, expect) { + // Now that we've re-run, we should have stopped the subscription, + // gotten a error, and lost the document. + test.equal(coll.find().count(), 0); + test.isTrue(errorFromRerun); + test.instanceOf(errorFromRerun, Meteor.Error); + test.equal(errorFromRerun.error, 412); + test.equal(errorFromRerun.reason, 'Explicit error'); + test.equal(_.size(conn._subscriptions), 0); // white-box test + + conn.subscribe( + 'publisherErrors', + collName, + { stopInHandler: true }, + { + onStop: function(error) { + if (error) { + gotErrorFromStopper = true; + } + } + } + ); + // Call a method. This method won't be processed until the publisher's + // function returns, so blocking on it being done ensures that we've + // gotten the removed/nosub/etc. + conn.call('nothing', expect(function() {})); + }, + function(test, expect) { + test.equal(coll.find().count(), 0); + // sub.stop does NOT call onError. + test.isFalse(gotErrorFromStopper); + test.equal(_.size(conn._subscriptions), 0); // white-box test + conn._stream.disconnect({ _permanent: true }); + } + ]; + })() + ); + + testAsyncMulti('livedata - publish multiple cursors', [ + function(test, expect) { + const sub = Meteor.subscribe( + 'multiPublish', + { normal: 1 }, + { + onReady: expect(function() { + test.isTrue(sub.ready()); + 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')) + } + ); + } + ]); +} + +const selfUrl = Meteor.isServer + ? Meteor.absoluteUrl() + : Meteor._relativeToSiteRootUrl('/'); + +if (Meteor.isServer) { + Meteor.methods({ + s2s: function(arg) { + check(arg, String); + return 's2s ' + arg; + } + }); +} +(function() { + testAsyncMulti('livedata - connect works from both client and server', [ + function(test, expect) { + const self = this; + self.conn = DDP.connect(selfUrl); + pollUntil( + expect, + function() { + return self.conn.status().connected; + }, + 10000 + ); + }, + + function(test, expect) { + const self = this; + if (self.conn.status().connected) { + self.conn.call( + 's2s', + 'foo', + expect(function(err, res) { + if (err) throw err; + test.equal(res, 's2s foo'); + }) + ); + } + } + ]); +})(); + +if (Meteor.isServer) { + (function() { + testAsyncMulti('livedata - method call on server blocks in a fiber way', [ + function(test, expect) { + const self = this; + self.conn = DDP.connect(selfUrl); + pollUntil( + expect, + function() { + return self.conn.status().connected; + }, + 10000 + ); + }, + + function(test, expect) { + const self = this; + if (self.conn.status().connected) { + test.equal(self.conn.call('s2s', 'foo'), 's2s foo'); + } + } + ]); + })(); +} + +(function() { + testAsyncMulti('livedata - connect fails to unknown place', [ + function(test, expect) { + const self = this; + self.conn = DDP.connect('example.com', { _dontPrintErrors: true }); + Meteor.setTimeout( + expect(function() { + test.isFalse(self.conn.status().connected, 'Not connected'); + self.conn.close(); + }), + 500 + ); + } + ]); +})(); + +if (Meteor.isServer) { + Meteor.publish('publisherCloning', function() { + const self = this; + const fields = { x: { y: 42 } }; + self.added('publisherCloning', 'a', fields); + fields.x.y = 43; + self.changed('publisherCloning', 'a', fields); + self.ready(); + }); +} else { + const PublisherCloningCollection = new Mongo.Collection('publisherCloning'); + testAsyncMulti('livedata - publish callbacks clone', [ + function(test, expect) { + Meteor.subscribe( + 'publisherCloning', + { normal: 1 }, + { + onReady: expect(function() { + test.equal(PublisherCloningCollection.findOne(), { + _id: 'a', + x: { y: 43 } + }); + }), + onError: failure() + } + ); + } + ]); +} + +testAsyncMulti('livedata - result by value', [ + function(test, expect) { + const self = this; + self.testId = Random.id(); + Meteor.call( + 'getArray', + self.testId, + expect(function(error, firstResult) { + test.isFalse(error); + test.isTrue(firstResult); + self.firstResult = firstResult; + }) + ); + }, + function(test, expect) { + const self = this; + Meteor.call( + 'pushToArray', + self.testId, + 'xxx', + expect(function(error) { + test.isFalse(error); + }) + ); + }, + function(test, expect) { + const self = this; + Meteor.call( + 'getArray', + self.testId, + expect(function(error, secondResult) { + test.isFalse(error); + test.equal(self.firstResult.length + 1, secondResult.length); + }) + ); + } +]); + +// XXX some things to test in greater detail: +// staying in simulation mode +// time warp +// serialization / beginAsync(true) / beginAsync(false) +// malformed messages (need raw wire access) +// method completion/satisfaction +// subscriptions (multiple APIs, including autorun?) +// subscription completion +// subscription attribute shadowing +// server method calling methods on other server (eg, should simulate) +// subscriptions and methods being idempotent +// reconnection +// reconnection not resulting in method re-execution +// reconnection tolerating all kinds of lost messages (including data) +// [probably lots more] diff --git a/packages/ddp-client/test/random_stream_tests.js b/packages/ddp-client/test/random_stream_tests.js new file mode 100644 index 0000000000..03b1ce05ec --- /dev/null +++ b/packages/ddp-client/test/random_stream_tests.js @@ -0,0 +1,44 @@ +Tinytest.add('livedata - DDP.randomStream', function(test) { + const randomSeed = Random.id(); + const context = { randomSeed: randomSeed }; + + let sequence = DDP._CurrentMethodInvocation.withValue(context, function() { + return DDP.randomStream('1'); + }); + + let seeds = sequence.alea.args; + + test.equal(seeds.length, 2); + test.equal(seeds[0], randomSeed); + test.equal(seeds[1], '1'); + + const id1 = sequence.id(); + + // Clone the sequence by building it the same way RandomStream.get does + const sequenceClone = Random.createWithSeeds.apply(null, seeds); + const id1Cloned = sequenceClone.id(); + const id2Cloned = sequenceClone.id(); + test.equal(id1, id1Cloned); + + // We should get the same sequence when we use the same key + sequence = DDP._CurrentMethodInvocation.withValue(context, function() { + return DDP.randomStream('1'); + }); + seeds = sequence.alea.args; + test.equal(seeds.length, 2); + test.equal(seeds[0], randomSeed); + test.equal(seeds[1], '1'); + + // But we should be at the 'next' position in the stream + const id2 = sequence.id(); + + // Technically these could be equal, but likely to be a bug if hit + // http://search.dilbert.com/comic/Random%20Number%20Generator + test.notEqual(id1, id2); + + test.equal(id2, id2Cloned); +}); + +Tinytest.add('livedata - DDP.randomStream with no-args', function(test) { + DDP.randomStream().id(); +}); diff --git a/packages/ddp-client/test/stub_stream.js b/packages/ddp-client/test/stub_stream.js new file mode 100644 index 0000000000..1b0186a348 --- /dev/null +++ b/packages/ddp-client/test/stub_stream.js @@ -0,0 +1,57 @@ +StubStream = function() { + const self = this; + + self.sent = []; + self.callbacks = Object.create(null); +}; + +_.extend(StubStream.prototype, { + // Methods from Stream + on: function(name, callback) { + const self = this; + + if (!self.callbacks[name]) self.callbacks[name] = [callback]; + else self.callbacks[name].push(callback); + }, + + send: function(data) { + const self = this; + self.sent.push(data); + }, + + status: function() { + return { status: 'connected', fake: true }; + }, + + reconnect: function() { + // no-op + }, + + _lostConnection: function() { + // no-op + }, + + // Methods for tests + receive: function(data) { + const self = this; + + if (typeof data === 'object') { + data = EJSON.stringify(data); + } + + _.each(self.callbacks['message'], function(cb) { + cb(data); + }); + }, + + reset: function() { + const self = this; + _.each(self.callbacks['reset'], function(cb) { + cb(); + }); + }, + + // Provide a tag to detect stub streams. + // We don't log heartbeat failures on stub streams, for example. + _isStub: true +}); From c73571a49afbbe64800cf639d4eb661b1202edc2 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 10 Nov 2022 16:58:46 -0300 Subject: [PATCH 0050/1965] Separate ddp-client packages into two packages - packages/ddp-client: current Fibers implementation; - Packages/ddp-client-async: New version without Fibers (Work in progress) --- packages/ddp-client/package.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 0cdc77a953..64cbf8d09e 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -9,6 +9,11 @@ Npm.depends({ }); Package.onUse((api) => { + if (process.env.DISABLE_FIBERS) { + api.use('ddp-client-async'); + api.export('DDP', 'server'); + return; + } api.use([ 'check', 'random', From 578094e5b62ca2656d36101e55851e8bdaf7d1b7 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 11 Nov 2022 18:54:45 -0300 Subject: [PATCH 0051/1965] Fix issues related to mongo tests - also changed the upsert_compatibility_test.js. --- packages/meteor/asl-helpers-client.js | 3 + packages/meteor/asl-helpers.js | 2 +- packages/meteor/fiber_helpers.js | 5 +- packages/meteor/package.js | 1 + packages/mongo-async/collection.js | 96 +++---------------- packages/mongo-async/doc_fetcher.js | 2 +- packages/mongo-async/mongo_driver.js | 12 +-- .../mongo-async/upsert_compatibility_test.js | 58 +++++------ 8 files changed, 57 insertions(+), 122 deletions(-) create mode 100644 packages/meteor/asl-helpers-client.js diff --git a/packages/meteor/asl-helpers-client.js b/packages/meteor/asl-helpers-client.js new file mode 100644 index 0000000000..688ae22205 --- /dev/null +++ b/packages/meteor/asl-helpers-client.js @@ -0,0 +1,3 @@ +Meteor._isPromise = (r) => { + return r && typeof r.then === 'function'; +}; diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js index 3701538c04..0d4ab7688a 100644 --- a/packages/meteor/asl-helpers.js +++ b/packages/meteor/asl-helpers.js @@ -16,7 +16,7 @@ Meteor._runAsync = (fn, ctx) => { }).run(); } - global.asyncLocalStorage.run(Meteor._getAslStore(), () => { + return global.asyncLocalStorage.run(Meteor._getAslStore(), () => { fn.call(ctx); }); }; diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 431f3543d7..7a400c1da6 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -103,7 +103,7 @@ class AsynchronousQueue { await this._scheduleRun(); } - runTask(task) { + async runTask(task) { const handle = { task: Meteor.bindEnvironment(task, function(e) { Meteor._debug('Exception from task', e); @@ -112,6 +112,9 @@ class AsynchronousQueue { name: task.name }; this._taskHandles.push(handle); + + // XXX: We should be doing this a different way. + await Meteor._sleepForMs(10); return this._scheduleRun(); } diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 504a2231e7..c8a131bfa4 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -36,6 +36,7 @@ Package.onUse(function (api) { api.addFiles('asl-helpers.js', 'server'); api.addFiles('fiber_helpers.js', 'server'); api.addFiles('fiber_stubs_client.js', 'client'); + api.addFiles('asl-helpers-client.js', 'client'); api.addFiles('startup_client.js', ['client']); api.addFiles('startup_server.js', ['server']); api.addFiles('debug.js', ['client', 'server']); diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 0209ff29ef..b6663e3682 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -496,8 +496,7 @@ Object.assign(Mongo.Collection.prototype, { // generating their result until the database has acknowledged // them. In the future maybe we should provide a flag to turn this // off. - - _insertSync(doc, callback) { + _insert(doc, callback) { // Make sure we were passed a document to insert if (!doc) { throw new Error('insert requires an argument'); @@ -539,6 +538,8 @@ Object.assign(Mongo.Collection.prototype, { // On inserts, always return the id that we generated; on all other // operations, just return the result from the collection. var chooseReturnValueFromCollectionResult = function(result) { + if (Meteor._isPromise(result)) return result; + if (doc._id) { return doc._id; } @@ -567,88 +568,15 @@ Object.assign(Mongo.Collection.prototype, { // If the user provided a callback and the collection implements this // operation asynchronously, then queryRet will be undefined, and the // result will be returned through the callback instead. - const result = this._collection.insert(doc, wrappedCallback); - return chooseReturnValueFromCollectionResult(result); - } catch (e) { - if (callback) { - callback(e); - return null; - } - throw e; - } - }, - - async _insertAsync(doc, callback) { - // Make sure we were passed a document to insert - if (!doc) { - throw new Error('insert requires an argument'); - } - - // Make a shallow clone of the document, preserving its prototype. - doc = Object.create( - Object.getPrototypeOf(doc), - Object.getOwnPropertyDescriptors(doc) - ); - - if ('_id' in doc) { - if ( - !doc._id || - !(typeof doc._id === 'string' || doc._id instanceof Mongo.ObjectID) - ) { - throw new Error( - 'Meteor requires document _id fields to be non-empty strings or ObjectIDs' - ); - } - } else { - let generateId = true; - - // Don't generate the id if we're the client and the 'outermost' call - // This optimization saves us passing both the randomSeed and the id - // Passing both is redundant. - if (this._isRemoteCollection()) { - const enclosing = DDP._CurrentMethodInvocation.get(); - if (!enclosing) { - generateId = false; - } + let result; + if (!!wrappedCallback) { + result = this._collection.insert(doc, wrappedCallback); + } else { + // If we don't have the callback, we assume the user is using the promise. + // We can't just pass this._collection.insert to the promisify because it would lose the context. + result = Meteor.promisify((cb) => this._collection.insert(doc, cb))(); } - if (generateId) { - doc._id = this._makeNewID(); - } - } - - // On inserts, always return the id that we generated; on all other - // operations, just return the result from the collection. - var chooseReturnValueFromCollectionResult = function(result) { - if (doc._id) { - return doc._id; - } - - // XXX what is this for?? - // It's some iteraction between the callback to _callMutatorMethod and - // the return value conversion - doc._id = result; - - return result; - }; - - const wrappedCallback = wrapCallback( - callback, - chooseReturnValueFromCollectionResult - ); - - if (this._isRemoteCollection()) { - const result = this._callMutatorMethod('insert', [doc], wrappedCallback); - return chooseReturnValueFromCollectionResult(result); - } - - // it's my collection. descend into the collection object - // and propagate any exception. - try { - // If the user provided a callback and the collection implements this - // operation asynchronously, then queryRet will be undefined, and the - // result will be returned through the callback instead. - const result = await this._collection.insert(doc, wrappedCallback); return chooseReturnValueFromCollectionResult(result); } catch (e) { if (callback) { @@ -669,7 +597,7 @@ Object.assign(Mongo.Collection.prototype, { * @param {Function} [callback] Optional. If present, called with an error object as the first argument and, if no error, the _id as the second. */ insert(doc, callback) { - return this._insertAsync(doc, callback); + return this._insert(doc, callback); }, /** @@ -762,7 +690,7 @@ Object.assign(Mongo.Collection.prototype, { return this._callMutatorMethod('remove', [selector], wrappedCallback); } - // it's my collection. descend into the collection object + // it's my collection. descend into the collection1 object // and propagate any exception. try { // If the user provided a callback and the collection implements this diff --git a/packages/mongo-async/doc_fetcher.js b/packages/mongo-async/doc_fetcher.js index 3d4740bf34..0fc7d06ab8 100644 --- a/packages/mongo-async/doc_fetcher.js +++ b/packages/mongo-async/doc_fetcher.js @@ -30,7 +30,7 @@ export class DocFetcher { const callbacks = [callback]; self._callbacksForOp.set(op, callbacks); - Meteor._runAsync(async function () { + return Meteor._runAsync(async function () { try { var doc = await self._mongoConnection.findOne( collectionName, {_id: id}) || null; diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index 0c1a9cfd1c..1bd3576977 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -1057,8 +1057,8 @@ MongoConnection.prototype._createSynchronousCursor = function( */ class AsynchronousCursor { constructor(dbCursor, cursorDescription, options) { - this._cursor = dbCursor; - this._cursorDescription = cursorDescription + this._dbCursor = dbCursor; + this._cursorDescription = cursorDescription; this._selfForIteration = options.selfForIteration || this; if (options.useTransform && cursorDescription.options.transform) { @@ -1079,7 +1079,7 @@ class AsynchronousCursor { // the Mongo->Meteor type replacement). async _rawNextObjectPromise() { try { - return this._cursor.next(); + return this._dbCursor.next(); } catch (e) { console.error(e); } @@ -1158,14 +1158,14 @@ class AsynchronousCursor { _rewind() { // known to be synchronous - this._cursor.rewind(); + this._dbCursor.rewind(); this._visitedIds = new LocalCollection._IdMap; } // Mostly usable for tailable cursors. close() { - this._cursor.close(); + this._dbCursor.close(); } fetch() { @@ -1178,7 +1178,7 @@ class AsynchronousCursor { * `collection.countDocuments` instead. */ count() { - return this._cursor.count(); + return this._dbCursor.count(); } // This method is NOT wrapped in Cursor. diff --git a/packages/mongo-async/upsert_compatibility_test.js b/packages/mongo-async/upsert_compatibility_test.js index dab3c6b3d3..d15ec03490 100644 --- a/packages/mongo-async/upsert_compatibility_test.js +++ b/packages/mongo-async/upsert_compatibility_test.js @@ -1,10 +1,10 @@ -Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS update', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type MONGO with MODIFIERS update', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); coll.insert({foo: 1}); - var result = coll.upsert({foo: 1}, {$set: {foo:2}}); - var updated = coll.findOne({foo: 2}); + var result = await coll.upsert({foo: 1}, {$set: {foo:2}}); + var updated = await coll.findOne({foo: 2}); test.equal(result.insertedId, undefined); test.equal(result.numberAffected, 1); @@ -15,12 +15,12 @@ Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS upda test.equal(EJSON.equals(updated, {foo: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS insert', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type MONGO with MODIFIERS insert', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); - var result = coll.upsert({foo: 1}, {$set: {bar:2}}); - var inserted = coll.findOne({foo: 1}); + var result = await coll.upsert({foo: 1}, {$set: {bar:2}}); + var inserted = await coll.findOne({foo: 1}); test.isTrue(result.insertedId !== undefined); test.equal(result.numberAffected, 1); @@ -32,13 +32,13 @@ Tinytest.add('mongo livedata - native upsert - id type MONGO with MODIFIERS inse test.equal(EJSON.equals(inserted, {foo: 1, bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT update', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type MONGO PLAIN OBJECT update', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); coll.insert({foo: 1, baz: 42}); - var result = coll.upsert({foo: 1}, {bar:2}); - var updated = coll.findOne({bar: 2}); + var result = await coll.upsert({foo: 1}, {bar:2}); + var updated = await coll.findOne({bar: 2}); test.isTrue(result.insertedId === undefined); test.equal(result.numberAffected, 1); @@ -49,12 +49,12 @@ Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT update test.equal(EJSON.equals(updated, {bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT insert', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type MONGO PLAIN OBJECT insert', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); - var result = coll.upsert({foo: 1}, {bar:2}); - var inserted = coll.findOne({bar: 2}); + var result = await coll.upsert({foo: 1}, {bar:2}); + var inserted = await coll.findOne({bar: 2}); test.isTrue(result.insertedId !== undefined); test.equal(result.numberAffected, 1); @@ -67,13 +67,13 @@ Tinytest.add('mongo livedata - native upsert - id type MONGO PLAIN OBJECT insert test.equal(EJSON.equals(inserted, {bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS update', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type STRING with MODIFIERS update', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); - coll.insert({foo: 1}); - var result = coll.upsert({foo: 1}, {$set: {foo:2}}); - var updated = coll.findOne({foo: 2}); + await coll.insert({foo: 1}); + var result = await coll.upsert({foo: 1}, {$set: {foo:2}}); + var updated = await coll.findOne({foo: 2}); test.equal(result.insertedId, undefined); test.equal(result.numberAffected, 1); @@ -84,12 +84,12 @@ Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS upd test.equal(EJSON.equals(updated, {foo: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS insert', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type STRING with MODIFIERS insert', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); - var result = coll.upsert({foo: 1}, {$set: {bar:2}}); - var inserted = coll.findOne({foo: 1}); + var result = await coll.upsert({foo: 1}, {$set: {bar:2}}); + var inserted = await coll.findOne({foo: 1}); test.isTrue(result.insertedId !== undefined); test.equal(result.numberAffected, 1); @@ -101,13 +101,13 @@ Tinytest.add('mongo livedata - native upsert - id type STRING with MODIFIERS ins test.equal(EJSON.equals(inserted, {foo: 1, bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT update', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type STRING PLAIN OBJECT update', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); - coll.insert({foo: 1, baz: 42}); - var result = coll.upsert({foo: 1}, {bar:2}); - var updated = coll.findOne({bar: 2}); + await coll.insert({foo: 1, baz: 42}); + var result = await coll.upsert({foo: 1}, {bar:2}); + var updated = await coll.findOne({bar: 2}); test.isTrue(result.insertedId === undefined); test.equal(result.numberAffected, 1); @@ -118,12 +118,12 @@ Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT updat test.equal(EJSON.equals(updated, {bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT insert', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - id type STRING PLAIN OBJECT insert', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'STRING'}); - var result = coll.upsert({foo: 1}, {bar:2}); - var inserted = coll.findOne({bar: 2}); + var result = await coll.upsert({foo: 1}, {bar:2}); + var inserted = await coll.findOne({bar: 2}); test.isTrue(result.insertedId !== undefined); test.equal(result.numberAffected, 1); @@ -135,12 +135,12 @@ Tinytest.add('mongo livedata - native upsert - id type STRING PLAIN OBJECT inser test.equal(EJSON.equals(inserted, {bar: 2}), true); }); -Tinytest.add('mongo livedata - native upsert - MONGO passing id insert', function (test) { +Tinytest.addAsync('mongo livedata - native upsert - MONGO passing id insert', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('native_upsert_'+collName, {idGeneration: 'MONGO'}); - var result = coll.upsert({foo: 1}, {_id: 'meu id'}); - var inserted = coll.findOne({_id: 'meu id'}); + var result = await coll.upsert({foo: 1}, {_id: 'meu id'}); + var inserted = await coll.findOne({_id: 'meu id'}); test.equal(result.insertedId, 'meu id'); test.equal(result.numberAffected, 1); From b8f2c071190c829975d22ef71f8a1f086c7bb75f Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 17 Nov 2022 17:43:48 -0300 Subject: [PATCH 0052/1965] Fixing tests on mongo --- packages/minimongo/local_collection.js | 18 +- packages/mongo-async/collection.js | 2 +- packages/mongo-async/collection_tests.js | 140 +++++----- packages/mongo-async/doc_fetcher_tests.js | 6 +- packages/mongo-async/mongo_driver.js | 2 +- packages/mongo-async/oplog_tests.js | 313 +++++++++++----------- packages/test-helpers/async_multi.js | 4 +- packages/test-helpers/callback_logger.js | 9 +- 8 files changed, 244 insertions(+), 250 deletions(-) diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 1466e0a783..bce810a2be 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -139,15 +139,15 @@ export default class LocalCollection { } }); - this._observeQueue.drain(); - - // Defer because the caller likely doesn't expect the callback to be run - // immediately. - if (callback) { - Meteor.defer(() => { - callback(null, id); - }); - } + this._observeQueue.drain().then(() => { + // Defer because the caller likely doesn't expect the callback to be run + // immediately. + if (callback) { + Meteor.defer(() => { + callback(null, id); + }); + } + }); return id; } diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 340125b260..855b2f3c14 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -570,7 +570,7 @@ Object.assign(Mongo.Collection.prototype, { // result will be returned through the callback instead. let result; if (!!wrappedCallback) { - result = this._collection.insert(doc, wrappedCallback); + this._collection.insert(doc, wrappedCallback); } else { // If we don't have the callback, we assume the user is using the promise. // We can't just pass this._collection.insert to the promisify because it would lose the context. diff --git a/packages/mongo-async/collection_tests.js b/packages/mongo-async/collection_tests.js index 78da9a1f18..c5d88db645 100644 --- a/packages/mongo-async/collection_tests.js +++ b/packages/mongo-async/collection_tests.js @@ -53,12 +53,12 @@ Tinytest.add('collection - call new Mongo.Collection with defineMutationMethods= } ); -Tinytest.add('collection - call find with sort function', - function (test) { - var initialize = function (collection) { - collection.insert({a: 2}); - collection.insert({a: 3}); - collection.insert({a: 1}); +Tinytest.addAsync('collection - call find with sort function', + async function (test) { + var initialize = async function (collection) { + await collection.insert({a: 2}); + await collection.insert({a: 3}); + await collection.insert({a: 1}); }; var sorter = function (a, b) { @@ -73,23 +73,23 @@ Tinytest.add('collection - call find with sort function', var localCollection = new Mongo.Collection(null); var namedCollection = new Mongo.Collection(collectionName, {connection: null}); - initialize(localCollection); - test.equal(getSorted(localCollection), [1, 2, 3]); + await initialize(localCollection); + test.equal(await getSorted(localCollection), [1, 2, 3]); - initialize(namedCollection); - test.equal(getSorted(namedCollection), [1, 2, 3]); + await initialize(namedCollection); + test.equal(await getSorted(namedCollection), [1, 2, 3]); } ); -Tinytest.add('collection - call native find with sort function', - function (test) { +Tinytest.addAsync('collection - call native find with sort function', + async function (test) { var collectionName = 'sortNative' + test.id; var nativeCollection = new Mongo.Collection(collectionName); if (Meteor.isServer) { - test.throws( + await test.throwsAsync( function () { - nativeCollection + return nativeCollection .find({}, { sort: function () {}, }) @@ -103,32 +103,32 @@ Tinytest.add('collection - call native find with sort function', } ); -Tinytest.add('collection - calling native find with maxTimeMs should timeout', - function(test) { +Tinytest.addAsync('collection - calling native find with maxTimeMs should timeout', + async function(test) { var collectionName = 'findOptions1' + test.id; var collection = new Mongo.Collection(collectionName); - collection.insert({a: 1}); + await collection.insert({a: 1}); function doTest() { return collection.find({$where: "sleep(100) || true"}, {maxTimeMs: 50}).count(); } if (Meteor.isServer) { - test.throws(doTest); + await test.throwsAsync(doTest); } } ); -Tinytest.add('collection - calling native find with $reverse hint should reverse on server', - function(test) { +Tinytest.addAsync('collection - calling native find with $reverse hint should reverse on server', + async function(test) { var collectionName = 'findOptions2' + test.id; var collection = new Mongo.Collection(collectionName); - collection.insert({a: 1}); - collection.insert({a: 2}); + await collection.insert({a: 1}); + await collection.insert({a: 2}); function m(doc) { return doc.a; } - var fwd = collection.find({}, {hint: {$natural: 1}}).map(m); - var rev = collection.find({}, {hint: {$natural: -1}}).map(m); + var fwd = await collection.find({}, {hint: {$natural: 1}}).map(m); + var rev = await collection.find({}, {hint: {$natural: -1}}).map(m); if (Meteor.isServer) { test.equal(fwd, rev.reverse()); } else { @@ -139,10 +139,10 @@ Tinytest.add('collection - calling native find with $reverse hint should reverse ); Tinytest.addAsync('collection - calling native find with good hint and maxTimeMs should succeed', - function(test, done) { + async function(test, done) { var collectionName = 'findOptions3' + test.id; var collection = new Mongo.Collection(collectionName); - collection.insert({a: 1}); + await collection.insert({a: 1}); Promise.resolve( Meteor.isServer && @@ -157,8 +157,8 @@ Tinytest.addAsync('collection - calling native find with good hint and maxTimeMs } ); -Tinytest.add('collection - calling find with a valid readPreference', - function(test) { +Tinytest.addAsync('collection - calling find with a valid readPreference', + async function(test) { if (Meteor.isServer) { const defaultReadPreference = 'primary'; const customReadPreference = 'secondaryPreferred'; @@ -170,8 +170,8 @@ Tinytest.add('collection - calling find with a valid readPreference', ); // Trigger the creation of _synchronousCursor - defaultCursor.count(); - customCursor.count(); + await defaultCursor.count(); + await customCursor.count(); // defaultCursor._synchronousCursor._dbCursor.operation is not an option anymore // as the cursor options are now private @@ -189,7 +189,7 @@ Tinytest.add('collection - calling find with a valid readPreference', } ); -Tinytest.add('collection - calling find with an invalid readPreference', +Tinytest.addAsync('collection - calling find with an invalid readPreference', function(test) { if (Meteor.isServer) { const invalidReadPreference = 'INVALID'; @@ -199,25 +199,25 @@ Tinytest.add('collection - calling find with an invalid readPreference', { readPreference: invalidReadPreference } ); - test.throws(function() { + return test.throwsAsync(function() { // Trigger the creation of _synchronousCursor - cursor.count(); + return cursor.count(); }, `Invalid read preference mode "${invalidReadPreference}"`); } } ); -Tinytest.add('collection - inserting a document with a binary should return a document with a binary', - function(test) { +Tinytest.addAsync('collection - inserting a document with a binary should return a document with a binary', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary1'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id, binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof MongoDB.Binary ); @@ -229,17 +229,17 @@ Tinytest.add('collection - inserting a document with a binary should return a do } ); -Tinytest.add('collection - inserting a document with a binary (sub type 0) should return a document with a uint8array', - function(test) { +Tinytest.addAsync('collection - inserting a document with a binary (sub type 0) should return a document with a uint8array', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary8'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id, binary: new MongoDB.Binary(Buffer.from('hello world'), 0) }); - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof Uint8Array ); @@ -251,18 +251,18 @@ Tinytest.add('collection - inserting a document with a binary (sub type 0) shoul } ); -Tinytest.add('collection - updating a document with a binary should return a document with a binary', - function(test) { +Tinytest.addAsync('collection - updating a document with a binary should return a document with a binary', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary2'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id }); - collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 6) } }); + await collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 6) } }); - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof MongoDB.Binary ); @@ -274,18 +274,18 @@ Tinytest.add('collection - updating a document with a binary should return a doc } ); -Tinytest.add('collection - updating a document with a binary (sub type 0) should return a document with a uint8array', - function(test) { +Tinytest.addAsync('collection - updating a document with a binary (sub type 0) should return a document with a uint8array', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary7'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id }); - collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 0) } }); + await collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 0) } }); - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof Uint8Array ); @@ -297,17 +297,17 @@ Tinytest.add('collection - updating a document with a binary (sub type 0) should } ); -Tinytest.add('collection - inserting a document with a uint8array should return a document with a uint8array', - function(test) { +Tinytest.addAsync('collection - inserting a document with a uint8array should return a document with a uint8array', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary3'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id, binary: new Uint8Array(Buffer.from('hello world')) }); - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof Uint8Array ); @@ -319,21 +319,21 @@ Tinytest.add('collection - inserting a document with a uint8array should return } ); -Tinytest.add('collection - updating a document with a uint8array should return a document with a uint8array', - function(test) { +Tinytest.addAsync('collection - updating a document with a uint8array should return a document with a uint8array', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary4'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id }); - collection.update( + await collection.update( { _id }, { $set: { binary: new Uint8Array(Buffer.from('hello world')) } } ) - const doc = collection.findOne({ _id }); + const doc = await collection.findOne({ _id }); test.ok( doc.binary instanceof Uint8Array ); @@ -345,42 +345,42 @@ Tinytest.add('collection - updating a document with a uint8array should return a } ); -Tinytest.add('collection - finding with a query with a uint8array field should return the correct document', - function(test) { +Tinytest.addAsync('collection - finding with a query with a uint8array field should return the correct document', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary5'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id, binary: new Uint8Array(Buffer.from('hello world')) }); - const doc = collection.findOne({ binary: new Uint8Array(Buffer.from('hello world')) }); + const doc = await collection.findOne({ binary: new Uint8Array(Buffer.from('hello world')) }); test.equal( doc._id, _id ); - collection.remove({}); + await collection.remove({}); } } ); -Tinytest.add('collection - finding with a query with a binary field should return the correct document', - function(test) { +Tinytest.addAsync('collection - finding with a query with a binary field should return the correct document', + async function(test) { if (Meteor.isServer) { const collection = new Mongo.Collection('testBinary6'); const _id = Random.id(); - collection.insert({ + await collection.insert({ _id, binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); - const doc = collection.findOne({ binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); + const doc = await collection.findOne({ binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); test.equal( doc._id, _id ); - collection.remove({}); + await collection.remove({}); } } ); diff --git a/packages/mongo-async/doc_fetcher_tests.js b/packages/mongo-async/doc_fetcher_tests.js index 9460ed8612..86c1164a69 100644 --- a/packages/mongo-async/doc_fetcher_tests.js +++ b/packages/mongo-async/doc_fetcher_tests.js @@ -1,12 +1,12 @@ import { DocFetcher } from "./doc_fetcher.js"; testAsyncMulti("mongo-livedata - doc fetcher", [ - function (test, expect) { + async function (test, expect) { var self = this; var collName = "docfetcher-" + Random.id(); var collection = new Mongo.Collection(collName); - var id1 = collection.insert({x: 1}); - var id2 = collection.insert({y: 2}); + var id1 = await collection.insert({x: 1}); + var id2 = await collection.insert({y: 2}); var fetcher = new DocFetcher( MongoInternals.defaultRemoteCollectionDriver().mongo); diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index cb0eb6e350..44f399142c 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -363,7 +363,7 @@ MongoConnection.prototype._insert = function (collection_name, document, ).then(({insertedId}) => { callback(null, insertedId); }).catch((e) => { - callback(e, null) + callback(e, null); }); } catch (err) { write.committed(); diff --git a/packages/mongo-async/oplog_tests.js b/packages/mongo-async/oplog_tests.js index bb3374f8fb..e6fead6ba6 100644 --- a/packages/mongo-async/oplog_tests.js +++ b/packages/mongo-async/oplog_tests.js @@ -1,190 +1,191 @@ var OplogCollection = new Mongo.Collection("oplog-" + Random.id()); -Tinytest.add("mongo-livedata - oplog - cursorSupported", function (test) { +Tinytest.addAsync("mongo-livedata - oplog - cursorSupported", async function (test) { var oplogEnabled = !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; - var supported = function (expected, selector, options) { + var supported = async function (expected, selector, options) { var cursor = OplogCollection.find(selector, options); - var handle = cursor.observeChanges({added: function () {}}); + var handle = await cursor.observeChanges({added: function () {}}); // If there's no oplog at all, we shouldn't ever use it. if (!oplogEnabled) expected = false; test.equal(!!handle._multiplexer._observeDriver._usesOplog, expected); - handle.stop(); + await handle.stop(); }; - supported(true, "asdf"); - supported(true, 1234); - supported(true, new Mongo.ObjectID()); + await supported(true, "asdf"); + await supported(true, 1234); + await supported(true, new Mongo.ObjectID()); - supported(true, {_id: "asdf"}); - supported(true, {_id: 1234}); - supported(true, {_id: new Mongo.ObjectID()}); + await supported(true, {_id: "asdf"}); + await supported(true, {_id: 1234}); + await supported(true, {_id: new Mongo.ObjectID()}); - supported(true, {foo: "asdf", + await supported(true, {foo: "asdf", bar: 1234, baz: new Mongo.ObjectID(), eeney: true, miney: false, moe: null}); - supported(true, {}); + await supported(true, {}); - supported(true, {$and: [{foo: "asdf"}, {bar: "baz"}]}); - supported(true, {foo: {x: 1}}); - supported(true, {foo: {$gt: 1}}); - supported(true, {foo: [1, 2, 3]}); + await supported(true, {$and: [{foo: "asdf"}, {bar: "baz"}]}); + await supported(true, {foo: {x: 1}}); + await supported(true, {foo: {$gt: 1}}); + await supported(true, {foo: [1, 2, 3]}); // No $where. - supported(false, {$where: "xxx"}); - supported(false, {$and: [{foo: "adsf"}, {$where: "xxx"}]}); + await supported(false, {$where: "xxx"}); + await supported(false, {$and: [{foo: "adsf"}, {$where: "xxx"}]}); // No geoqueries. - supported(false, {x: {$near: [1,1]}}); + await supported(false, {x: {$near: [1,1]}}); // Nothing Minimongo doesn't understand. (Minimongo happens to fail to // implement $elemMatch inside $all which MongoDB supports.) - supported(false, {x: {$all: [{$elemMatch: {y: 2}}]}}); + await supported(false, {x: {$all: [{$elemMatch: {y: 2}}]}}); - supported(true, {}, { sort: {x:1} }); - supported(true, {}, { sort: {x:1}, limit: 5 }); - supported(false, {}, { sort: {$natural:1}, limit: 5 }); - supported(false, {}, { limit: 5 }); - supported(false, {}, { skip: 2, limit: 5 }); - supported(false, {}, { skip: 2 }); + await supported(true, {}, { sort: {x:1} }); + await supported(true, {}, { sort: {x:1}, limit: 5 }); + await supported(false, {}, { sort: {$natural:1}, limit: 5 }); + await supported(false, {}, { limit: 5 }); + await supported(false, {}, { skip: 2, limit: 5 }); + await supported(false, {}, { skip: 2 }); }); -process.env.MONGO_OPLOG_URL && testAsyncMulti( - "mongo-livedata - oplog - entry skipping", [ - function (test, expect) { - var self = this; - self.collectionName = Random.id(); - self.collection = new Mongo.Collection(self.collectionName); - self.collection.createIndex({species: 1}); - - // Fill collection with lots of irrelevant objects (red cats) and some - // relevant ones (blue dogs). - - // After updating to mongo 3.2 with the 2.1.18 driver it was no longer - // possible to make this test fail with TOO_FAR_BEHIND = 2000. - // The documents waiting to be processed would hardly go beyond 1000 - // using mongo 3.2 with WiredTiger - MongoInternals.defaultRemoteCollectionDriver() - .mongo._oplogHandle._defineTooFarBehind(500); - - self.IRRELEVANT_SIZE = 15000; - self.RELEVANT_SIZE = 10; - var docs = []; - var i; - for (i = 0; i < self.IRRELEVANT_SIZE; ++i) { - docs.push({ - name: "cat " + i, - species: 'cat', - color: 'red' - }); - } - for (i = 0; i < self.RELEVANT_SIZE; ++i) { - docs.push({ - name: "dog " + i, - species: 'dog', - color: 'blue' - }); - } - // XXX implement bulk insert #1255 - var rawCollection = self.collection.rawCollection(); - rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { - test.isFalse(err); - }))); - }, - - function (test, expect) { - var self = this; - - test.equal(self.collection.find().count(), - self.IRRELEVANT_SIZE + self.RELEVANT_SIZE); - - var blueDog5Id = null; - var gotSpot = false; - - // Watch for blue dogs. - const gotSpotPromise = new Promise(resolve => { - self.subHandle = self.collection.find({ - species: 'dog', - color: 'blue', - }).observeChanges({ - added(id, fields) { - if (fields.name === 'dog 5') { - blueDog5Id = id; - } - }, - changed(id, fields) { - if (EJSON.equals(id, blueDog5Id) && - fields.name === 'spot') { - gotSpot = true; - resolve(); - } - }, - }); - }); - - test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); - test.isTrue(blueDog5Id); - test.isFalse(gotSpot); - - self.skipped = false; - self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() - .mongo._oplogHandle.onSkippedEntries(function () { - self.skipped = true; - }); - - // Dye all the cats blue. This adds lots of oplog mentries that look like - // they might in theory be relevant (since they say "something you didn't - // know about is now blue", and who knows, maybe it's a dog) which puts - // the OplogObserveDriver into FETCHING mode, which performs poorly. - self.collection.update({species: 'cat'}, - {$set: {color: 'blue'}}, - {multi: true}); - self.collection.update(blueDog5Id, {$set: {name: 'spot'}}); - - // We ought to see the spot change soon! - return gotSpotPromise; - }, - - function (test, expect) { - var self = this; - test.isTrue(self.skipped); - - //This gets the TOO_FAR_BEHIND back to its initial value - MongoInternals.defaultRemoteCollectionDriver() - .mongo._oplogHandle._resetTooFarBehind(); - - self.skipHandle.stop(); - self.subHandle.stop(); - self.collection.remove({}); - } - ] -); - - -// Meteor.isServer && Tinytest.addAsync( -// "mongo-livedata - oplog - _onFailover", -// async function (test) { -// const driver = MongoInternals.defaultRemoteCollectionDriver(); -// const failoverPromise = new Promise(resolve => { -// driver.mongo._onFailover(() => { -// resolve(true); +// TODO -> Index here. +// process.env.MONGO_OPLOG_URL && testAsyncMulti( +// "mongo-livedata - oplog - entry skipping", [ +// function (test, expect) { +// var self = this; +// self.collectionName = Random.id(); +// self.collection = new Mongo.Collection(self.collectionName); +// self.collection.createIndex({species: 1}); +// +// // Fill collection with lots of irrelevant objects (red cats) and some +// // relevant ones (blue dogs). +// +// // After updating to mongo 3.2 with the 2.1.18 driver it was no longer +// // possible to make this test fail with TOO_FAR_BEHIND = 2000. +// // The documents waiting to be processed would hardly go beyond 1000 +// // using mongo 3.2 with WiredTiger +// MongoInternals.defaultRemoteCollectionDriver() +// .mongo._oplogHandle._defineTooFarBehind(500); +// +// self.IRRELEVANT_SIZE = 15000; +// self.RELEVANT_SIZE = 10; +// var docs = []; +// var i; +// for (i = 0; i < self.IRRELEVANT_SIZE; ++i) { +// docs.push({ +// name: "cat " + i, +// species: 'cat', +// color: 'red' +// }); +// } +// for (i = 0; i < self.RELEVANT_SIZE; ++i) { +// docs.push({ +// name: "dog " + i, +// species: 'dog', +// color: 'blue' +// }); +// } +// // XXX implement bulk insert #1255 +// var rawCollection = self.collection.rawCollection(); +// rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { +// test.isFalse(err); +// }))); +// }, +// +// function (test, expect) { +// var self = this; +// +// test.equal(self.collection.find().count(), +// self.IRRELEVANT_SIZE + self.RELEVANT_SIZE); +// +// var blueDog5Id = null; +// var gotSpot = false; +// +// // Watch for blue dogs. +// const gotSpotPromise = new Promise(resolve => { +// self.subHandle = self.collection.find({ +// species: 'dog', +// color: 'blue', +// }).observeChanges({ +// added(id, fields) { +// if (fields.name === 'dog 5') { +// blueDog5Id = id; +// } +// }, +// changed(id, fields) { +// if (EJSON.equals(id, blueDog5Id) && +// fields.name === 'spot') { +// gotSpot = true; +// resolve(); +// } +// }, +// }); // }); -// }); // +// test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); +// test.isTrue(blueDog5Id); +// test.isFalse(gotSpot); // -// await driver.mongo.db.admin().command({ -// replSetStepDown: 1, -// force: true -// }); +// self.skipped = false; +// self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() +// .mongo._oplogHandle.onSkippedEntries(function () { +// self.skipped = true; +// }); // -// try { -// const result = await failoverPromise; -// test.isTrue(result); -// } catch (e) { -// test.fail({ message: "Error waiting on Promise", value: JSON.stringify(e) }); +// // Dye all the cats blue. This adds lots of oplog mentries that look like +// // they might in theory be relevant (since they say "something you didn't +// // know about is now blue", and who knows, maybe it's a dog) which puts +// // the OplogObserveDriver into FETCHING mode, which performs poorly. +// self.collection.update({species: 'cat'}, +// {$set: {color: 'blue'}}, +// {multi: true}); +// self.collection.update(blueDog5Id, {$set: {name: 'spot'}}); +// +// // We ought to see the spot change soon! +// return gotSpotPromise; +// }, +// +// function (test, expect) { +// var self = this; +// test.isTrue(self.skipped); +// +// //This gets the TOO_FAR_BEHIND back to its initial value +// MongoInternals.defaultRemoteCollectionDriver() +// .mongo._oplogHandle._resetTooFarBehind(); +// +// self.skipHandle.stop(); +// self.subHandle.stop(); +// self.collection.remove({}); // } -// }); +// ] +// ); + + +Meteor.isServer && Tinytest.addAsync( + "mongo-livedata - oplog - _onFailover", + async function (test) { + const driver = MongoInternals.defaultRemoteCollectionDriver(); + const failoverPromise = new Promise(resolve => { + driver.mongo._onFailover(() => { + resolve(true); + }); + }); + + + await driver.mongo.db.admin().command({ + replSetStepDown: 1, + force: true + }); + + try { + const result = await failoverPromise; + test.isTrue(result); + } catch (e) { + test.fail({ message: "Error waiting on Promise", value: JSON.stringify(e) }); + } + }); diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index 04be6aedfe..57f88f6030 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -65,13 +65,13 @@ _.extend(ExpectationManager.prototype, { throw new Error("Too late to add more expectations to the test"); self.outstanding++; - return function (/* arguments */) { + return async function (/* arguments */) { if (self.dead) return; if (typeof expected === "function") { try { - expected.apply({}, arguments); + await expected.apply({}, arguments); } catch (e) { if (self.cancel()) self.test.exception(e); diff --git a/packages/test-helpers/callback_logger.js b/packages/test-helpers/callback_logger.js index fb4f30ee07..8e8c242198 100644 --- a/packages/test-helpers/callback_logger.js +++ b/packages/test-helpers/callback_logger.js @@ -16,14 +16,7 @@ var TIMEOUT = 1000; withCallbackLogger = function (test, callbackNames, async, fun) { var logger = new CallbackLogger(test, callbackNames); - if (async) { - if (!Fiber) - throw new Error("Fiber is not available"); - logger.fiber = Fiber(_.bind(fun, null, logger)); - logger.fiber.run(); - } else { - fun(logger); - } + return fun(logger); }; var CallbackLogger = function (test, callbackNames) { From 4721aab03592d85d53eb4f7c0f086e1f9c884963 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 17 Nov 2022 17:47:41 -0300 Subject: [PATCH 0053/1965] Fixing tests on mongo livedata: - Adding TODOS to tests that are probably throwing errors because of DDP issues. - Rewriting test suite. --- packages/minimongo/local_collection.js | 3 +- packages/mongo-async/mongo_livedata_tests.js | 4993 +++++++++--------- 2 files changed, 2482 insertions(+), 2514 deletions(-) diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index bce810a2be..9b4946f4ea 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -139,7 +139,8 @@ export default class LocalCollection { } }); - this._observeQueue.drain().then(() => { + // TODO -> Check here. + Promise.resolve(this._observeQueue.drain()).then(() => { // Defer because the caller likely doesn't expect the callback to be run // immediately. if (callback) { diff --git a/packages/mongo-async/mongo_livedata_tests.js b/packages/mongo-async/mongo_livedata_tests.js index c6a2484728..08c6559f4c 100644 --- a/packages/mongo-async/mongo_livedata_tests.js +++ b/packages/mongo-async/mongo_livedata_tests.js @@ -58,13 +58,13 @@ Meteor.methods({ } }); -var runInFence = function (f) { +var runInFence = async function (f) { if (Meteor.isClient) { - f(); + await f(); } else { var fence = new DDPServer._WriteFence; - DDPServer._CurrentWriteFence.withValue(fence, f); - fence.armAndWait(); + await DDPServer._CurrentWriteFence.withValue(fence, f); + await fence.armAndWait(); } }; @@ -89,22 +89,22 @@ var upsert = function (coll, useUpdate, query, mod, options, callback) { options = {}; } - if (useUpdate) { - if (callback) - return coll.update(query, mod, - _.extend({ upsert: true }, options), - function (err, result) { - callback(err, ! err && { - numberAffected: result - }); - }); - return { - numberAffected: coll.update(query, mod, - _.extend({ upsert: true }, options)) - }; - } else { + if (!useUpdate) { return coll.upsert(query, mod, options, callback); } + + if (callback) { + return coll.update(query, mod, + _.extend({ upsert: true }, options), + function (err, result) { + callback(err, ! err && { + numberAffected: result + }); + }); + } + + return Promise.resolve(coll.update(query, mod, + _.extend({ upsert: true }, options))).then(r => ({numberAffected: r})); }; var upsertTestMethod = "livedata_upsert_test_method"; @@ -117,16 +117,16 @@ var upsertTestMethodColl; // // Client-side exceptions in here will NOT cause the test to fail! Because it's // a stub, those exceptions will get caught and logged. -var upsertTestMethodImpl = function (coll, useUpdate, test) { - coll.remove({}); - var result1 = upsert(coll, useUpdate, { foo: "bar" }, { foo: "bar" }); +var upsertTestMethodImpl = async function (coll, useUpdate, test) { + await coll.remove({}); + var result1 = await upsert(coll, useUpdate, { foo: "bar" }, { foo: "bar" }); if (! test) { test = { equal: function (a, b) { if (! EJSON.equals(a, b)) throw new Error("Not equal: " + - JSON.stringify(a) + ", " + JSON.stringify(b)); + JSON.stringify(a) + ", " + JSON.stringify(b)); }, isTrue: function (a) { if (! a) @@ -147,12 +147,12 @@ var upsertTestMethodImpl = function (coll, useUpdate, test) { if (! useUpdate) test.isTrue(result1.insertedId); var fooId = result1.insertedId; - var obj = coll.findOne({ foo: "bar" }); + var obj = await coll.findOne({ foo: "bar" }); test.isTrue(obj); if (! useUpdate) test.equal(obj._id, result1.insertedId); - var result2 = upsert(coll, useUpdate, { _id: fooId }, - { $set: { foo: "baz " } }); + var result2 = await upsert(coll, useUpdate, { _id: fooId }, + { $set: { foo: "baz " } }); test.isTrue(result2); test.equal(result2.numberAffected, 1); test.isFalse(result2.insertedId); @@ -164,13 +164,13 @@ if (Meteor.isServer) { check(run, String); check(useUpdate, Boolean); upsertTestMethodColl = new Mongo.Collection(upsertTestMethod + "_collection_" + run, options); - upsertTestMethodImpl(upsertTestMethodColl, useUpdate); + return upsertTestMethodImpl(upsertTestMethodColl, useUpdate); }; Meteor.methods(m); } Meteor._FailureTestCollection = - new Mongo.Collection("___meteor_failure_test_collection"); + new Mongo.Collection("___meteor_failure_test_collection"); // For test "document with a custom type" var Dog = function (name, color, actions) { @@ -183,8 +183,8 @@ _.extend(Dog.prototype, { getName: function () { return this.name;}, getColor: function () { return this.name;}, equals: function (other) { return other.name === this.name && - other.color === this.color && - EJSON.equals(other.actions, this.actions);}, + other.color === this.color && + EJSON.equals(other.actions, this.actions);}, toJSONValue: function () { return {color: this.color, name: this.name, actions: this.actions};}, typeName: function () { return "dog"; }, clone: function () { return new Dog(this.name, this.color); }, @@ -194,1719 +194,1696 @@ EJSON.addType("dog", function (o) { return new Dog(o.name, o.color, o.actions);} // Parameterize tests. -_.each( ['STRING', 'MONGO'], function(idGeneration) { +// TODO -> Re add MONGO here ['STRING', 'MONGO'] +_.each( ['STRING'], function(idGeneration) { -var collectionOptions = { idGeneration: idGeneration}; + var collectionOptions = { idGeneration: idGeneration}; -testAsyncMulti("mongo-livedata - database error reporting. " + idGeneration, [ - function (test, expect) { - var ftc = Meteor._FailureTestCollection; + Tinytest.addAsync("mongo-livedata - database error reporting. " + idGeneration, + async function (test, expect) { + const ftc = Meteor._FailureTestCollection; - var exception = function (err, res) { - test.instanceOf(err, Error); - }; - - _.each(["insert", "remove", "update"], function (op) { - var arg = (op === "insert" ? {} : 'bla'); - var arg2 = {}; - - var callOp = function (callback) { - if (op === "update") { - ftc[op](arg, arg2, callback); - } else { - ftc[op](arg, callback); - } - }; - - if (Meteor.isServer) { - test.throws(function () { - callOp(); - }); - - callOp(expect(exception)); - } - - if (Meteor.isClient) { - callOp(expect(exception)); - - // This would log to console in normal operation. - Meteor._suppress_log(1); - callOp(); - } - }); - } -]); - - -Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll, coll2; - if (Meteor.isClient) { - coll = new Mongo.Collection(null, collectionOptions) ; // local, unmanaged - coll2 = new Mongo.Collection(null, collectionOptions); // local, unmanaged - } else { - coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); - coll2 = new Mongo.Collection("livedata_test_collection_2_"+run, collectionOptions); - } - - var log = ''; - var obs = coll.find({run: run}, {sort: ["x"]}).observe({ - addedAt: function (doc, before_index, before) { - log += 'a(' + doc.x + ',' + before_index + ',' + before + ')'; - }, - changedAt: function (new_doc, old_doc, at_index) { - log += 'c(' + new_doc.x + ',' + at_index + ',' + old_doc.x + ')'; - }, - movedTo: function (doc, old_index, new_index) { - log += 'm(' + doc.x + ',' + old_index + ',' + new_index + ')'; - }, - removedAt: function (doc, at_index) { - log += 'r(' + doc.x + ',' + at_index + ')'; - } - }); - - var captureObserve = function (f) { - if (Meteor.isClient) { - f(); - } else { - var fence = new DDPServer._WriteFence; - DDPServer._CurrentWriteFence.withValue(fence, f); - fence.armAndWait(); - } - - var ret = log; - log = ''; - return ret; - }; - - var expectObserve = function (expected, f) { - if (!(expected instanceof Array)) - expected = [expected]; - - test.include(expected, captureObserve(f)); - }; - - test.equal(coll.find({run: run}).count(), 0); - test.equal(coll.findOne("abc"), undefined); - test.equal(coll.findOne({run: run}), undefined); - - expectObserve('a(1,0,null)', function () { - var id = coll.insert({run: run, x: 1}); - test.equal(coll.find({run: run}).count(), 1); - test.equal(coll.findOne(id).x, 1); - test.equal(coll.findOne({run: run}).x, 1); - }); - - expectObserve('a(4,1,null)', function () { - var id2 = coll.insert({run: run, x: 4}); - test.equal(coll.find({run: run}).count(), 2); - test.equal(coll.find({_id: id2}).count(), 1); - test.equal(coll.findOne(id2).x, 4); - }); - - test.equal(coll.findOne({run: run}, {sort: ["x"], skip: 0}).x, 1); - test.equal(coll.findOne({run: run}, {sort: ["x"], skip: 1}).x, 4); - test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 0}).x, 4); - test.equal(coll.findOne({run: run}, {sort: {x: -1}, skip: 1}).x, 1); - - - // - applySkipLimit is no longer an option - // Note that the current behavior is inconsistent on the client. - // (https://github.com/meteor/meteor/issues/1201) - if (Meteor.isServer) { - test.equal(coll.find({run: run}, {limit: 1}).count(), 1); - } - - var cur = coll.find({run: run}, {sort: ["x"]}); - var total = 0; - var index = 0; - var context = {}; - cur.forEach(function (doc, i, cursor) { - test.equal(i, index++); - test.isTrue(cursor === cur); - test.isTrue(context === this); - total *= 10; - if (Meteor.isServer) { - // Verify that the callbacks from forEach run sequentially and that - // forEach waits for them to complete (issue# 321). If they do not run - // sequentially, then the second callback could execute during the first - // callback's sleep sleep and the *= 10 will occur before the += 1, then - // total (at test.equal time) will be 5. If forEach does not wait for the - // callbacks to complete, then total (at test.equal time) will be 0. - Meteor._sleepForMs(5); - } - total += doc.x; - // verify the meteor environment is set up here - coll2.insert({total:total}); - }, context); - test.equal(total, 14); - - index = 0; - test.equal(cur.map(function (doc, i, cursor) { - // XXX we could theoretically make map run its iterations in parallel or - // something which would make this fail - test.equal(i, index++); - test.isTrue(cursor === cur); - test.isTrue(context === this); - return doc.x * 2; - }, context), [2, 8]); - - test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), - [4, 1]); - - expectObserve('', function () { - var count = coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); - test.equal(count, 0); - }); - - expectObserve('c(3,0,1)c(6,1,4)', function () { - var count = coll.update({run: run}, {$inc: {x: 2}}, {multi: true}); - test.equal(count, 2); - test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), - [6, 3]); - }); - - expectObserve(['c(13,0,3)m(13,0,1)', 'm(6,1,0)c(13,1,3)', - 'c(13,0,3)m(6,1,0)', 'm(3,0,1)c(13,1,3)'], function () { - coll.update({run: run, x: 3}, {$inc: {x: 10}}, {multi: true}); - test.equal(_.pluck(coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), - [13, 6]); - }); - - expectObserve('r(13,1)', function () { - var count = coll.remove({run: run, x: {$gt: 10}}); - test.equal(count, 1); - test.equal(coll.find({run: run}).count(), 1); - }); - - expectObserve('r(6,0)', function () { - coll.remove({run: run}); - test.equal(coll.find({run: run}).count(), 0); - }); - - expectObserve('', function () { - var count = coll.remove({run: run}); - test.equal(count, 0); - test.equal(coll.find({run: run}).count(), 0); - }); - - obs.stop(); - onComplete(); -}); - -Tinytest.addAsync("mongo-livedata - fuzz test, " + idGeneration, function(test, onComplete) { - - var run = Random.id(); - var coll; - if (Meteor.isClient) { - coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged - } else { - coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); - } - - // fuzz test of observe(), especially the server-side diffing - var actual = []; - var correct = []; - var counters = {add: 0, change: 0, move: 0, remove: 0}; - - var obs = coll.find({run: run}, {sort: ["x"]}).observe({ - addedAt: function (doc, before_index) { - counters.add++; - actual.splice(before_index, 0, doc.x); - }, - changedAt: function (new_doc, old_doc, at_index) { - counters.change++; - test.equal(actual[at_index], old_doc.x); - actual[at_index] = new_doc.x; - }, - movedTo: function (doc, old_index, new_index) { - counters.move++; - test.equal(actual[old_index], doc.x); - actual.splice(old_index, 1); - actual.splice(new_index, 0, doc.x); - }, - removedAt: function (doc, at_index) { - counters.remove++; - test.equal(actual[at_index], doc.x); - actual.splice(at_index, 1); - } - }); - - if (Meteor.isServer) { - // For now, has to be polling (not oplog) because it is ordered observe. - test.isTrue(obs._multiplexer._observeDriver._suspendPolling); - } - - var step = 0; - - // Use non-deterministic randomness so we can have a shorter fuzz - // test (fewer iterations). For deterministic (fully seeded) - // randomness, remove the call to Random.fraction(). - var seededRandom = new SeededRandom("foobard" + Random.fraction()); - // Random integer in [0,n) - var rnd = function (n) { - return seededRandom.nextIntBetween(0, n-1); - }; - - var finishObserve = function (f) { - if (Meteor.isClient) { - f(); - } else { - var fence = new DDPServer._WriteFence; - DDPServer._CurrentWriteFence.withValue(fence, f); - fence.armAndWait(); - } - }; - - var doStep = function () { - if (step++ === 5) { // run N random tests - obs.stop(); - onComplete(); - return; - } - - var max_counters = _.clone(counters); - - finishObserve(function () { - if (Meteor.isServer) - obs._multiplexer._observeDriver._suspendPolling(); - - // Do a batch of 1-10 operations - var batch_count = rnd(10) + 1; - for (var i = 0; i < batch_count; i++) { - // 25% add, 25% remove, 25% change in place, 25% change and move - var x; - var op = rnd(4); - var which = rnd(correct.length); - if (op === 0 || step < 2 || !correct.length) { - // Add - x = rnd(1000000); - coll.insert({run: run, x: x}); - correct.push(x); - max_counters.add++; - } else if (op === 1 || op === 2) { - var val; - x = correct[which]; - if (op === 1) { - // Small change, not likely to cause a move - val = x + (rnd(2) ? -1 : 1); - } else { - // Large change, likely to cause a move - val = rnd(1000000); - } - coll.update({run: run, x: x}, {$set: {x: val}}); - correct[which] = val; - max_counters.change++; - max_counters.move++; - } else { - coll.remove({run: run, x: correct[which]}); - correct.splice(which, 1); - max_counters.remove++; - } - } - if (Meteor.isServer) - obs._multiplexer._observeDriver._resumePolling(); - - }); - - // Did we actually deliver messages that mutated the array in the - // right way? - correct.sort(function (a,b) {return a-b;}); - test.equal(actual, correct); - - // Did we limit ourselves to one 'moved' message per change, - // rather than O(results) moved messages? - _.each(max_counters, function (v, k) { - test.isTrue(max_counters[k] >= counters[k], k); - }); - - Meteor.defer(doStep); - }; - - doStep(); - -}); - -Tinytest.addAsync("mongo-livedata - scribbling, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll; - if (Meteor.isClient) { - coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged - } else { - coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); - } - - var numAddeds = 0; - var handle = coll.find({run: run}).observe({ - addedAt: function (o) { - // test that we can scribble on the object we get back from Mongo without - // breaking anything. The worst possible scribble is messing with _id. - delete o._id; - numAddeds++; - } - }); - _.each([123, 456, 789], function (abc) { - runInFence(function () { - coll.insert({run: run, abc: abc}); - }); - }); - handle.stop(); - // will be 6 (1+2+3) if we broke diffing! - test.equal(numAddeds, 3); - - onComplete(); -}); - -if (Meteor.isServer) { - Tinytest.addAsync("mongo-livedata - extended scribbling, " + idGeneration, function (test, onComplete) { - function error() { - throw new Meteor.Error('unsafe object mutation'); - } - - const denyModifications = { - get(target, key) { - const type = Object.prototype.toString.call(target[key]); - if (type === '[object Object]' || type === '[object Array]') { - return freeze(target[key]); - } else { - return target[key]; - } - }, - set: error, - deleteProperty: error, - defineProperty: error, - }; - - // Object.freeze only throws in silent mode - // So we make our own version that always throws. - function freeze(obj) { - return new Proxy(obj, denyModifications); - } - - const origApplyCallback = ObserveMultiplexer.prototype._applyCallback; - ObserveMultiplexer.prototype._applyCallback = function(callback, args) { - // Make sure that if anything touches the original object, this will throw - return origApplyCallback.call(this, callback, freeze(args)); - } - - const run = test.runId(); - const coll = new Mongo.Collection(`livedata_test_scribble_collection_${run}`, collectionOptions); - const expectMutatable = (o) => { - try { - o.a[0].c = 3; - } catch (error) { - test.fail(); - } - } - const expectNotMutatable = (o) => { - try { - o.a[0].c = 3; - test.fail(); - } catch (error) {} - } - const handle = coll.find({run}).observe({ - addedAt: expectMutatable, - changedAt: function(id, o) { - expectMutatable(o); - } - }); - - const handle2 = coll.find({run}).observeChanges({ - added: expectNotMutatable, - changed: function(id, o) { - expectNotMutatable(o); - } - }, { nonMutatingCallbacks: true }); - - runInFence(function () { - coll.insert({run, a: [ {c: 1} ]}); - coll.update({run}, { $set: { 'a.0.c': 2 } }); - }); - - handle.stop(); - handle2.stop(); - - ObserveMultiplexer.prototype._applyCallback = origApplyCallback; - onComplete(); - }); -} - -Tinytest.addAsync("mongo-livedata - stop handle in callback, " + idGeneration, function (test, onComplete) { - var run = Random.id(); - var coll; - if (Meteor.isClient) { - coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged - } else { - coll = new Mongo.Collection("stopHandleInCallback-"+run, collectionOptions); - } - - var output = []; - - var handle = coll.find().observe({ - added: function (doc) { - output.push({added: doc._id}); - }, - changed: function (newDoc) { - output.push('changed'); - handle.stop(); - } - }); - - test.equal(output, []); - - // Insert a document. Observe that the added callback is called. - var docId; - runInFence(function () { - docId = coll.insert({foo: 42}); - }); - test.length(output, 1); - test.equal(output.shift(), {added: docId}); - - // Update it. Observe that the changed callback is called. This should also - // stop the observation. - runInFence(function() { - coll.update(docId, {$set: {bar: 10}}); - }); - test.length(output, 1); - test.equal(output.shift(), 'changed'); - - // Update again. This shouldn't call the callback because we stopped the - // observation. - runInFence(function() { - coll.update(docId, {$set: {baz: 40}}); - }); - test.length(output, 0); - - test.equal(coll.find().count(), 1); - test.equal(coll.findOne(docId), - {_id: docId, foo: 42, bar: 10, baz: 40}); - - onComplete(); -}); - -// This behavior isn't great, but it beats deadlock. -if (Meteor.isServer) { - Tinytest.addAsync("mongo-livedata - recursive observe throws, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("observeInCallback-"+run, collectionOptions); - - var callbackCalled = false; - var handle = coll.find({}).observe({ - added: function (newDoc) { - callbackCalled = true; - test.throws(function () { - coll.find({}).observe(); - }); - } - }); - test.isFalse(callbackCalled); - // Insert a document. Observe that the added callback is called. - runInFence(function () { - coll.insert({foo: 42}); - }); - test.isTrue(callbackCalled); - - handle.stop(); - - onComplete(); - }); - - Tinytest.addAsync("mongo-livedata - cursor dedup, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("cursorDedup-"+run, collectionOptions); - - var observer = function (noAdded) { - var output = []; - var callbacks = { - changed: function (newDoc) { - output.push({changed: newDoc._id}); - } - }; - if (!noAdded) { - callbacks.added = function (doc) { - output.push({added: doc._id}); + const exception = function (err) { + test.instanceOf(err, Error); }; + + const toAwait = ["insert", "remove", "update"].map(async (op) => { + const arg = (op === "insert" ? {} : 'bla'); + const arg2 = {}; + + const callOp = async function (callback) { + if (op === "update") { + await ftc[op](arg, arg2, callback); + } else { + await ftc[op](arg, callback); + } + }; + + if (Meteor.isServer) { + await test.throwsAsync(async function () { + await callOp(); + }); + + await callOp(expect(exception)); + } + + if (Meteor.isClient) { + await callOp(expect(exception)); + + // This would log to console in normal operation. + Meteor._suppress_log(1); + await callOp(); + } + }); + + await Promise.all(toAwait); } - var handle = coll.find({foo: 22}).observe(callbacks); - return {output: output, handle: handle}; - }; + ); - // Insert a doc and start observing. - var docId1 = coll.insert({foo: 22}); - var o1 = observer(); - // Initial add. - test.length(o1.output, 1); - test.equal(o1.output.shift(), {added: docId1}); - // Insert another doc (blocking until observes have fired). - var docId2; - runInFence(function () { - docId2 = coll.insert({foo: 22, bar: 5}); - }); - // Observed add. - test.length(o1.output, 1); - test.equal(o1.output.shift(), {added: docId2}); - - // Second identical observe. - var o2 = observer(); - // Initial adds. - test.length(o2.output, 2); - test.include([docId1, docId2], o2.output[0].added); - test.include([docId1, docId2], o2.output[1].added); - test.notEqual(o2.output[0].added, o2.output[1].added); - o2.output.length = 0; - // Original observe not affected. - test.length(o1.output, 0); - - // White-box test: both observes should share an ObserveMultiplexer. - var observeMultiplexer = o1.handle._multiplexer; - test.isTrue(observeMultiplexer); - test.isTrue(observeMultiplexer === o2.handle._multiplexer); - - // Update. Both observes fire. - runInFence(function () { - coll.update(docId1, {$set: {x: 'y'}}); - }); - test.length(o1.output, 1); - test.length(o2.output, 1); - test.equal(o1.output.shift(), {changed: docId1}); - test.equal(o2.output.shift(), {changed: docId1}); - - // Stop first handle. Second handle still around. - o1.handle.stop(); - test.length(o1.output, 0); - test.length(o2.output, 0); - - // Another update. Just the second handle should fire. - runInFence(function () { - coll.update(docId2, {$set: {z: 'y'}}); - }); - test.length(o1.output, 0); - test.length(o2.output, 1); - test.equal(o2.output.shift(), {changed: docId2}); - - // Stop second handle. Nothing should happen, but the multiplexer should - // be stopped. - test.isTrue(observeMultiplexer._handles); // This will change. - o2.handle.stop(); - test.length(o1.output, 0); - test.length(o2.output, 0); - // White-box: ObserveMultiplexer has nulled its _handles so you can't - // accidentally join to it. - test.isNull(observeMultiplexer._handles); - - // Start yet another handle on the same query. - var o3 = observer(); - // Initial adds. - test.length(o3.output, 2); - test.include([docId1, docId2], o3.output[0].added); - test.include([docId1, docId2], o3.output[1].added); - test.notEqual(o3.output[0].added, o3.output[1].added); - // Old observers not called. - test.length(o1.output, 0); - test.length(o2.output, 0); - // White-box: Different ObserveMultiplexer. - test.isTrue(observeMultiplexer !== o3.handle._multiplexer); - - // Start another handle with no added callback. Regression test for #589. - var o4 = observer(true); - - o3.handle.stop(); - o4.handle.stop(); - - onComplete(); - }); - - Tinytest.addAsync("mongo-livedata - async server-side insert, " + idGeneration, function (test, onComplete) { - // Tests that insert returns before the callback runs. Relies on the fact - // that mongo does not run the callback before spinning off the event loop. - var cname = Random.id(); - var coll = new Mongo.Collection(cname); - var doc = { foo: "bar" }; - var x = 0; - coll.insert(doc, function (err, result) { - test.equal(err, null); - test.equal(x, 1); - onComplete(); - }); - x++; - }); - - Tinytest.addAsync("mongo-livedata - async server-side update, " + idGeneration, function (test, onComplete) { - // Tests that update returns before the callback runs. - var cname = Random.id(); - var coll = new Mongo.Collection(cname); - var doc = { foo: "bar" }; - var x = 0; - var id = coll.insert(doc); - coll.update(id, { $set: { foo: "baz" } }, function (err, result) { - test.equal(err, null); - test.equal(result, 1); - test.equal(x, 1); - onComplete(); - }); - x++; - }); - - Tinytest.addAsync("mongo-livedata - async server-side remove, " + idGeneration, function (test, onComplete) { - // Tests that remove returns before the callback runs. - var cname = Random.id(); - var coll = new Mongo.Collection(cname); - var doc = { foo: "bar" }; - var x = 0; - var id = coll.insert(doc); - coll.remove(id, function (err, result) { - test.equal(err, null); - test.isFalse(coll.findOne(id)); - test.equal(x, 1); - onComplete(); - }); - x++; - }); - - // compares arrays a and b w/o looking at order - var setsEqual = function (a, b) { - a = _.map(a, EJSON.stringify); - b = _.map(b, EJSON.stringify); - return _.isEmpty(_.difference(a, b)) && _.isEmpty(_.difference(b, a)); - }; - - // This test mainly checks the correctness of oplog code dealing with limited - // queries. Compitablity with poll-diff is added as well. - Tinytest.add("mongo-livedata - observe sorted, limited " + idGeneration, function (test) { + Tinytest.addAsync("mongo-livedata - basics, " + idGeneration, async function (test) { var run = test.runId(); - var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); - - var observer = function () { - var state = {}; - var output = []; - var callbacks = { - changed: function (newDoc) { - output.push({changed: newDoc._id}); - state[newDoc._id] = newDoc; - }, - added: function (newDoc) { - output.push({added: newDoc._id}); - state[newDoc._id] = newDoc; - }, - removed: function (oldDoc) { - output.push({removed: oldDoc._id}); - delete state[oldDoc._id]; - } - }; - var handle = coll.find({foo: 22}, - {sort: {bar: 1}, limit: 3}).observe(callbacks); - - return {output: output, handle: handle, state: state}; - }; - var clearOutput = function (o) { o.output.splice(0, o.output.length); }; - - var ins = function (doc) { - var id; runInFence(function () { id = coll.insert(doc); }); - return id; - }; - var rem = function (sel) { runInFence(function () { coll.remove(sel); }); }; - var upd = function (sel, mod, opt) { - runInFence(function () { - coll.update(sel, mod, opt); - }); - }; - // tests '_id' subfields for all documents in oplog buffer - var testOplogBufferIds = function (ids) { - if (!usesOplog) - return; - var bufferIds = []; - o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { - bufferIds.push(id); - }); - - test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); - }; - var testSafeAppendToBufferFlag = function (expected) { - if (!usesOplog) - return; - test.equal(o.handle._multiplexer._observeDriver._safeAppendToBuffer, - expected); - }; - - // We'll describe our state as follows. 5:1 means "the document with - // _id=docId1 and bar=5". We list documents as - // [ currently published | in the buffer ] outside the buffer - // If safeToAppendToBuffer is true, we'll say ]! instead. - - // Insert a doc and start observing. - var docId1 = ins({foo: 22, bar: 5}); - waitUntilOplogCaughtUp(); - - // State: [ 5:1 | ]! - var o = observer(); - var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; - // Initial add. - test.length(o.output, 1); - test.equal(o.output.shift(), {added: docId1}); - testSafeAppendToBufferFlag(true); - - // Insert another doc (blocking until observes have fired). - // State: [ 5:1 6:2 | ]! - var docId2 = ins({foo: 22, bar: 6}); - // Observed add. - test.length(o.output, 1); - test.equal(o.output.shift(), {added: docId2}); - testSafeAppendToBufferFlag(true); - - var docId3 = ins({ foo: 22, bar: 3 }); - // State: [ 3:3 5:1 6:2 | ]! - test.length(o.output, 1); - test.equal(o.output.shift(), {added: docId3}); - testSafeAppendToBufferFlag(true); - - // Add a non-matching document - ins({ foo: 13 }); - // It shouldn't be added - test.length(o.output, 0); - - // Add something that matches but is too big to fit in - var docId4 = ins({ foo: 22, bar: 7 }); - // State: [ 3:3 5:1 6:2 | 7:4 ]! - // It shouldn't be added but should end up in the buffer. - test.length(o.output, 0); - testOplogBufferIds([docId4]); - testSafeAppendToBufferFlag(true); - - // Let's add something small enough to fit in - var docId5 = ins({ foo: 22, bar: -1 }); - // State: [ -1:5 3:3 5:1 | 6:2 7:4 ]! - // We should get an added and a removed events - test.length(o.output, 2); - // doc 2 was removed from the published set as it is too big to be in - test.isTrue(setsEqual(o.output, [{added: docId5}, {removed: docId2}])); - clearOutput(o); - testOplogBufferIds([docId2, docId4]); - testSafeAppendToBufferFlag(true); - - // Now remove something and that doc 2 should be right back - rem(docId5); - // State: [ 3:3 5:1 6:2 | 7:4 ]! - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{removed: docId5}, {added: docId2}])); - clearOutput(o); - testOplogBufferIds([docId4]); - testSafeAppendToBufferFlag(true); - - // Add some negative numbers overflowing the buffer. - // New documents will take the published place, [3 5 6] will take the buffer - // and 7 will be outside of the buffer in MongoDB. - var docId6 = ins({ foo: 22, bar: -1 }); - var docId7 = ins({ foo: 22, bar: -2 }); - var docId8 = ins({ foo: 22, bar: -3 }); - // State: [ -3:8 -2:7 -1:6 | 3:3 5:1 6:2 ] 7:4 - test.length(o.output, 6); - var expected = [{added: docId6}, {removed: docId2}, - {added: docId7}, {removed: docId1}, - {added: docId8}, {removed: docId3}]; - test.isTrue(setsEqual(o.output, expected)); - clearOutput(o); - testOplogBufferIds([docId1, docId2, docId3]); - testSafeAppendToBufferFlag(false); - - // If we update first 3 docs (increment them by 20), it would be - // interesting. - upd({ bar: { $lt: 0 }}, { $inc: { bar: 20 } }, { multi: true }); - // State: [ 3:3 5:1 6:2 | ] 7:4 17:8 18:7 19:6 - // which triggers re-poll leaving us at - // State: [ 3:3 5:1 6:2 | 7:4 17:8 18:7 ] 19:6 - - // The updated documents can't find their place in published and they can't - // be buffered as we are not aware of the situation outside of the buffer. - // But since our buffer becomes empty, it will be refilled partially with - // updated documents. - test.length(o.output, 6); - var expectedRemoves = [{removed: docId6}, - {removed: docId7}, - {removed: docId8}]; - var expectedAdds = [{added: docId3}, - {added: docId1}, - {added: docId2}]; - - test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); - clearOutput(o); - testOplogBufferIds([docId4, docId7, docId8]); - testSafeAppendToBufferFlag(false); - - // Remove first 4 docs (3, 1, 2, 4) forcing buffer to become empty and - // schedule a repoll. - rem({ bar: { $lt: 10 } }); - // State: [ 17:8 18:7 19:6 | ]! - - // XXX the oplog code analyzes the events one by one: one remove after - // another. Poll-n-diff code, on the other side, analyzes the batch action - // of multiple remove. Because of that difference, expected outputs differ. - if (usesOplog) { - expectedRemoves = [{removed: docId3}, {removed: docId1}, - {removed: docId2}, {removed: docId4}]; - expectedAdds = [{added: docId4}, {added: docId8}, - {added: docId7}, {added: docId6}]; - - test.length(o.output, 8); - } else { - expectedRemoves = [{removed: docId3}, {removed: docId1}, - {removed: docId2}]; - expectedAdds = [{added: docId8}, {added: docId7}, {added: docId6}]; - - test.length(o.output, 6); - } - - test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); - clearOutput(o); - testOplogBufferIds([]); - testSafeAppendToBufferFlag(true); - - var docId9 = ins({ foo: 22, bar: 21 }); - var docId10 = ins({ foo: 22, bar: 31 }); - var docId11 = ins({ foo: 22, bar: 41 }); - var docId12 = ins({ foo: 22, bar: 51 }); - // State: [ 17:8 18:7 19:6 | 21:9 31:10 41:11 ] 51:12 - - testOplogBufferIds([docId9, docId10, docId11]); - testSafeAppendToBufferFlag(false); - test.length(o.output, 0); - upd({ bar: { $lt: 20 } }, { $inc: { bar: 5 } }, { multi: true }); - // State: [ 21:9 22:8 23:7 | 24:6 31:10 41:11 ] 51:12 - test.length(o.output, 4); - test.isTrue(setsEqual(o.output, [{removed: docId6}, - {added: docId9}, - {changed: docId7}, - {changed: docId8}])); - clearOutput(o); - testOplogBufferIds([docId6, docId10, docId11]); - testSafeAppendToBufferFlag(false); - - rem(docId9); - // State: [ 22:8 23:7 24:6 | 31:10 41:11 ] 51:12 - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{removed: docId9}, {added: docId6}])); - clearOutput(o); - testOplogBufferIds([docId10, docId11]); - testSafeAppendToBufferFlag(false); - - upd({ bar: { $gt: 25 } }, { $inc: { bar: -7.5 } }, { multi: true }); - // State: [ 22:8 23:7 23.5:10 | 24:6 ] 33.5:11 43.5:12 - // 33.5 doesn't update in-place in buffer, because it the driver is not sure - // it can do it: because the buffer does not have the safe append flag set, - // for all it knows there is a different doc which is less than 33.5. - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{removed: docId6}, {added: docId10}])); - clearOutput(o); - testOplogBufferIds([docId6]); - testSafeAppendToBufferFlag(false); - - // Force buffer objects to be moved into published set so we can check them - rem(docId7); - rem(docId8); - rem(docId10); - // State: [ 24:6 | ] 33.5:11 43.5:12 - // triggers repoll - // State: [ 24:6 33.5:11 43.5:12 | ]! - test.length(o.output, 6); - test.isTrue(setsEqual(o.output, [{removed: docId7}, {removed: docId8}, - {removed: docId10}, {added: docId6}, - {added: docId11}, {added: docId12}])); - - test.length(_.keys(o.state), 3); - test.equal(o.state[docId6], { _id: docId6, foo: 22, bar: 24 }); - test.equal(o.state[docId11], { _id: docId11, foo: 22, bar: 33.5 }); - test.equal(o.state[docId12], { _id: docId12, foo: 22, bar: 43.5 }); - clearOutput(o); - testOplogBufferIds([]); - testSafeAppendToBufferFlag(true); - - var docId13 = ins({ foo: 22, bar: 50 }); - var docId14 = ins({ foo: 22, bar: 51 }); - var docId15 = ins({ foo: 22, bar: 52 }); - var docId16 = ins({ foo: 22, bar: 53 }); - // State: [ 24:6 33.5:11 43.5:12 | 50:13 51:14 52:15 ] 53:16 - test.length(o.output, 0); - testOplogBufferIds([docId13, docId14, docId15]); - testSafeAppendToBufferFlag(false); - - // Update something that's outside the buffer to be in the buffer, writing - // only to the sort key. - upd(docId16, {$set: {bar: 10}}); - // State: [ 10:16 24:6 33.5:11 | 43.5:12 50:13 51:14 ] 52:15 - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{removed: docId12}, {added: docId16}])); - clearOutput(o); - testOplogBufferIds([docId12, docId13, docId14]); - testSafeAppendToBufferFlag(false); - - o.handle.stop(); - }); - - Tinytest.addAsync("mongo-livedata - observe sorted, limited, sort fields " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); - - var observer = function () { - var state = {}; - var output = []; - var callbacks = { - changed: function (newDoc) { - output.push({changed: newDoc._id}); - state[newDoc._id] = newDoc; - }, - added: function (newDoc) { - output.push({added: newDoc._id}); - state[newDoc._id] = newDoc; - }, - removed: function (oldDoc) { - output.push({removed: oldDoc._id}); - delete state[oldDoc._id]; - } - }; - var handle = coll.find({}, {sort: {x: 1}, - limit: 2, - fields: {y: 1}}).observe(callbacks); - - return {output: output, handle: handle, state: state}; - }; - var clearOutput = function (o) { o.output.splice(0, o.output.length); }; - var ins = function (doc) { - var id; runInFence(function () { id = coll.insert(doc); }); - return id; - }; - var rem = function (id) { - runInFence(function () { coll.remove(id); }); - }; - - var o = observer(); - - var docId1 = ins({ x: 1, y: 1222 }); - var docId2 = ins({ x: 5, y: 5222 }); - - test.length(o.output, 2); - test.equal(o.output, [{added: docId1}, {added: docId2}]); - clearOutput(o); - - var docId3 = ins({ x: 7, y: 7222 }); - test.length(o.output, 0); - - var docId4 = ins({ x: -1, y: -1222 }); - - // Becomes [docId4 docId1 | docId2 docId3] - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{added: docId4}, {removed: docId2}])); - - test.equal(_.size(o.state), 2); - test.equal(o.state[docId4], {_id: docId4, y: -1222}); - test.equal(o.state[docId1], {_id: docId1, y: 1222}); - clearOutput(o); - - rem(docId2); - // Becomes [docId4 docId1 | docId3] - test.length(o.output, 0); - - rem(docId4); - // Becomes [docId1 docId3] - test.length(o.output, 2); - test.isTrue(setsEqual(o.output, [{added: docId3}, {removed: docId4}])); - - test.equal(_.size(o.state), 2); - test.equal(o.state[docId3], {_id: docId3, y: 7222}); - test.equal(o.state[docId1], {_id: docId1, y: 1222}); - clearOutput(o); - - onComplete(); - }); - - Tinytest.add("mongo-livedata - observe sorted, limited, big initial set" + idGeneration, function (test) { - var run = test.runId(); - var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); - - var observer = function () { - var state = {}; - var output = []; - var callbacks = { - changed: function (newDoc) { - output.push({changed: newDoc._id}); - state[newDoc._id] = newDoc; - }, - added: function (newDoc) { - output.push({added: newDoc._id}); - state[newDoc._id] = newDoc; - }, - removed: function (oldDoc) { - output.push({removed: oldDoc._id}); - delete state[oldDoc._id]; - } - }; - var handle = coll.find({}, {sort: {x: 1, y: 1}, limit: 3}) - .observe(callbacks); - - return {output: output, handle: handle, state: state}; - }; - var clearOutput = function (o) { o.output.splice(0, o.output.length); }; - var ins = function (doc) { - var id; runInFence(function () { id = coll.insert(doc); }); - return id; - }; - var rem = function (id) { - runInFence(function () { coll.remove(id); }); - }; - // tests '_id' subfields for all documents in oplog buffer - var testOplogBufferIds = function (ids) { - var bufferIds = []; - o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { - bufferIds.push(id); - }); - - test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); - }; - var testSafeAppendToBufferFlag = function (expected) { - if (expected) { - test.isTrue(o.handle._multiplexer._observeDriver._safeAppendToBuffer); - } else { - test.isFalse(o.handle._multiplexer._observeDriver._safeAppendToBuffer); - } - }; - - var ids = {}; - _.each([2, 4, 1, 3, 5, 5, 9, 1, 3, 2, 5], function (x, i) { - ids[i] = ins({ x: x, y: i }); - }); - - // Ensure that we are past all the 'i' entries before we run the query, so - // that we get the expected phase transitions. - waitUntilOplogCaughtUp(); - - var o = observer(); - var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; - // x: [1 1 2 | 2 3 3] 4 5 5 5 9 - // id: [2 7 0 | 9 3 8] 1 4 5 10 6 - - test.length(o.output, 3); - test.isTrue(setsEqual([{added: ids[2]}, {added: ids[7]}, {added: ids[0]}], o.output)); - usesOplog && testOplogBufferIds([ids[9], ids[3], ids[8]]); - usesOplog && testSafeAppendToBufferFlag(false); - clearOutput(o); - - rem(ids[0]); - // x: [1 1 2 | 3 3] 4 5 5 5 9 - // id: [2 7 9 | 3 8] 1 4 5 10 6 - test.length(o.output, 2); - test.isTrue(setsEqual([{removed: ids[0]}, {added: ids[9]}], o.output)); - usesOplog && testOplogBufferIds([ids[3], ids[8]]); - usesOplog && testSafeAppendToBufferFlag(false); - clearOutput(o); - - rem(ids[7]); - // x: [1 2 3 | 3] 4 5 5 5 9 - // id: [2 9 3 | 8] 1 4 5 10 6 - test.length(o.output, 2); - test.isTrue(setsEqual([{removed: ids[7]}, {added: ids[3]}], o.output)); - usesOplog && testOplogBufferIds([ids[8]]); - usesOplog && testSafeAppendToBufferFlag(false); - clearOutput(o); - - rem(ids[3]); - // x: [1 2 3 | 4 5 5] 5 9 - // id: [2 9 8 | 1 4 5] 10 6 - test.length(o.output, 2); - test.isTrue(setsEqual([{removed: ids[3]}, {added: ids[8]}], o.output)); - usesOplog && testOplogBufferIds([ids[1], ids[4], ids[5]]); - usesOplog && testSafeAppendToBufferFlag(false); - clearOutput(o); - - rem({ x: {$lt: 4} }); - // x: [4 5 5 | 5 9] - // id: [1 4 5 | 10 6] - test.length(o.output, 6); - test.isTrue(setsEqual([{removed: ids[2]}, {removed: ids[9]}, {removed: ids[8]}, - {added: ids[5]}, {added: ids[4]}, {added: ids[1]}], o.output)); - usesOplog && testOplogBufferIds([ids[10], ids[6]]); - usesOplog && testSafeAppendToBufferFlag(true); - clearOutput(o); - }); -} - - -testAsyncMulti('mongo-livedata - empty documents, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); + var coll, coll2; if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName); - Meteor.subscribe('c-' + this.collectionName, expect()); + coll = new Mongo.Collection(null, collectionOptions) ; // local, unmanaged + coll2 = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + coll2 = new Mongo.Collection("livedata_test_collection_2_"+run, collectionOptions); } - }, function (test, expect) { - var coll = new Mongo.Collection(this.collectionName, collectionOptions); - coll.insert({}, expect(function (err, id) { - test.isFalse(err); - test.isTrue(id); - var cursor = coll.find(); - test.equal(cursor.count(), 1); - })); + var log = ''; + var obs = await coll.find({run: run}, {sort: ["x"]}).observe({ + addedAt: function (doc, before_index, before) { + log += 'a(' + doc.x + ',' + before_index + ',' + before + ')'; + }, + changedAt: function (new_doc, old_doc, at_index) { + log += 'c(' + new_doc.x + ',' + at_index + ',' + old_doc.x + ')'; + }, + movedTo: function (doc, old_index, new_index) { + log += 'm(' + doc.x + ',' + old_index + ',' + new_index + ')'; + }, + removedAt: function (doc, at_index) { + log += 'r(' + doc.x + ',' + at_index + ')'; + } + }); + + var captureObserve = async function (f) { + if (Meteor.isClient) { + await f(); + } else { + var fence = new DDPServer._WriteFence; + await DDPServer._CurrentWriteFence.withValue(fence, f); + await fence.armAndWait(); + } + + var ret = log; + log = ''; + return ret; + }; + + var expectObserve = async function (expected, f) { + if (!(expected instanceof Array)) + expected = [expected]; + + test.include(expected, await captureObserve(f)); + }; + + test.equal(await coll.find({run: run}).count(), 0); + test.equal(await coll.findOne("abc"), undefined); + test.equal(await coll.findOne({run: run}), undefined); + + await expectObserve('a(1,0,null)', async function () { + var id = await coll.insert({run: run, x: 1}); + test.equal(await coll.find({run: run}).count(), 1); + test.equal((await coll.findOne(id)).x, 1); + test.equal((await coll.findOne({run: run})).x, 1); + }); + + await expectObserve('a(4,1,null)', async function () { + var id2 = await coll.insert({run: run, x: 4}); + test.equal(await coll.find({run: run}).count(), 2); + test.equal(await coll.find({_id: id2}).count(), 1); + test.equal((await coll.findOne(id2)).x, 4); + }); + + test.equal((await coll.findOne({run: run}, {sort: ["x"], skip: 0})).x, 1); + test.equal((await coll.findOne({run: run}, {sort: ["x"], skip: 1})).x, 4); + test.equal((await coll.findOne({run: run}, {sort: {x: -1}, skip: 0})).x, 4); + test.equal((await coll.findOne({run: run}, {sort: {x: -1}, skip: 1})).x, 1); + + + // - applySkipLimit is no longer an option + // Note that the current behavior is inconsistent on the client. + // (https://github.com/meteor/meteor/issues/1201) + if (Meteor.isServer) { + test.equal(await coll.find({run: run}, {limit: 1}).count(), 1); + } + + var cur = coll.find({run: run}, {sort: ["x"]}); + var total = 0; + var index = 0; + var context = {}; + await cur.forEach(async function (doc, i, cursor) { + test.equal(i, index++); + test.isTrue(cursor === cur); + test.isTrue(context === this); + total *= 10; + if (Meteor.isServer) { + // Verify that the callbacks from forEach run sequentially and that + // forEach waits for them to complete (issue# 321). If they do not run + // sequentially, then the second callback could execute during the first + // callback's sleep sleep and the *= 10 will occur before the += 1, then + // total (at test.equal time) will be 5. If forEach does not wait for the + // callbacks to complete, then total (at test.equal time) will be 0. + await Meteor._sleepForMs(5); + } + total += doc.x; + // verify the meteor environment is set up here + await coll2.insert({total:total}); + }, context); + test.equal(total, 14); + + index = 0; + test.equal(await cur.map(function (doc, i, cursor) { + // XXX we could theoretically make map run its iterations in parallel or + // something which would make this fail + test.equal(i, index++); + test.isTrue(cursor === cur); + test.isTrue(context === this); + return doc.x * 2; + }, context), [2, 8]); + + test.equal(_.pluck(await coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [4, 1]); + + await expectObserve('', async function () { + var count = await coll.update({run: run, x: -1}, {$inc: {x: 2}}, {multi: true}); + test.equal(count, 0); + }); + + await expectObserve('c(3,0,1)c(6,1,4)', async function () { + var count = await coll.update({run: run}, {$inc: {x: 2}}, {multi: true}); + test.equal(count, 2); + test.equal(_.pluck(await coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [6, 3]); + }); + + await expectObserve(['c(13,0,3)m(13,0,1)', 'm(6,1,0)c(13,1,3)', + 'c(13,0,3)m(6,1,0)', 'm(3,0,1)c(13,1,3)'], async function () { + await coll.update({run: run, x: 3}, {$inc: {x: 10}}, {multi: true}); + test.equal(_.pluck(await coll.find({run: run}, {sort: {x: -1}}).fetch(), "x"), + [13, 6]); + }); + + await expectObserve('r(13,1)', async function () { + var count = await coll.remove({run: run, x: {$gt: 10}}); + test.equal(count, 1); + test.equal(await coll.find({run: run}).count(), 1); + }); + + await expectObserve('r(6,0)', async function () { + await coll.remove({run: run}); + test.equal(await coll.find({run: run}).count(), 0); + }); + + await expectObserve('', async function () { + var count = await coll.remove({run: run}); + test.equal(count, 0); + test.equal(await coll.find({run: run}).count(), 0); + }); + + obs.stop(); + }); + + // TODO -> Related to DDP? Cannot read properties of undefined (reading '_CurrentMethodInvocation') + // Tinytest.onlyAsync("mongo-livedata - fuzz test, " + idGeneration, async function(test) { + // var run = Random.id(); + // var coll; + // if (Meteor.isClient) { + // coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + // } else { + // coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + // } + // + // // fuzz test of observe(), especially the server-side diffing + // var actual = []; + // var correct = []; + // var counters = {add: 0, change: 0, move: 0, remove: 0}; + // + // var obs = await coll.find({run: run}, {sort: ["x"]}).observe({ + // addedAt: function (doc, before_index) { + // counters.add++; + // actual.splice(before_index, 0, doc.x); + // }, + // changedAt: function (new_doc, old_doc, at_index) { + // counters.change++; + // test.equal(actual[at_index], old_doc.x); + // actual[at_index] = new_doc.x; + // }, + // movedTo: function (doc, old_index, new_index) { + // counters.move++; + // test.equal(actual[old_index], doc.x); + // actual.splice(old_index, 1); + // actual.splice(new_index, 0, doc.x); + // }, + // removedAt: function (doc, at_index) { + // counters.remove++; + // test.equal(actual[at_index], doc.x); + // actual.splice(at_index, 1); + // } + // }); + // + // if (Meteor.isServer) { + // // For now, has to be polling (not oplog) because it is ordered observe. + // test.isTrue(obs._multiplexer._observeDriver._suspendPolling); + // } + // + // var step = 0; + // + // // Use non-deterministic randomness so we can have a shorter fuzz + // // test (fewer iterations). For deterministic (fully seeded) + // // randomness, remove the call to Random.fraction(). + // var seededRandom = new SeededRandom("foobard" + Random.fraction()); + // // Random integer in [0,n) + // var rnd = function (n) { + // return seededRandom.nextIntBetween(0, n-1); + // }; + // + // var finishObserve = async function (f) { + // if (Meteor.isClient) { + // await f(); + // } else { + // var fence = new DDPServer._WriteFence; + // await DDPServer._CurrentWriteFence.withValue(fence, f); + // await fence.armAndWait(); + // } + // }; + // + // var doStep = async function () { + // if (step++ === 5) { // run N random tests + // await obs.stop(); + // return; + // } + // + // var max_counters = _.clone(counters); + // + // await finishObserve(async function () { + // if (Meteor.isServer) + // obs._multiplexer._observeDriver._suspendPolling(); + // + // // Do a batch of 1-10 operations + // var batch_count = rnd(10) + 1; + // for (var i = 0; i < batch_count; i++) { + // // 25% add, 25% remove, 25% change in place, 25% change and move + // var x; + // var op = rnd(4); + // var which = rnd(correct.length); + // if (op === 0 || step < 2 || !correct.length) { + // // Add + // x = rnd(1000000); + // await coll.insert({run: run, x: x}); + // correct.push(x); + // max_counters.add++; + // } else if (op === 1 || op === 2) { + // var val; + // x = correct[which]; + // if (op === 1) { + // // Small change, not likely to cause a move + // val = x + (rnd(2) ? -1 : 1); + // } else { + // // Large change, likely to cause a move + // val = rnd(1000000); + // } + // await coll.update({run: run, x: x}, {$set: {x: val}}); + // correct[which] = val; + // max_counters.change++; + // max_counters.move++; + // } else { + // await coll.remove({run: run, x: correct[which]}); + // correct.splice(which, 1); + // max_counters.remove++; + // } + // } + // if (Meteor.isServer) + // obs._multiplexer._observeDriver._resumePolling(); + // + // }); + // + // // Did we actually deliver messages that mutated the array in the + // // right way? + // correct.sort(function (a,b) {return a-b;}); + // test.equal(actual, correct); + // + // // Did we limit ourselves to one 'moved' message per change, + // // rather than O(results) moved messages? + // _.each(max_counters, function (v, k) { + // test.isTrue(max_counters[k] >= counters[k], k); + // }); + // + // await doStep(); + // }; + // + // await doStep(); + // }); + + // TODO -> Adapt this one + // On the client the insert does a method call and this is broke for now. + // Tinytest.addAsync("mongo-livedata - scribbling, " + idGeneration, async function (test) { + // var run = test.runId(); + // var coll; + // if (Meteor.isClient) { + // coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + // } else { + // coll = new Mongo.Collection("livedata_test_collection_"+run, collectionOptions); + // } + // + // var numAddeds = 0; + // var handle = await coll.find({run: run}).observe({ + // addedAt: function (o) { + // // test that we can scribble on the object we get back from Mongo without + // // breaking anything. The worst possible scribble is messing with _id. + // delete o._id; + // numAddeds++; + // } + // }); + // + // for (const abc of [123,456,789]) { + // await runInFence(async () => { + // await coll.insert({run: run, abc: abc}); + // }); + // } + // + // await handle.stop(); + // // will be 6 (1+2+3) if we broke diffing! + // test.equal(numAddeds, 3); + // }); + + if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - extended scribbling, " + idGeneration, async function (test) { + function error() { + throw new Meteor.Error('unsafe object mutation'); + } + + const denyModifications = { + get(target, key) { + const type = Object.prototype.toString.call(target[key]); + if (type === '[object Object]' || type === '[object Array]') { + return freeze(target[key]); + } else { + return target[key]; + } + }, + set: error, + deleteProperty: error, + defineProperty: error, + }; + + // Object.freeze only throws in silent mode + // So we make our own version that always throws. + function freeze(obj) { + return new Proxy(obj, denyModifications); + } + + // TODO -> Maybe revisit this? Probably when we are back to just "mongo" it will work again. + const ObserveMultiplexer = Package['mongo-async'].ObserveMultiplexer; + const origApplyCallback = ObserveMultiplexer.prototype._applyCallback; + ObserveMultiplexer.prototype._applyCallback = function(callback, args) { + // Make sure that if anything touches the original object, this will throw + return origApplyCallback.call(this, callback, freeze(args)); + }; + + const run = test.runId(); + const coll = new Mongo.Collection(`livedata_test_scribble_collection_${run}`, collectionOptions); + const expectMutatable = (o) => { + try { + o.a[0].c = 3; + } catch (error) { + test.fail(); + } + } + const expectNotMutatable = (o) => { + try { + o.a[0].c = 3; + test.fail(); + } catch (error) {} + } + const handle = await coll.find({run}).observe({ + addedAt: expectMutatable, + changedAt: function(id, o) { + expectMutatable(o); + } + }); + + const handle2 = await coll.find({run}).observeChanges({ + added: expectNotMutatable, + changed: function(id, o) { + expectNotMutatable(o); + } + }, { nonMutatingCallbacks: true }); + + await runInFence(async function () { + await coll.insert({run, a: [ {c: 1} ]}); + await coll.update({run}, { $set: { 'a.0.c': 2 } }); + }); + + await handle.stop(); + await handle2.stop(); + + ObserveMultiplexer.prototype._applyCallback = origApplyCallback; + }); } -]); + + +// FIXME -> Here uses oplog, so need to fix it. + Tinytest.addAsync("mongo-livedata - stop handle in callback, " + idGeneration, async function (test) { + var run = Random.id(); + var coll; + if (Meteor.isClient) { + coll = new Mongo.Collection(null, collectionOptions); // local, unmanaged + } else { + coll = new Mongo.Collection("stopHandleInCallback-"+run, collectionOptions); + } + + var output = []; + + // Unordered callbacks use oplog, while ordered uses the polling. + // And that's the issue, oplog is broken with all the changes and it's not triggering the callbacks. + var handle = await coll.find().observe({ + added: function addedFromTest(doc) { + output.push({added: doc._id}); + }, + changed: function changedFromTest() { + output.push('changed'); + handle.stop(); + } + }); + + test.equal(output, []); + + // Insert a document. Observe that the added callback is called. + var docId; + await runInFence(async function () { + docId = await coll.insert({foo: 42}); + }); + test.length(output, 1); + test.equal(output.shift(), {added: docId}); + + // Update it. Observe that the changed callback is called. This should also + // stop the observation. + await runInFence(async function() { + await coll.update(docId, {$set: {bar: 10}}); + }); + test.length(output, 1); + test.equal(output.shift(), 'changed'); + + // Update again. This shouldn't call the callback because we stopped the + // observation. + await runInFence(async function() { + await coll.update(docId, {$set: {baz: 40}}); + }); + test.length(output, 0); + + test.equal(await coll.find().count(), 1); + test.equal(await coll.findOne(docId), + {_id: docId, foo: 42, bar: 10, baz: 40}); + }); + + // Tinytest.onlyAsync("mong-livedata - iiiiii414124122 " + idGeneration, async () => { return 'oii'}) +// This behavior isn't great, but it beats deadlock. + if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - recursive observe throws, " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeInCallback-"+run, collectionOptions); + + var callbackCalled = false; + var handle = await coll.find({}).observe({ + addedAt: async function () { + callbackCalled = true; + await test.throwsAsync(async function () { + await coll.find({}).observe(); + }); + } + }); + test.isFalse(callbackCalled); + // Insert a document. Observe that the added callback is called. + await runInFence(async function () { + await coll.insert({foo: 42}); + }); + test.isTrue(callbackCalled); + + await handle.stop(); + }); + + // TODO -> Check after DDP. + // Tinytest.onlyAsync("mongo-livedata - cursor dedup, " + idGeneration, async function (test) { + // var run = test.runId(); + // var coll = new Mongo.Collection("cursorDedup-"+run, collectionOptions); + // + // var observer = async function (noAdded) { + // var output = []; + // var callbacks = { + // changed: function (newDoc) { + // output.push({changed: newDoc._id}); + // } + // }; + // if (!noAdded) { + // callbacks.added = function (doc) { + // output.push({added: doc._id}); + // }; + // } + // + // var handle = await coll.find({foo: 22}).observe(callbacks); + // return {output: output, handle: handle}; + // }; + // + // // Insert a doc and start observing. + // var docId1 = await coll.insert({foo: 22}); + // var o1 = await observer(); + // // Initial add. + // test.length(o1.output, 1); + // test.equal(o1.output.shift(), {added: docId1}); + // + // // Insert another doc (blocking until observes have fired). + // var docId2; + // await runInFence(async function () { + // docId2 = await coll.insert({foo: 22, bar: 5}); + // }); + // // Observed add. + // test.length(o1.output, 1); + // test.equal(o1.output.shift(), {added: docId2}); + // + // // Second identical observe. + // var o2 = await observer(); + // // Initial adds. + // test.length(o2.output, 2); + // test.include([docId1, docId2], o2.output[0].added); + // test.include([docId1, docId2], o2.output[1].added); + // test.notEqual(o2.output[0].added, o2.output[1].added); + // o2.output.length = 0; + // // Original observe not affected. + // test.length(o1.output, 0); + // + // // White-box test: both observes should share an ObserveMultiplexer. + // var observeMultiplexer = o1.handle._multiplexer; + // test.isTrue(observeMultiplexer); + // test.isTrue(observeMultiplexer === o2.handle._multiplexer); + // + // // Update. Both observes fire. + // await runInFence(function () { + // return coll.update(docId1, {$set: {x: 'y'}}); + // }); + // test.length(o1.output, 1); + // test.length(o2.output, 1); + // test.equal(o1.output.shift(), {changed: docId1}); + // test.equal(o2.output.shift(), {changed: docId1}); + // + // // Stop first handle. Second handle still around. + // await o1.handle.stop(); + // test.length(o1.output, 0); + // test.length(o2.output, 0); + // + // // Another update. Just the second handle should fire. + // await runInFence(function () { + // return coll.update(docId2, {$set: {z: 'y'}}); + // }); + // test.length(o1.output, 0); + // test.length(o2.output, 1); + // test.equal(o2.output.shift(), {changed: docId2}); + // + // // Stop second handle. Nothing should happen, but the multiplexer should + // // be stopped. + // test.isTrue(observeMultiplexer._handles); // This will change. + // await o2.handle.stop(); + // test.length(o1.output, 0); + // test.length(o2.output, 0); + // // White-box: ObserveMultiplexer has nulled its _handles so you can't + // // accidentally join to it. + // test.isNull(observeMultiplexer._handles); + // + // // Start yet another handle on the same query. + // var o3 = await observer(); + // // Initial adds. + // test.length(o3.output, 2); + // test.include([docId1, docId2], o3.output[0].added); + // test.include([docId1, docId2], o3.output[1].added); + // test.notEqual(o3.output[0].added, o3.output[1].added); + // // Old observers not called. + // test.length(o1.output, 0); + // test.length(o2.output, 0); + // // White-box: Different ObserveMultiplexer. + // test.isTrue(observeMultiplexer !== o3.handle._multiplexer); + // + // // Start another handle with no added callback. Regression test for #589. + // var o4 = await observer(true); + // + // await o3.handle.stop(); + // await o4.handle.stop(); + // }); + + Tinytest.addAsync("mongo-livedata - async server-side insert, " + idGeneration, function (test, onComplete) { + // Tests that insert returns before the callback runs. Relies on the fact + // that mongo does not run the callback before spinning off the event loop. + var cname = Random.id(); + var coll = new Mongo.Collection(cname); + var doc = { foo: "bar" }; + var x = 0; + coll.insert(doc, function (err, result) { + test.equal(err, null); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + + Tinytest.addAsync("mongo-livedata - async server-side update, " + idGeneration, function (test, onComplete) { + // Tests that update returns before the callback runs. + const cname = Random.id(); + const coll = new Mongo.Collection(cname); + const doc = { foo: "bar" }; + let x = 0; + coll.insert(doc, (_, id) => { + coll.update(id, { $set: { foo: "baz" } }, function (err, result) { + test.equal(err, null); + test.equal(result, 1); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + + }); + + Tinytest.addAsync("mongo-livedata - async server-side remove, " + idGeneration, function (test, onComplete) { + // Tests that remove returns before the callback runs. + const cname = Random.id(); + const coll = new Mongo.Collection(cname); + const doc = { foo: "bar" }; + let x = 0; + coll.insert(doc, (_, id) => { + coll.remove(id, async function (err, _) { + test.equal(err, null); + test.isFalse(await coll.findOne(id)); + test.equal(x, 1); + onComplete(); + }); + x++; + }); + }); + + // compares arrays a and b w/o looking at order + var setsEqual = function (a, b) { + a = _.map(a, EJSON.stringify); + b = _.map(b, EJSON.stringify); + return _.isEmpty(_.difference(a, b)) && _.isEmpty(_.difference(b, a)); + }; + + // TODO -> Also uses oplog + // This test mainly checks the correctness of oplog code dealing with limited + // queries. Compitablity with poll-diff is added as well. + Tinytest.addAsync("mongo-livedata - observe sorted, limited " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = async function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = await coll.find({foo: 22}, + {sort: {bar: 1}, limit: 3}).observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + + var ins = async function (doc) { + var id; await runInFence(async function () { id = await coll.insert(doc); }); + return id; + }; + var rem = async function (sel) { await runInFence(function () { return coll.remove(sel); }); }; + var upd = async function (sel, mod, opt) { + await runInFence(function () { + return coll.update(sel, mod, opt); + }); + }; + // tests '_id' subfields for all documents in oplog buffer + var testOplogBufferIds = function (ids) { + if (!usesOplog) + return; + var bufferIds = []; + o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { + bufferIds.push(id); + }); + + test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); + }; + var testSafeAppendToBufferFlag = function (expected) { + if (!usesOplog) + return; + test.equal(o.handle._multiplexer._observeDriver._safeAppendToBuffer, + expected); + }; + + // We'll describe our state as follows. 5:1 means "the document with + // _id=docId1 and bar=5". We list documents as + // [ currently published | in the buffer ] outside the buffer + // If safeToAppendToBuffer is true, we'll say ]! instead. + + // Insert a doc and start observing. + var docId1 = await ins({foo: 22, bar: 5}); + await waitUntilOplogCaughtUp(); + + // State: [ 5:1 | ]! + var o = await observer(); + var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; + // Initial add. + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId1}); + testSafeAppendToBufferFlag(true); + + // Insert another doc (blocking until observes have fired). + // State: [ 5:1 6:2 | ]! + var docId2 = await ins({foo: 22, bar: 6}); + // Observed add. + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId2}); + testSafeAppendToBufferFlag(true); + + var docId3 = await ins({ foo: 22, bar: 3 }); + // State: [ 3:3 5:1 6:2 | ]! + test.length(o.output, 1); + test.equal(o.output.shift(), {added: docId3}); + testSafeAppendToBufferFlag(true); + + // Add a non-matching document + await ins({ foo: 13 }); + // It shouldn't be added + test.length(o.output, 0); + + // Add something that matches but is too big to fit in + var docId4 = await ins({ foo: 22, bar: 7 }); + // State: [ 3:3 5:1 6:2 | 7:4 ]! + // It shouldn't be added but should end up in the buffer. + test.length(o.output, 0); + testOplogBufferIds([docId4]); + testSafeAppendToBufferFlag(true); + + // Let's add something small enough to fit in + var docId5 = await ins({ foo: 22, bar: -1 }); + // State: [ -1:5 3:3 5:1 | 6:2 7:4 ]! + // We should get an added and a removed events + test.length(o.output, 2); + // doc 2 was removed from the published set as it is too big to be in + test.isTrue(setsEqual(o.output, [{added: docId5}, {removed: docId2}])); + clearOutput(o); + testOplogBufferIds([docId2, docId4]); + testSafeAppendToBufferFlag(true); + + // Now remove something and that doc 2 should be right back + await rem(docId5); + // State: [ 3:3 5:1 6:2 | 7:4 ]! + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId5}, {added: docId2}])); + clearOutput(o); + testOplogBufferIds([docId4]); + testSafeAppendToBufferFlag(true); + + // Add some negative numbers overflowing the buffer. + // New documents will take the published place, [3 5 6] will take the buffer + // and 7 will be outside of the buffer in MongoDB. + var docId6 = await ins({ foo: 22, bar: -1 }); + var docId7 = await ins({ foo: 22, bar: -2 }); + var docId8 = await ins({ foo: 22, bar: -3 }); + // State: [ -3:8 -2:7 -1:6 | 3:3 5:1 6:2 ] 7:4 + test.length(o.output, 6); + var expected = [{added: docId6}, {removed: docId2}, + {added: docId7}, {removed: docId1}, + {added: docId8}, {removed: docId3}]; + test.isTrue(setsEqual(o.output, expected)); + clearOutput(o); + testOplogBufferIds([docId1, docId2, docId3]); + testSafeAppendToBufferFlag(false); + + // If we update first 3 docs (increment them by 20), it would be + // interesting. + await upd({ bar: { $lt: 0 }}, { $inc: { bar: 20 } }, { multi: true }); + // State: [ 3:3 5:1 6:2 | ] 7:4 17:8 18:7 19:6 + // which triggers re-poll leaving us at + // State: [ 3:3 5:1 6:2 | 7:4 17:8 18:7 ] 19:6 + + // The updated documents can't find their place in published and they can't + // be buffered as we are not aware of the situation outside of the buffer. + // But since our buffer becomes empty, it will be refilled partially with + // updated documents. + test.length(o.output, 6); + var expectedRemoves = [{removed: docId6}, + {removed: docId7}, + {removed: docId8}]; + var expectedAdds = [{added: docId3}, + {added: docId1}, + {added: docId2}]; + + test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); + clearOutput(o); + testOplogBufferIds([docId4, docId7, docId8]); + testSafeAppendToBufferFlag(false); + + // Remove first 4 docs (3, 1, 2, 4) forcing buffer to become empty and + // schedule a repoll. + await rem({ bar: { $lt: 10 } }); + // State: [ 17:8 18:7 19:6 | ]! + + // XXX the oplog code analyzes the events one by one: one remove after + // another. Poll-n-diff code, on the other side, analyzes the batch action + // of multiple remove. Because of that difference, expected outputs differ. + if (usesOplog) { + expectedRemoves = [{removed: docId3}, {removed: docId1}, + {removed: docId2}, {removed: docId4}]; + expectedAdds = [{added: docId4}, {added: docId8}, + {added: docId7}, {added: docId6}]; + + test.length(o.output, 8); + } else { + expectedRemoves = [{removed: docId3}, {removed: docId1}, + {removed: docId2}]; + expectedAdds = [{added: docId8}, {added: docId7}, {added: docId6}]; + + test.length(o.output, 6); + } + + test.isTrue(setsEqual(o.output, expectedAdds.concat(expectedRemoves))); + clearOutput(o); + testOplogBufferIds([]); + testSafeAppendToBufferFlag(true); + + var docId9 = await ins({ foo: 22, bar: 21 }); + var docId10 = await ins({ foo: 22, bar: 31 }); + var docId11 = await ins({ foo: 22, bar: 41 }); + var docId12 = await ins({ foo: 22, bar: 51 }); + // State: [ 17:8 18:7 19:6 | 21:9 31:10 41:11 ] 51:12 + + testOplogBufferIds([docId9, docId10, docId11]); + testSafeAppendToBufferFlag(false); + test.length(o.output, 0); + await upd({ bar: { $lt: 20 } }, { $inc: { bar: 5 } }, { multi: true }); + // State: [ 21:9 22:8 23:7 | 24:6 31:10 41:11 ] 51:12 + test.length(o.output, 4); + test.isTrue(setsEqual(o.output, [{removed: docId6}, + {added: docId9}, + {changed: docId7}, + {changed: docId8}])); + clearOutput(o); + testOplogBufferIds([docId6, docId10, docId11]); + testSafeAppendToBufferFlag(false); + + await rem(docId9); + // State: [ 22:8 23:7 24:6 | 31:10 41:11 ] 51:12 + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId9}, {added: docId6}])); + clearOutput(o); + testOplogBufferIds([docId10, docId11]); + testSafeAppendToBufferFlag(false); + + await upd({ bar: { $gt: 25 } }, { $inc: { bar: -7.5 } }, { multi: true }); + // State: [ 22:8 23:7 23.5:10 | 24:6 ] 33.5:11 43.5:12 + // 33.5 doesn't update in-place in buffer, because it the driver is not sure + // it can do it: because the buffer does not have the safe append flag set, + // for all it knows there is a different doc which is less than 33.5. + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId6}, {added: docId10}])); + clearOutput(o); + testOplogBufferIds([docId6]); + testSafeAppendToBufferFlag(false); + + // Force buffer objects to be moved into published set so we can check them + await rem(docId7); + await rem(docId8); + await rem(docId10); + // State: [ 24:6 | ] 33.5:11 43.5:12 + // triggers repoll + // State: [ 24:6 33.5:11 43.5:12 | ]! + test.length(o.output, 6); + test.isTrue(setsEqual(o.output, [{removed: docId7}, {removed: docId8}, + {removed: docId10}, {added: docId6}, + {added: docId11}, {added: docId12}])); + + test.length(_.keys(o.state), 3); + test.equal(o.state[docId6], { _id: docId6, foo: 22, bar: 24 }); + test.equal(o.state[docId11], { _id: docId11, foo: 22, bar: 33.5 }); + test.equal(o.state[docId12], { _id: docId12, foo: 22, bar: 43.5 }); + clearOutput(o); + testOplogBufferIds([]); + testSafeAppendToBufferFlag(true); + + var docId13 = await ins({ foo: 22, bar: 50 }); + var docId14 = await ins({ foo: 22, bar: 51 }); + var docId15 = await ins({ foo: 22, bar: 52 }); + var docId16 = await ins({ foo: 22, bar: 53 }); + // State: [ 24:6 33.5:11 43.5:12 | 50:13 51:14 52:15 ] 53:16 + test.length(o.output, 0); + testOplogBufferIds([docId13, docId14, docId15]); + testSafeAppendToBufferFlag(false); + + // Update something that's outside the buffer to be in the buffer, writing + // only to the sort key. + await upd(docId16, {$set: {bar: 10}}); + // State: [ 10:16 24:6 33.5:11 | 43.5:12 50:13 51:14 ] 52:15 + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{removed: docId12}, {added: docId16}])); + clearOutput(o); + testOplogBufferIds([docId12, docId13, docId14]); + testSafeAppendToBufferFlag(false); + + await o.handle.stop(); + }); + // TODO -> Also uses oplog + Tinytest.addAsync("mongo-livedata - observe sorted, limited, sort fields " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = async function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = await coll.find({}, {sort: {x: 1}, + limit: 2, + fields: {y: 1}}).observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + var ins = async function (doc) { + var id; await runInFence(async function () { id = await coll.insert(doc); }); + return id; + }; + var rem = function (id) { + return runInFence(function () { return coll.remove(id); }); + }; + + var o = await observer(); + + var docId1 = await ins({ x: 1, y: 1222 }); + var docId2 = await ins({ x: 5, y: 5222 }); + + test.length(o.output, 2); + test.equal(o.output, [{added: docId1}, {added: docId2}]); + clearOutput(o); + + var docId3 = await ins({ x: 7, y: 7222 }); + test.length(o.output, 0); + + var docId4 = await ins({ x: -1, y: -1222 }); + + // Becomes [docId4 docId1 | docId2 docId3] + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{added: docId4}, {removed: docId2}])); + + test.equal(_.size(o.state), 2); + test.equal(o.state[docId4], {_id: docId4, y: -1222}); + test.equal(o.state[docId1], {_id: docId1, y: 1222}); + clearOutput(o); + + await rem(docId2); + // Becomes [docId4 docId1 | docId3] + test.length(o.output, 0); + + await rem(docId4); + // Becomes [docId1 docId3] + test.length(o.output, 2); + test.isTrue(setsEqual(o.output, [{added: docId3}, {removed: docId4}])); + + test.equal(_.size(o.state), 2); + test.equal(o.state[docId3], {_id: docId3, y: 7222}); + test.equal(o.state[docId1], {_id: docId1, y: 1222}); + clearOutput(o); + }); + // TODO -> Also uses oplog + Tinytest.addAsync("mongo-livedata - observe sorted, limited, big initial set" + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("observeLimit-"+run, collectionOptions); + + var observer = async function () { + var state = {}; + var output = []; + var callbacks = { + changed: function (newDoc) { + output.push({changed: newDoc._id}); + state[newDoc._id] = newDoc; + }, + added: function (newDoc) { + output.push({added: newDoc._id}); + state[newDoc._id] = newDoc; + }, + removed: function (oldDoc) { + output.push({removed: oldDoc._id}); + delete state[oldDoc._id]; + } + }; + var handle = await coll.find({}, {sort: {x: 1, y: 1}, limit: 3}) + .observe(callbacks); + + return {output: output, handle: handle, state: state}; + }; + var clearOutput = function (o) { o.output.splice(0, o.output.length); }; + var ins = async function (doc) { + var id; + await runInFence(async function () { + id = await coll.insert(doc); + }); + return id; + }; + var rem = async function (id) { + await runInFence(async function () { await coll.remove(id); }); + }; + // tests '_id' subfields for all documents in oplog buffer + var testOplogBufferIds = function (ids) { + var bufferIds = []; + o.handle._multiplexer._observeDriver._unpublishedBuffer.forEach(function (x, id) { + bufferIds.push(id); + }); + + test.isTrue(setsEqual(ids, bufferIds), "expected: " + ids + "; got: " + bufferIds); + }; + var testSafeAppendToBufferFlag = function (expected) { + if (expected) { + test.isTrue(o.handle._multiplexer._observeDriver._safeAppendToBuffer); + } else { + test.isFalse(o.handle._multiplexer._observeDriver._safeAppendToBuffer); + } + }; + + var ids = {}; + for (const [idx, val] of [2, 4, 1, 3, 5, 5, 9, 1, 3, 2, 5].entries()) { + ids[idx] = await ins({ x: val, y: idx }); + } + + // Ensure that we are past all the 'i' entries before we run the query, so + // that we get the expected phase transitions. + await waitUntilOplogCaughtUp(); + + var o = await observer(); + var usesOplog = o.handle._multiplexer._observeDriver._usesOplog; + // x: [1 1 2 | 2 3 3] 4 5 5 5 9 + // id: [2 7 0 | 9 3 8] 1 4 5 10 6 + + test.length(o.output, 3); + test.isTrue(setsEqual([{added: ids[2]}, {added: ids[7]}, {added: ids[0]}], o.output)); + usesOplog && testOplogBufferIds([ids[9], ids[3], ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + await rem(ids[0]); + // x: [1 1 2 | 3 3] 4 5 5 5 9 + // id: [2 7 9 | 3 8] 1 4 5 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[0]}, {added: ids[9]}], o.output)); + usesOplog && testOplogBufferIds([ids[3], ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + await rem(ids[7]); + // x: [1 2 3 | 3] 4 5 5 5 9 + // id: [2 9 3 | 8] 1 4 5 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[7]}, {added: ids[3]}], o.output)); + usesOplog && testOplogBufferIds([ids[8]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + await rem(ids[3]); + // x: [1 2 3 | 4 5 5] 5 9 + // id: [2 9 8 | 1 4 5] 10 6 + test.length(o.output, 2); + test.isTrue(setsEqual([{removed: ids[3]}, {added: ids[8]}], o.output)); + usesOplog && testOplogBufferIds([ids[1], ids[4], ids[5]]); + usesOplog && testSafeAppendToBufferFlag(false); + clearOutput(o); + + await rem({ x: {$lt: 4} }); + // x: [4 5 5 | 5 9] + // id: [1 4 5 | 10 6] + test.length(o.output, 6); + test.isTrue(setsEqual([{removed: ids[2]}, {removed: ids[9]}, {removed: ids[8]}, + {added: ids[5]}, {added: ids[4]}, {added: ids[1]}], o.output)); + usesOplog && testOplogBufferIds([ids[10], ids[6]]); + usesOplog && testSafeAppendToBufferFlag(true); + clearOutput(o); + }); + } + + + testAsyncMulti('mongo-livedata - empty documents, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + const coll = new Mongo.Collection(this.collectionName, collectionOptions); + + const id = await runAndThrowIfNeeded(() => coll.insert({}), test); + + test.isTrue(id); + test.equal(await coll.find().count(), 1); + } + ]); // Regression test for #2413. -testAsyncMulti('mongo-livedata - upsert without callback, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName); - Meteor.subscribe('c-' + this.collectionName, expect()); - } - }, function (test, expect) { - var coll = new Mongo.Collection(this.collectionName, collectionOptions); + testAsyncMulti('mongo-livedata - upsert without callback, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function () { + const coll = new Mongo.Collection(this.collectionName, collectionOptions); - // No callback! Before fixing #2413, this method never returned and - // so no future DDP methods worked either. - coll.upsert('foo', {bar: 1}); - // Do something else on the same method and expect it to actually work. - // (If the bug comes back, this will 'async batch timeout'.) - coll.insert({}, expect(function(){})); - } -]); + // No callback! Before fixing #2413, this method never returned and + // so no future DDP methods worked either. + await coll.upsert('foo', {bar: 1}); + // Do something else on the same method and expect it to actually work. + // (If the bug comes back, this will 'async batch timeout'.) + await coll.insert({}); + } + ]); // Regression test for https://github.com/meteor/meteor/issues/8666. -testAsyncMulti('mongo-livedata - upsert with an undefined selector, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName); - Meteor.subscribe('c-' + this.collectionName, expect()); - } - }, function (test, expect) { - var coll = new Mongo.Collection(this.collectionName, collectionOptions); - var testWidget = { - name: 'Widget name' - }; - coll.upsert(testWidget._id, testWidget, expect(function (error, insertDetails) { - test.isFalse(error); + testAsyncMulti('mongo-livedata - upsert with an undefined selector, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + const coll = new Mongo.Collection(this.collectionName, collectionOptions); + const testWidget = { + name: 'Widget name' + }; + + const insertDetails = await runAndThrowIfNeeded(() => coll.upsert(testWidget._id, testWidget), test); test.equal( - coll.findOne(insertDetails.insertedId), - Object.assign({ _id: insertDetails.insertedId }, testWidget) + await coll.findOne(insertDetails.insertedId), + Object.assign({ _id: insertDetails.insertedId }, testWidget) ); - })); - } -]); + } + ]); // See https://github.com/meteor/meteor/issues/594. -testAsyncMulti('mongo-livedata - document with length, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); - } - }, function (test, expect) { - var self = this; - var coll = self.coll = new Mongo.Collection(self.collectionName, collectionOptions); + testAsyncMulti('mongo-livedata - document with length, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + const self = this; + const coll = self.coll = new Mongo.Collection(self.collectionName, collectionOptions); - coll.insert({foo: 'x', length: 0}, expect(function (err, id) { - test.isFalse(err); + const id = await runAndThrowIfNeeded(() => coll.insert({foo: 'x', length: 0}), test); test.isTrue(id); self.docId = id; - test.equal(coll.findOne(self.docId), - {_id: self.docId, foo: 'x', length: 0}); - })); - }, - function (test, expect) { - var self = this; - var coll = self.coll; - coll.update(self.docId, {$set: {length: 5}}, expect(function (err) { - test.isFalse(err); - test.equal(coll.findOne(self.docId), - {_id: self.docId, foo: 'x', length: 5}); - })); - } -]); + test.equal(await coll.findOne(self.docId), + {_id: self.docId, foo: 'x', length: 0}); + }, + async function (test) { + const self = this; + const coll = self.coll; -testAsyncMulti('mongo-livedata - document with a date, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); + await runAndThrowIfNeeded(() => coll.update(self.docId, {$set: {length: 5}}), test); + test.equal(await coll.findOne(self.docId), + {_id: self.docId, foo: 'x', length: 5}); } - }, function (test, expect) { + ]); - var coll = new Mongo.Collection(this.collectionName, collectionOptions); - var docId; - coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { - test.isFalse(err); + testAsyncMulti('mongo-livedata - document with a date, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + const coll = new Mongo.Collection(this.collectionName, collectionOptions); + const id = await runAndThrowIfNeeded(() => coll.insert({d: new Date(1356152390004)}), test); test.isTrue(id); - docId = id; - var cursor = coll.find(); - test.equal(cursor.count(), 1); - test.equal(coll.findOne().d.getFullYear(), 2012); - })); - } -]); - -testAsyncMulti('mongo-livedata - document goes through a transform, ' + idGeneration, [ - function (test, expect) { - var self = this; - var seconds = function (doc) { - doc.seconds = function () {return doc.d.getSeconds();}; - return doc; - }; - TRANSFORMS["seconds"] = seconds; - self.collectionOptions = { - idGeneration: idGeneration, - transform: seconds, - transformName: "seconds" - }; - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); + test.equal(await coll.find().count(), 1); + test.equal((await coll.findOne()).d.getFullYear(), 2012); } - }, function (test, expect) { - var self = this; - self.coll = new Mongo.Collection(self.collectionName, self.collectionOptions); - var obs; - var expectAdd = expect(function (doc) { - test.equal(doc.seconds(), 50); - }); - var expectRemove = expect(function (doc) { - test.equal(doc.seconds(), 50); - obs.stop(); - }); - self.coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { - test.isFalse(err); + ]); + +// FIXME + testAsyncMulti('mongo-livedata - document goes through a transform, ' + idGeneration, [ + function (test, expect) { + var self = this; + var seconds = function (doc) { + doc.seconds = function () {return doc.d.getSeconds();}; + return doc; + }; + TRANSFORMS["seconds"] = seconds; + self.collectionOptions = { + idGeneration: idGeneration, + transform: seconds, + transformName: "seconds" + }; + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test, expect) { + var self = this; + self.coll = new Mongo.Collection(self.collectionName, self.collectionOptions); + var obs; + var expectAdd = expect(function (doc) { + test.equal(doc.seconds(), 50); + }); + var expectRemove = expect(function (doc) { + test.equal(doc.seconds(), 50); + return obs.stop(); + }); + const id = await runAndThrowIfNeeded(() => self.coll.insert({d: new Date(1356152390004)}), test, false); test.isTrue(id); var cursor = self.coll.find(); - obs = cursor.observe({ + obs = await cursor.observe({ added: expectAdd, removed: expectRemove }); - test.equal(cursor.count(), 1); - test.equal(cursor.fetch()[0].seconds(), 50); - test.equal(self.coll.findOne().seconds(), 50); - test.equal(self.coll.findOne({}, {transform: null}).seconds, undefined); - test.equal(self.coll.findOne({}, { + test.equal(await cursor.count(), 1); + test.equal((await cursor.fetch())[0].seconds(), 50); + test.equal((await self.coll.findOne()).seconds(), 50); + test.equal((await self.coll.findOne({}, {transform: null})).seconds, undefined); + test.equal((await self.coll.findOne({}, { transform: function (doc) {return {seconds: doc.d.getSeconds()};} - }).seconds, 50); - self.coll.remove(id); - })); - }, - function (test, expect) { - var self = this; - self.coll.insert({d: new Date(1356152390004)}, expect(function (err, id) { - test.isFalse(err); - test.isTrue(id); - self.id1 = id; - })); - self.coll.insert({d: new Date(1356152391004)}, expect(function (err, id) { - test.isFalse(err); - test.isTrue(id); - self.id2 = id; - })); - } -]); + })).seconds, 50); + await self.coll.remove(id); + }, + async function (test) { + var self = this; + self.id1 = await runAndThrowIfNeeded(() => self.coll.insert({d: new Date(1356152390004)}), test, false); + test.isTrue(self.id1); -testAsyncMulti('mongo-livedata - transform sets _id if not present, ' + idGeneration, [ - function (test, expect) { - var self = this; - var justId = function (doc) { - return _.omit(doc, '_id'); - }; - TRANSFORMS["justId"] = justId; - var collectionOptions = { - idGeneration: idGeneration, - transform: justId, - transformName: "justId" - }; - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); + self.id2 = await runAndThrowIfNeeded(() => self.coll.insert({d: new Date(1356152391004)}), test, false); + test.isTrue(self.id2); } - }, function (test, expect) { - var self = this; - self.coll = new Mongo.Collection(this.collectionName, collectionOptions); - self.coll.insert({}, expect(function (err, id) { - test.isFalse(err); + ]); + + testAsyncMulti('mongo-livedata - transform sets _id if not present, ' + idGeneration, [ + function (test, expect) { + var self = this; + var justId = function (doc) { + return _.omit(doc, '_id'); + }; + TRANSFORMS["justId"] = justId; + var collectionOptions = { + idGeneration: idGeneration, + transform: justId, + transformName: "justId" + }; + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + var self = this; + self.coll = new Mongo.Collection(this.collectionName, collectionOptions); + const id = await runAndThrowIfNeeded(() => self.coll.insert({}), test); test.isTrue(id); - test.equal(self.coll.findOne()._id, id); - })); - } -]); - -var bin = Base64.decode( - "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyBy" + - "ZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJv" + - "bSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhl" + - "IG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdo" + - "dCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdl" + - "bmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9y" + - "dCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); - -testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ - function (test, expect) { - // XXX probably shouldn't use EJSON's private test symbols - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); + test.equal((await self.coll.findOne())._id, id); } - }, function (test, expect) { - var coll = new Mongo.Collection(this.collectionName, collectionOptions); - var docId; - coll.insert({b: bin}, expect(function (err, id) { - test.isFalse(err); + ]); + + var bin = Base64.decode( + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyBy" + + "ZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJv" + + "bSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhl" + + "IG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdo" + + "dCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdl" + + "bmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9y" + + "dCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); + + testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ + function (test, expect) { + // XXX probably shouldn't use EJSON's private test symbols + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, async function (test) { + const coll = new Mongo.Collection(this.collectionName, collectionOptions); + const id = await runAndThrowIfNeeded(() => coll.insert({b: bin}), test); test.isTrue(id); - docId = id; - var cursor = coll.find(); - test.equal(cursor.count(), 1); - var inColl = coll.findOne(); + test.equal(await coll.find().count(), 1); + var inColl = await coll.findOne(); test.isTrue(EJSON.isBinary(inColl.b)); test.equal(inColl.b, bin); - })); - } -]); - -testAsyncMulti('mongo-livedata - document with a custom type, ' + idGeneration, [ - function (test, expect) { - this.collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); - Meteor.subscribe('c-' + this.collectionName, expect()); } - }, + ]); - function (test, expect) { - var self = this; - self.coll = new Mongo.Collection(this.collectionName, collectionOptions); - var docId; - // Dog is implemented at the top of the file, outside of the idGeneration - // loop (so that we only call EJSON.addType once). - var d = new Dog("reginald", null); - self.coll.insert({d: d}, expect(function (err, id) { - test.isFalse(err); + testAsyncMulti('mongo-livedata - document with a custom type, ' + idGeneration, [ + function (test, expect) { + this.collectionName = Random.id(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', this.collectionName, collectionOptions); + Meteor.subscribe('c-' + this.collectionName, expect()); + } + }, + + async function (test) { + var self = this; + self.coll = new Mongo.Collection(this.collectionName, collectionOptions); + var docId; + // Dog is implemented at the top of the file, outside of the idGeneration + // loop (so that we only call EJSON.addType once). + var d = new Dog("reginald", null); + const id = await runAndThrowIfNeeded(() => self.coll.insert({d}), test, false); test.isTrue(id); docId = id; self.docId = docId; var cursor = self.coll.find(); - test.equal(cursor.count(), 1); - var inColl = self.coll.findOne(); + test.equal(await cursor.count(), 1); + var inColl = await self.coll.findOne(); test.isTrue(inColl); inColl && test.equal(inColl.d.speak(), "woof"); inColl && test.isNull(inColl.d.color); - })); - }, + }, - function (test, expect) { - var self = this; - self.coll.insert(new Dog("rover", "orange"), expect(function (err, id) { - test.isTrue(err); - test.isFalse(id); - })); - }, - - function (test, expect) { - var self = this; - self.coll.update( - self.docId, new Dog("rover", "orange"), expect(function (err) { + function (test, expect) { + var self = this; + self.coll.insert(new Dog("rover", "orange"), expect(function (err, id) { test.isTrue(err); + test.isFalse(id); })); - } -]); + }, -if (Meteor.isServer) { - Tinytest.addAsync("mongo-livedata - update return values, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); + async function (test, expect) { + var self = this; + self.coll.update( + self.docId, new Dog("rover", "orange"), expect(function (err) { + test.isTrue(err); + })); + } + ]); - coll.insert({ foo: "bar" }); - coll.insert({ foo: "baz" }); - test.equal(coll.update({}, { $set: { foo: "qux" } }, { multi: true }), - 2); - coll.update({}, { $set: { foo: "quux" } }, { multi: true }, function (err, result) { - test.isFalse(err); + if (Meteor.isServer) { + Tinytest.addAsync("mongo-livedata - update return values, " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); + + await coll.insert({ foo: "bar" }); + await coll.insert({ foo: "baz" }); + test.equal(await coll.update({}, { $set: { foo: "qux" } }, { multi: true }), + 2); + const result = await runAndThrowIfNeeded(() => coll.update({}, { $set: { foo: "quux" } }, { multi: true }), test); test.equal(result, 2); - onComplete(); }); - }); - Tinytest.addAsync("mongo-livedata - remove return values, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); + Tinytest.addAsync("mongo-livedata - remove return values, " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_update_result_"+run, collectionOptions); - coll.insert({ foo: "bar" }); - coll.insert({ foo: "baz" }); - test.equal(coll.remove({}), 2); - coll.insert({ foo: "bar" }); - coll.insert({ foo: "baz" }); - coll.remove({}, function (err, result) { - test.isFalse(err); + await coll.insert({ foo: "bar" }); + await coll.insert({ foo: "baz" }); + test.equal(await coll.remove({}), 2); + await coll.insert({ foo: "bar" }); + await coll.insert({ foo: "baz" }); + const result = await runAndThrowIfNeeded(() => coll.remove({}), test); test.equal(result, 2); - onComplete(); - }); - }); - - - Tinytest.addAsync("mongo-livedata - id-based invalidation, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - var coll = new Mongo.Collection("livedata_invalidation_collection_"+run, collectionOptions); - - coll.allow({ - update: function () {return true;}, - remove: function () {return true;} }); - var id1 = coll.insert({x: 42, is1: true}); - var id2 = coll.insert({x: 50, is2: true}); - var polls = {}; - var handlesToStop = []; - var observe = function (name, query) { - var handle = coll.find(query).observeChanges({ - // Make sure that we only poll on invalidation, not due to time, and - // keep track of when we do. Note: this option disables the use of - // oplogs (which admittedly is somewhat irrelevant to this feature). - _testOnlyPollCallback: function () { - polls[name] = (name in polls ? polls[name] + 1 : 1); - } + Tinytest.addAsync("mongo-livedata - id-based invalidation, " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_invalidation_collection_"+run, collectionOptions); + + coll.allow({ + update: function () {return true;}, + remove: function () {return true;} }); - handlesToStop.push(handle); - }; - observe("all", {}); - observe("id1Direct", id1); - observe("id1InQuery", {_id: id1, z: null}); - observe("id2Direct", id2); - observe("id2InQuery", {_id: id2, z: null}); - observe("bothIds", {_id: {$in: [id1, id2]}}); + var id1 = await coll.insert({x: 42, is1: true}); + var id2 = await coll.insert({x: 50, is2: true}); - var resetPollsAndRunInFence = function (f) { - polls = {}; - runInFence(f); - }; + var polls = {}; + var handlesToStop = []; + var observe = async function (name, query) { + var handle = await coll.find(query).observeChanges({ + // Make sure that we only poll on invalidation, not due to time, and + // keep track of when we do. Note: this option disables the use of + // oplogs (which admittedly is somewhat irrelevant to this feature). + _testOnlyPollCallback: function () { + polls[name] = (name in polls ? polls[name] + 1 : 1); + } + }); + handlesToStop.push(handle); + }; - // Update id1 directly. This should poll all but the "id2" queries. "all" - // and "bothIds" increment by 2 because they are looking at both. - resetPollsAndRunInFence(function () { - coll.update(id1, {$inc: {x: 1}}); + await observe("all", {}); + await observe("id1Direct", id1); + await observe("id1InQuery", {_id: id1, z: null}); + await observe("id2Direct", id2); + await observe("id2InQuery", {_id: id2, z: null}); + await observe("bothIds", {_id: {$in: [id1, id2]}}); + + var resetPollsAndRunInFence = async function (f) { + polls = {}; + await runInFence(f); + }; + + // Update id1 directly. This should poll all but the "id2" queries. "all" + // and "bothIds" increment by 2 because they are looking at both. + await resetPollsAndRunInFence(async function () { + await coll.update(id1, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id1Direct: 1, id1InQuery: 1, bothIds: 1}); + + // Update id2 using a funny query. This should poll all but the "id1" + // queries. + await resetPollsAndRunInFence(async function () { + await coll.update({_id: id2, q: null}, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id2Direct: 1, id2InQuery: 1, bothIds: 1}); + + // Update both using a $in query. Should poll each of them exactly once. + await resetPollsAndRunInFence(async function () { + await coll.update({_id: {$in: [id1, id2]}, q: null}, {$inc: {x: 1}}); + }); + test.equal( + polls, + {all: 1, id1Direct: 1, id1InQuery: 1, id2Direct: 1, id2InQuery: 1, + bothIds: 1}); + + _.each(handlesToStop, function (h) {h.stop();}); }); - test.equal( - polls, - {all: 1, id1Direct: 1, id1InQuery: 1, bothIds: 1}); - // Update id2 using a funny query. This should poll all but the "id1" - // queries. - resetPollsAndRunInFence(function () { - coll.update({_id: id2, q: null}, {$inc: {x: 1}}); + Tinytest.addAsync("mongo-livedata - upsert error parse, " + idGeneration, async function (test) { + var run = test.runId(); + var coll = new Mongo.Collection("livedata_upsert_errorparse_collection_"+run, collectionOptions); + + await coll.insert({_id:'foobar', foo: 'bar'}); + var err; + try { + await coll.update({foo: 'bar'}, {_id: 'cowbar'}); + } catch (e) { + err = e; + } + test.isTrue(err); + test.isTrue(MongoInternals.Connection._isCannotChangeIdError(err)); + + try { + await coll.insert({_id: 'foobar'}); + } catch (e) { + err = e; + } + test.isTrue(err); + // duplicate id error is not same as change id error + test.isFalse(MongoInternals.Connection._isCannotChangeIdError(err)); }); - test.equal( - polls, - {all: 1, id2Direct: 1, id2InQuery: 1, bothIds: 1}); - // Update both using a $in query. Should poll each of them exactly once. - resetPollsAndRunInFence(function () { - coll.update({_id: {$in: [id1, id2]}, q: null}, {$inc: {x: 1}}); - }); - test.equal( - polls, - {all: 1, id1Direct: 1, id1InQuery: 1, id2Direct: 1, id2InQuery: 1, - bothIds: 1}); - - _.each(handlesToStop, function (h) {h.stop();}); - onComplete(); - }); - - Tinytest.add("mongo-livedata - upsert error parse, " + idGeneration, function (test) { - var run = test.runId(); - var coll = new Mongo.Collection("livedata_upsert_errorparse_collection_"+run, collectionOptions); - - coll.insert({_id:'foobar', foo: 'bar'}); - var err; - try { - coll.update({foo: 'bar'}, {_id: 'cowbar'}); - } catch (e) { - err = e; - } - test.isTrue(err); - test.isTrue(MongoInternals.Connection._isCannotChangeIdError(err)); - - try { - coll.insert({_id: 'foobar'}); - } catch (e) { - err = e; - } - test.isTrue(err); - // duplicate id error is not same as change id error - test.isFalse(MongoInternals.Connection._isCannotChangeIdError(err)); - }); - -} // end Meteor.isServer + } // end Meteor.isServer // This test is duplicated below (with some changes) for async upserts that go // over the network. -_.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { - _.each([true, false], function (useUpdate) { - _.each([true, false], function (useDirectCollection) { - Tinytest.add("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert" + (minimongo ? " minimongo" : "") + (useDirectCollection ? " direct collection " : "") + ", " + idGeneration, function (test) { - var run = test.runId(); - var options = collectionOptions; - // We don't get ids back when we use update() to upsert, or when we are - // directly calling MongoConnection.upsert(). - var skipIds = useUpdate || (! minimongo && useDirectCollection); - if (minimongo) - options = _.extend({}, collectionOptions, { connection: null }); - var coll = new Mongo.Collection( - "livedata_upsert_collection_"+run+ - (useUpdate ? "_update_" : "") + - (minimongo ? "_minimongo_" : "") + - (useDirectCollection ? "_direct_" : "") + "", - options - ); - if (useDirectCollection) - coll = coll._collection; + // TODO -> FIXME + _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { + _.each([true, false], function (useUpdate) { + _.each([true, false], function (useDirectCollection) { + Tinytest.addAsync("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert" + (minimongo ? " minimongo" : "") + (useDirectCollection ? " direct collection " : "") + ", " + idGeneration, async function (test) { + var run = test.runId(); + var options = collectionOptions; + // We don't get ids back when we use update() to upsert, or when we are + // directly calling MongoConnection.upsert(). + var skipIds = useUpdate || (! minimongo && useDirectCollection); + if (minimongo) + options = _.extend({}, collectionOptions, { connection: null }); + var coll = new Mongo.Collection( + "livedata_upsert_collection_"+run+ + (useUpdate ? "_update_" : "") + + (minimongo ? "_minimongo_" : "") + + (useDirectCollection ? "_direct_" : "") + "", + options + ); + if (useDirectCollection) + coll = coll._collection; - var result1 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); - test.equal(result1.numberAffected, 1); - if (! skipIds) - test.isTrue(result1.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); + var result1 = await upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'bar'}); + test.equal(result1.numberAffected, 1); + if (! skipIds) + test.isTrue(result1.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]); - var result2 = upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); - test.equal(result2.numberAffected, 1); - if (! skipIds) - test.isFalse(result2.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); + var result2 = await upsert(coll, useUpdate, {foo: 'bar'}, {foo: 'baz'}); + test.equal(result2.numberAffected, 1); + if (! skipIds) + test.isFalse(result2.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); - coll.remove({}); + await coll.remove({}); - // Test values that require transformation to go into Mongo: + // Test values that require transformation to go into Mongo: - var t1 = new Mongo.ObjectID(); - var t2 = new Mongo.ObjectID(); - var result3 = upsert(coll, useUpdate, {foo: t1}, {foo: t1}); - test.equal(result3.numberAffected, 1); - if (! skipIds) - test.isTrue(result3.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{foo: t1, _id: result3.insertedId}]); + var t1 = new Mongo.ObjectID(); + var t2 = new Mongo.ObjectID(); + var result3 = await upsert(coll, useUpdate, {foo: t1}, {foo: t1}); + test.equal(result3.numberAffected, 1); + if (! skipIds) + test.isTrue(result3.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{foo: t1, _id: result3.insertedId}]); - var result4 = upsert(coll, useUpdate, {foo: t1}, {foo: t2}); - test.equal(result2.numberAffected, 1); - if (! skipIds) - test.isFalse(result2.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); + var result4 = await upsert(coll, useUpdate, {foo: t1}, {foo: t2}); + test.equal(result2.numberAffected, 1); + if (! skipIds) + test.isFalse(result2.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); - coll.remove({}); + await coll.remove({}); - // Test modification by upsert + // Test modification by upsert - var result5 = upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 1}}); - test.equal(result5.numberAffected, 1); - if (! skipIds) - test.isTrue(result5.insertedId); - var davidId = result5.insertedId; - compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 1, _id: davidId}]); + var result5 = await upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 1}}); + test.equal(result5.numberAffected, 1); + if (! skipIds) + test.isTrue(result5.insertedId); + var davidId = result5.insertedId; + compareResults(test, skipIds, await coll.find().fetch(), [{name: 'David', foo: 1, _id: davidId}]); - test.throws(function () { - // test that bad modifier fails fast - upsert(coll, useUpdate, {name: 'David'}, {$blah: {foo: 2}}); + await test.throwsAsync(function () { + // test that bad modifier fails fast + return upsert(coll, useUpdate, {name: 'David'}, {$blah: {foo: 2}}); + }); + + + var result6 = await upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 2}}); + test.equal(result6.numberAffected, 1); + if (! skipIds) + test.isFalse(result6.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{name: 'David', foo: 2, + _id: result5.insertedId}]); + + var emilyId = await coll.insert({name: 'Emily', foo: 2}); + compareResults(test, skipIds, await coll.find().fetch(), [{name: 'David', foo: 2, _id: davidId}, + {name: 'Emily', foo: 2, _id: emilyId}]); + + // multi update by upsert + var result7 = await upsert(coll, useUpdate, {foo: 2}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}); + test.equal(result7.numberAffected, 2); + if (! skipIds) + test.isFalse(result7.insertedId); + compareResults(test, skipIds, await coll.find().fetch(), [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}]); + + // insert by multi upsert + var result8 = await upsert(coll, useUpdate, {foo: 3}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}); + test.equal(result8.numberAffected, 1); + if (! skipIds) + test.isTrue(result8.insertedId); + var fredId = result8.insertedId; + compareResults(test, skipIds, await coll.find().fetch(), + [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); + + // test `insertedId` option + var result9 = await upsert(coll, useUpdate, {name: 'Steve'}, + {name: 'Steve'}, + {insertedId: 'steve'}); + test.equal(result9.numberAffected, 1); + if (! skipIds) + test.equal(result9.insertedId, 'steve'); + compareResults(test, skipIds, await coll.find().fetch(), + [{name: 'David', foo: 2, bar: 7, _id: davidId}, + {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}, + {name: 'Steve', _id: 'steve'}]); + test.isTrue(await coll.findOne('steve')); + test.isFalse(await coll.findOne('fred')); + + // Test $ operator in selectors. + + var result10 = await upsert(coll, useUpdate, + {$or: [{name: 'David'}, {name: 'Emily'}]}, + {$set: {foo: 3}}, {multi: true}); + test.equal(result10.numberAffected, 2); + if (! skipIds) + test.isFalse(result10.insertedId); + compareResults(test, skipIds, + [await coll.findOne({name: 'David'}), await coll.findOne({name: 'Emily'})], + [{name: 'David', foo: 3, bar: 7, _id: davidId}, + {name: 'Emily', foo: 3, bar: 7, _id: emilyId}] + ); + + var result11 = await upsert( + coll, useUpdate, + { + name: 'Charlie', + $or: [{ foo: 2}, { bar: 7 }] + }, + { $set: { foo: 3 } } + ); + test.equal(result11.numberAffected, 1); + if (! skipIds) + test.isTrue(result11.insertedId); + var charlieId = result11.insertedId; + compareResults(test, skipIds, + await coll.find({ name: 'Charlie' }).fetch(), + [{name: 'Charlie', foo: 3, _id: charlieId}]); }); - - - var result6 = upsert(coll, useUpdate, {name: 'David'}, {$set: {foo: 2}}); - test.equal(result6.numberAffected, 1); - if (! skipIds) - test.isFalse(result6.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, - _id: result5.insertedId}]); - - var emilyId = coll.insert({name: 'Emily', foo: 2}); - compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, _id: davidId}, - {name: 'Emily', foo: 2, _id: emilyId}]); - - // multi update by upsert - var result7 = upsert(coll, useUpdate, {foo: 2}, - {$set: {bar: 7}, - $setOnInsert: {name: 'Fred', foo: 2}}, - {multi: true}); - test.equal(result7.numberAffected, 2); - if (! skipIds) - test.isFalse(result7.insertedId); - compareResults(test, skipIds, coll.find().fetch(), [{name: 'David', foo: 2, bar: 7, _id: davidId}, - {name: 'Emily', foo: 2, bar: 7, _id: emilyId}]); - - // insert by multi upsert - var result8 = upsert(coll, useUpdate, {foo: 3}, - {$set: {bar: 7}, - $setOnInsert: {name: 'Fred', foo: 2}}, - {multi: true}); - test.equal(result8.numberAffected, 1); - if (! skipIds) - test.isTrue(result8.insertedId); - var fredId = result8.insertedId; - compareResults(test, skipIds, coll.find().fetch(), - [{name: 'David', foo: 2, bar: 7, _id: davidId}, - {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, - {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); - - // test `insertedId` option - var result9 = upsert(coll, useUpdate, {name: 'Steve'}, - {name: 'Steve'}, - {insertedId: 'steve'}); - test.equal(result9.numberAffected, 1); - if (! skipIds) - test.equal(result9.insertedId, 'steve'); - compareResults(test, skipIds, coll.find().fetch(), - [{name: 'David', foo: 2, bar: 7, _id: davidId}, - {name: 'Emily', foo: 2, bar: 7, _id: emilyId}, - {name: 'Fred', foo: 2, bar: 7, _id: fredId}, - {name: 'Steve', _id: 'steve'}]); - test.isTrue(coll.findOne('steve')); - test.isFalse(coll.findOne('fred')); - - // Test $ operator in selectors. - - var result10 = upsert(coll, useUpdate, - {$or: [{name: 'David'}, {name: 'Emily'}]}, - {$set: {foo: 3}}, {multi: true}); - test.equal(result10.numberAffected, 2); - if (! skipIds) - test.isFalse(result10.insertedId); - compareResults(test, skipIds, - [coll.findOne({name: 'David'}), coll.findOne({name: 'Emily'})], - [{name: 'David', foo: 3, bar: 7, _id: davidId}, - {name: 'Emily', foo: 3, bar: 7, _id: emilyId}] - ); - - var result11 = upsert( - coll, useUpdate, - { - name: 'Charlie', - $or: [{ foo: 2}, { bar: 7 }] - }, - { $set: { foo: 3 } } - ); - test.equal(result11.numberAffected, 1); - if (! skipIds) - test.isTrue(result11.insertedId); - var charlieId = result11.insertedId; - compareResults(test, skipIds, - coll.find({ name: 'Charlie' }).fetch(), - [{name: 'Charlie', foo: 3, _id: charlieId}]); }); }); }); -}); -var asyncUpsertTestName = function (useNetwork, useDirectCollection, - useUpdate, idGeneration) { - return "mongo-livedata - async " + - (useUpdate ? "update " : "") + - "upsert " + - (useNetwork ? "over network " : "") + - (useDirectCollection ? ", direct collection " : "") + - idGeneration; -}; + var asyncUpsertTestName = function (useNetwork, useDirectCollection, + useUpdate, idGeneration) { + return "mongo-livedata - async " + + (useUpdate ? "update " : "") + + "upsert " + + (useNetwork ? "over network " : "") + + (useDirectCollection ? ", direct collection " : "") + + idGeneration; + }; +// TODO -> FIXME // This is a duplicate of the test above, with some changes to make it work for // callback style. On the client, we test server-backed and in-memory // collections, and run the tests for both the Mongo.Collection and the @@ -1914,341 +1891,353 @@ var asyncUpsertTestName = function (useNetwork, useDirectCollection, // the Mongo.Collection and the MongoConnection. // // XXX Rewrite with testAsyncMulti, that would simplify things a lot! -_.each(Meteor.isServer ? [false] : [true, false], function (useNetwork) { - _.each(useNetwork ? [false] : [true, false], function (useDirectCollection) { - _.each([true, false], function (useUpdate) { - Tinytest.addAsync(asyncUpsertTestName(useNetwork, useDirectCollection, useUpdate, idGeneration), function (test, onComplete) { - var coll; - var run = test.runId(); - var collName = "livedata_upsert_collection_"+run+ +if (Meteor.isServer) { + _.each(Meteor.isServer ? [false] : [true, false], function (useNetwork) { + _.each(useNetwork ? [false] : [true, false], function (useDirectCollection) { + _.each([true, false], function (useUpdate) { + Tinytest.addAsync(asyncUpsertTestName(useNetwork, useDirectCollection, useUpdate, idGeneration), function (test, onComplete) { + var coll; + var run = test.runId(); + var collName = "livedata_upsert_collection_"+run+ (useUpdate ? "_update_" : "") + (useNetwork ? "_network_" : "") + (useDirectCollection ? "_direct_" : ""); - var next0 = function () { - // Test starts here. - upsert(coll, useUpdate, {_id: 'foo'}, {_id: 'foo', foo: 'bar'}, next1); - }; + var next0 = function () { + // Test starts here. + upsert(coll, useUpdate, {_id: 'foo'}, {_id: 'foo', foo: 'bar'}, next1); + }; - if (useNetwork) { - Meteor.call("createInsecureCollection", collName, collectionOptions); - coll = new Mongo.Collection(collName, collectionOptions); - Meteor.subscribe("c-" + collName, next0); - } else { - var opts = _.clone(collectionOptions); - if (Meteor.isClient) - opts.connection = null; - coll = new Mongo.Collection(collName, opts); - if (useDirectCollection) - coll = coll._collection; - } - - var result1; - var next1 = function (err, result) { - result1 = result; - test.equal(result1.numberAffected, 1); - if (! useUpdate) { - test.isTrue(result1.insertedId); - test.equal(result1.insertedId, 'foo'); - } - compareResults(test, useUpdate, coll.find().fetch(), [{foo: 'bar', _id: 'foo'}]); - upsert(coll, useUpdate, {_id: 'foo'}, {foo: 'baz'}, next2); - }; - - if (! useNetwork) { - next0(); - } - - var t1, t2, result2; - var next2 = function (err, result) { - result2 = result; - test.equal(result2.numberAffected, 1); - if (! useUpdate) - test.isFalse(result2.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); - coll.remove({_id: 'foo'}); - compareResults(test, useUpdate, coll.find().fetch(), []); - - // Test values that require transformation to go into Mongo: - - t1 = new Mongo.ObjectID(); - t2 = new Mongo.ObjectID(); - upsert(coll, useUpdate, {_id: t1}, {_id: t1, foo: 'bar'}, next3); - }; - - var result3; - var next3 = function (err, result) { - result3 = result; - test.equal(result3.numberAffected, 1); - if (! useUpdate) { - test.isTrue(result3.insertedId); - test.equal(t1, result3.insertedId); - } - compareResults(test, useUpdate, coll.find().fetch(), [{_id: t1, foo: 'bar'}]); - - upsert(coll, useUpdate, {_id: t1}, {foo: t2}, next4); - }; - - var next4 = function (err, result4) { - test.equal(result2.numberAffected, 1); - if (! useUpdate) - test.isFalse(result2.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); - - coll.remove({_id: t1}); - - // Test modification by upsert - upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 1}}, next5); - }; - - var result5; - var next5 = function (err, result) { - result5 = result; - test.equal(result5.numberAffected, 1); - if (! useUpdate) { - test.isTrue(result5.insertedId); - test.equal(result5.insertedId, 'David'); - } - var davidId = result5.insertedId; - compareResults(test, useUpdate, coll.find().fetch(), [{foo: 1, _id: davidId}]); - - if (! Meteor.isClient && useDirectCollection) { - // test that bad modifier fails - // The stub throws an exception about the invalid modifier, which - // livedata logs (so we suppress it). - Meteor._suppress_log(1); - upsert(coll, useUpdate, {_id: 'David'}, {$blah: {foo: 2}}, function (err) { - if (! (Meteor.isClient && useDirectCollection)) - test.isTrue(err); - upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); - }); + if (useNetwork) { + Meteor.call("createInsecureCollection", collName, collectionOptions); + coll = new Mongo.Collection(collName, collectionOptions); + Meteor.subscribe("c-" + collName, next0); } else { - // XXX skip this test for now for LocalCollection; the fact that - // we're in a nested sequence of callbacks means we're inside a - // Meteor.defer, which means the exception just gets - // logged. Something should be done about this at some point? Maybe - // LocalCollection callbacks don't really have to be deferred. - upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); + var opts = _.clone(collectionOptions); + if (Meteor.isClient) + opts.connection = null; + coll = new Mongo.Collection(collName, opts); + if (useDirectCollection) + coll = coll._collection; } - }; - var result6; - var next6 = function (err, result) { - result6 = result; - test.equal(result6.numberAffected, 1); - if (! useUpdate) - test.isFalse(result6.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}]); + var result1; + var next1 = async function (err, result) { + result1 = result; + test.equal(result1.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result1.insertedId); + test.equal(result1.insertedId, 'foo'); + } + compareResults(test, useUpdate, await coll.find().fetch(), [{foo: 'bar', _id: 'foo'}]); + upsert(coll, useUpdate, {_id: 'foo'}, {foo: 'baz'}, next2); + }; - var emilyId = coll.insert({_id: 'Emily', foo: 2}); - compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}, - {_id: 'Emily', foo: 2}]); - - // multi update by upsert. - // We can't actually update multiple documents since we have to do it by - // id, but at least make sure the multi flag doesn't mess anything up. - upsert(coll, useUpdate, {_id: 'Emily'}, - {$set: {bar: 7}, - $setOnInsert: {name: 'Fred', foo: 2}}, - {multi: true}, next7); - }; - - var result7; - var next7 = function (err, result) { - result7 = result; - test.equal(result7.numberAffected, 1); - if (! useUpdate) - test.isFalse(result7.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), [{_id: 'David', foo: 2}, - {_id: 'Emily', foo: 2, bar: 7}]); - - // insert by multi upsert - upsert(coll, useUpdate, {_id: 'Fred'}, - {$set: {bar: 7}, - $setOnInsert: {name: 'Fred', foo: 2}}, - {multi: true}, next8); - - }; - - var result8; - var next8 = function (err, result) { - result8 = result; - - test.equal(result8.numberAffected, 1); - if (! useUpdate) { - test.isTrue(result8.insertedId); - test.equal(result8.insertedId, 'Fred'); + if (! useNetwork) { + next0(); } - var fredId = result8.insertedId; - compareResults(test, useUpdate, coll.find().fetch(), - [{_id: 'David', foo: 2}, - {_id: 'Emily', foo: 2, bar: 7}, - {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); - onComplete(); - }; - }); - }); - }); -}); -if (Meteor.isClient) { - Tinytest.addAsync("mongo-livedata - async update/remove return values over network " + idGeneration, function (test, onComplete) { - var coll; - var run = test.runId(); - var collName = "livedata_upsert_collection_"+run; - Meteor.call("createInsecureCollection", collName, collectionOptions); - coll = new Mongo.Collection(collName, collectionOptions); - Meteor.subscribe("c-" + collName, function () { - coll.insert({ _id: "foo" }); - coll.insert({ _id: "bar" }); - coll.update({ _id: "foo" }, { $set: { foo: 1 } }, { multi: true }, function (err, result) { - test.isFalse(err); - test.equal(result, 1); - coll.update({ _id: "foo" }, { _id: "foo", foo: 2 }, function (err, result) { - test.isFalse(err); - test.equal(result, 1); - coll.update({ _id: "baz" }, { $set: { foo: 1 } }, function (err, result) { - test.isFalse(err); - test.equal(result, 0); - coll.remove({ _id: "foo" }, function (err, result) { - test.equal(result, 1); - coll.remove({ _id: "baz" }, function (err, result) { - test.equal(result, 0); - onComplete(); + var t1, t2, result2; + var next2 = async function (err, result) { + result2 = result; + test.equal(result2.numberAffected, 1); + if (! useUpdate) + test.isFalse(result2.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]); + await coll.remove({_id: 'foo'}); + compareResults(test, useUpdate, await coll.find().fetch(), []); + + // Test values that require transformation to go into Mongo: + + t1 = new Mongo.ObjectID(); + t2 = new Mongo.ObjectID(); + upsert(coll, useUpdate, {_id: t1}, {_id: t1, foo: 'bar'}, next3); + }; + + var result3; + var next3 = async function (err, result) { + result3 = result; + test.equal(result3.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result3.insertedId); + test.equal(t1, result3.insertedId); + } + compareResults(test, useUpdate, await coll.find().fetch(), [{_id: t1, foo: 'bar'}]); + + upsert(coll, useUpdate, {_id: t1}, {foo: t2}, next4); + }; + + var next4 = async function (err, result4) { + test.equal(result2.numberAffected, 1); + if (! useUpdate) + test.isFalse(result2.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]); + + await coll.remove({_id: t1}); + + // Test modification by upsert + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 1}}, next5); + }; + + var result5; + var next5 = async function (err, result) { + result5 = result; + test.equal(result5.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result5.insertedId); + test.equal(result5.insertedId, 'David'); + } + var davidId = result5.insertedId; + compareResults(test, useUpdate, await coll.find().fetch(), [{foo: 1, _id: davidId}]); + + if (! Meteor.isClient && useDirectCollection) { + // test that bad modifier fails + // The stub throws an exception about the invalid modifier, which + // livedata logs (so we suppress it). + Meteor._suppress_log(1); + upsert(coll, useUpdate, {_id: 'David'}, {$blah: {foo: 2}}, function (err) { + if (! (Meteor.isClient && useDirectCollection)) + test.isTrue(err); + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); }); - }); - }); + } else { + // XXX skip this test for now for LocalCollection; the fact that + // we're in a nested sequence of callbacks means we're inside a + // Meteor.defer, which means the exception just gets + // logged. Something should be done about this at some point? Maybe + // LocalCollection callbacks don't really have to be deferred. + upsert(coll, useUpdate, {_id: 'David'}, {$set: {foo: 2}}, next6); + } + }; + + var result6; + var next6 = async function (err, result) { + result6 = result; + test.equal(result6.numberAffected, 1); + if (! useUpdate) + test.isFalse(result6.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), [{_id: 'David', foo: 2}]); + + var emilyId = await coll.insert({_id: 'Emily', foo: 2}); + compareResults(test, useUpdate, await coll.find().fetch(), [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2}]); + + // multi update by upsert. + // We can't actually update multiple documents since we have to do it by + // id, but at least make sure the multi flag doesn't mess anything up. + upsert(coll, useUpdate, {_id: 'Emily'}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}, next7); + }; + + var result7; + var next7 = async function (err, result) { + result7 = result; + test.equal(result7.numberAffected, 1); + if (! useUpdate) + test.isFalse(result7.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2, bar: 7}]); + + // insert by multi upsert + upsert(coll, useUpdate, {_id: 'Fred'}, + {$set: {bar: 7}, + $setOnInsert: {name: 'Fred', foo: 2}}, + {multi: true}, next8); + + }; + + var result8; + var next8 = async function (err, result) { + result8 = result; + + test.equal(result8.numberAffected, 1); + if (! useUpdate) { + test.isTrue(result8.insertedId); + test.equal(result8.insertedId, 'Fred'); + } + var fredId = result8.insertedId; + compareResults(test, useUpdate, await coll.find().fetch(), + [{_id: 'David', foo: 2}, + {_id: 'Emily', foo: 2, bar: 7}, + {name: 'Fred', foo: 2, bar: 7, _id: fredId}]); + onComplete(); + }; }); }); }); }); } + if (Meteor.isClient) { + Tinytest.addAsync("mongo-livedata - async update/remove return values over network " + idGeneration, function (test, onComplete) { + var coll; + var run = test.runId(); + var collName = "livedata_upsert_collection_"+run; + Meteor.call("createInsecureCollection", collName, collectionOptions); + coll = new Mongo.Collection(collName, collectionOptions); + Meteor.subscribe("c-" + collName, function () { + coll.insert({ _id: "foo" }, (e1) => { + test.isFalse(e1); + coll.insert({ _id: "bar" }, (e2) => { + test.isFalse(e2); + coll.update({ _id: "foo" }, { $set: { foo: 1 } }, { multi: true }, function (err, result) { + test.isFalse(err); + test.equal(result, 1); + coll.update({ _id: "foo" }, { _id: "foo", foo: 2 }, function (err, result) { + test.isFalse(err); + test.equal(result, 1); + coll.update({ _id: "baz" }, { $set: { foo: 1 } }, function (err, result) { + test.isFalse(err); + test.equal(result, 0); + coll.remove({ _id: "foo" }, function (err, result) { + test.equal(result, 1); + coll.remove({ _id: "baz" }, function (err, result) { + test.equal(result, 0); + onComplete(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + } + +// TODO -> FIXME // Runs a method and its stub which do some upserts. The method throws an error // if we don't get the right return values. -if (Meteor.isClient) { - _.each([true, false], function (useUpdate) { - Tinytest.addAsync("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert in method, " + idGeneration, function (test, onComplete) { - var run = test.runId(); - upsertTestMethodColl = new Mongo.Collection(upsertTestMethod + "_collection_" + run, collectionOptions); - var m = {}; - delete Meteor.connection._methodHandlers[upsertTestMethod]; - m[upsertTestMethod] = function (run, useUpdate, options) { - upsertTestMethodImpl(upsertTestMethodColl, useUpdate, test); - }; - Meteor.methods(m); - Meteor.call(upsertTestMethod, run, useUpdate, collectionOptions, function (err, result) { + if (Meteor.isClient) { + _.each([true, false], function (useUpdate) { + Tinytest.addAsync("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert in method, " + idGeneration, async function (test) { + var run = test.runId(); + upsertTestMethodColl = new Mongo.Collection(upsertTestMethod + "_collection_" + run, collectionOptions); + var m = {}; + delete Meteor.connection._methodHandlers[upsertTestMethod]; + m[upsertTestMethod] = function (run, useUpdate, options) { + return upsertTestMethodImpl(upsertTestMethodColl, useUpdate, test); + }; + Meteor.methods(m); + let err; + try { + await Meteor.callAsync(upsertTestMethod, run, useUpdate, collectionOptions); + } catch (e) { + err = e; + } + test.isFalse(err); - onComplete(); + }); + }); + } + + _.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { + _.each([true, false], function (useUpdate) { + Tinytest.addAsync("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert by id" + (minimongo ? " minimongo" : "") + ", " + idGeneration, async function (test) { + var run = test.runId(); + var options = collectionOptions; + if (minimongo) + options = _.extend({}, collectionOptions, { connection: null }); + var coll = new Mongo.Collection("livedata_upsert_by_id_collection_"+run, options); + + var ret; + ret = await upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 1}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'foo'); + compareResults(test, useUpdate, await coll.find().fetch(), + [{_id: 'foo', x: 1}]); + + ret = await upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 2}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), + [{_id: 'foo', x: 2}]); + + ret = await upsert(coll, useUpdate, {_id: 'bar'}, {$set: {x: 1}}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'bar'); + compareResults(test, useUpdate, await coll.find().fetch(), + [{_id: 'foo', x: 2}, + {_id: 'bar', x: 1}]); + + await coll.remove({}); + ret = await upsert(coll, useUpdate, {_id: 'traq'}, {x: 1}); + + test.equal(ret.numberAffected, 1); + var myId = ret.insertedId; + if (useUpdate) { + myId = (await coll.findOne())._id; + } + // Starting with Mongo 2.6, upsert with entire document takes _id from the + // query, so the above upsert actually does an insert with _id traq + // instead of a random _id. Whenever we are using our simulated upsert, + // we have this behavior (whether running against Mongo 2.4 or 2.6). + // https://jira.mongodb.org/browse/SERVER-5289 + test.equal(myId, 'traq'); + compareResults(test, useUpdate, await coll.find().fetch(), + [{x: 1, _id: 'traq'}]); + + // this time, insert as _id 'traz' + ret = await upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 2}); + test.equal(ret.numberAffected, 1); + if (! useUpdate) + test.equal(ret.insertedId, 'traz'); + compareResults(test, useUpdate, await coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 2, _id: 'traz'}]); + + // now update _id 'traz' + ret = await upsert(coll, useUpdate, {_id: 'traz'}, {x: 3}); + test.equal(ret.numberAffected, 1); + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 3, _id: 'traz'}]); + + // now update, passing _id (which is ok as long as it's the same) + ret = await upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 4}); + test.equal(ret.numberAffected, 1); + test.isFalse(ret.insertedId); + compareResults(test, useUpdate, await coll.find().fetch(), + [{x: 1, _id: 'traq'}, + {x: 4, _id: 'traz'}]); + }); }); }); -} - -_.each(Meteor.isServer ? [true, false] : [true], function (minimongo) { - _.each([true, false], function (useUpdate) { - Tinytest.add("mongo-livedata - " + (useUpdate ? "update " : "") + "upsert by id" + (minimongo ? " minimongo" : "") + ", " + idGeneration, function (test) { - var run = test.runId(); - var options = collectionOptions; - if (minimongo) - options = _.extend({}, collectionOptions, { connection: null }); - var coll = new Mongo.Collection("livedata_upsert_by_id_collection_"+run, options); - - var ret; - ret = upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 1}}); - test.equal(ret.numberAffected, 1); - if (! useUpdate) - test.equal(ret.insertedId, 'foo'); - compareResults(test, useUpdate, coll.find().fetch(), - [{_id: 'foo', x: 1}]); - - ret = upsert(coll, useUpdate, {_id: 'foo'}, {$set: {x: 2}}); - test.equal(ret.numberAffected, 1); - if (! useUpdate) - test.isFalse(ret.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), - [{_id: 'foo', x: 2}]); - - ret = upsert(coll, useUpdate, {_id: 'bar'}, {$set: {x: 1}}); - test.equal(ret.numberAffected, 1); - if (! useUpdate) - test.equal(ret.insertedId, 'bar'); - compareResults(test, useUpdate, coll.find().fetch(), - [{_id: 'foo', x: 2}, - {_id: 'bar', x: 1}]); - - coll.remove({}); - ret = upsert(coll, useUpdate, {_id: 'traq'}, {x: 1}); - - test.equal(ret.numberAffected, 1); - var myId = ret.insertedId; - if (useUpdate) { - myId = coll.findOne()._id; - } - // Starting with Mongo 2.6, upsert with entire document takes _id from the - // query, so the above upsert actually does an insert with _id traq - // instead of a random _id. Whenever we are using our simulated upsert, - // we have this behavior (whether running against Mongo 2.4 or 2.6). - // https://jira.mongodb.org/browse/SERVER-5289 - test.equal(myId, 'traq'); - compareResults(test, useUpdate, coll.find().fetch(), - [{x: 1, _id: 'traq'}]); - - // this time, insert as _id 'traz' - ret = upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 2}); - test.equal(ret.numberAffected, 1); - if (! useUpdate) - test.equal(ret.insertedId, 'traz'); - compareResults(test, useUpdate, coll.find().fetch(), - [{x: 1, _id: 'traq'}, - {x: 2, _id: 'traz'}]); - - // now update _id 'traz' - ret = upsert(coll, useUpdate, {_id: 'traz'}, {x: 3}); - test.equal(ret.numberAffected, 1); - test.isFalse(ret.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), - [{x: 1, _id: 'traq'}, - {x: 3, _id: 'traz'}]); - - // now update, passing _id (which is ok as long as it's the same) - ret = upsert(coll, useUpdate, {_id: 'traz'}, {_id: 'traz', x: 4}); - test.equal(ret.numberAffected, 1); - test.isFalse(ret.insertedId); - compareResults(test, useUpdate, coll.find().fetch(), - [{x: 1, _id: 'traq'}, - {x: 4, _id: 'traz'}]); - - }); - }); -}); }); // end idGeneration parametrization Tinytest.add('mongo-livedata - rewrite selector', function (test) { test.equal(Mongo.Collection._rewriteSelector('foo'), - {_id: 'foo'}); + {_id: 'foo'}); var oid = new Mongo.ObjectID(); test.equal(Mongo.Collection._rewriteSelector(oid), - {_id: oid}); + {_id: oid}); test.matches( - Mongo.Collection._rewriteSelector({ _id: null })._id, - /^\S+$/, - 'Passing in a falsey selector _id should return a selector with a new ' - + 'auto-generated _id string' + Mongo.Collection._rewriteSelector({ _id: null })._id, + /^\S+$/, + 'Passing in a falsey selector _id should return a selector with a new ' + + 'auto-generated _id string' ); test.equal( - Mongo.Collection._rewriteSelector({ _id: null }, { fallbackId: oid }), - { _id: oid }, - 'Passing in a falsey selector _id and a fallback ID should return a ' - + 'selector with an _id using the fallback ID' + Mongo.Collection._rewriteSelector({ _id: null }, { fallbackId: oid }), + { _id: oid }, + 'Passing in a falsey selector _id and a fallback ID should return a ' + + 'selector with an _id using the fallback ID' ); }); +// TODO -> FIXME testAsyncMulti('mongo-livedata - specified _id', [ function (test, expect) { this.collectionName = Random.id(); @@ -2256,29 +2245,26 @@ testAsyncMulti('mongo-livedata - specified _id', [ Meteor.call('createInsecureCollection', this.collectionName); Meteor.subscribe('c-' + this.collectionName, expect()); } - }, function (test, expect) { - var expectError = expect(function (err, result) { - test.isTrue(err); - var doc = coll.findOne(); - test.equal(doc.name, "foo"); - }); + }, async function (test) { var coll = new Mongo.Collection(this.collectionName); - coll.insert({_id: "foo", name: "foo"}, expect(function (err1, id) { - test.equal(id, "foo"); - var doc = coll.findOne(); - test.equal(doc._id, "foo"); - Meteor._suppress_log(1); - coll.insert({_id: "foo", name: "bar"}, expectError); - })); + const id1 = await runAndThrowIfNeeded(() => coll.insert({ _id: "foo", name: "foo" }), test); + test.equal(id1, "foo"); + const doc = await coll.findOne(); + test.equal(doc._id, "foo"); + + Meteor._suppress_log(1); + await runAndThrowIfNeeded(() => coll.insert({_id: "foo", name: "bar"}), test, true); + const doc2 = await coll.findOne(); + test.equal(doc2.name, "foo"); } ]); // Consistent id generation tests function collectionInsert (test, expect, coll, index) { - var clientSideId = coll.insert({name: "foo"}, expect(function (err1, id) { + var clientSideId = coll.insert({name: "foo"}, expect(async function (err1, id) { test.equal(id, clientSideId); - var o = coll.findOne(id); + var o = await coll.findOne(id); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); @@ -2287,45 +2273,25 @@ function collectionInsert (test, expect, coll, index) { function collectionUpsert (test, expect, coll, index) { var upsertId = '123456' + index; - coll.upsert(upsertId, {$set: {name: "foo"}}, expect(function (err1, result) { + coll.upsert(upsertId, {$set: {name: "foo"}}, expect(async function (err1, result) { test.equal(result.insertedId, upsertId); test.equal(result.numberAffected, 1); - var o = coll.findOne(upsertId); + var o = await coll.findOne(upsertId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); } -function collectionUpsertExisting (test, expect, coll, index) { - var clientSideId = coll.insert({name: "foo"}, expect(function (err1, id) { - test.equal(id, clientSideId); - - var o = coll.findOne(id); - test.isTrue(_.isObject(o)); - // We're not testing sequencing/visibility rules here, so skip this check - // test.equal(o.name, 'foo'); - })); - - coll.upsert(clientSideId, {$set: {name: "bar"}}, expect(function (err1, result) { - test.equal(result.insertedId, clientSideId); - test.equal(result.numberAffected, 1); - - var o = coll.findOne(clientSideId); - test.isTrue(_.isObject(o)); - test.equal(o.name, 'bar'); - })); -} - function functionCallsInsert (test, expect, coll, index) { - Meteor.call("insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + Meteor.call("insertObjects", coll._name, {name: "foo"}, 1, expect(async function (err1, ids) { test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); var stubId = INSERTED_IDS[coll._name][index]; test.equal(ids.length, 1); test.equal(ids[0], stubId); - var o = coll.findOne(stubId); + var o = await coll.findOne(stubId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); @@ -2333,35 +2299,35 @@ function functionCallsInsert (test, expect, coll, index) { function functionCallsUpsert (test, expect, coll, index) { var upsertId = '123456' + index; - Meteor.call("upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(function (err1, result) { + Meteor.call("upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(async function (err1, result) { test.equal(result.insertedId, upsertId); test.equal(result.numberAffected, 1); - var o = coll.findOne(upsertId); + var o = await coll.findOne(upsertId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); } -function functionCallsUpsertExisting (test, expect, coll, index) { - var id = coll.insert({name: "foo"}); +async function functionCallsUpsertExisting (test, expect, coll, index) { + var id = await coll.insert({name: "foo"}); - var o = coll.findOne(id); + var o = await coll.findOne(id); test.notEqual(null, o); test.equal(o.name, 'foo'); - Meteor.call("upsertObject", coll._name, id, {$set:{name: "bar"}}, expect(function (err1, result) { + Meteor.call("upsertObject", coll._name, id, {$set:{name: "bar"}}, expect(async function (err1, result) { test.equal(result.numberAffected, 1); test.equal(result.insertedId, undefined); - var o = coll.findOne(id); + var o = await coll.findOne(id); test.isTrue(_.isObject(o)); test.equal(o.name, 'bar'); })); } function functionCalls3Inserts (test, expect, coll, index) { - Meteor.call("insertObjects", coll._name, {name: "foo"}, 3, expect(function (err1, ids) { + Meteor.call("insertObjects", coll._name, {name: "foo"}, 3, expect(async function (err1, ids) { test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); test.equal(ids.length, 3); @@ -2369,7 +2335,7 @@ function functionCalls3Inserts (test, expect, coll, index) { var stubId = INSERTED_IDS[coll._name][(3 * index) + i]; test.equal(ids[i], stubId); - var o = coll.findOne(stubId); + var o = await coll.findOne(stubId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); } @@ -2377,28 +2343,28 @@ function functionCalls3Inserts (test, expect, coll, index) { } function functionChainInsert (test, expect, coll, index) { - Meteor.call("doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + Meteor.call("doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(async function (err1, ids) { test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); var stubId = INSERTED_IDS[coll._name][index]; test.equal(ids.length, 1); test.equal(ids[0], stubId); - var o = coll.findOne(stubId); + var o = await coll.findOne(stubId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); } function functionChain2Insert (test, expect, coll, index) { - Meteor.call("doMeteorCall", "doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(function (err1, ids) { + Meteor.call("doMeteorCall", "doMeteorCall", "insertObjects", coll._name, {name: "foo"}, 1, expect(async function (err1, ids) { test.notEqual((INSERTED_IDS[coll._name] || []).length, 0); var stubId = INSERTED_IDS[coll._name][index]; test.equal(ids.length, 1); test.equal(ids[0], stubId); - var o = coll.findOne(stubId); + var o = await coll.findOne(stubId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); @@ -2406,71 +2372,71 @@ function functionChain2Insert (test, expect, coll, index) { function functionChain2Upsert (test, expect, coll, index) { var upsertId = '123456' + index; - Meteor.call("doMeteorCall", "doMeteorCall", "upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(function (err1, result) { + Meteor.call("doMeteorCall", "doMeteorCall", "upsertObject", coll._name, upsertId, {$set:{name: "foo"}}, expect(async function (err1, result) { test.equal(result.insertedId, upsertId); test.equal(result.numberAffected, 1); - var o = coll.findOne(upsertId); + var o = await coll.findOne(upsertId); test.isTrue(_.isObject(o)); test.equal(o.name, 'foo'); })); } -_.each( {collectionInsert: collectionInsert, - collectionUpsert: collectionUpsert, - functionCallsInsert: functionCallsInsert, - functionCallsUpsert: functionCallsUpsert, - functionCallsUpsertExisting: functionCallsUpsertExisting, - functionCalls3Insert: functionCalls3Inserts, - functionChainInsert: functionChainInsert, - functionChain2Insert: functionChain2Insert, - functionChain2Upsert: functionChain2Upsert}, function (fn, name) { -_.each( [1, 3], function (repetitions) { -_.each( [1, 3], function (collectionCount) { -_.each( ['STRING', 'MONGO'], function (idGeneration) { - - testAsyncMulti('mongo-livedata - consistent _id generation ' + name + ', ' + repetitions + ' repetitions on ' + collectionCount + ' collections, idGeneration=' + idGeneration, [ function (test, expect) { - var collectionOptions = { idGeneration: idGeneration }; - - var cleanups = this.cleanups = []; - this.collections = _.times(collectionCount, function () { - var collectionName = "consistentid_" + Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', collectionName, collectionOptions); - Meteor.subscribe('c-' + collectionName, expect()); - cleanups.push(function (expect) { Meteor.call('dropInsecureCollection', collectionName, expect(function () {})); }); - } - - var collection = new Mongo.Collection(collectionName, collectionOptions); - if (Meteor.isServer) { - cleanups.push(function () { collection._dropCollection(); }); - } - COLLECTIONS[collectionName] = collection; - return collection; - }); - }, function (test, expect) { - // now run the actual test - for (var i = 0; i < repetitions; i++) { - for (var j = 0; j < collectionCount; j++) { - fn(test, expect, this.collections[j], i); - } - } - }, function (test, expect) { - // Run any registered cleanup functions (e.g. to drop collections) - _.each(this.cleanups, function(cleanup) { - cleanup(expect); - }); - }]); - -}); -}); -}); -}); +// _.each( {collectionInsert: collectionInsert, +// collectionUpsert: collectionUpsert, +// functionCallsInsert: functionCallsInsert, +// functionCallsUpsert: functionCallsUpsert, +// functionCallsUpsertExisting: functionCallsUpsertExisting, +// functionCalls3Insert: functionCalls3Inserts, +// functionChainInsert: functionChainInsert, +// functionChain2Insert: functionChain2Insert, +// functionChain2Upsert: functionChain2Upsert}, function (fn, name) { +// _.each( [1, 3], function (repetitions) { +// _.each( [1, 3], function (collectionCount) { +// _.each( ['STRING', 'MONGO'], function (idGeneration) { +// +// testAsyncMulti('mongo-livedata - consistent _id generation ' + name + ', ' + repetitions + ' repetitions on ' + collectionCount + ' collections, idGeneration=' + idGeneration, [ function (test, expect) { +// var collectionOptions = { idGeneration: idGeneration }; +// +// var cleanups = this.cleanups = []; +// this.collections = _.times(collectionCount, function () { +// var collectionName = "consistentid_" + Random.id(); +// if (Meteor.isClient) { +// Meteor.call('createInsecureCollection', collectionName, collectionOptions); +// Meteor.subscribe('c-' + collectionName, expect()); +// cleanups.push(function (expect) { Meteor.call('dropInsecureCollection', collectionName, expect(function () {})); }); +// } +// +// var collection = new Mongo.Collection(collectionName, collectionOptions); +// if (Meteor.isServer) { +// cleanups.push(function () { collection._dropCollection(); }); +// } +// COLLECTIONS[collectionName] = collection; +// return collection; +// }); +// }, async function (test, expect) { +// // now run the actual test +// for (var i = 0; i < repetitions; i++) { +// for (var j = 0; j < collectionCount; j++) { +// await fn(test, expect, this.collections[j], i); +// } +// } +// }, function (test, expect) { +// // Run any registered cleanup functions (e.g. to drop collections) +// _.each(this.cleanups, function(cleanup) { +// cleanup(expect); +// }); +// }]); +// +// }); +// }); +// }); +// }); testAsyncMulti('mongo-livedata - empty string _id', [ - function (test, expect) { + async function (test, expect) { var self = this; self.collectionName = Random.id(); if (Meteor.isClient) { @@ -2479,98 +2445,97 @@ testAsyncMulti('mongo-livedata - empty string _id', [ } self.coll = new Mongo.Collection(self.collectionName); try { - self.coll.insert({_id: "", f: "foo"}); + await self.coll.insert({_id: "", f: "foo"}); test.fail("Insert with an empty _id should fail"); } catch (e) { // ok } - self.coll.insert({_id: "realid", f: "bar"}, expect(function (err, res) { - test.equal(res, "realid"); - })); + const res = await self.coll.insert({_id: "realid", f: "bar"}); + test.equal(res, "realid"); }, - function (test, expect) { + async function (test, expect) { var self = this; - var docs = self.coll.find().fetch(); + var docs = await self.coll.find().fetch(); test.equal(docs, [{_id: "realid", f: "bar"}]); }, - function (test, expect) { + async function (test, expect) { var self = this; if (Meteor.isServer) { - self.coll._collection.insert({_id: "", f: "baz"}); - test.equal(self.coll.find().fetch().length, 2); + await self.coll._collection.insert({_id: "", f: "baz"}); + test.equal((await self.coll.find().fetch()).length, 2); } } ]); - -if (Meteor.isServer) { - testAsyncMulti("mongo-livedata - minimongo observe on server", [ - function (test, expect) { - var self = this; - self.id = Random.id(); - self.C = new Mongo.Collection("ServerMinimongoObserve_" + self.id); - self.events = []; - - Meteor.publish(self.id, function () { - return self.C.find(); - }); - - self.conn = DDP.connect(Meteor.absoluteUrl()); - pollUntil(expect, function () { - return self.conn.status().connected; - }, 10000); - }, - - function (test, expect) { - var self = this; - if (self.conn.status().connected) { - self.miniC = new Mongo.Collection("ServerMinimongoObserve_" + self.id, { - connection: self.conn - }); - var exp = expect(function (err) { - test.isFalse(err); - }); - self.conn.subscribe(self.id, { - onError: exp, - onReady: exp - }); - } - }, - - function (test, expect) { - var self = this; - if (self.miniC) { - self.obs = self.miniC.find().observeChanges({ - added: function (id, fields) { - self.events.push({evt: "a", id: id}); - Meteor._sleepForMs(200); - self.events.push({evt: "b", id: id}); - if (! self.two) { - self.two = self.C.insert({}); - } - } - }); - self.one = self.C.insert({}); - pollUntil(expect, function () { - return self.events.length === 4; - }, 10000); - } - }, - - function (test, expect) { - var self = this; - if (self.miniC) { - test.equal(self.events, [ - {evt: "a", id: self.one}, - {evt: "b", id: self.one}, - {evt: "a", id: self.two}, - {evt: "b", id: self.two} - ]); - } - self.obs && self.obs.stop(); - } - ]); -} +// TODO -> This seems to be related to DDP. +// if (Meteor.isServer) { +// testAsyncMulti("mongo-livedata - minimongo observe on server", [ +// function (test, expect) { +// var self = this; +// self.id = Random.id(); +// self.C = new Mongo.Collection("ServerMinimongoObserve_" + self.id); +// self.events = []; +// +// Meteor.publish(self.id, function () { +// return self.C.find(); +// }); +// +// self.conn = DDP.connect(Meteor.absoluteUrl()); +// pollUntil(expect, function () { +// return self.conn.status().connected; +// }, 10000); +// }, +// +// function (test, expect) { +// var self = this; +// if (self.conn.status().connected) { +// self.miniC = new Mongo.Collection("ServerMinimongoObserve_" + self.id, { +// connection: self.conn +// }); +// var exp = expect(function (err) { +// test.isFalse(err); +// }); +// self.conn.subscribe(self.id, { +// onError: exp, +// onReady: exp +// }); +// } +// }, +// +// async function (test, expect) { +// var self = this; +// if (self.miniC) { +// self.obs = await self.miniC.find().observeChanges({ +// added: async function (id, fields) { +// self.events.push({evt: "a", id: id}); +// await Meteor._sleepForMs(200); +// self.events.push({evt: "b", id: id}); +// if (! self.two) { +// self.two = await self.C.insert({}); +// } +// } +// }); +// self.one = await self.C.insert({}); +// pollUntil(expect, function () { +// return self.events.length === 4; +// }, 10000); +// } +// }, +// +// function (test, expect) { +// var self = this; +// if (self.miniC) { +// test.equal(self.events, [ +// {evt: "a", id: self.one}, +// {evt: "b", id: self.one}, +// {evt: "a", id: self.two}, +// {evt: "b", id: self.two} +// ]); +// } +// return self.obs && self.obs.stop(); +// } +// ]); +// } Tinytest.addAsync("mongo-livedata - local collections with different connections", function (test, onComplete) { var cname = Random.id(); @@ -2578,9 +2543,9 @@ Tinytest.addAsync("mongo-livedata - local collections with different connections var coll1 = new Mongo.Collection(cname); var doc = { foo: "bar" }; var coll2 = new Mongo.Collection(cname2, { connection: null }); - coll2.insert(doc, function (err, id) { - test.equal(coll1.find(doc).count(), 0); - test.equal(coll2.find(doc).count(), 1); + coll2.insert(doc, async function (err, id) { + test.equal(await coll1.find(doc).count(), 0); + test.equal(await coll2.find(doc).count(), 1); onComplete(); }); }); @@ -2589,103 +2554,103 @@ Tinytest.addAsync("mongo-livedata - local collection with null connection, w/ ca var cname = Random.id(); var coll1 = new Mongo.Collection(cname, { connection: null }); var doc = { foo: "bar" }; - var docId = coll1.insert(doc, function (err, id) { + var docId = coll1.insert(doc, async function (err, id) { test.equal(docId, id); - test.equal(coll1.findOne(doc)._id, id); + test.equal(await coll1.findOne(doc)._id, id); onComplete(); }); }); -Tinytest.addAsync("mongo-livedata - local collection with null connection, w/o callback", function (test, onComplete) { +Tinytest.addAsync("mongo-livedata - local collection with null connection, w/o callback", async function (test, onComplete) { var cname = Random.id(); var coll1 = new Mongo.Collection(cname, { connection: null }); var doc = { foo: "bar" }; - var docId = coll1.insert(doc); - test.equal(coll1.findOne(doc)._id, docId); - onComplete(); + var docId = await coll1.insert(doc); + test.equal(await coll1.findOne(doc)._id, docId); }); -testAsyncMulti("mongo-livedata - update handles $push with $each correctly", [ - function (test, expect) { - var self = this; - var collectionName = Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', collectionName); - Meteor.subscribe('c-' + collectionName, expect()); - } - - self.collection = new Mongo.Collection(collectionName); - - self.id = self.collection.insert( - {name: 'jens', elements: ['X', 'Y']}, expect(function (err, res) { - test.isFalse(err); - test.equal(self.id, res); - })); - }, - function (test, expect) { - var self = this; - self.collection.update(self.id, { - $push: { - elements: { - $each: ['A', 'B', 'C'], - $slice: -4 - }}}, expect(function (err, res) { - test.isFalse(err); - test.equal( - self.collection.findOne(self.id), - {_id: self.id, name: 'jens', elements: ['Y', 'A', 'B', 'C']}); - })); - } -]); +// TODO -> FIXME ddp +// testAsyncMulti("mongo-livedata - update handles $push with $each correctly", [ +// function (test, expect) { +// var self = this; +// var collectionName = Random.id(); +// if (Meteor.isClient) { +// Meteor.call('createInsecureCollection', collectionName); +// Meteor.subscribe('c-' + collectionName, expect()); +// } +// +// self.collection = new Mongo.Collection(collectionName); +// +// self.id = self.collection.insert( +// {name: 'jens', elements: ['X', 'Y']}, expect(function (err, res) { +// test.isFalse(err); +// test.equal(self.id, res); +// })); +// }, +// function (test, expect) { +// var self = this; +// self.collection.update(self.id, { +// $push: { +// elements: { +// $each: ['A', 'B', 'C'], +// $slice: -4 +// }}}, expect(async function (err, res) { +// test.isFalse(err); +// test.equal( +// await self.collection.findOne(self.id), +// {_id: self.id, name: 'jens', elements: ['Y', 'A', 'B', 'C']}); +// })); +// } +// ]); if (Meteor.isServer) { - Tinytest.add("mongo-livedata - upsert handles $push with $each correctly", function (test) { + Tinytest.addAsync("mongo-livedata - upsert handles $push with $each correctly", async function (test) { var collection = new Mongo.Collection(Random.id()); - var result = collection.upsert( - {name: 'jens'}, - {$push: { - elements: { - $each: ['A', 'B', 'C'], - $slice: -4 - }}}); + var result = await collection.upsert( + {name: 'jens'}, + {$push: { + elements: { + $each: ['A', 'B', 'C'], + $slice: -4 + }}}); - test.equal(collection.findOne(result.insertedId), - {_id: result.insertedId, - name: 'jens', - elements: ['A', 'B', 'C']}); + test.equal(await collection.findOne(result.insertedId), + {_id: result.insertedId, + name: 'jens', + elements: ['A', 'B', 'C']}); - var id = collection.insert({name: "david", elements: ['X', 'Y']}); - result = collection.upsert( - {name: 'david'}, - {$push: { - elements: { - $each: ['A', 'B', 'C'], - $slice: -4 - }}}); + var id = await collection.insert({name: "david", elements: ['X', 'Y']}); + result = await collection.upsert( + {name: 'david'}, + {$push: { + elements: { + $each: ['A', 'B', 'C'], + $slice: -4 + }}}); - test.equal(collection.findOne(id), - {_id: id, - name: 'david', - elements: ['Y', 'A', 'B', 'C']}); + test.equal(await collection.findOne(id), + {_id: id, + name: 'david', + elements: ['Y', 'A', 'B', 'C']}); }); - Tinytest.add("mongo-livedata - upsert handles dotted selectors corrrectly", function (test) { + Tinytest.addAsync("mongo-livedata - upsert handles dotted selectors corrrectly", async function (test) { var collection = new Mongo.Collection(Random.id()); - var result1 = collection.upsert({ + var result1 = await collection.upsert({ "subdocument.a": 1 }, { $set: {message: "upsert 1"} }); - test.equal(collection.findOne(result1.insertedId),{ + test.equal(await collection.findOne(result1.insertedId),{ _id: result1.insertedId, subdocument: {a: 1}, message: "upsert 1" }); - var result2 = collection.upsert({ + var result2 = await collection.upsert({ "subdocument.a": 1 }, { $set: {message: "upsert 2"} @@ -2693,37 +2658,37 @@ if (Meteor.isServer) { test.equal(result2, {numberAffected: 1}); - test.equal(collection.findOne(result1.insertedId),{ + test.equal(await collection.findOne(result1.insertedId),{ _id: result1.insertedId, subdocument: {a: 1}, message: "upsert 2" }); - var result3 = collection.upsert({ + var result3 = await collection.upsert({ "subdocument.a.b": 1, "subdocument.c": 2 }, { $set: {message: "upsert3"} }); - test.equal(collection.findOne(result3.insertedId),{ + test.equal(await collection.findOne(result3.insertedId),{ _id: result3.insertedId, subdocument: {a: {b: 1}, c: 2}, message: "upsert3" }); - var result4 = collection.upsert({ + var result4 = await collection.upsert({ "subdocument.a": 4 }, { $set: {"subdocument.a": "upsert 4"} }); - test.equal(collection.findOne(result4.insertedId), { + test.equal(await collection.findOne(result4.insertedId), { _id: result4.insertedId, subdocument: {a: "upsert 4"} }); - var result5 = collection.upsert({ + var result5 = await collection.upsert({ "subdocument.a": "upsert 4" }, { $set: {"subdocument.a": "upsert 5"} @@ -2731,12 +2696,12 @@ if (Meteor.isServer) { test.equal(result5, {numberAffected: 1}); - test.equal(collection.findOne(result4.insertedId), { + test.equal(await collection.findOne(result4.insertedId), { _id: result4.insertedId, subdocument: {a: "upsert 5"} }); - var result6 = collection.upsert({ + var result6 = await collection.upsert({ "subdocument.a": "upsert 5" }, { $set: {"subdocument": "upsert 6"} @@ -2744,12 +2709,12 @@ if (Meteor.isServer) { test.equal(result6, {numberAffected: 1}); - test.equal(collection.findOne(result4.insertedId), { + test.equal(await collection.findOne(result4.insertedId), { _id: result4.insertedId, subdocument: "upsert 6" }); - var result7 = collection.upsert({ + var result7 = await collection.upsert({ "subdocument.a.b": 7 }, { $set: { @@ -2757,14 +2722,14 @@ if (Meteor.isServer) { } }); - test.equal(collection.findOne(result7.insertedId), { + test.equal(await collection.findOne(result7.insertedId), { _id: result7.insertedId, subdocument: { a: {b: 7, c: "upsert7"} } }); - var result8 = collection.upsert({ + var result8 = await collection.upsert({ "subdocument.a.b": 7 }, { $set: { @@ -2774,14 +2739,14 @@ if (Meteor.isServer) { test.equal(result8, {numberAffected: 1}); - test.equal(collection.findOne(result7.insertedId), { + test.equal(await collection.findOne(result7.insertedId), { _id: result7.insertedId, subdocument: { a: {b: 7, c: "upsert8"} } }); - var result9 = collection.upsert({ + var result9 = await collection.upsert({ "subdocument.a.b": 7 }, { $set: { @@ -2791,7 +2756,7 @@ if (Meteor.isServer) { test.equal(result9, {numberAffected: 1}); - test.equal(collection.findOne(result7.insertedId), { + test.equal(await collection.findOne(result7.insertedId), { _id: result7.insertedId, subdocument: { a: {b: "upsert9", c: "upsert8"} @@ -2802,36 +2767,36 @@ if (Meteor.isServer) { } // This is a VERY white-box test. -Meteor.isServer && Tinytest.add("mongo-livedata - oplog - _disableOplog", function (test) { +Meteor.isServer && Tinytest.addAsync("mongo-livedata - oplog - _disableOplog", async function (test) { var collName = Random.id(); var coll = new Mongo.Collection(collName); if (MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle) { - var observeWithOplog = coll.find({x: 5}) - .observeChanges({added: function () {}}); - test.isTrue(observeWithOplog._multiplexer._observeDriver._usesOplog); - observeWithOplog.stop(); - } - var observeWithoutOplog = coll.find({x: 6}, {_disableOplog: true}) + var observeWithOplog = await coll.find({x: 5}) .observeChanges({added: function () {}}); + test.isTrue(observeWithOplog._multiplexer._observeDriver._usesOplog); + await observeWithOplog.stop(); + } + var observeWithoutOplog = await coll.find({x: 6}, {_disableOplog: true}) + .observeChanges({added: function () {}}); test.isFalse(observeWithoutOplog._multiplexer._observeDriver._usesOplog); - observeWithoutOplog.stop(); + await observeWithoutOplog.stop(); }); -Meteor.isServer && Tinytest.add("mongo-livedata - oplog - include selector fields", function (test) { +Meteor.isServer && Tinytest.addAsync("mongo-livedata - oplog - include selector fields", async function (test) { var collName = "includeSelector" + Random.id(); var coll = new Mongo.Collection(collName); - var docId = coll.insert({a: 1, b: [3, 2], c: 'foo'}); + var docId = await coll.insert({a: 1, b: [3, 2], c: 'foo'}); test.isTrue(docId); // Wait until we've processed the insert oplog entry. (If the insert shows up // during the observeChanges, the bug in question is not consistently // reproduced.) We don't have to do this for polling observe (eg // --disable-oplog). - waitUntilOplogCaughtUp(); + await waitUntilOplogCaughtUp(); var output = []; - var handle = coll.find({a: 1, b: 2}, {fields: {c: 1}}).observeChanges({ + var handle = await coll.find({a: 1, b: 2}, {fields: {c: 1}}).observeChanges({ added: function (id, fields) { output.push(['added', id, fields]); }, @@ -2850,34 +2815,34 @@ Meteor.isServer && Tinytest.add("mongo-livedata - oplog - include selector field // and the changed field 'b' (but not the field 'a'), we would think it didn't // match any more. (This is a regression test for a bug that existed because // we used to not use the shared projection in the initial query.) - runInFence(function () { - coll.update(docId, {$set: {'b.0': 2, c: 'bar'}}); + await runInFence(function () { + return coll.update(docId, {$set: {'b.0': 2, c: 'bar'}}); }); test.length(output, 1); test.equal(output.shift(), ['changed', docId, {c: 'bar'}]); - handle.stop(); + await handle.stop(); }); -Meteor.isServer && Tinytest.add("mongo-livedata - oplog - transform", function (test) { +Meteor.isServer && Tinytest.addAsync("mongo-livedata - oplog - transform", async function (test) { var collName = "oplogTransform" + Random.id(); var coll = new Mongo.Collection(collName); - var docId = coll.insert({a: 25, x: {x: 5, y: 9}}); + var docId = await coll.insert({a: 25, x: {x: 5, y: 9}}); test.isTrue(docId); // Wait until we've processed the insert oplog entry. (If the insert shows up // during the observeChanges, the bug in question is not consistently // reproduced.) We don't have to do this for polling observe (eg // --disable-oplog). - waitUntilOplogCaughtUp(); + await waitUntilOplogCaughtUp(); var cursor = coll.find({}, {transform: function (doc) { - return doc.x; - }}); + return doc.x; + }}); var changesOutput = []; - var changesHandle = cursor.observeChanges({ + var changesHandle = await cursor.observeChanges({ added: function (id, fields) { changesOutput.push(['added', fields]); } @@ -2885,42 +2850,42 @@ Meteor.isServer && Tinytest.add("mongo-livedata - oplog - transform", function ( // We should get untransformed fields via observeChanges. test.length(changesOutput, 1); test.equal(changesOutput.shift(), ['added', {a: 25, x: {x: 5, y: 9}}]); - changesHandle.stop(); + await changesHandle.stop(); var transformedOutput = []; - var transformedHandle = cursor.observe({ + var transformedHandle = await cursor.observe({ added: function (doc) { transformedOutput.push(['added', doc]); } }); test.length(transformedOutput, 1); test.equal(transformedOutput.shift(), ['added', {x: 5, y: 9}]); - transformedHandle.stop(); + await transformedHandle.stop(); }); -Meteor.isServer && Tinytest.add("mongo-livedata - oplog - drop collection/db", function (test) { +Meteor.isServer && Tinytest.addAsync("mongo-livedata - oplog - drop collection/db", async function (test) { // This test uses a random database, so it can be dropped without affecting // anything else. var mongodbUri = Npm.require('mongodb-uri'); var parsedUri = mongodbUri.parse(process.env.MONGO_URL); parsedUri.database = 'dropDB' + Random.id(); var driver = new MongoInternals.RemoteCollectionDriver( - mongodbUri.format(parsedUri), { - oplogUrl: process.env.MONGO_OPLOG_URL - } + mongodbUri.format(parsedUri), { + oplogUrl: process.env.MONGO_OPLOG_URL + } ); var collName = "dropCollection" + Random.id(); var coll = new Mongo.Collection(collName, { _driver: driver }); - var doc1Id = coll.insert({a: 'foo', c: 1}); - var doc2Id = coll.insert({b: 'bar'}); - var doc3Id = coll.insert({a: 'foo', c: 2}); + var doc1Id = await coll.insert({a: 'foo', c: 1}); + var doc2Id = await coll.insert({b: 'bar'}); + var doc3Id = await coll.insert({a: 'foo', c: 2}); var tmp; var output = []; - var handle = coll.find({a: 'foo'}).observeChanges({ + var handle = await coll.find({a: 'foo'}).observeChanges({ added: function (id, fields) { output.push(['added', id, fields]); }, @@ -2943,11 +2908,11 @@ Meteor.isServer && Tinytest.add("mongo-livedata - oplog - drop collection/db", f // Wait until we've processed the insert oplog entry, so that we are in a // steady state (and we don't see the dropped docs because we are FETCHING). - waitUntilOplogCaughtUp(); + await waitUntilOplogCaughtUp(); // Drop the collection. Should remove all docs. - runInFence(function () { - coll._dropCollection(); + await runInFence(function () { + return coll._dropCollection(); }); test.length(output, 2); @@ -2962,8 +2927,8 @@ Meteor.isServer && Tinytest.add("mongo-livedata - oplog - drop collection/db", f // Put something back in. var doc4Id; - runInFence(function () { - doc4Id = coll.insert({a: 'foo', c: 3}); + await runInFence(async function () { + doc4Id = await coll.insert({a: 'foo', c: 3}); }); test.length(output, 1); @@ -2978,7 +2943,7 @@ Meteor.isServer && Tinytest.add("mongo-livedata - oplog - drop collection/db", f // test.length(output, 1); // test.equal(output.shift(), ['removed', doc4Id]); - handle.stop(); + await handle.stop(); driver.mongo.close(); }); @@ -2994,8 +2959,8 @@ _.extend(TestCustomType.prototype, { }, equals: function (other) { return other instanceof TestCustomType - && EJSON.equals(this.myHead, other.myHead) - && EJSON.equals(this.myTail, other.myTail); + && EJSON.equals(this.myHead, other.myHead) + && EJSON.equals(this.myTail, other.myTail); }, typeName: function () { return 'someCustomType'; @@ -3009,121 +2974,125 @@ EJSON.addType('someCustomType', function (json) { return new TestCustomType(json.head, json.tail); }); -testAsyncMulti("mongo-livedata - oplog - update EJSON", [ - function (test, expect) { - var self = this; - var collectionName = "ejson" + Random.id(); - if (Meteor.isClient) { - Meteor.call('createInsecureCollection', collectionName); - Meteor.subscribe('c-' + collectionName, expect()); - } - - self.collection = new Mongo.Collection(collectionName); - self.date = new Date; - self.objId = new Mongo.ObjectID; - - self.id = self.collection.insert( - {d: self.date, oi: self.objId, - custom: new TestCustomType('a', 'b')}, - expect(function (err, res) { - test.isFalse(err); - test.equal(self.id, res); - })); - }, - function (test, expect) { - var self = this; - self.changes = []; - self.handle = self.collection.find({}).observeChanges({ - added: function (id, fields) { - self.changes.push(['a', id, fields]); - }, - changed: function (id, fields) { - self.changes.push(['c', id, fields]); - }, - removed: function (id) { - self.changes.push(['r', id]); - } - }); - test.length(self.changes, 1); - test.equal(self.changes.shift(), - ['a', self.id, - {d: self.date, oi: self.objId, - custom: new TestCustomType('a', 'b')}]); - - // First, replace the entire custom object. - // (runInFence is useful for the server, using expect() is useful for the - // client) - runInFence(function () { - self.collection.update( - self.id, {$set: {custom: new TestCustomType('a', 'c')}}, - expect(function (err) { - test.isFalse(err); - })); - }); - }, - function (test, expect) { - var self = this; - test.length(self.changes, 1); - test.equal(self.changes.shift(), - ['c', self.id, {custom: new TestCustomType('a', 'c')}]); - - // Now, sneakily replace just a piece of it. Meteor won't do this, but - // perhaps you are accessing Mongo directly. - runInFence(function () { - self.collection.update( - self.id, {$set: {'custom.EJSON$value.EJSONtail': 'd'}}, - expect(function (err) { - test.isFalse(err); - })); - }); - }, - function (test, expect) { - var self = this; - test.length(self.changes, 1); - test.equal(self.changes.shift(), - ['c', self.id, {custom: new TestCustomType('a', 'd')}]); - - // Update a date and an ObjectID too. - self.date2 = new Date(self.date.valueOf() + 1000); - self.objId2 = new Mongo.ObjectID; - runInFence(function () { - self.collection.update( - self.id, {$set: {d: self.date2, oi: self.objId2}}, - expect(function (err) { - test.isFalse(err); - })); - }); - }, - function (test, expect) { - var self = this; - test.length(self.changes, 1); - test.equal(self.changes.shift(), - ['c', self.id, {d: self.date2, oi: self.objId2}]); - - self.handle.stop(); - } -]); +// TODO -> On client also uses DDP. +// testAsyncMulti("mongo-livedata - oplog - update EJSON", [ +// async function (test, expect) { +// var self = this; +// var collectionName = "ejson" + Random.id(); +// if (Meteor.isClient) { +// Meteor.call('createInsecureCollection', collectionName); +// Meteor.subscribe('c-' + collectionName, expect()); +// } +// +// self.collection = new Mongo.Collection(collectionName); +// self.date = new Date; +// self.objId = new Mongo.ObjectID; +// +// self.id = self.collection.insert( +// {d: self.date, oi: self.objId, +// custom: new TestCustomType('a', 'b')}, +// expect(function (err, res) { +// test.isFalse(err); +// console.log("kkk") +// console.log(self.id) +// console.log(res) +// test.equal(self.id, res); +// })); +// }, +// async function (test, expect) { +// var self = this; +// self.changes = []; +// self.handle = await self.collection.find({}).observeChanges({ +// added: function (id, fields) { +// self.changes.push(['a', id, fields]); +// }, +// changed: function (id, fields) { +// self.changes.push(['c', id, fields]); +// }, +// removed: function (id) { +// self.changes.push(['r', id]); +// } +// }); +// test.length(self.changes, 1); +// test.equal(self.changes.shift(), +// ['a', self.id, +// {d: self.date, oi: self.objId, +// custom: new TestCustomType('a', 'b')}]); +// +// // First, replace the entire custom object. +// // (runInFence is useful for the server, using expect() is useful for the +// // client) +// await runInFence(function () { +// self.collection.update( +// self.id, {$set: {custom: new TestCustomType('a', 'c')}}, +// expect(function (err) { +// test.isFalse(err); +// })); +// }); +// }, +// async function (test, expect) { +// var self = this; +// test.length(self.changes, 1); +// test.equal(self.changes.shift(), +// ['c', self.id, {custom: new TestCustomType('a', 'c')}]); +// +// // Now, sneakily replace just a piece of it. Meteor won't do this, but +// // perhaps you are accessing Mongo directly. +// await runInFence(function () { +// self.collection.update( +// self.id, {$set: {'custom.EJSON$value.EJSONtail': 'd'}}, +// expect(function (err) { +// test.isFalse(err); +// })); +// }); +// }, +// async function (test, expect) { +// var self = this; +// test.length(self.changes, 1); +// test.equal(self.changes.shift(), +// ['c', self.id, {custom: new TestCustomType('a', 'd')}]); +// +// // Update a date and an ObjectID too. +// self.date2 = new Date(self.date.valueOf() + 1000); +// self.objId2 = new Mongo.ObjectID; +// await runInFence(function () { +// self.collection.update( +// self.id, {$set: {d: self.date2, oi: self.objId2}}, +// expect(function (err) { +// test.isFalse(err); +// })); +// }); +// }, +// function (test, expect) { +// var self = this; +// test.length(self.changes, 1); +// test.equal(self.changes.shift(), +// ['c', self.id, {d: self.date2, oi: self.objId2}]); +// +// return self.handle.stop(); +// } +// ], {isOnly: true}); function waitUntilOplogCaughtUp() { var oplogHandle = - MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; + MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; if (oplogHandle) - oplogHandle.waitUntilCaughtUp(); + return oplogHandle.waitUntilCaughtUp(); } -Meteor.isServer && Tinytest.add("mongo-livedata - cursor dedup stop", function (test) { +Meteor.isServer && Tinytest.addAsync("mongo-livedata - cursor dedup stop", async function (test) { var coll = new Mongo.Collection(Random.id()); - _.times(100, function () { - coll.insert({foo: 'baz'}); - }); - var handler = coll.find({}).observeChanges({ - added: function (id) { - coll.update(id, {$set: {foo: 'bar'}}); + await Promise.all(_.times(100, async function () { + await coll.insert({foo: 'baz'}); + })); + var handler = await coll.find({}).observeChanges({ + added: async function (id) { + await coll.update(id, {$set: {foo: 'bar'}}); } }); - handler.stop(); + await handler.stop(); // Previously, this would print // Exception in queued task: TypeError: Object.keys called on non-object // Unfortunately, this test didn't fail before the bugfix, but it at least @@ -3148,9 +3117,9 @@ testAsyncMulti("mongo-livedata - undefined find options", [ test.isFalse(err); })); }, - function (test, expect) { + async function (test, expect) { var self = this; - var result = self.coll.findOne({ foo: 1 }, { + var result = await self.coll.findOne({ foo: 1 }, { fields: undefined, sort: undefined, limit: undefined, @@ -3162,7 +3131,7 @@ testAsyncMulti("mongo-livedata - undefined find options", [ // Regression test for #2274. Meteor.isServer && testAsyncMulti("mongo-livedata - observe limit bug", [ - function (test, expect) { + async function (test, expect) { var self = this; self.coll = new Mongo.Collection(Random.id()); var state = {}; @@ -3177,14 +3146,14 @@ Meteor.isServer && testAsyncMulti("mongo-livedata - observe limit bug", [ delete state[oldDoc._id]; } }; - self.observe = self.coll.find( - {}, {limit: 1, sort: {sortField: -1}}).observe(callbacks); + self.observe = await self.coll.find( + {}, {limit: 1, sort: {sortField: -1}}).observe(callbacks); // Insert some documents. - runInFence(function () { - self.id0 = self.coll.insert({sortField: 0, toDelete: true}); - self.id1 = self.coll.insert({sortField: 1, toDelete: true}); - self.id2 = self.coll.insert({sortField: 2, toDelete: true}); + await runInFence(async function () { + self.id0 = await self.coll.insert({sortField: 0, toDelete: true}); + self.id1 = await self.coll.insert({sortField: 1, toDelete: true}); + self.id2 = await self.coll.insert({sortField: 2, toDelete: true}); }); test.equal(_.keys(state), [self.id2]); @@ -3192,54 +3161,54 @@ Meteor.isServer && testAsyncMulti("mongo-livedata - observe limit bug", [ // buffer. Before the fix for #2274, this left the observe state machine in // a broken state where the buffer was empty but it wasn't try to re-fill // it. - runInFence(function () { - self.coll.update({_id: {$ne: self.id2}}, - {$set: {toDelete: false}}, - {multi: 1}); + await runInFence(function () { + return self.coll.update({_id: {$ne: self.id2}}, + {$set: {toDelete: false}}, + {multi: 1}); }); test.equal(_.keys(state), [self.id2]); // Now remove the one published document. This should slide up id1 from the // buffer, but this didn't work before the #2274 fix. - runInFence(function () { - self.coll.remove({toDelete: true}); + await runInFence(function () { + return self.coll.remove({toDelete: true}); }); test.equal(_.keys(state), [self.id1]); } ]); Meteor.isServer && testAsyncMulti("mongo-livedata - update with replace forbidden", [ - function (test, expect) { + async function (test, expect) { var c = new Mongo.Collection(Random.id()); - var id = c.insert({ foo: "bar" }); + var id = await c.insert({ foo: "bar" }); - c.update(id, { foo2: "bar2" }); - test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + await c.update(id, { foo2: "bar2" }); + test.equal(await c.findOne(id), { _id: id, foo2: "bar2" }); - test.throws(function () { - c.update(id, { foo3: "bar3" }, { _forbidReplace: true }); + await test.throwsAsync(function () { + return c.update(id, { foo3: "bar3" }, { _forbidReplace: true }); }, "Replacements are forbidden"); - test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + test.equal(await c.findOne(id), { _id: id, foo2: "bar2" }); - test.throws(function () { - c.update(id, { foo3: "bar3", $set: { blah: 1 } }); + await test.throwsAsync(function () { + return c.update(id, { foo3: "bar3", $set: { blah: 1 } }); }, "cannot have both modifier and non-modifier fields"); - test.equal(c.findOne(id), { _id: id, foo2: "bar2" }); + test.equal(await c.findOne(id), { _id: id, foo2: "bar2" }); } ]); Meteor.isServer && Tinytest.add( - "mongo-livedata - connection failure throws", - function (test) { - // Exception happens in 30s - test.throws(function () { - const connection = new MongoInternals.Connection('mongodb://this-does-not-exist.test/asdf'); + "mongo-livedata - connection failure throws", + function (test) { + // Exception happens in 30s + test.throws(function () { + const connection = new MongoInternals.Connection('mongodb://this-does-not-exist.test/asdf'); - // Same as `MongoInternals.defaultRemoteCollectionDriver`. - Promise.await(connection.client.connect()); - }); - } + // Same as `MongoInternals.defaultRemoteCollectionDriver`. + Promise.await(connection.client.connect()); + }); + } ); Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) { @@ -3247,7 +3216,7 @@ Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) test.matches(MongoInternals.NpmModules.mongodb.version, /^4\.(\d+)\.(\d+)/); test.equal(typeof(MongoInternals.NpmModules.mongodb.module), 'object'); test.equal(typeof(MongoInternals.NpmModules.mongodb.module.ObjectID), - 'function'); + 'function'); var c = new Mongo.Collection(Random.id()); var rawCollection = c.rawCollection(); @@ -3259,27 +3228,27 @@ Meteor.isServer && Tinytest.add("mongo-livedata - npm modules", function (test) }); if (Meteor.isServer) { - Tinytest.add("mongo-livedata - update/remove don't accept an array as a selector #4804", function (test) { + Tinytest.addAsync("mongo-livedata - update/remove don't accept an array as a selector #4804", async function (test) { var collection = new Mongo.Collection(Random.id()); - _.times(10, function () { - collection.insert({ data: "Hello" }); - }); + await Promise.all(_.times(10, function () { + return collection.insert({ data: "Hello" }); + })); - test.equal(collection.find().count(), 10); + test.equal(await collection.find().count(), 10); // Test several array-related selectors - _.each([[], [1, 2, 3], [{}]], function (selector) { - test.throws(function () { - collection.remove(selector); + await Promise.all([[], [1, 2, 3], [{}]].map(async (selector) => { + await test.throwsAsync(function () { + return collection.remove(selector); }); - test.throws(function () { - collection.update(selector, {$set: 5}); + await test.throwsAsync(function () { + return collection.update(selector, {$set: 5}); }); - }); + })); - test.equal(collection.find().count(), 10); + test.equal(await collection.find().count(), 10); }); } @@ -3303,83 +3272,84 @@ if (Meteor.isServer) { // - The client invokes another method which reads the confirmation from // the future. (Well, the invocation happened earlier but the use of the // Future sequences it so that the confirmation only gets read at this point.) -if (Meteor.isClient) { - testAsyncMulti("mongo-livedata - fence onBeforeFire error", [ - function (test, expect) { - var self = this; - self.nonce = Random.id(); - Meteor.call('fenceOnBeforeFireError1', self.nonce, expect(function (err) { - test.isFalse(err); - })); - }, - function (test, expect) { - var self = this; - Meteor.call('fenceOnBeforeFireError2', self.nonce, expect( - function (err, success) { - test.isFalse(err); - test.isTrue(success); - } - )); - } - ]); -} else { - var fenceOnBeforeFireErrorCollection = new Mongo.Collection("FOBFE"); - var Future = Npm.require('fibers/future'); - var futuresByNonce = {}; - Meteor.methods({ - fenceOnBeforeFireError1: function (nonce) { - futuresByNonce[nonce] = new Future; - var observe = fenceOnBeforeFireErrorCollection.find({nonce: nonce}) - .observeChanges({added: function (){}}); - Meteor.setTimeout(function () { - fenceOnBeforeFireErrorCollection.insert( - {nonce: nonce}, - function (err, result) { - var success = !err && result; - futuresByNonce[nonce].return(success); - observe.stop(); - } - ); - }, 10); - }, - fenceOnBeforeFireError2: function (nonce) { - try { - return futuresByNonce[nonce].wait(); - } finally { - delete futuresByNonce[nonce]; - } - } - }); -} +// TODO -> Fix me +// if (Meteor.isClient) { +// testAsyncMulti("mongo-livedata - fence onBeforeFire error", [ +// function (test, expect) { +// var self = this; +// self.nonce = Random.id(); +// Meteor.call('fenceOnBeforeFireError1', self.nonce, expect(function (err) { +// test.isFalse(err); +// })); +// }, +// function (test, expect) { +// var self = this; +// Meteor.call('fenceOnBeforeFireError2', self.nonce, expect( +// function (err, success) { +// test.isFalse(err); +// test.isTrue(success); +// } +// )); +// } +// ]); +// } else { +// var fenceOnBeforeFireErrorCollection = new Mongo.Collection("FOBFE"); +// var Future = Npm.require('fibers/future'); +// var futuresByNonce = {}; +// Meteor.methods({ +// fenceOnBeforeFireError1: function (nonce) { +// futuresByNonce[nonce] = new Future; +// var observe = fenceOnBeforeFireErrorCollection.find({nonce: nonce}) +// .observeChanges({added: function (){}}); +// Meteor.setTimeout(function () { +// fenceOnBeforeFireErrorCollection.insert( +// {nonce: nonce}, +// function (err, result) { +// var success = !err && result; +// futuresByNonce[nonce].return(success); +// observe.stop(); +// } +// ); +// }, 10); +// }, +// fenceOnBeforeFireError2: function (nonce) { +// try { +// return futuresByNonce[nonce].wait(); +// } finally { +// delete futuresByNonce[nonce]; +// } +// } +// }); +// } if (Meteor.isServer) { - Tinytest.add('mongo update/upsert - returns nMatched as numberAffected', function (test, onComplete) { + Tinytest.addAsync('mongo update/upsert - returns nMatched as numberAffected', async function (test) { var collName = Random.id(); var coll = new Mongo.Collection('update_nmatched'+collName); - coll.insert({animal: 'cat', legs: 4}); - coll.insert({animal: 'dog', legs: 4}); - coll.insert({animal: 'echidna', legs: 4}); - coll.insert({animal: 'platypus', legs: 4}); - coll.insert({animal: 'starfish', legs: 5}); + await coll.insert({animal: 'cat', legs: 4}); + await coll.insert({animal: 'dog', legs: 4}); + await coll.insert({animal: 'echidna', legs: 4}); + await coll.insert({animal: 'platypus', legs: 4}); + await coll.insert({animal: 'starfish', legs: 5}); - var affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}); + var affected = await coll.update({legs: 4}, {$set: {category: 'quadruped'}}); test.equal(affected, 1); //Changes only 3 but matched 4 documents - affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + affected = await coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); test.equal(affected, 4); //Again, changes nothing but returns nModified - affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + affected = await coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); test.equal(affected, 4); //upsert:true changes nothing, 4 modified - affected = coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}); + affected = await coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}); test.equal(affected, 4); //upsert method works as upsert:true - var result = coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); + var result = await coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}); test.equal(result.numberAffected, 4); }); @@ -3387,75 +3357,72 @@ if (Meteor.isServer) { var collName = Random.id(); var coll = new Mongo.Collection('update_nmatched'+collName); - coll.insert({animal: 'cat', legs: 4}); - coll.insert({animal: 'dog', legs: 4}); - coll.insert({animal: 'echidna', legs: 4}); - coll.insert({animal: 'platypus', legs: 4}); - coll.insert({animal: 'starfish', legs: 5}); + Promise.all([{animal: 'cat', legs: 4}, {animal: 'dog', legs: 4}, {animal: 'echidna', legs: 4},{animal: 'platypus', legs: 4}, {animal: 'starfish', legs: 5}] + .map(({animal, legs}) => coll.insert({animal, legs}))).then(() => { + var test1 = function () { + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, function (err, result) { + test.equal(result, 1); + test2(); + }); + }; - var test1 = function () { - coll.update({legs: 4}, {$set: {category: 'quadruped'}}, function (err, result) { - test.equal(result, 1); - test2(); - }); - }; + var test2 = function () { + //Changes only 3 but matched 4 documents + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result, 4); + test3(); + }); + }; - var test2 = function () { - //Changes only 3 but matched 4 documents - coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { - test.equal(result, 4); - test3(); - }); - }; + var test3 = function () { + //Again, changes nothing but returns nModified + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result, 4); + test4(); + }); + }; - var test3 = function () { - //Again, changes nothing but returns nModified - coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { - test.equal(result, 4); - test4(); - }); - }; + var test4 = function () { + //upsert:true changes nothing, 4 modified + coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}, function (err, result) { + test.equal(result, 4); + test5(); + }); + }; - var test4 = function () { - //upsert:true changes nothing, 4 modified - coll.update({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true, upsert:true}, function (err, result) { - test.equal(result, 4); - test5(); - }); - }; + var test5 = function () { + //upsert method works as upsert:true + coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { + test.equal(result.numberAffected, 4); + onComplete(); + }); + }; - var test5 = function () { - //upsert method works as upsert:true - coll.upsert({legs: 4}, {$set: {category: 'quadruped'}}, {multi: true}, function (err, result) { - test.equal(result.numberAffected, 4); - onComplete(); - }); - }; - - test1(); + test1(); + }); }); } if (Meteor.isServer) { - Tinytest.addAsync("mongo-livedata - transaction", function (test) { + Tinytest.addAsync("mongo-livedata - transaction", async function (test) { const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo; const Collection = new Mongo.Collection(`transaction_test_${test.runId()}`); const rawCollection = Collection.rawCollection(); - Collection.insert({ _id: "a" }); - Collection.insert({ _id: "b" }); + await Collection.insert({ _id: "a" }); + await Collection.insert({ _id: "b" }); let changeCount = 0; - return new Promise(resolve => { - function finalize() { - observeHandle.stop(); + return new Promise(async resolve => { + async function finalize() { + await observeHandle.stop(); Meteor.clearTimeout(timeout); resolve(); } - const observeHandle = Collection.find().observeChanges({ + const observeHandle = await Collection.find().observeChanges({ changed(id, fields) { let expectedValue; @@ -3484,9 +3451,9 @@ if (Meteor.isServer) { let promise = Promise.resolve(); ["a", "b"].forEach((id, index) => { promise = promise.then(() => rawCollection.updateMany( - { _id: id }, - { $set: { field: `updated${index + 1}` } }, - { session } + { _id: id }, + { $set: { field: `updated${index + 1}` } }, + { session } )); }); return promise; From 4a1b73222ffac5504f8035ada83f1841c19bbbf6 Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:45:02 -0600 Subject: [PATCH 0054/1965] Add support for top level await to meteor-babel --- npm-packages/meteor-babel/options.js | 3 +- npm-packages/meteor-babel/package-lock.json | 3912 ++++++++++++++++++- npm-packages/meteor-babel/package.json | 2 +- 3 files changed, 3875 insertions(+), 42 deletions(-) diff --git a/npm-packages/meteor-babel/options.js b/npm-packages/meteor-babel/options.js index 2d582a3102..2a056e48c4 100644 --- a/npm-packages/meteor-babel/options.js +++ b/npm-packages/meteor-babel/options.js @@ -20,7 +20,8 @@ function getReifyOptions(features) { const reifyOptions = { avoidModernSyntax: true, enforceStrictMode: false, - dynamicImport: true + dynamicImport: true, + topLevelAwait: features.topLevelAwait }; if (features) { diff --git a/npm-packages/meteor-babel/package-lock.json b/npm-packages/meteor-babel/package-lock.json index 7fec2af89a..d71bcadac9 100644 --- a/npm-packages/meteor-babel/package-lock.json +++ b/npm-packages/meteor-babel/package-lock.json @@ -1,8 +1,3861 @@ { "name": "@meteorjs/babel", "version": "7.16.0-beta.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@meteorjs/babel", + "version": "7.16.0-beta.1", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.17.2", + "@babel/parser": "^7.17.0", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/preset-react": "^7.16.7", + "@babel/runtime": "^7.17.2", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0", + "@meteorjs/reify": "meteor/reify#eace2425af6e69ce12c293959c7da56ebb629f79", + "babel-preset-meteor": "^7.10.0", + "babel-preset-minify": "^0.5.1", + "convert-source-map": "^1.6.0", + "lodash": "^4.17.21", + "meteor-babel-helpers": "0.0.3", + "typescript": "^4.5.4" + }, + "devDependencies": { + "@babel/plugin-proposal-decorators": "7.14.5", + "@babel/plugin-syntax-decorators": "7.14.5", + "d3": "4.13.0", + "fibers": "5.0.0", + "meteor-promise": "0.9.0", + "mocha": "6.2.3", + "promise": "8.1.0", + "source-map": "0.6.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz", + "integrity": "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.9.tgz", + "integrity": "sha512-p3QjZmMGHDGdpcwEYYWu7i7oJShJvtgMjJeb0W95PPhSm++3lm8YXYOh45Y6iCN9PkZLTZ7CIX5nFrp7pw7TXw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.2.tgz", + "integrity": "sha512-R3VH5G42VSDolRHyUO4V2cfag8WHcZyxdq5Z/m8Xyb92lW/Erm/6kM+XtRFGf3Mulre3mveni2NHfEUws8wSvw==", + "dependencies": { + "@ampproject/remapping": "^2.0.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.0", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/compat-data": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/helper-compilation-targets": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "dependencies": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/browserslist": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dependencies": { + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/@babel/core/node_modules/caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/@babel/core/node_modules/electron-to-chromium": { + "version": "1.4.68", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz", + "integrity": "sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA==" + }, + "node_modules/@babel/core/node_modules/node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + }, + "node_modules/@babel/generator": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.0.tgz", + "integrity": "sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw==", + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", + "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "dependencies": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.8.tgz", + "integrity": "sha512-bpYvH8zJBWzeqi1o+co8qOrw+EXzQ/0c74gVmY205AWXy9nifHrOg77y+1zwxX5lXE7Icq4sPlSQ4O2kWBrteQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.7", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", + "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "regexpu-core": "^4.7.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", + "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", + "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", + "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-wrap-function": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz", + "integrity": "sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", + "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", + "dependencies": { + "@babel/helper-function-name": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.0.tgz", + "integrity": "sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz", + "integrity": "sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", + "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-replace-supers": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.5.tgz", + "integrity": "sha512-LYz5nvQcvYeRVjui1Ykn28i+3aUiXwQ/3MGoEy0InTaz1pJo/lAzmIDXX+BQny/oufgHzJ6vnEEiXQ8KZjEVFg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-decorators": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", + "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", + "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", + "dependencies": { + "@babel/compat-data": "^7.14.7", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", + "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.14.5.tgz", + "integrity": "sha512-c4sZMRWL4GSvP1EXy0woIP7m4jkVcEuG8R1TOZxPBPtp4FSM/kiPZub9UIs/Jrb5ZAOzvTUSGYrWsrSu1JvoPw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", + "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", + "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", + "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", + "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz", + "integrity": "sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz", + "integrity": "sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", + "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", + "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", + "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", + "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", + "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", + "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", + "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", + "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz", + "integrity": "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", + "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", + "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", + "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "dependencies": { + "regenerator-transform": "^0.14.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", + "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", + "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", + "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", + "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", + "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", + "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", + "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-react-display-name": "^7.16.7", + "@babel/plugin-transform-react-jsx": "^7.16.7", + "@babel/plugin-transform-react-jsx-development": "^7.16.7", + "@babel/plugin-transform-react-pure-annotations": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react/node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/preset-react/node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.0.tgz", + "integrity": "sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg==", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.0", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.0", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@meteorjs/reify": { + "name": "reify", + "version": "0.22.2", + "resolved": "git+ssh://git@github.com/meteor/reify.git#eace2425af6e69ce12c293959c7da56ebb629f79", + "integrity": "sha512-96EYy8G6K6r500nRIsbtlUbZCu1oYcnqT+RoGZjENXQ3EPTsM/EXJ3jOMc2+rbgjAT+sNq/WEbtYwEAJxusBQQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.8.1", + "acorn-dynamic-import": "^4.0.0", + "magic-string": "^0.25.3", + "semver": "^5.4.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@meteorjs/reify/node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@meteorjs/reify/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "deprecated": "This is probably built in to whatever tool you're using. If you still need it... idk", + "peerDependencies": { + "acorn": "^6.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "node_modules/babel-helper-evaluate-path": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", + "integrity": "sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==" + }, + "node_modules/babel-helper-flip-expressions": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", + "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" + }, + "node_modules/babel-helper-is-nodes-equiv": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", + "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" + }, + "node_modules/babel-helper-is-void-0": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", + "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" + }, + "node_modules/babel-helper-mark-eval-scopes": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", + "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" + }, + "node_modules/babel-helper-remove-or-void": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", + "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" + }, + "node_modules/babel-helper-to-multiple-sequence-expressions": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", + "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-minify-builtins": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", + "integrity": "sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==" + }, + "node_modules/babel-plugin-minify-constant-folding": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz", + "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==", + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0" + } + }, + "node_modules/babel-plugin-minify-dead-code-elimination": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", + "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==", + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-mark-eval-scopes": "^0.4.3", + "babel-helper-remove-or-void": "^0.4.3", + "lodash": "^4.17.11" + } + }, + "node_modules/babel-plugin-minify-flip-comparisons": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", + "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=", + "dependencies": { + "babel-helper-is-void-0": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-guarded-expressions": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz", + "integrity": "sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==", + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-flip-expressions": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-infinity": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", + "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" + }, + "node_modules/babel-plugin-minify-mangle-names": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", + "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==", + "dependencies": { + "babel-helper-mark-eval-scopes": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-numeric-literals": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", + "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" + }, + "node_modules/babel-plugin-minify-replace": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz", + "integrity": "sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==" + }, + "node_modules/babel-plugin-minify-simplify": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz", + "integrity": "sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==", + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-flip-expressions": "^0.4.3", + "babel-helper-is-nodes-equiv": "^0.0.1", + "babel-helper-to-multiple-sequence-expressions": "^0.5.0" + } + }, + "node_modules/babel-plugin-minify-type-constructors": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", + "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=", + "dependencies": { + "babel-helper-is-void-0": "^0.4.3" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-transform-inline-consecutive-adds": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", + "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" + }, + "node_modules/babel-plugin-transform-member-expression-literals": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", + "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" + }, + "node_modules/babel-plugin-transform-merge-sibling-variables": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", + "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" + }, + "node_modules/babel-plugin-transform-minify-booleans": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", + "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" + }, + "node_modules/babel-plugin-transform-property-literals": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", + "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=", + "dependencies": { + "esutils": "^2.0.2" + } + }, + "node_modules/babel-plugin-transform-regexp-constructors": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", + "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" + }, + "node_modules/babel-plugin-transform-remove-console": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", + "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" + }, + "node_modules/babel-plugin-transform-remove-debugger": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", + "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" + }, + "node_modules/babel-plugin-transform-remove-undefined": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz", + "integrity": "sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==", + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0" + } + }, + "node_modules/babel-plugin-transform-simplify-comparison-operators": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", + "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" + }, + "node_modules/babel-plugin-transform-undefined-to-void": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", + "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" + }, + "node_modules/babel-preset-meteor": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/babel-preset-meteor/-/babel-preset-meteor-7.10.0.tgz", + "integrity": "sha512-bcdNfRCQAjTV42cUcmaG5/ltLZZQLpZajUcP+o0Lr+aLTY/XLNkGfASM5383wdXiAkEFl0sDOXeknnLlQtrmdg==", + "dependencies": { + "@babel/plugin-proposal-async-generator-functions": "^7.13.15", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-logical-assignment-operators": "^7.13.8", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-object-rest-spread": "^7.13.8", + "@babel/plugin-proposal-optional-catch-binding": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.13.0", + "@babel/plugin-transform-async-to-generator": "^7.13.0", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.13.16", + "@babel/plugin-transform-classes": "^7.13.0", + "@babel/plugin-transform-computed-properties": "^7.13.0", + "@babel/plugin-transform-destructuring": "^7.13.17", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.13.0", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.13.0", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.13.0", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.13.0", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13" + } + }, + "node_modules/babel-preset-minify": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", + "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==", + "dependencies": { + "babel-plugin-minify-builtins": "^0.5.0", + "babel-plugin-minify-constant-folding": "^0.5.0", + "babel-plugin-minify-dead-code-elimination": "^0.5.1", + "babel-plugin-minify-flip-comparisons": "^0.4.3", + "babel-plugin-minify-guarded-expressions": "^0.4.4", + "babel-plugin-minify-infinity": "^0.4.3", + "babel-plugin-minify-mangle-names": "^0.5.0", + "babel-plugin-minify-numeric-literals": "^0.4.3", + "babel-plugin-minify-replace": "^0.5.0", + "babel-plugin-minify-simplify": "^0.5.1", + "babel-plugin-minify-type-constructors": "^0.4.3", + "babel-plugin-transform-inline-consecutive-adds": "^0.4.3", + "babel-plugin-transform-member-expression-literals": "^6.9.4", + "babel-plugin-transform-merge-sibling-variables": "^6.9.4", + "babel-plugin-transform-minify-booleans": "^6.9.4", + "babel-plugin-transform-property-literals": "^6.9.4", + "babel-plugin-transform-regexp-constructors": "^0.4.3", + "babel-plugin-transform-remove-console": "^6.9.4", + "babel-plugin-transform-remove-debugger": "^6.9.4", + "babel-plugin-transform-remove-undefined": "^0.5.0", + "babel-plugin-transform-simplify-comparison-operators": "^6.9.4", + "babel-plugin-transform-undefined-to-void": "^6.9.4", + "lodash": "^4.17.11" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dependencies": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001248", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz", + "integrity": "sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-js-compat": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.0.tgz", + "integrity": "sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A==", + "dependencies": { + "browserslist": "^4.19.1", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/browserslist": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "dependencies": { + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/core-js-compat/node_modules/caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/core-js-compat/node_modules/electron-to-chromium": { + "version": "1.4.68", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz", + "integrity": "sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA==" + }, + "node_modules/core-js-compat/node_modules/node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/d3": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz", + "integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==", + "dev": true, + "dependencies": { + "d3-array": "1.2.1", + "d3-axis": "1.0.8", + "d3-brush": "1.0.4", + "d3-chord": "1.0.4", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-dispatch": "1.0.3", + "d3-drag": "1.2.1", + "d3-dsv": "1.0.8", + "d3-ease": "1.0.3", + "d3-force": "1.1.0", + "d3-format": "1.2.2", + "d3-geo": "1.9.1", + "d3-hierarchy": "1.1.5", + "d3-interpolate": "1.1.6", + "d3-path": "1.0.5", + "d3-polygon": "1.0.3", + "d3-quadtree": "1.0.3", + "d3-queue": "3.0.7", + "d3-random": "1.1.0", + "d3-request": "1.0.6", + "d3-scale": "1.0.7", + "d3-selection": "1.3.0", + "d3-shape": "1.2.0", + "d3-time": "1.0.8", + "d3-time-format": "2.1.1", + "d3-timer": "1.0.7", + "d3-transition": "1.1.1", + "d3-voronoi": "1.1.2", + "d3-zoom": "1.7.1" + } + }, + "node_modules/d3-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", + "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==", + "dev": true + }, + "node_modules/d3-axis": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz", + "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=", + "dev": true + }, + "node_modules/d3-brush": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz", + "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=", + "dev": true, + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/d3-chord": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz", + "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=", + "dev": true, + "dependencies": { + "d3-array": "1", + "d3-path": "1" + } + }, + "node_modules/d3-collection": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", + "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=", + "dev": true + }, + "node_modules/d3-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", + "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=", + "dev": true + }, + "node_modules/d3-dispatch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", + "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=", + "dev": true + }, + "node_modules/d3-drag": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz", + "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==", + "dev": true, + "dependencies": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "node_modules/d3-dsv": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", + "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", + "dev": true, + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, + "node_modules/d3-ease": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", + "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=", + "dev": true + }, + "node_modules/d3-force": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", + "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==", + "dev": true, + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "node_modules/d3-format": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz", + "integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==", + "dev": true + }, + "node_modules/d3-geo": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.9.1.tgz", + "integrity": "sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA==", + "dev": true, + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hierarchy": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz", + "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=", + "dev": true + }, + "node_modules/d3-interpolate": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.6.tgz", + "integrity": "sha512-mOnv5a+pZzkNIHtw/V6I+w9Lqm9L5bG3OTXPM5A+QO0yyVMQ4W1uZhR+VOJmazaOZXri2ppbiZ5BUNWT0pFM9A==", + "dev": true, + "dependencies": { + "d3-color": "1" + } + }, + "node_modules/d3-path": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz", + "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=", + "dev": true + }, + "node_modules/d3-polygon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz", + "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=", + "dev": true + }, + "node_modules/d3-quadtree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", + "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=", + "dev": true + }, + "node_modules/d3-queue": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", + "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=", + "dev": true + }, + "node_modules/d3-random": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", + "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=", + "dev": true + }, + "node_modules/d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", + "dev": true, + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-dsv": "1", + "xmlhttprequest": "1" + } + }, + "node_modules/d3-scale": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", + "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "dev": true, + "dependencies": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "node_modules/d3-selection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz", + "integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==", + "dev": true + }, + "node_modules/d3-shape": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz", + "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=", + "dev": true, + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-time": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz", + "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ==", + "dev": true + }, + "node_modules/d3-time-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz", + "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==", + "dev": true, + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/d3-timer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", + "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==", + "dev": true + }, + "node_modules/d3-transition": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.1.tgz", + "integrity": "sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==", + "dev": true, + "dependencies": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=", + "dev": true + }, + "node_modules/d3-zoom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.1.tgz", + "integrity": "sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ==", + "dev": true, + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.793", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.793.tgz", + "integrity": "sha512-l9NrGV6Mr4ov5mayYPvIWcwklNw5ROmy6rllzz9dCACw9nKE5y+s5uQk+CBJMetxrWZ6QJFsvEfG6WDcH2IGUg==" + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", + "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fibers": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", + "integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/meteor-babel-helpers": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", + "integrity": "sha1-8uXZ+HlvvS6JAQI9dpnlsgLqn7A=" + }, + "node_modules/meteor-promise": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/meteor-promise/-/meteor-promise-0.9.0.tgz", + "integrity": "sha512-O1Fj1Oa5FfyIkAkDtZVnoYYEIC3miy7lvEeIQZVYunGSbOuivSbfAiPPsD+P45WNlcBALhUo94UzlHeIKBYNuQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/mocha/node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-environment-flags/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "dev": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dependencies": { + "regenerate": "^1.4.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dependencies": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" + }, + "node_modules/regjsparser": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + } + }, "dependencies": { "@ampproject/remapping": { "version": "2.1.1", @@ -1042,17 +4895,21 @@ } }, "@meteorjs/reify": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.23.0.tgz", - "integrity": "sha512-sHQCbZHoM+PxT+pWvkJDsaOpJP+tMQ31Mr2t1T0YcXl18eScb0bQNafe8TugNCc4pngByppfscVX4ppr84MzDw==", + "version": "git+ssh://git@github.com/meteor/reify.git#eace2425af6e69ce12c293959c7da56ebb629f79", + "integrity": "sha512-96EYy8G6K6r500nRIsbtlUbZCu1oYcnqT+RoGZjENXQ3EPTsM/EXJ3jOMc2+rbgjAT+sNq/WEbtYwEAJxusBQQ==", + "from": "@meteorjs/reify@meteor/reify#eace2425af6e69ce12c293959c7da56ebb629f79", "requires": { - "acorn": "^6.1.1", + "acorn": "^8.8.1", "acorn-dynamic-import": "^4.0.0", "magic-string": "^0.25.3", - "periscopic": "^2.0.3", - "semver": "^5.7.1" + "semver": "^5.4.1" }, "dependencies": { + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1060,20 +4917,17 @@ } } }, - "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" - }, "acorn": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "peer": true }, "acorn-dynamic-import": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "requires": {} }, "ansi-colors": { "version": "3.2.3", @@ -1961,11 +5815,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2180,14 +6029,6 @@ "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", "dev": true }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "requires": { - "@types/estree": "*" - } - }, "is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", @@ -2277,11 +6118,11 @@ } }, "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "requires": { - "sourcemap-codec": "^1.4.4" + "sourcemap-codec": "^1.4.8" } }, "meteor-babel-helpers": { @@ -2498,15 +6339,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "periscopic": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz", - "integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==", - "requires": { - "estree-walker": "^2.0.2", - "is-reference": "^1.1.4" - } - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", diff --git a/npm-packages/meteor-babel/package.json b/npm-packages/meteor-babel/package.json index a896f5a3a1..1b92aa8b52 100644 --- a/npm-packages/meteor-babel/package.json +++ b/npm-packages/meteor-babel/package.json @@ -41,7 +41,7 @@ "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", "@babel/types": "^7.17.0", - "@meteorjs/reify": "0.23.0", + "@meteorjs/reify": "meteor/reify#eace2425af6e69ce12c293959c7da56ebb629f79", "babel-preset-meteor": "^7.10.0", "babel-preset-minify": "^0.5.1", "convert-source-map": "^1.6.0", From e6d139adbd6023e0841f0ef55a58699a6fc4835e Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:47:41 -0600 Subject: [PATCH 0055/1965] Update async-await plugin to support top level await --- .../meteor-babel/plugins/async-await.js | 159 ++++++++++++------ 1 file changed, 104 insertions(+), 55 deletions(-) diff --git a/npm-packages/meteor-babel/plugins/async-await.js b/npm-packages/meteor-babel/plugins/async-await.js index c1745ba2f9..51a033a9e6 100644 --- a/npm-packages/meteor-babel/plugins/async-await.js +++ b/npm-packages/meteor-babel/plugins/async-await.js @@ -3,71 +3,120 @@ module.exports = function (babel) { const t = babel.types; + function visitFunction(path, opts, reuseFiber) { + const node = path.node; + if (!node.async) { + return; + } + + // The original function becomes a non-async function that + // returns a Promise. + node.async = false; + + // The inner function should inherit lexical environment items + // like `this`, `super`, and `arguments` from the outer + // function, and arrow functions provide exactly that behavior. + const innerFn = t.arrowFunctionExpression( + // The inner function has no parameters of its own, but can + // refer to the outer parameters of the original function. + [], + node.body, + // The inner function called by Promise.asyncApply should be + // async if we have native async/await support. + !!opts.useNativeAsyncAwait + ); + + let args = [innerFn]; + + if (reuseFiber) { + // context, args, allowReuseOfCurrentFiber + args.push(t.identifier('undefined'), t.identifier('undefined'), t.booleanLiteral(true)); + } + + const promiseResultExpression = t.callExpression( + t.memberExpression( + t.identifier("Promise"), + t.identifier("asyncApply"), + false + ), args + ); + + // Calling the async function with Promise.asyncApply is + // important to ensure that the part before the first await + // expression runs synchronously in its own Fiber, even when + // there is native support for async/await. + if (node.type === "ArrowFunctionExpression") { + node.body = promiseResultExpression; + } else { + node.body = t.blockStatement([ + t.returnStatement(promiseResultExpression) + ]); + } + } + + function visitAwaitExpression(path, opts) { + if (opts.useNativeAsyncAwait) { + // No need to transform await expressions if we have native + // support for them. + return; + } + + const node = path.node; + path.replaceWith(t.callExpression( + t.memberExpression( + t.identifier("Promise"), + t.identifier(node.all ? "awaitAll" : "await"), + false + ), + [node.argument] + )); + } + return { name: "transform-meteor-async-await", visitor: { Function: { exit: function (path) { - const node = path.node; - if (! node.async) { - return; - } - - // The original function becomes a non-async function that - // returns a Promise. - node.async = false; - - // The inner function should inherit lexical environment items - // like `this`, `super`, and `arguments` from the outer - // function, and arrow functions provide exactly that behavior. - const innerFn = t.arrowFunctionExpression( - // The inner function has no parameters of its own, but can - // refer to the outer parameters of the original function. - [], - node.body, - // The inner function called by Promise.asyncApply should be - // async if we have native async/await support. - !! this.opts.useNativeAsyncAwait - ); - - const promiseResultExpression = t.callExpression( - t.memberExpression( - t.identifier("Promise"), - t.identifier("asyncApply"), - false - ), [innerFn] - ); - - // Calling the async function with Promise.asyncApply is - // important to ensure that the part before the first await - // expression runs synchronously in its own Fiber, even when - // there is native support for async/await. - if (node.type === "ArrowFunctionExpression") { - node.body = promiseResultExpression; - } else { - node.body = t.blockStatement([ - t.returnStatement(promiseResultExpression) - ]); - } + return visitFunction(path, this.opts); } }, AwaitExpression: function (path) { - if (this.opts.useNativeAsyncAwait) { - // No need to transform await expressions if we have native - // support for them. - return; - } + return visitAwaitExpression(path, this.opts); + }, + Program: { + // When top level await is enabled, reify wraps the module in an async function + // after the above visitors finished. This visitor ensures that wrapper is also + // transpiled. + exit: function (path) { + let asyncFunctionPath = path.get('body.0'); + if (asyncFunctionPath.node) { + asyncFunctionPath = asyncFunctionPath.get('expression'); + } + if (asyncFunctionPath.node) { + asyncFunctionPath = asyncFunctionPath.get('argument'); + } + if (asyncFunctionPath.node) { + asyncFunctionPath = asyncFunctionPath.get('arguments'); + } + if (asyncFunctionPath) { + asyncFunctionPath = asyncFunctionPath[0]; + } - const node = path.node; - path.replaceWith(t.callExpression( - t.memberExpression( - t.identifier("Promise"), - t.identifier(node.all ? "awaitAll" : "await"), - false - ), - [node.argument] - )); + if (!asyncFunctionPath || asyncFunctionPath.type !== 'FunctionExpression' || !asyncFunctionPath.node.async) { + return + } + + let ifPath = asyncFunctionPath.get('body.body').find(subPath => subPath.type === 'IfStatement'); + let awaitExpressionPath = ifPath.get('consequent.expression'); + if (awaitExpressionPath.type === 'AwaitExpression') { + visitAwaitExpression(awaitExpressionPath, this.opts); + } + + // Configures it to re-use the parent fiber. + // TODO: figure out another solution since this is not spec compliant + visitFunction(asyncFunctionPath, this.opts, true); + } } } }; From c27106100b76145681efd1a5d06348fd9dcdb43d Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:49:33 -0600 Subject: [PATCH 0056/1965] Add support for top level await to babel-compiler --- .../.npm/package/npm-shrinkwrap.json | 2024 +++++++++-------- packages/babel-compiler/babel-compiler.js | 4 + packages/babel-compiler/package.js | 3 +- 3 files changed, 1106 insertions(+), 925 deletions(-) diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index 4453e83e92..9252725448 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -1,943 +1,1119 @@ { "lockfileVersion": 1, "dependencies": { - "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==" - }, - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" - }, - "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" - }, - "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dependencies": { - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==" - } - } - }, - "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==" - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==" - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==" - }, - "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==" - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==" - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==" - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==" - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==" - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==" - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==" - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==" - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==" - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==" - }, - "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==" - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==" - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==" - }, - "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==" - }, - "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==" - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==" - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" - }, - "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==" - }, - "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==" - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==" - }, - "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==" - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==" - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==" - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==" - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==" - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==" - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==" - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==" - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==" - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" - }, - "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==" - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==" - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==" - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==" - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==" - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==" - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==" - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==" - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==" - }, - "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==" - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==" - }, - "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==" - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==" - }, - "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==" - }, - "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==" - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==" - }, - "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==" - }, - "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==" - }, - "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==" - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==" - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", - "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==" - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==" - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==" - }, - "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==" - }, - "@babel/plugin-transform-runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", - "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==" - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==" - }, - "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==" - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==" - }, - "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==" - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==" - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==" - }, - "@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==" - }, - "@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==" - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==" - }, - "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==" - }, - "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==" - }, - "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==" - }, "@meteorjs/babel": { "version": "7.16.0-beta.1", - "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.1.tgz", - "integrity": "sha512-PSyp2+oO2nrGMBTXd3VAP0EzHLW4bFqRIzmbTfHnr/s0dGhb7XaaGg3sOGAInewrFNCWfMHNe3hSiyOvC9bS2A==" - }, - "@meteorjs/reify": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.23.0.tgz", - "integrity": "sha512-sHQCbZHoM+PxT+pWvkJDsaOpJP+tMQ31Mr2t1T0YcXl18eScb0bQNafe8TugNCc4pngByppfscVX4ppr84MzDw==", "dependencies": { + "@ampproject/remapping": { + "version": "2.1.1" + }, + "@babel/code-frame": { + "version": "7.16.7" + }, + "@babel/compat-data": { + "version": "7.14.9" + }, + "@babel/core": { + "version": "7.17.2", + "dependencies": { + "@babel/compat-data": { + "version": "7.17.0" + }, + "@babel/helper-compilation-targets": { + "version": "7.16.7" + }, + "@babel/helper-validator-option": { + "version": "7.16.7" + }, + "browserslist": { + "version": "4.19.1" + }, + "caniuse-lite": { + "version": "1.0.30001312" + }, + "electron-to-chromium": { + "version": "1.4.68" + }, + "node-releases": { + "version": "2.0.2" + } + } + }, + "@babel/generator": { + "version": "7.17.0", + "dependencies": { + "source-map": { + "version": "0.5.7" + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.14.5" + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.14.5" + }, + "@babel/helper-compilation-targets": { + "version": "7.14.5" + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.8" + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.14.5" + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1" + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7" + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.14.5" + }, + "@babel/helper-function-name": { + "version": "7.14.5" + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5" + }, + "@babel/helper-hoist-variables": { + "version": "7.16.7" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.7" + }, + "@babel/helper-module-imports": { + "version": "7.14.5" + }, + "@babel/helper-module-transforms": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.16.7" + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7" + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5" + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.14.5" + }, + "@babel/helper-replace-supers": { + "version": "7.14.5" + }, + "@babel/helper-simple-access": { + "version": "7.16.7" + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.14.5" + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5" + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7" + }, + "@babel/helper-validator-option": { + "version": "7.14.5" + }, + "@babel/helper-wrap-function": { + "version": "7.14.5" + }, + "@babel/helpers": { + "version": "7.17.2" + }, + "@babel/highlight": { + "version": "7.16.10" + }, + "@babel/parser": { + "version": "7.17.0" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.9" + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.16.7" + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.17.1" + }, + "@babel/helper-function-name": { + "version": "7.16.7" + }, + "@babel/helper-get-function-arity": { + "version": "7.16.7" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.16.7" + }, + "@babel/helper-optimise-call-expression": { + "version": "7.16.7" + }, + "@babel/helper-plugin-utils": { + "version": "7.16.7" + }, + "@babel/helper-replace-supers": { + "version": "7.16.7" + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.14.5" + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.5" + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.5" + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.7" + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.5" + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.14.5" + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4" + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13" + }, + "@babel/plugin-syntax-decorators": { + "version": "7.14.5" + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3" + }, + "@babel/plugin-syntax-jsx": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4" + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3" + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3" + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3" + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3" + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.14.5" + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.14.5" + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.14.5" + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.14.5" + }, + "@babel/plugin-transform-classes": { + "version": "7.14.9" + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.14.5" + }, + "@babel/plugin-transform-destructuring": { + "version": "7.14.7" + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5" + }, + "@babel/plugin-transform-for-of": { + "version": "7.14.5" + }, + "@babel/plugin-transform-literals": { + "version": "7.14.5" + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.14.5" + }, + "@babel/plugin-transform-parameters": { + "version": "7.14.5" + }, + "@babel/plugin-transform-property-literals": { + "version": "7.14.5" + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.16.7" + }, + "@babel/helper-module-imports": { + "version": "7.16.7" + }, + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.16.7" + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.16.7" + }, + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.14.5" + }, + "@babel/plugin-transform-runtime": { + "version": "7.17.0", + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.16.7" + }, + "@babel/helper-plugin-utils": { + "version": "7.16.7" + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.14.5" + }, + "@babel/plugin-transform-spread": { + "version": "7.14.6" + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.14.5" + }, + "@babel/plugin-transform-template-literals": { + "version": "7.14.5" + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.14.5" + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.14.5" + }, + "@babel/preset-react": { + "version": "7.16.7", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.16.7" + }, + "@babel/helper-validator-option": { + "version": "7.16.7" + } + } + }, + "@babel/runtime": { + "version": "7.17.2" + }, + "@babel/template": { + "version": "7.16.7" + }, + "@babel/traverse": { + "version": "7.17.0", + "dependencies": { + "@babel/helper-function-name": { + "version": "7.16.7" + }, + "@babel/helper-get-function-arity": { + "version": "7.16.7" + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7" + } + } + }, + "@babel/types": { + "version": "7.17.0" + }, + "@jridgewell/resolve-uri": { + "version": "3.0.5" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.4" + }, + "reify": { + "version": "0.22.2", + "resolved": "/Users/zodern/dev/npm-packages/reify/reify-0.22.2.tgz", + "integrity": "sha512-jZH4rLcaj6u+qn62NPs6XbMJUIs6A/lZ0at+2j0Q+FrNXgVeNxzH6CZ8LJDUzfMgTbGP9FeTirdw1/JYi/q5Mw==", + "dependencies": { + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "acorn": { + "version": "6.4.2" + }, + "acorn-dynamic-import": { + "version": "4.0.0" + }, + "ansi-colors": { + "version": "3.2.3" + }, + "ansi-regex": { + "version": "3.0.0" + }, + "ansi-styles": { + "version": "3.2.1" + }, + "argparse": { + "version": "1.0.10" + }, + "asap": { + "version": "2.0.6" + }, + "babel-helper-evaluate-path": { + "version": "0.5.0" + }, + "babel-helper-flip-expressions": { + "version": "0.4.3" + }, + "babel-helper-is-nodes-equiv": { + "version": "0.0.1" + }, + "babel-helper-is-void-0": { + "version": "0.4.3" + }, + "babel-helper-mark-eval-scopes": { + "version": "0.4.3" + }, + "babel-helper-remove-or-void": { + "version": "0.4.3" + }, + "babel-helper-to-multiple-sequence-expressions": { + "version": "0.5.0" + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3" + }, + "babel-plugin-minify-builtins": { + "version": "0.5.0" + }, + "babel-plugin-minify-constant-folding": { + "version": "0.5.0" + }, + "babel-plugin-minify-dead-code-elimination": { + "version": "0.5.1" + }, + "babel-plugin-minify-flip-comparisons": { + "version": "0.4.3" + }, + "babel-plugin-minify-guarded-expressions": { + "version": "0.4.4" + }, + "babel-plugin-minify-infinity": { + "version": "0.4.3" + }, + "babel-plugin-minify-mangle-names": { + "version": "0.5.0" + }, + "babel-plugin-minify-numeric-literals": { + "version": "0.4.3" + }, + "babel-plugin-minify-replace": { + "version": "0.5.0" + }, + "babel-plugin-minify-simplify": { + "version": "0.5.1" + }, + "babel-plugin-minify-type-constructors": { + "version": "0.4.3" + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.1" + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.2" + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1" + }, + "babel-plugin-transform-inline-consecutive-adds": { + "version": "0.4.3" + }, + "babel-plugin-transform-member-expression-literals": { + "version": "6.9.4" + }, + "babel-plugin-transform-merge-sibling-variables": { + "version": "6.9.4" + }, + "babel-plugin-transform-minify-booleans": { + "version": "6.9.4" + }, + "babel-plugin-transform-property-literals": { + "version": "6.9.4" + }, + "babel-plugin-transform-regexp-constructors": { + "version": "0.4.3" + }, + "babel-plugin-transform-remove-console": { + "version": "6.9.4" + }, + "babel-plugin-transform-remove-debugger": { + "version": "6.9.4" + }, + "babel-plugin-transform-remove-undefined": { + "version": "0.5.0" + }, + "babel-plugin-transform-simplify-comparison-operators": { + "version": "6.9.4" + }, + "babel-plugin-transform-undefined-to-void": { + "version": "6.9.4" + }, + "babel-preset-meteor": { + "version": "7.10.0" + }, + "babel-preset-minify": { + "version": "0.5.1" + }, + "balanced-match": { + "version": "1.0.2" + }, + "brace-expansion": { + "version": "1.1.11" + }, + "browser-stdout": { + "version": "1.3.1" + }, + "browserslist": { + "version": "4.16.6" + }, + "call-bind": { + "version": "1.0.2" + }, + "camelcase": { + "version": "5.3.1" + }, + "caniuse-lite": { + "version": "1.0.30001248" + }, + "chalk": { + "version": "2.4.2" + }, + "cliui": { + "version": "5.0.0", + "dependencies": { + "ansi-regex": { + "version": "4.1.0" + }, + "string-width": { + "version": "3.1.0" + }, + "strip-ansi": { + "version": "5.2.0" + } + } + }, + "color-convert": { + "version": "1.9.3" + }, + "color-name": { + "version": "1.1.3" + }, + "colorette": { + "version": "1.2.2" + }, + "commander": { + "version": "2.20.3" + }, + "concat-map": { + "version": "0.0.1" + }, + "convert-source-map": { + "version": "1.8.0" + }, + "core-js-compat": { + "version": "3.21.0", + "dependencies": { + "browserslist": { + "version": "4.19.1" + }, + "caniuse-lite": { + "version": "1.0.30001312" + }, + "electron-to-chromium": { + "version": "1.4.68" + }, + "node-releases": { + "version": "2.0.2" + }, + "semver": { + "version": "7.0.0" + } + } + }, + "d3": { + "version": "4.13.0" + }, + "d3-array": { + "version": "1.2.1" + }, + "d3-axis": { + "version": "1.0.8" + }, + "d3-brush": { + "version": "1.0.4" + }, + "d3-chord": { + "version": "1.0.4" + }, + "d3-collection": { + "version": "1.0.4" + }, + "d3-color": { + "version": "1.0.3" + }, + "d3-dispatch": { + "version": "1.0.3" + }, + "d3-drag": { + "version": "1.2.1" + }, + "d3-dsv": { + "version": "1.0.8" + }, + "d3-ease": { + "version": "1.0.3" + }, + "d3-force": { + "version": "1.1.0" + }, + "d3-format": { + "version": "1.2.2" + }, + "d3-geo": { + "version": "1.9.1" + }, + "d3-hierarchy": { + "version": "1.1.5" + }, + "d3-interpolate": { + "version": "1.1.6" + }, + "d3-path": { + "version": "1.0.5" + }, + "d3-polygon": { + "version": "1.0.3" + }, + "d3-quadtree": { + "version": "1.0.3" + }, + "d3-queue": { + "version": "3.0.7" + }, + "d3-random": { + "version": "1.1.0" + }, + "d3-request": { + "version": "1.0.6" + }, + "d3-scale": { + "version": "1.0.7" + }, + "d3-selection": { + "version": "1.3.0" + }, + "d3-shape": { + "version": "1.2.0" + }, + "d3-time": { + "version": "1.0.8" + }, + "d3-time-format": { + "version": "2.1.1" + }, + "d3-timer": { + "version": "1.0.7" + }, + "d3-transition": { + "version": "1.1.1" + }, + "d3-voronoi": { + "version": "1.1.2" + }, + "d3-zoom": { + "version": "1.7.1" + }, + "debug": { + "version": "4.3.3" + }, + "decamelize": { + "version": "1.2.0" + }, + "define-properties": { + "version": "1.1.3" + }, + "detect-libc": { + "version": "1.0.3" + }, + "diff": { + "version": "3.5.0" + }, + "electron-to-chromium": { + "version": "1.3.793" + }, + "emoji-regex": { + "version": "7.0.3" + }, + "es-abstract": { + "version": "1.18.5" + }, + "es-to-primitive": { + "version": "1.2.1" + }, + "escalade": { + "version": "3.1.1" + }, + "escape-string-regexp": { + "version": "1.0.5" + }, + "esprima": { + "version": "4.0.1" + }, + "esutils": { + "version": "2.0.3" + }, + "fibers": { + "version": "5.0.0" + }, + "find-up": { + "version": "3.0.0" + }, + "flat": { + "version": "4.1.1" + }, + "fs.realpath": { + "version": "1.0.0" + }, + "function-bind": { + "version": "1.1.1" + }, + "gensync": { + "version": "1.0.0-beta.2" + }, + "get-caller-file": { + "version": "2.0.5" + }, + "get-intrinsic": { + "version": "1.1.1" + }, + "glob": { + "version": "7.1.3" + }, + "globals": { + "version": "11.12.0" + }, + "growl": { + "version": "1.10.5" + }, + "has": { + "version": "1.0.3" + }, + "has-bigints": { + "version": "1.0.1" + }, + "has-flag": { + "version": "3.0.0" + }, + "has-symbols": { + "version": "1.0.2" + }, + "he": { + "version": "1.2.0" + }, + "iconv-lite": { + "version": "0.4.24" + }, + "inflight": { + "version": "1.0.6" + }, + "inherits": { + "version": "2.0.4" + }, + "internal-slot": { + "version": "1.0.3" + }, + "is-bigint": { + "version": "1.0.2" + }, + "is-boolean-object": { + "version": "1.1.1" + }, + "is-buffer": { + "version": "2.0.5" + }, + "is-callable": { + "version": "1.2.3" + }, + "is-core-module": { + "version": "2.8.1" + }, + "is-date-object": { + "version": "1.0.4" + }, + "is-fullwidth-code-point": { + "version": "2.0.0" + }, + "is-negative-zero": { + "version": "2.0.1" + }, + "is-number-object": { + "version": "1.0.5" + }, + "is-regex": { + "version": "1.1.3" + }, + "is-string": { + "version": "1.0.6" + }, + "is-symbol": { + "version": "1.0.4" + }, + "isexe": { + "version": "2.0.0" + }, + "js-tokens": { + "version": "4.0.0" + }, + "js-yaml": { + "version": "3.13.1" + }, + "jsesc": { + "version": "2.5.2" + }, + "json5": { + "version": "2.2.0" + }, + "locate-path": { + "version": "3.0.0" + }, + "lodash": { + "version": "4.17.21" + }, + "lodash.debounce": { + "version": "4.0.8" + }, + "log-symbols": { + "version": "2.2.0" + }, + "magic-string": { + "version": "0.25.9" + }, + "meteor-babel-helpers": { + "version": "0.0.3" + }, + "meteor-promise": { + "version": "0.9.0" + }, + "minimatch": { + "version": "3.0.4" + }, + "minimist": { + "version": "1.2.6" + }, + "mkdirp": { + "version": "0.5.4" + }, + "mocha": { + "version": "6.2.3", + "dependencies": { + "debug": { + "version": "3.2.6" + }, + "ms": { + "version": "2.1.1" + }, + "object.assign": { + "version": "4.1.0" + }, + "supports-color": { + "version": "6.0.0" + } + } + }, + "ms": { + "version": "2.1.2" + }, + "node-environment-flags": { + "version": "1.0.5", + "dependencies": { + "semver": { + "version": "5.7.1" + } + } + }, + "node-releases": { + "version": "1.1.73" + }, + "object-inspect": { + "version": "1.11.0" + }, + "object-keys": { + "version": "1.1.1" + }, + "object.assign": { + "version": "4.1.2" + }, + "object.getownpropertydescriptors": { + "version": "2.1.2" + }, + "once": { + "version": "1.4.0" + }, + "p-limit": { + "version": "2.3.0" + }, + "p-locate": { + "version": "3.0.0" + }, + "p-try": { + "version": "2.2.0" + }, + "path-exists": { + "version": "3.0.0" + }, + "path-is-absolute": { + "version": "1.0.1" + }, + "path-parse": { + "version": "1.0.7" + }, + "picocolors": { + "version": "1.0.0" + }, + "promise": { + "version": "8.1.0" + }, + "regenerate": { + "version": "1.4.2" + }, + "regenerate-unicode-properties": { + "version": "8.2.0" + }, + "regenerator-runtime": { + "version": "0.13.9" + }, + "regenerator-transform": { + "version": "0.14.5" + }, + "regexpu-core": { + "version": "4.7.1" + }, + "regjsgen": { + "version": "0.5.2" + }, + "regjsparser": { + "version": "0.6.9", + "dependencies": { + "jsesc": { + "version": "0.5.0" + } + } + }, + "require-directory": { + "version": "2.1.1" + }, + "require-main-filename": { + "version": "2.0.0" + }, + "resolve": { + "version": "1.22.0" + }, + "rw": { + "version": "1.3.3" + }, + "safe-buffer": { + "version": "5.1.2" + }, + "safer-buffer": { + "version": "2.1.2" + }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "6.3.0" + }, + "set-blocking": { + "version": "2.0.0" + }, + "side-channel": { + "version": "1.0.4" + }, + "source-map": { + "version": "0.6.1" + }, + "sourcemap-codec": { + "version": "1.4.8" + }, + "sprintf-js": { + "version": "1.0.3" + }, + "string-width": { + "version": "2.1.1" + }, + "string.prototype.trimend": { + "version": "1.0.4" + }, + "string.prototype.trimstart": { + "version": "1.0.4" + }, + "strip-ansi": { + "version": "4.0.0" + }, + "strip-json-comments": { + "version": "2.0.1" + }, + "supports-color": { + "version": "5.5.0" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0" + }, + "to-fast-properties": { + "version": "2.0.0" + }, + "typescript": { + "version": "4.5.4" + }, + "unbox-primitive": { + "version": "1.0.1" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4" + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0" + }, + "which": { + "version": "1.3.1" + }, + "which-boxed-primitive": { + "version": "1.0.2" + }, + "which-module": { + "version": "2.0.0" + }, + "wide-align": { + "version": "1.1.3" + }, + "wrap-ansi": { + "version": "5.1.0", + "dependencies": { + "ansi-regex": { + "version": "4.1.0" + }, + "string-width": { + "version": "3.1.0" + }, + "strip-ansi": { + "version": "5.2.0" + } + } + }, + "wrappy": { + "version": "1.0.2" + }, + "xmlhttprequest": { + "version": "1.8.0" + }, + "y18n": { + "version": "4.0.3" + }, + "yargs": { + "version": "13.3.2", + "dependencies": { + "ansi-regex": { + "version": "4.1.0" + }, + "string-width": { + "version": "3.1.0" + }, + "strip-ansi": { + "version": "5.2.0" + } + } + }, + "yargs-parser": { + "version": "13.1.2" + }, + "yargs-unparser": { + "version": "1.6.0" } } }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" - }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" - }, - "babel-helper-evaluate-path": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", - "integrity": "sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==" - }, - "babel-helper-flip-expressions": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", - "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" - }, - "babel-helper-is-nodes-equiv": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", - "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" - }, - "babel-helper-is-void-0": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", - "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" - }, - "babel-helper-mark-eval-scopes": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", - "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" - }, - "babel-helper-remove-or-void": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", - "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" - }, - "babel-helper-to-multiple-sequence-expressions": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", - "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==" - }, - "babel-plugin-minify-builtins": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", - "integrity": "sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==" - }, - "babel-plugin-minify-constant-folding": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz", - "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==" - }, - "babel-plugin-minify-dead-code-elimination": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", - "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==" - }, - "babel-plugin-minify-flip-comparisons": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", - "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=" - }, - "babel-plugin-minify-guarded-expressions": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz", - "integrity": "sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==" - }, - "babel-plugin-minify-infinity": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", - "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" - }, - "babel-plugin-minify-mangle-names": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", - "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==" - }, - "babel-plugin-minify-numeric-literals": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", - "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" - }, - "babel-plugin-minify-replace": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz", - "integrity": "sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==" - }, - "babel-plugin-minify-simplify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz", - "integrity": "sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==" - }, - "babel-plugin-minify-type-constructors": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", - "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=" - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==" - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==" - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==" - }, - "babel-plugin-transform-inline-consecutive-adds": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", - "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" - }, - "babel-plugin-transform-member-expression-literals": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", - "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" - }, - "babel-plugin-transform-merge-sibling-variables": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", - "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" - }, - "babel-plugin-transform-minify-booleans": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", - "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" - }, - "babel-plugin-transform-property-literals": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", - "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=" - }, - "babel-plugin-transform-regexp-constructors": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", - "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" - }, - "babel-plugin-transform-remove-console": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", - "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" - }, - "babel-plugin-transform-remove-debugger": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", - "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" - }, - "babel-plugin-transform-remove-undefined": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz", - "integrity": "sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==" - }, - "babel-plugin-transform-simplify-comparison-operators": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", - "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" - }, - "babel-plugin-transform-undefined-to-void": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", - "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" - }, - "babel-preset-meteor": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/babel-preset-meteor/-/babel-preset-meteor-7.10.0.tgz", - "integrity": "sha512-bcdNfRCQAjTV42cUcmaG5/ltLZZQLpZajUcP+o0Lr+aLTY/XLNkGfASM5383wdXiAkEFl0sDOXeknnLlQtrmdg==" - }, - "babel-preset-minify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", - "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==" - }, - "browserslist": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.2.tgz", - "integrity": "sha512-97XU1CTZ5TwU9Qy/Taj+RtiI6SQM1WIhZ9osT7EY0oO2aWXGABZT2OZeRL+6PfaQsiiMIjjwIoYFPq4APgspgQ==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" - }, - "caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" - }, - "core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" - }, - "electron-to-chromium": { - "version": "1.4.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", - "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" - }, - "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, "json5": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==" }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==" - }, - "meteor-babel-helpers": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", - "integrity": "sha1-8uXZ+HlvvS6JAQI9dpnlsgLqn7A=" - }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "periscopic": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz", - "integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==" - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==" - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==" - }, - "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==" - }, - "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" - }, - "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" } } } diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index ea08fed9c0..5ad96476d2 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -87,8 +87,12 @@ BCp.processOneFileForTarget = function (inputFile, source) { // Start with a much simpler set of Babel presets and plugins if // we're compiling for Node 8. features.nodeMajorVersion = parseInt(process.versions.node, 10); + + features.topLevelAwait = true; } else if (arch === "web.browser") { features.modernBrowsers = true; + + // TODO: add an env var to enable top level await for the client } if (! features.hasOwnProperty("jscript")) { diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 40999ff266..b5d80d1259 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -5,7 +5,8 @@ Package.describe({ }); Npm.depends({ - '@meteorjs/babel': '7.16.0-beta.1', + // '@meteorjs/babel': '7.16.0-beta.1', + '@meteorjs/babel': 'file:///../../../../npm-packages/meteor-babel', 'json5': '2.1.1' }); From faa611f9b64a66f9783bf91f3ae2afa0f996f4d2 Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:50:08 -0600 Subject: [PATCH 0057/1965] Update modules to support top level await --- .../modules/.npm/package/npm-shrinkwrap.json | 2759 ++++++++++++++++- packages/modules/package.js | 2 +- 2 files changed, 2711 insertions(+), 50 deletions(-) diff --git a/packages/modules/.npm/package/npm-shrinkwrap.json b/packages/modules/.npm/package/npm-shrinkwrap.json index 06ade402a7..eb83371bf6 100644 --- a/packages/modules/.npm/package/npm-shrinkwrap.json +++ b/packages/modules/.npm/package/npm-shrinkwrap.json @@ -1,60 +1,2721 @@ { "lockfileVersion": 1, "dependencies": { - "@meteorjs/reify": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@meteorjs/reify/-/reify-0.24.0.tgz", - "integrity": "sha512-h7ZC9K9Ifqrkq7PBE8SnLD/2R6nhid63rNpJDDkjCLEYBZizEg7FBFe3QuyW7R0ZNKQ5uWFX9PxIARzHbcb3eA==" - }, - "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" - }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==" - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" + "reify": { + "version": "0.22.2", + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==" + } + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/compat-data": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz", + "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==" + }, + "@babel/core": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", + "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", + "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==", + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", + "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==" + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/helpers": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.4.tgz", + "integrity": "sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + } + } + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", + "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", + "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", + "dependencies": { + "@babel/compat-data": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz", + "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==" + }, + "@babel/helper-compilation-targets": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", + "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" + }, + "caniuse-lite": { + "version": "1.0.30001423", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz", + "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==" + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==" + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==" + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==" + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==" + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==" + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==" + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==" + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==" + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==" + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==" + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==" + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==" + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz", + "integrity": "sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-classes": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", + "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/compat-data": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz", + "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-compilation-targets": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", + "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" + }, + "caniuse-lite": { + "version": "1.0.30001423", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz", + "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==" + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz", + "integrity": "sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/compat-data": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz", + "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==" + }, + "@babel/helper-compilation-targets": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", + "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" + }, + "caniuse-lite": { + "version": "1.0.30001423", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz", + "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==" + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + }, + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + }, + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + }, + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + }, + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + }, + "@babel/generator": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz", + "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + }, + "@babel/parser": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz", + "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + } + } + }, + "@babel/preset-env": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", + "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", + "dependencies": { + "@babel/compat-data": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz", + "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==" + }, + "@babel/helper-compilation-targets": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz", + "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==" + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==" + }, + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==" + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==" + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==" + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" + }, + "caniuse-lite": { + "version": "1.0.30001423", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz", + "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==" + }, + "core-js-compat": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", + "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==" + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==" + }, + "@babel/runtime": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", + "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==" + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/traverse": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz", + "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==", + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/types": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz", + "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==" + } + } + }, + "@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==" + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==" + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==" + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==" + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + }, + "caniuse-lite": { + "version": "1.0.30001425", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001425.tgz", + "integrity": "sha512-/pzFv0OmNG6W0ym80P3NtapU0QEiDS3VuYAZMGoLLqiC7f6FJFe1MjpQDREGApeenD9wloeytmVDj+JLXPC6qw==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "core-js-compat": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", + "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==" + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==" + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==" + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" + } + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + }, + "mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-0wE74YMgOkCgBUj8VyIDwmLUjTsS13WV1Pg7l0SHea2qzZzlq7MDnfbPsHKcELBRk3+izEVkRofjmClpycudCA==", + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==" + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==" + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==" + }, + "recast": { + "version": "0.20.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", + "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==" + }, + "regenerator-runtime": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==" + }, + "regexpu-core": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", + "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==" + }, + "regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + }, + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==" + }, + "workerpool": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", + "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } }, "meteor-babel-helpers": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", "integrity": "sha512-PgfmiyT/HiBaxwGHxS4t3Qi0fpmEW3O0WW2VfrgekiMGz3aZPd9/4PRIaMMZsfyjQ1vyEm6dZqTAFZENbuoTxw==" - }, - "periscopic": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz", - "integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" } } } diff --git a/packages/modules/package.js b/packages/modules/package.js index 41e52aedbb..ab4ee57a0e 100644 --- a/packages/modules/package.js +++ b/packages/modules/package.js @@ -6,7 +6,7 @@ Package.describe({ }); Npm.depends({ - "@meteorjs/reify": "0.24.0", + "@meteorjs/reify": "meteor/reify#eace2425af6e69ce12c293959c7da56ebb629f79", "meteor-babel-helpers": "0.0.3" }); From c921ab59f87c692c77be3ff0b9b347007bbba92c Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:51:14 -0600 Subject: [PATCH 0058/1965] Update ecmaVersion for analyzeScope --- tools/isobuild/js-analyze.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/isobuild/js-analyze.js b/tools/isobuild/js-analyze.js index 1b8abee398..1b8a15b91d 100644 --- a/tools/isobuild/js-analyze.js +++ b/tools/isobuild/js-analyze.js @@ -273,7 +273,7 @@ export function findAssignedGlobals(source, hash) { // But it can't pull references outward, so for our purposes it is safe to // ignore. const scopeManager = analyzeScope(ast, { - ecmaVersion: 6, + ecmaVersion: 9, sourceType: "module", ignoreEval: true, // Ensures we don't treat top-level var declarations as globals. From 93d0bc5e348e5984ddf9f8a0ef330dc661e9d123 Mon Sep 17 00:00:00 2001 From: zodern Date: Thu, 17 Nov 2022 22:53:46 -0600 Subject: [PATCH 0059/1965] First attempt at runtime for loading packages with TLA --- packages/meteor/define-package.js | 114 ++++++++++++++++++++++++++++++ tools/isobuild/linker.js | 88 +++++++++++++++++------ 2 files changed, 179 insertions(+), 23 deletions(-) diff --git a/packages/meteor/define-package.js b/packages/meteor/define-package.js index c5d327b44c..fccae3c1fe 100644 --- a/packages/meteor/define-package.js +++ b/packages/meteor/define-package.js @@ -1,9 +1,28 @@ function PackageRegistry() { this._promiseInfoMap = Object.create(null); + this._packageQueue = []; + this._running = false; } var PRp = PackageRegistry.prototype; +var ASYNC_MAIN_MODULE = {}; + +// If potentialPromise is a promise, calls callback with the resolved value +// Otherwise, synchronously calls the callback with the value +PRp._waitForModule = function (potentialPromise, callback) { + if ( + isThenable(potentialPromise) && + potentialPromise.asyncMainModule === ASYNC_MAIN_MODULE + ) { + potentialPromise.then((results) => { + callback(results); + }); + } else { + callback(potentialPromise); + } +} + // Set global.Package[name] = pkg || {}. If additional arguments are // supplied, their keys will be copied into pkg if not already present. // This method is defined on the prototype of global.Package so that it @@ -28,6 +47,12 @@ PRp._define = function definePackage(name, pkg) { info.resolve(pkg); } + // if (this._packageQueue.length > 0) { + // this._packageQueue.unshift().load(); + // } else { + // this._running = false; + // } + return pkg; }; @@ -60,6 +85,95 @@ PRp._promise = function promise(name) { return info.promise; }; +// On the server, load is run immediately +// If it has async modules that are eagerly evaluated, it will return a +// promise that resolves after the package has been fully loaded. +PRp.load = function (name, deps, load) { + // console.log('start load', name); + var self = this; + + if (typeof Meteor === 'undefined' || Meteor.isServer) { + var result = load() || {}; + + var mainModule = result.mainModule; + var exports = result.exports; + + if (mainModule && mainModule.asyncMainModule === ASYNC_MAIN_MODULE) { + return mainModule.then(function (mainExports) { + console.log('defineAsync', name, mainModule, exports); + self._define(name, mainExports, exports); + }); + } + + self._define(name, mainModule, exports); + // console.log('define', name, mainModule, exports); + return; + // this._packageQueue.push({ + // name: name, + // load: load, + // deps: deps + // }); + + // if (!this._running) { + // this._running = true; + // this._packageQueue.unshift().load(); + // } + } + + // TODO: implement + +} + +function isThenable(value) { + return typeof value === 'object' && value !== null && + typeof value.then === 'function'; +} + +PRp._evaluateEagerModules = function(require, paths, mainModuleIndex) { + let index = -1; + let result; + let promise; + let resolve; + + function evaluateNext() { + index += 1; + + if (index === paths.length) { + if (resolve) { + resolve(result); + } + return; + } + + let path = paths[index]; + let exports = require(path); + + if (isThenable(exports)) { + if (!promise) { + promise = new Promise(_resolve => resolve = _resolve); + promise.asyncMainModule = ASYNC_MAIN_MODULE; + } + + exports.then(resolvedExports => { + if (index === mainModuleIndex) { + result = resolvedExports; + } + evaluateNext(); + }); + // TODO: handle error + } else { + if (index === mainModuleIndex) { + result = exports; + } + evaluateNext(); + } + } + + evaluateNext(); + + return promise ? promise : result; +} + // Initialize the Package namespace used by all Meteor packages. global.Package = new PackageRegistry(); diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index 8edd4fc117..76fe2202ba 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -439,7 +439,7 @@ Object.assign(Module.prototype, { assert.ok(_.isNumber(moduleCount)); assert.ok(_.isNumber(sourceWidth)); - let exportsName; + let exportsIndex = -1; // Now that we have installed everything in this package or // application, first evaluate the bare files, then require the @@ -458,21 +458,33 @@ Object.assign(Module.prototype, { }); if (eagerModuleFiles.length > 0) { - _.each(eagerModuleFiles, file => { + let code = 'Package._evaluateEagerModules(require,['; + let paths = eagerModuleFiles.map((file, index) => { if (file.mainModule) { - exportsName = "exports"; + exportsIndex = index; } - chunks.push( - file.mainModule ? "\nvar " + exportsName + " = " : "\n", - "require(", - JSON.stringify(file.absModuleId), - ");" - ); + return JSON.stringify(file.absModuleId); }); + + code += paths.join(',\n '); + code += ']'; + + if (exportsIndex !== -1) { + code += `, ${exportsIndex}`; + } + + code += ');'; + + if (exportsIndex !== -1) { + code = '\nvar exports = ' + code; + } + + chunks.push(code); } - return exportsName; + + return exportsIndex === -1 ? undefined : 'exports'; } }); @@ -964,8 +976,22 @@ var SOURCE_MAP_INSTRUCTIONS_COMMENT = banner([ var getHeader = function (options) { var chunks = []; + // TODO: find a better check that also works for packages that + // load before the Meteor package + if (options.name !== 'meteor') { + let depsCode = Object.values(options.imports).map(k => JSON.stringify(k)).join(', '); + + chunks.push( + `Package.load("${options.name}", [`, + depsCode, + '], function () {' + ); + + } else { + chunks.push("(function() {\n\n") + } + chunks.push( - "(function () {\n\n", getImportCode(options.imports, "/* Imports */\n", false), ); @@ -1015,33 +1041,47 @@ var getFooter = function ({ name, exported, exportsName, + imports }) { var chunks = []; - if (name && exported) { + if (name === 'meteor') { + chunks.push("Package._define(" + JSON.stringify(name) + ", "); + if (!_.isEmpty(exported)) { + const scratch = {}; + _.each(exported, symbol => scratch[symbol] = symbol); + const symbolTree = writeSymbolTree(buildSymbolTree(scratch)); + chunks.push(symbolTree); + } + chunks.push(');\n'); + + } else if (exported || exportsName) { chunks.push("\n\n/* Exports */\n"); + chunks.push('return {\n'); + + if (exportsName) { + chunks.push(` mainModule: ${exportsName},`); + } // Even if there are no exports, we need to define Package.foo, // because the existence of Package.foo is how another package // (e.g., one that weakly depends on foo) can tell if foo is loaded. - chunks.push("Package._define(" + JSON.stringify(name)); - - if (exportsName) { - // If we have an exports object, use it as Package[name]. - chunks.push(", ", exportsName); - } if (! _.isEmpty(exported)) { const scratch = {}; _.each(exported, symbol => scratch[symbol] = symbol); const symbolTree = writeSymbolTree(buildSymbolTree(scratch)); - chunks.push(", ", symbolTree); + chunks.push("exports: ", symbolTree); } - - chunks.push(");\n"); + chunks.push('};\n'); + + } + if (name !== 'meteor') { + chunks.push("\n});\n"); + } else { + chunks.push("\n})();\n"); } - chunks.push("\n})();\n"); return chunks.join(''); }; @@ -1150,6 +1190,7 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, { // Otherwise we're making a package and we have to actually combine the files // into a single scope. var header = getHeader({ + name, imports, packageVariables: _.union(assignedVariables, declaredExports) }); @@ -1164,7 +1205,8 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, { var footer = getFooter({ exported: declaredExports, exportsName, - name + name, + imports }); if (includeSourceMapInstructions) { From cf30fc3c789a858e5c875e7f372cdd34ce3d3ddf Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 18 Nov 2022 18:37:32 -0300 Subject: [PATCH 0060/1965] Started changing observe_changes_tests.js. --- packages/mongo-async/observe_changes_tests.js | 254 ++++++++---------- 1 file changed, 112 insertions(+), 142 deletions(-) diff --git a/packages/mongo-async/observe_changes_tests.js b/packages/mongo-async/observe_changes_tests.js index 7088229d65..121c7d2e0f 100644 --- a/packages/mongo-async/observe_changes_tests.js +++ b/packages/mongo-async/observe_changes_tests.js @@ -14,58 +14,56 @@ _.each ([{added: 'added', forceOrdered: true}, Tinytest.addAsync("observeChanges - single id - basics " + added + (forceOrdered ? " force ordered" : ""), - function (test, onComplete) { + async function (test, onComplete) { var c = makeCollection(); var counter = 0; var callbacks = [added, "changed", "removed"]; if (forceOrdered) callbacks.push("movedBefore"); - withCallbackLogger(test, + await withCallbackLogger(test, callbacks, Meteor.isServer, - function (logger) { - var barid = c.insert({thing: "stuff"}); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + async function (logger) { + var barid = await c.insert({thing: "stuff"}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - var handle = c.find(fooid).observeChanges(logger); + var handle = await c.find(fooid).observeChanges(logger); if (added === 'added') { logger.expectResult(added, [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); } else { logger.expectResult(added, [fooid, {noodles: "good", bacon: "bad", apples: "ok"}, null]); } - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); logger.expectResult("changed", [fooid, {noodles: "alright", potatoes: "tasty", bacon: undefined}]); - c.remove(fooid); + await c.remove(fooid); logger.expectResult("removed", [fooid]); - logger.expectNoResult(() => { - c.remove(barid); - c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + await logger.expectNoResult(async () => { + await c.remove(barid); + await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); }); - handle.stop(); + await handle.stop(); const badCursor = c.find({}, {fields: {noodles: 1, _id: false}}); - test.throws(function () { - badCursor.observeChanges(logger); + await test.throwsAsync(function () { + return badCursor.observeChanges(logger); }); - - onComplete(); - }); + }); }); }); -Tinytest.addAsync("observeChanges - callback isolation", function (test, onComplete) { +Tinytest.addAsync("observeChanges - callback isolation", async function (test) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { var handles = []; var cursor = c.find(); - handles.push(cursor.observeChanges(logger)); + handles.push(await cursor.observeChanges(logger)); // fields-tampering observer - handles.push(cursor.observeChanges({ + handles.push(await cursor.observeChanges({ added: function(id, fields) { fields.apples = 'green'; }, @@ -74,193 +72,184 @@ Tinytest.addAsync("observeChanges - callback isolation", function (test, onCompl }, })); - var fooid = c.insert({apples: "ok"}); + var fooid = await c.insert({apples: "ok"}); logger.expectResult("added", [fooid, {apples: "ok"}]); - c.update(fooid, {apples: "not ok"}); + await c.update(fooid, {apples: "not ok"}); logger.expectResult("changed", [fooid, {apples: "not ok"}]); - test.equal(c.findOne(fooid).apples, "not ok"); + test.equal((await c.findOne(fooid)).apples, "not ok"); - _.each(handles, function(handle) { handle.stop(); }); - onComplete(); + await Promise.all(handles.map(h => h.stop())); }); - }); -Tinytest.addAsync("observeChanges - single id - initial adds", function (test, onComplete) { +Tinytest.addAsync("observeChanges - single id - initial adds", async function (test) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - var handle = c.find(fooid).observeChanges(logger); + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var handle = await c.find(fooid).observeChanges(logger); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); -Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, onComplete) { +Tinytest.addAsync("observeChanges - unordered - initial adds", async function (test) { var c = makeCollection(); - 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"}); - var handle = c.find().observeChanges(logger); + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var barid = await c.insert({noodles: "good", bacon: "weird", apples: "ok"}); + var handle = await c.find().observeChanges(logger); logger.expectResultUnordered([ {callback: "added", args: [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]}, {callback: "added", args: [barid, {noodles: "good", bacon: "weird", apples: "ok"}]} ]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); -Tinytest.addAsync("observeChanges - unordered - basics", function (test, onComplete) { +Tinytest.addAsync("observeChanges - unordered - basics", async function (test) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var handle = c.find().observeChanges(logger); - var barid = c.insert({thing: "stuff"}); + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var handle = await c.find().observeChanges(logger); + var barid = await c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {thing: "stuff"}]); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); logger.expectResultOnly("changed", [fooid, {noodles: "alright", potatoes: "tasty", bacon: undefined}]); - c.remove(fooid); + await c.remove(fooid); logger.expectResultOnly("removed", [fooid]); - c.remove(barid); + await c.remove(barid); logger.expectResultOnly("removed", [barid]); - fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); if (Meteor.isServer) { - Tinytest.addAsync("observeChanges - unordered - specific fields", function (test, onComplete) { + Tinytest.addAsync("observeChanges - unordered - specific fields", async function (test, onComplete) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var handle = c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); - var barid = c.insert({thing: "stuff"}); + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var handle = await c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); + var barid = await c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {}]); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); logger.expectResultOnly("changed", [fooid, {noodles: "alright", bacon: undefined}]); - c.update(fooid, {noodles: "alright", potatoes: "meh", apples: "ok"}); - c.remove(fooid); + await c.update(fooid, {noodles: "alright", potatoes: "meh", apples: "ok"}); + await c.remove(fooid); logger.expectResultOnly("removed", [fooid]); - c.remove(barid); + await c.remove(barid); logger.expectResultOnly("removed", [barid]); - fooid = c.insert({noodles: "good", bacon: "bad"}); + fooid = await c.insert({noodles: "good", bacon: "bad"}); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); - Tinytest.addAsync("observeChanges - unordered - specific fields + selector on excluded fields", function (test, onComplete) { + Tinytest.addAsync("observeChanges - unordered - specific fields + selector on excluded fields", async function (test) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var handle = c.find({ mac: 1, cheese: 2 }, + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var handle = await c.find({ mac: 1, cheese: 2 }, {fields:{noodles: 1, bacon: 1, eggs: 1}}).observeChanges(logger); - var barid = c.insert({thing: "stuff", mac: 1, cheese: 2}); + var barid = await c.insert({thing: "stuff", mac: 1, cheese: 2}); logger.expectResultOnly("added", [barid, {}]); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok", mac: 1, cheese: 2}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok", mac: 1, cheese: 2}); logger.expectResultOnly("changed", [fooid, {noodles: "alright", bacon: undefined}]); // Doesn't get update event, since modifies only hidden fields - logger.expectNoResult(() => { + await logger.expectNoResult(() => c.update(fooid, { noodles: "alright", potatoes: "meh", apples: "ok", mac: 1, cheese: 2 - }); - }); + }) + ); - c.remove(fooid); + await c.remove(fooid); logger.expectResultOnly("removed", [fooid]); - c.remove(barid); + await c.remove(barid); logger.expectResultOnly("removed", [barid]); - fooid = c.insert({noodles: "good", bacon: "bad", mac: 1, cheese: 2}); + fooid = await c.insert({noodles: "good", bacon: "bad", mac: 1, cheese: 2}); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); - logger.expectNoResult(); + await logger.expectNoResult(); handle.stop(); - onComplete(); }); }); } -Tinytest.addAsync("observeChanges - unordered - specific fields + modify on excluded fields", function (test, onComplete) { +Tinytest.addAsync("observeChanges - unordered - specific fields + modify on excluded fields", async function (test, onComplete) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var handle = c.find({ mac: 1, cheese: 2 }, + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var handle = await c.find({ mac: 1, cheese: 2 }, {fields:{noodles: 1, bacon: 1, eggs: 1}}).observeChanges(logger); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok", mac: 1, cheese: 2}); logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad"}]); // Noodles go into shadow, mac appears as eggs - c.update(fooid, {$rename: { noodles: 'shadow', apples: 'eggs' }}); + await c.update(fooid, {$rename: { noodles: 'shadow', apples: 'eggs' }}); logger.expectResultOnly("changed", [fooid, {eggs:"ok", noodles: undefined}]); - c.remove(fooid); + await c.remove(fooid); logger.expectResultOnly("removed", [fooid]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); Tinytest.addAsync( "observeChanges - unordered - unset parent of observed field", - function (test, onComplete) { + async function (test) { var c = makeCollection(); - withCallbackLogger( + await withCallbackLogger( test, ['added', 'changed', 'removed'], Meteor.isServer, - function (logger) { - var handle = c.find({}, {fields: {'type.name': 1}}).observeChanges(logger); - var id = c.insert({ type: { name: 'foobar' } }); + async function (logger) { + var handle = await c.find({}, {fields: {'type.name': 1}}).observeChanges(logger); + var id = await c.insert({ type: { name: 'foobar' } }); logger.expectResultOnly('added', [id, { type: { name: 'foobar' } }]); - c.update(id, { $unset: { type: 1 } }); - test.equal(c.find().fetch(), [{ _id: id }]); + await c.update(id, { $unset: { type: 1 } }); + test.equal(await c.find().fetch(), [{ _id: id }]); logger.expectResultOnly('changed', [id, { type: undefined }]); - handle.stop(); - onComplete(); + await handle.stop(); } ); } @@ -268,34 +257,33 @@ Tinytest.addAsync( -Tinytest.addAsync("observeChanges - unordered - enters and exits result set through change", function (test, onComplete) { +Tinytest.addAsync("observeChanges - unordered - enters and exits result set through change", async function (test) { var c = makeCollection(); - withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - var handle = c.find({noodles: "good"}).observeChanges(logger); - var barid = c.insert({thing: "stuff"}); + await withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, async function (logger) { + var handle = await c.find({noodles: "good"}).observeChanges(logger); + var barid = await c.insert({thing: "stuff"}); - var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); + var fooid = await c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectResultOnly("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); - c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); + await c.update(fooid, {noodles: "alright", potatoes: "tasty", apples: "ok"}); logger.expectResultOnly("removed", [fooid]); - c.remove(fooid); - c.remove(barid); + await c.remove(fooid); + await c.remove(barid); - fooid = c.insert({noodles: "ok", bacon: "bad", apples: "ok"}); - c.update(fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}); + fooid = await c.insert({noodles: "ok", bacon: "bad", apples: "ok"}); + await c.update(fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}); logger.expectResult("added", [fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}]); - logger.expectNoResult(); - handle.stop(); - onComplete(); + await logger.expectNoResult(); + await handle.stop(); }); }); if (Meteor.isServer) { testAsyncMulti("observeChanges - tailable", [ - function (test, expect) { + async function (test, expect) { var self = this; var collName = "cap_" + Random.id(); var coll = new Mongo.Collection(collName); @@ -314,7 +302,7 @@ if (Meteor.isServer) { self.expects.push(expect()); var cursor = coll.find({y: {$ne: 7}}, {tailable: true}); - self.handle = cursor.observeChanges({ + self.handle = await cursor.observeChanges({ added: function (id, fields) { self.xs.push(fields.x); test.notEqual(self.expects.length, 0); @@ -363,11 +351,11 @@ if (Meteor.isServer) { testAsyncMulti("observeChanges - bad query", [ - function (test, expect) { + async function (test, expect) { var c = makeCollection(); var observeThrows = function () { - test.throws(function () { - c.find({__id: {$in: null}}).observeChanges({ + return test.throwsAsync(function () { + return c.find({__id: {$in: null}}).observeChanges({ added: function () { test.fail("added shouldn't be called"); } @@ -376,49 +364,31 @@ testAsyncMulti("observeChanges - bad query", [ }; if (Meteor.isClient) { - observeThrows(); + await observeThrows(); return; } // Test that if two copies of the same bad observeChanges run in parallel // and are de-duped, both observeChanges calls will throw. - var Fiber = Npm.require('fibers'); - var Future = Npm.require('fibers/future'); - var f1 = new Future; - var f2 = new Future; - Fiber(function () { - // The observeChanges call in here will yield when we talk to mongod, - // which will allow the second Fiber to start and observe a duplicate - // query. - observeThrows(); - f1['return'](); - }).run(); - Fiber(function () { - test.isFalse(f1.isResolved()); // first observe hasn't thrown yet - observeThrows(); - f2['return'](); - }).run(); - f1.wait(); - f2.wait(); + await Promise.all(['ob1', 'ob2'].map(() => observeThrows())); } ]); if (Meteor.isServer) { Tinytest.addAsync( "observeChanges - EnvironmentVariable", - function (test, onComplete) { + async function (test) { var c = makeCollection(); var environmentVariable = new Meteor.EnvironmentVariable; - environmentVariable.withValue(true, function() { - var handle = c.find({}, { fields: { 'type.name': 1 }}).observeChanges({ + await environmentVariable.withValue(true, async function() { + var handle = await c.find({}, { fields: { 'type.name': 1 }}).observeChanges({ added: function() { test.isTrue(environmentVariable.get()); handle.stop(); - onComplete(); } }); }); - c.insert({ type: { name: 'foobar' } }); + await c.insert({ type: { name: 'foobar' } }); } ); } From f5be18fb019121678981cd5f2a41975eba50a0e2 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:49:47 -0300 Subject: [PATCH 0061/1965] docs: documented bind Enviroment --- packages/meteor/dynamics_nodejs.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 2a278acd5e..fe0ca03287 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -200,6 +200,9 @@ const bindEnvironmentFibers = (func, onException, _this) => { }; }; +// This function has two reasons: +// 1. Return the function to be executed on the MeteorJS context, having it assinged in the async localstorage. +// 2. Better error handling, the error message will be more clear. const bindEnvironmentAsync = (func, onException, _this) => { var dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); var boundValues = Array.isArray(dynamics) ? dynamics.slice() : []; From 078661e8897b342de458ce9499a9c2e2ffc393d8 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:50:01 -0300 Subject: [PATCH 0062/1965] chore: added async to collection.js --- packages/mongo-async/collection.js | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 855b2f3c14..287b370949 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -745,16 +745,16 @@ Object.assign(Mongo.Collection.prototype, { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. - _ensureIndex(index, options) { + async _ensureIndex(index, options) { var self = this; if (!self._collection._ensureIndex || !self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); if (self._collection.createIndex) { - self._collection.createIndex(index, options); + await self._collection.createIndex(index, options); } else { import { Log } from 'meteor/logging'; - Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${options?.name ? `, index name: ${options.name}` : `, index: ${JSON.stringify(index)}`}`) - self._collection._ensureIndex(index, options); + Log.debug(`_ensureIndex has been deprecated, please use the new 'createIndex' instead${ options?.name ? `, index name: ${ options.name }` : `, index: ${ JSON.stringify(index) }` }`) + await self._collection._ensureIndex(index, options); } }, @@ -770,37 +770,38 @@ Object.assign(Mongo.Collection.prototype, { * @param {Boolean} options.unique Define that the index values must be unique, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-unique/) * @param {Boolean} options.sparse Define that the index is sparse, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-sparse/) */ - createIndex(index, options) { + async createIndex(index, options) { var self = this; if (!self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); + console.dir(index, options) try { - self._collection.createIndex(index, options); + await self._collection.createIndex(index, options); } catch (e) { if (e.message.includes('An equivalent index already exists with the same name but different options.') && Meteor.settings?.packages?.mongo?.reCreateIndexOnOptionMismatch) { import { Log } from 'meteor/logging'; - - Log.info(`Re-creating index ${index} for ${self._name} due to options mismatch.`); - self._collection._dropIndex(index); - self._collection.createIndex(index, options); + Log.info(`Re-creating index ${ index } for ${ self._name } due to options mismatch.`); + await self._collection._dropIndex(index); + await self._collection.createIndex(index, options); } else { - throw new Meteor.Error(`An error occurred when creating an index for collection "${self._name}: ${e.message}`); + console.error(e); + throw new Meteor.Error(`An error occurred when creating an index for collection "${ self._name }: ${ e.message }`); } } }, - _dropIndex(index) { + async _dropIndex(index) { var self = this; if (!self._collection._dropIndex) throw new Error('Can only call _dropIndex on server collections'); self._collection._dropIndex(index); }, - _dropCollection() { + async _dropCollection() { var self = this; if (!self._collection.dropCollection) throw new Error('Can only call _dropCollection on server collections'); - self._collection.dropCollection(); + await self._collection.dropCollection(); }, _createCappedCollection(byteSize, maxDocuments) { @@ -830,7 +831,7 @@ Object.assign(Mongo.Collection.prototype, { * @summary Returns the [`Db`](http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html) object corresponding to this collection's database connection from the [npm `mongodb` driver module](https://www.npmjs.com/package/mongodb) which is wrapped by `Mongo.Collection`. * @locus Server * @memberof Mongo.Collection - * @instance + * @ce */ rawDatabase() { var self = this; From 63e411176aacc462e7184c84b0645edbd73818bf Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:50:17 -0300 Subject: [PATCH 0063/1965] chore: updated mongo_driver.js to async --- packages/mongo-async/mongo_driver.js | 45 +++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index 44f399142c..ae1f477c7c 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -426,19 +426,25 @@ MongoConnection.prototype._remove = function (collection_name, selector, } }; -MongoConnection.prototype._dropCollection = function (collectionName, cb) { +MongoConnection.prototype._dropCollection = async function (collectionName, cb) { var self = this; var write = self._maybeBeginWrite(); var refresh = function () { - Meteor.refresh({collection: collectionName, id: null, - dropCollection: true}); + return Meteor.refresh({ + collection: collectionName, + id: null, + dropCollection: true + }); }; - cb = bindEnvironmentForWrite(writeCallback(write, refresh, cb)); + // TODO[FIBERS]: Check if this is correct after the DDP changes. + const fn = bindEnvironmentForWrite( + writeCallback(write, refresh, cb) + ); try { var collection = self.rawCollection(collectionName); - collection.drop(cb); + await Meteor.promisify(collection.drop)(fn); } catch (e) { write.committed(); throw e; @@ -447,17 +453,17 @@ MongoConnection.prototype._dropCollection = function (collectionName, cb) { // For testing only. Slightly better than `c.rawDatabase().dropDatabase()` // because it lets the test's fence wait for it to be complete. -MongoConnection.prototype._dropDatabase = function (cb) { +MongoConnection.prototype._dropDatabase = async function (cb) { var self = this; var write = self._maybeBeginWrite(); var refresh = function () { Meteor.refresh({ dropDatabase: true }); }; - cb = bindEnvironmentForWrite(writeCallback(write, refresh, cb)); + const fn = Meteor.bindEnvironment(writeCallback(write, refresh, cb)) try { - self.db.dropDatabase(cb); + await Meteor.promisify(self.db.dropDatabase)(fn); } catch (e) { write.committed(); throw e; @@ -838,8 +844,17 @@ MongoConnection.prototype.createIndex = async function (collectionName, index, // We expect this function to be called at startup, not from within a method, // so we don't interact with the write fence. - var collection = self.rawCollection(collectionName); - var indexName = await collection.createIndex(index, options); + var collection = self.rawCollection(collectionName) + console.dir(index) + const p = new Promise(async (resolve, reject) => { + try { + const j = await collection.createIndex(index, options) + resolve(j) + } catch (e) { + reject(e); + } + }) + var indexName = await p }; MongoConnection.prototype._ensureIndex = MongoConnection.prototype.createIndex; @@ -850,7 +865,15 @@ MongoConnection.prototype._dropIndex = async function (collectionName, index) { // This function is only used by test code, not within a method, so we don't // interact with the write fence. var collection = self.rawCollection(collectionName); - var indexName = await collection.dropIndex(index); + const p = new Promise(async (resolve, reject) => { + try { + const k = await collection.dropIndex(index) + resolve(k) + } catch (e) { + reject(e); + } + }) + var indexName = await p; }; // CURSORS From c79f71b07af6bcf0fd5697b9758a43f00d2e22dc Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:50:55 -0300 Subject: [PATCH 0064/1965] chore: commented code in package.js to focus on tests --- packages/mongo-async/package.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/mongo-async/package.js b/packages/mongo-async/package.js index f31b7efe27..e3687ef120 100644 --- a/packages/mongo-async/package.js +++ b/packages/mongo-async/package.js @@ -93,13 +93,13 @@ Package.onTest(function (api) { 'ddp', 'base64']); // XXX test order dependency: the allow_tests "partial allow" test // fails if it is run before mongo_livedata_tests. - api.addFiles('mongo_livedata_tests.js', ['client', 'server']); - api.addFiles('upsert_compatibility_test.js', 'server'); - api.addFiles('allow_tests.js', ['client', 'server']); - api.addFiles('collection_tests.js', ['client', 'server']); - api.addFiles('collection_async_tests.js', ['client', 'server']); - api.addFiles('observe_changes_tests.js', ['client', 'server']); + // api.addFiles('mongo_livedata_tests.js', ['client', 'server']); + // api.addFiles('upsert_compatibility_test.js', 'server'); + // api.addFiles('allow_tests.js', ['client', 'server']); + // api.addFiles('collection_tests.js', ['client', 'server']); + // api.addFiles('collection_async_tests.js', ['client', 'server']); + // api.addFiles('observe_changes_tests.js', ['client', 'server']); api.addFiles('oplog_tests.js', 'server'); - api.addFiles('oplog_v2_converter_tests.js', 'server'); - api.addFiles('doc_fetcher_tests.js', 'server'); + // api.addFiles('oplog_v2_converter_tests.js', 'server'); + // api.addFiles('doc_fetcher_tests.js', 'server'); }); From bf1b8a100eb2423256c5525b502a6ac27e8c900b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:52:32 -0300 Subject: [PATCH 0065/1965] chore: updated shrinkwarp of babel-compiler --- .../.npm/package/npm-shrinkwrap.json | 746 +++++++++--------- 1 file changed, 358 insertions(+), 388 deletions(-) diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index 4453e83e92..1504288778 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -2,206 +2,213 @@ "lockfileVersion": 1, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==" }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", + "integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", "dependencies": { "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" } } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==" + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", + "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + } + } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==" }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==" }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==" }, "@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz", + "integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==" }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==" }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==" }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==" }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==" - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==" }, "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==" }, "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==" }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==" }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==" }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==" }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==" }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==" }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==" }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==" }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==" }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==" }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -219,9 +226,9 @@ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" }, "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==" }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", @@ -249,139 +256,139 @@ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" }, "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==" }, "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==" }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==" }, "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==" }, "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==" }, "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==" }, "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==" }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==" }, "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==" + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==" }, "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==" }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==" }, "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==" }, "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==" + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==" }, "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==" }, "@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==" }, "@babel/plugin-transform-react-jsx": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", - "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==" }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==" }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==" }, "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==" }, "@babel/plugin-transform-runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", - "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==" }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==" }, "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==" }, "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==" }, "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==" }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==" }, "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==" }, "@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==" }, "@babel/runtime": { "version": "7.17.2", @@ -389,39 +396,49 @@ "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==" }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==" + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==" }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", + "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==" + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==" }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==" + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==" }, "@meteorjs/babel": { - "version": "7.16.0-beta.1", - "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.1.tgz", - "integrity": "sha512-PSyp2+oO2nrGMBTXd3VAP0EzHLW4bFqRIzmbTfHnr/s0dGhb7XaaGg3sOGAInewrFNCWfMHNe3hSiyOvC9bS2A==" + "version": "7.16.0-beta.7", + "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.7.tgz", + "integrity": "sha512-++ZngcRX51mu0fgijc7VYQnVlPr2tiq5p+sr1tOSQoe6gqH4toRCzVr4lzmPkrBXHl9IvSvkWcQYIGP5iSRsJw==" }, "@meteorjs/reify": { "version": "0.23.0", @@ -436,9 +453,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, "acorn": { "version": "6.4.2", @@ -463,38 +480,33 @@ "babel-helper-flip-expressions": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", - "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" + "integrity": "sha512-rSrkRW4YQ2ETCWww9gbsWk4N0x1BOtln349Tk0dlCS90oT68WMLyGR7WvaMp3eAnsVrCqdUtC19lo1avyGPejA==" }, "babel-helper-is-nodes-equiv": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", - "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" + "integrity": "sha512-ri/nsMFVRqXn7IyT5qW4/hIAGQxuYUFHa3qsxmPtbk6spZQcYlyDogfVpNm2XYOslH/ULS4VEJGUqQX5u7ACQw==" }, "babel-helper-is-void-0": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", - "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" + "integrity": "sha512-07rBV0xPRM3TM5NVJEOQEkECX3qnHDjaIbFvWYPv+T1ajpUiVLiqTfC+MmiZxY5KOL/Ec08vJdJD9kZiP9UkUg==" }, "babel-helper-mark-eval-scopes": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", - "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" + "integrity": "sha512-+d/mXPP33bhgHkdVOiPkmYoeXJ+rXRWi7OdhwpyseIqOS8CmzHQXHUp/+/Qr8baXsT0kjGpMHHofHs6C3cskdA==" }, "babel-helper-remove-or-void": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", - "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" + "integrity": "sha512-eYNceYtcGKpifHDir62gHJadVXdg9fAhuZEXiRQnJJ4Yi4oUTpqpNY//1pM4nVyjjDMPYaC2xSf0I+9IqVzwdA==" }, "babel-helper-to-multiple-sequence-expressions": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==" - }, "babel-plugin-minify-builtins": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", @@ -506,14 +518,14 @@ "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==" }, "babel-plugin-minify-dead-code-elimination": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", - "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.2.tgz", + "integrity": "sha512-krq9Lwi0QIzyAlcNBXTL4usqUvevB4BzktdEsb8srcXC1AaYqRJiAQw6vdKdJSaXbz6snBvziGr6ch/aoRCfpA==" }, "babel-plugin-minify-flip-comparisons": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", - "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=" + "integrity": "sha512-8hNwgLVeJzpeLVOVArag2DfTkbKodzOHU7+gAZ8mGBFGPQHK6uXVpg3jh5I/F6gfi5Q5usWU2OKcstn1YbAV7A==" }, "babel-plugin-minify-guarded-expressions": { "version": "0.4.4", @@ -523,17 +535,17 @@ "babel-plugin-minify-infinity": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", - "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" + "integrity": "sha512-X0ictxCk8y+NvIf+bZ1HJPbVZKMlPku3lgYxPmIp62Dp8wdtbMLSekczty3MzvUOlrk5xzWYpBpQprXUjDRyMA==" }, "babel-plugin-minify-mangle-names": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", - "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==" + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.1.tgz", + "integrity": "sha512-8KMichAOae2FHlipjNDTo2wz97MdEb2Q0jrn4NIRXzHH7SJ3c5TaNNBkeTHbk9WUsMnqpNUx949ugM9NFWewzw==" }, "babel-plugin-minify-numeric-literals": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", - "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" + "integrity": "sha512-5D54hvs9YVuCknfWywq0eaYDt7qYxlNwCqW9Ipm/kYeS9gYhJd0Rr/Pm2WhHKJ8DC6aIlDdqSBODSthabLSX3A==" }, "babel-plugin-minify-replace": { "version": "0.5.0", @@ -548,62 +560,62 @@ "babel-plugin-minify-type-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", - "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=" + "integrity": "sha512-4ADB0irJ/6BeXWHubjCJmrPbzhxDgjphBMjIjxCc25n4NGJ00NsYqwYt+F/OvE9RXx8KaSW7cJvp+iZX436tnQ==" }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==" }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==" }, "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==" }, "babel-plugin-transform-inline-consecutive-adds": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", - "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" + "integrity": "sha512-8D104wbzzI5RlxeVPYeQb9QsUyepiH1rAO5hpPpQ6NPRgQLpIVwkS/Nbx944pm4K8Z+rx7CgjPsFACz/VCBN0Q==" }, "babel-plugin-transform-member-expression-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", - "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" + "integrity": "sha512-Xq9/Rarpj+bjOZSl1nBbZYETsNEDDJSrb6Plb1sS3/36FukWFLLRysgecva5KZECjUJTrJoQqjJgtWToaflk5Q==" }, "babel-plugin-transform-merge-sibling-variables": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", - "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.5.tgz", + "integrity": "sha512-xj/KrWi6/uP+DrD844h66Qh2cZN++iugEIgH8QcIxhmZZPNP6VpOE9b4gP2FFW39xDAY43kCmYMM6U0QNKN8fw==" }, "babel-plugin-transform-minify-booleans": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", - "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" + "integrity": "sha512-9pW9ePng6DZpzGPalcrULuhSCcauGAbn8AeU3bE34HcDkGm8Ldt0ysjGkyb64f0K3T5ilV4mriayOVv5fg0ASA==" }, "babel-plugin-transform-property-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", - "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=" + "integrity": "sha512-Pf8JHTjTPxecqVyL6KSwD/hxGpoTZjiEgV7nCx0KFQsJYM0nuuoCajbg09KRmZWeZbJ5NGTySABYv8b/hY1eEA==" }, "babel-plugin-transform-regexp-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", - "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" + "integrity": "sha512-JjymDyEyRNhAoNFp09y/xGwYVYzT2nWTGrBrWaL6eCg2m+B24qH2jR0AA8V8GzKJTgC8NW6joJmc6nabvWBD/g==" }, "babel-plugin-transform-remove-console": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", - "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" + "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==" }, "babel-plugin-transform-remove-debugger": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", - "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" + "integrity": "sha512-Kd+eTBYlXfwoFzisburVwrngsrz4xh9I0ppoJnU/qlLysxVBRgI4Pj+dk3X8F5tDiehp3hhP8oarRMT9v2Z3lw==" }, "babel-plugin-transform-remove-undefined": { "version": "0.5.0", @@ -613,12 +625,12 @@ "babel-plugin-transform-simplify-comparison-operators": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", - "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" + "integrity": "sha512-GLInxhGAQWJ9YIdjwF6dAFlmh4U+kN8pL6Big7nkDzHoZcaDQOtBm28atEhQJq6m9GpAovbiGEShKqXv4BSp0A==" }, "babel-plugin-transform-undefined-to-void": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", - "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" + "integrity": "sha512-D2UbwxawEY1xVc9svYAUZQM2xarwSNXue2qDIx6CeV2EuMGaes/0su78zlIDIAgE7BvnMw4UpmSo9fDy+znghg==" }, "babel-preset-meteor": { "version": "7.10.0", @@ -626,24 +638,19 @@ "integrity": "sha512-bcdNfRCQAjTV42cUcmaG5/ltLZZQLpZajUcP+o0Lr+aLTY/XLNkGfASM5383wdXiAkEFl0sDOXeknnLlQtrmdg==" }, "babel-preset-minify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", - "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.2.tgz", + "integrity": "sha512-v4GL+kk0TfovbRIKZnC3HPbu2cAGmPAby7BsOmuPdMJfHV+4FVdsGXTH/OOGQRKYdjemBuL1+MsE6mobobhe9w==" }, "browserslist": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.2.tgz", - "integrity": "sha512-97XU1CTZ5TwU9Qy/Taj+RtiI6SQM1WIhZ9osT7EY0oO2aWXGABZT2OZeRL+6PfaQsiiMIjjwIoYFPq4APgspgQ==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" }, "caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==" + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" }, "chalk": { "version": "2.4.2", @@ -658,39 +665,27 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" }, "electron-to-chromium": { - "version": "1.4.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", - "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==" + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "escalade": { "version": "3.1.1", @@ -700,7 +695,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "estree-walker": { "version": "2.0.2", @@ -722,11 +717,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -740,17 +730,12 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==" }, "is-reference": { "version": "1.2.1", @@ -780,22 +765,22 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==" + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" }, "meteor-babel-helpers": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", - "integrity": "sha1-8uXZ+HlvvS6JAQI9dpnlsgLqn7A=" + "integrity": "sha512-PgfmiyT/HiBaxwGHxS4t3Qi0fpmEW3O0WW2VfrgekiMGz3aZPd9/4PRIaMMZsfyjQ1vyEm6dZqTAFZENbuoTxw==" }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "ms": { "version": "2.1.2", @@ -803,19 +788,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "path-parse": { "version": "1.0.7", @@ -838,62 +813,52 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==" }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==" + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==" }, "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==" + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==" }, "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" }, "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "dependencies": { "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" } } }, "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -912,12 +877,12 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" }, "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -930,14 +895,19 @@ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" }, "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" }, "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==" } } } From 1f2a9c555d56cf0341a15f24e89ac2b1032bff93 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:52:50 -0300 Subject: [PATCH 0066/1965] tests: updated to async in calling native find --- packages/mongo-async/collection_tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mongo-async/collection_tests.js b/packages/mongo-async/collection_tests.js index c5d88db645..a6a1d79979 100644 --- a/packages/mongo-async/collection_tests.js +++ b/packages/mongo-async/collection_tests.js @@ -147,8 +147,8 @@ Tinytest.addAsync('collection - calling native find with good hint and maxTimeMs Promise.resolve( Meteor.isServer && collection.rawCollection().createIndex({ a: 1 }) - ).then(() => { - test.equal(collection.find({}, { + ).then(async () => { + test.equal(await collection.find({}, { hint: {a: 1}, maxTimeMs: 1000 }).count(), 1); From ee4e8d20209488425ad28fd0362d4674909b6f0b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 14:53:27 -0300 Subject: [PATCH 0067/1965] wip-test: trying to make oplog - entry skipping pass --- packages/mongo-async/oplog_tests.js | 271 ++++++++++++++-------------- 1 file changed, 139 insertions(+), 132 deletions(-) diff --git a/packages/mongo-async/oplog_tests.js b/packages/mongo-async/oplog_tests.js index e6fead6ba6..e7e3f03de3 100644 --- a/packages/mongo-async/oplog_tests.js +++ b/packages/mongo-async/oplog_tests.js @@ -2,11 +2,14 @@ var OplogCollection = new Mongo.Collection("oplog-" + Random.id()); Tinytest.addAsync("mongo-livedata - oplog - cursorSupported", async function (test) { var oplogEnabled = - !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; + !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle; var supported = async function (expected, selector, options) { var cursor = OplogCollection.find(selector, options); - var handle = await cursor.observeChanges({added: function () {}}); + var handle = await cursor.observeChanges({ + added: function () { + } + }); // If there's no oplog at all, we shouldn't ever use it. if (!oplogEnabled) expected = false; @@ -18,152 +21,156 @@ Tinytest.addAsync("mongo-livedata - oplog - cursorSupported", async function (te await supported(true, 1234); await supported(true, new Mongo.ObjectID()); - await supported(true, {_id: "asdf"}); - await supported(true, {_id: 1234}); - await supported(true, {_id: new Mongo.ObjectID()}); + await supported(true, { _id: "asdf" }); + await supported(true, { _id: 1234 }); + await supported(true, { _id: new Mongo.ObjectID() }); - await supported(true, {foo: "asdf", - bar: 1234, - baz: new Mongo.ObjectID(), - eeney: true, - miney: false, - moe: null}); + await supported(true, { + foo: "asdf", + bar: 1234, + baz: new Mongo.ObjectID(), + eeney: true, + miney: false, + moe: null + }); await supported(true, {}); - await supported(true, {$and: [{foo: "asdf"}, {bar: "baz"}]}); - await supported(true, {foo: {x: 1}}); - await supported(true, {foo: {$gt: 1}}); - await supported(true, {foo: [1, 2, 3]}); + await supported(true, { $and: [{ foo: "asdf" }, { bar: "baz" }] }); + await supported(true, { foo: { x: 1 } }); + await supported(true, { foo: { $gt: 1 } }); + await supported(true, { foo: [1, 2, 3] }); // No $where. - await supported(false, {$where: "xxx"}); - await supported(false, {$and: [{foo: "adsf"}, {$where: "xxx"}]}); + await supported(false, { $where: "xxx" }); + await supported(false, { $and: [{ foo: "adsf" }, { $where: "xxx" }] }); // No geoqueries. - await supported(false, {x: {$near: [1,1]}}); + await supported(false, { x: { $near: [1, 1] } }); // Nothing Minimongo doesn't understand. (Minimongo happens to fail to // implement $elemMatch inside $all which MongoDB supports.) - await supported(false, {x: {$all: [{$elemMatch: {y: 2}}]}}); + await supported(false, { x: { $all: [{ $elemMatch: { y: 2 } }] } }); - await supported(true, {}, { sort: {x:1} }); - await supported(true, {}, { sort: {x:1}, limit: 5 }); - await supported(false, {}, { sort: {$natural:1}, limit: 5 }); + await supported(true, {}, { sort: { x: 1 } }); + await supported(true, {}, { sort: { x: 1 }, limit: 5 }); + await supported(false, {}, { sort: { $natural: 1 }, limit: 5 }); await supported(false, {}, { limit: 5 }); await supported(false, {}, { skip: 2, limit: 5 }); await supported(false, {}, { skip: 2 }); }); // TODO -> Index here. -// process.env.MONGO_OPLOG_URL && testAsyncMulti( -// "mongo-livedata - oplog - entry skipping", [ -// function (test, expect) { -// var self = this; -// self.collectionName = Random.id(); -// self.collection = new Mongo.Collection(self.collectionName); -// self.collection.createIndex({species: 1}); -// -// // Fill collection with lots of irrelevant objects (red cats) and some -// // relevant ones (blue dogs). -// -// // After updating to mongo 3.2 with the 2.1.18 driver it was no longer -// // possible to make this test fail with TOO_FAR_BEHIND = 2000. -// // The documents waiting to be processed would hardly go beyond 1000 -// // using mongo 3.2 with WiredTiger -// MongoInternals.defaultRemoteCollectionDriver() -// .mongo._oplogHandle._defineTooFarBehind(500); -// -// self.IRRELEVANT_SIZE = 15000; -// self.RELEVANT_SIZE = 10; -// var docs = []; -// var i; -// for (i = 0; i < self.IRRELEVANT_SIZE; ++i) { -// docs.push({ -// name: "cat " + i, -// species: 'cat', -// color: 'red' -// }); -// } -// for (i = 0; i < self.RELEVANT_SIZE; ++i) { -// docs.push({ -// name: "dog " + i, -// species: 'dog', -// color: 'blue' -// }); -// } -// // XXX implement bulk insert #1255 -// var rawCollection = self.collection.rawCollection(); -// rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { -// test.isFalse(err); -// }))); -// }, -// -// function (test, expect) { -// var self = this; -// -// test.equal(self.collection.find().count(), -// self.IRRELEVANT_SIZE + self.RELEVANT_SIZE); -// -// var blueDog5Id = null; -// var gotSpot = false; -// -// // Watch for blue dogs. -// const gotSpotPromise = new Promise(resolve => { -// self.subHandle = self.collection.find({ -// species: 'dog', -// color: 'blue', -// }).observeChanges({ -// added(id, fields) { -// if (fields.name === 'dog 5') { -// blueDog5Id = id; -// } -// }, -// changed(id, fields) { -// if (EJSON.equals(id, blueDog5Id) && -// fields.name === 'spot') { -// gotSpot = true; -// resolve(); -// } -// }, -// }); -// }); -// -// test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); -// test.isTrue(blueDog5Id); -// test.isFalse(gotSpot); -// -// self.skipped = false; -// self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() -// .mongo._oplogHandle.onSkippedEntries(function () { -// self.skipped = true; -// }); -// -// // Dye all the cats blue. This adds lots of oplog mentries that look like -// // they might in theory be relevant (since they say "something you didn't -// // know about is now blue", and who knows, maybe it's a dog) which puts -// // the OplogObserveDriver into FETCHING mode, which performs poorly. -// self.collection.update({species: 'cat'}, -// {$set: {color: 'blue'}}, -// {multi: true}); -// self.collection.update(blueDog5Id, {$set: {name: 'spot'}}); -// -// // We ought to see the spot change soon! -// return gotSpotPromise; -// }, -// -// function (test, expect) { -// var self = this; -// test.isTrue(self.skipped); -// -// //This gets the TOO_FAR_BEHIND back to its initial value -// MongoInternals.defaultRemoteCollectionDriver() -// .mongo._oplogHandle._resetTooFarBehind(); -// -// self.skipHandle.stop(); -// self.subHandle.stop(); -// self.collection.remove({}); -// } -// ] -// ); +process.env.MONGO_OPLOG_URL && testAsyncMulti( + "mongo-livedata - oplog - entry skipping", [ + async function (test, expect) { + var self = this; + self.collectionName = Random.id(); + self.collection = new Mongo.Collection(self.collectionName); + await self.collection.createIndex({ species: 1 }); + + // Fill collection with lots of irrelevant objects (red cats) and some + // relevant ones (blue dogs). + + // After updating to mongo 3.2 with the 2.1.18 driver it was no longer + // possible to make this test fail with TOO_FAR_BEHIND = 2000. + // The documents waiting to be processed would hardly go beyond 1000 + // using mongo 3.2 with WiredTiger + MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle._defineTooFarBehind(500); + + self.IRRELEVANT_SIZE = 15000; + self.RELEVANT_SIZE = 10; + var docs = []; + var i; + for (i = 0; i < self.IRRELEVANT_SIZE; ++i) { + docs.push({ + name: "cat " + i, + species: 'cat', + color: 'red' + }); + } + for (i = 0; i < self.RELEVANT_SIZE; ++i) { + docs.push({ + name: "dog " + i, + species: 'dog', + color: 'blue' + }); + } + // XXX implement bulk insert #1255 + var rawCollection = self.collection.rawCollection(); + rawCollection.insertMany(docs, Meteor.bindEnvironment(expect(function (err) { + test.isFalse(err); + }))); + }, + + async function (test, expect) { + var self = this; + + test.equal((await self.collection.find().count()), + self.IRRELEVANT_SIZE + self.RELEVANT_SIZE); + + var blueDog5Id = null; + var gotSpot = false; + let resolver; const gotSpotPromise = new Promise(resolve => resolver = resolve) + let resolver2; const gotSpotPromise2 = new Promise(resolve => resolver = resolve) + self.subHandle = await self.collection.find({ + species: 'dog', + color: 'blue', + }).observeChanges({ + added(id, fields) { + console.log(id, fields) + if (fields.name === 'dog 5') { + blueDog5Id = true + resolver2() + } + }, + changed(id, fields) { + if (EJSON.equals(id, blueDog5Id) && + fields.name === 'spot') { + gotSpot = true; + resolver(); + } + }, + }); + console.log(self.subHandle._multiplexer._observeDriver._usesOplog); + console.log(blueDog5Id); + console.log(gotSpot); + test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); + test.isTrue(blueDog5Id); + test.isFalse(gotSpot); + + self.skipped = false; + self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle.onSkippedEntries(function () { + self.skipped = true; + }); + + // Dye all the cats blue. This adds lots of oplog mentries that look like + // they might in theory be relevant (since they say "something you didn't + // know about is now blue", and who knows, maybe it's a dog) which puts + // the OplogObserveDriver into FETCHING mode, which performs poorly. + await self.collection.update({ species: 'cat' }, + { $set: { color: 'blue' } }, + { multi: true }); + await self.collection.update(blueDog5Id, { $set: { name: 'spot' } }); + + // We ought to see the spot change soon! + return await Promise.all[gotSpotPromise, gotSpotPromise2]; + }, + + async function (test, expect) { + var self = this; + test.isTrue(self.skipped); + + //This gets the TOO_FAR_BEHIND back to its initial value + MongoInternals.defaultRemoteCollectionDriver() + .mongo._oplogHandle._resetTooFarBehind(); + + await self.skipHandle.stop(); + await self.subHandle.stop(); + await self.collection.remove({}); + } + ] +); Meteor.isServer && Tinytest.addAsync( From 580a4c588d2dacafb0b7a43751d085cd9c1e9bf7 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:40:43 -0300 Subject: [PATCH 0068/1965] chore: removed console.log in colletction.js --- packages/mongo-async/collection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 287b370949..9b4b1fb045 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -774,7 +774,6 @@ Object.assign(Mongo.Collection.prototype, { var self = this; if (!self._collection.createIndex) throw new Error('Can only call createIndex on server collections'); - console.dir(index, options) try { await self._collection.createIndex(index, options); } catch (e) { From 4ad4db28dec8c335722669d60094be6098b1a95d Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:41:02 -0300 Subject: [PATCH 0069/1965] tests: adjusted tests for oplog --- packages/mongo-async/oplog_tests.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/mongo-async/oplog_tests.js b/packages/mongo-async/oplog_tests.js index e7e3f03de3..d3aa5792e5 100644 --- a/packages/mongo-async/oplog_tests.js +++ b/packages/mongo-async/oplog_tests.js @@ -111,7 +111,7 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( var blueDog5Id = null; var gotSpot = false; let resolver; const gotSpotPromise = new Promise(resolve => resolver = resolve) - let resolver2; const gotSpotPromise2 = new Promise(resolve => resolver = resolve) + let resolver2; const gotSpotPromise2 = new Promise(resolve => resolver2 = resolve) self.subHandle = await self.collection.find({ species: 'dog', color: 'blue', @@ -119,7 +119,7 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( added(id, fields) { console.log(id, fields) if (fields.name === 'dog 5') { - blueDog5Id = true + blueDog5Id = id resolver2() } }, @@ -131,13 +131,7 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( } }, }); - console.log(self.subHandle._multiplexer._observeDriver._usesOplog); - console.log(blueDog5Id); - console.log(gotSpot); test.isTrue(self.subHandle._multiplexer._observeDriver._usesOplog); - test.isTrue(blueDog5Id); - test.isFalse(gotSpot); - self.skipped = false; self.skipHandle = MongoInternals.defaultRemoteCollectionDriver() .mongo._oplogHandle.onSkippedEntries(function () { @@ -151,10 +145,13 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( await self.collection.update({ species: 'cat' }, { $set: { color: 'blue' } }, { multi: true }); + test.isTrue(blueDog5Id); + test.isFalse(gotSpot); await self.collection.update(blueDog5Id, { $set: { name: 'spot' } }); + // We ought to see the spot change soon! - return await Promise.all[gotSpotPromise, gotSpotPromise2]; + return Promise.all([gotSpotPromise, gotSpotPromise2]); }, async function (test, expect) { From be98140822a93e1e425ac62aaa8ab79b58d64b3e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:50:56 -0300 Subject: [PATCH 0070/1965] tests: removed console.log from oplog_tests --- packages/mongo-async/oplog_tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mongo-async/oplog_tests.js b/packages/mongo-async/oplog_tests.js index d3aa5792e5..09bdbf35f6 100644 --- a/packages/mongo-async/oplog_tests.js +++ b/packages/mongo-async/oplog_tests.js @@ -117,7 +117,6 @@ process.env.MONGO_OPLOG_URL && testAsyncMulti( color: 'blue', }).observeChanges({ added(id, fields) { - console.log(id, fields) if (fields.name === 'dog 5') { blueDog5Id = id resolver2() From b6b551c72b2575ac2b59a4b433ea9457b44b810e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:51:59 -0300 Subject: [PATCH 0071/1965] chore: renamed variables for promise await --- packages/mongo-async/mongo_driver.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index ae1f477c7c..14977cfa1e 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -848,8 +848,8 @@ MongoConnection.prototype.createIndex = async function (collectionName, index, console.dir(index) const p = new Promise(async (resolve, reject) => { try { - const j = await collection.createIndex(index, options) - resolve(j) + const i = await collection.createIndex(index, options) + resolve(i) } catch (e) { reject(e); } @@ -867,8 +867,8 @@ MongoConnection.prototype._dropIndex = async function (collectionName, index) { var collection = self.rawCollection(collectionName); const p = new Promise(async (resolve, reject) => { try { - const k = await collection.dropIndex(index) - resolve(k) + const i = await collection.dropIndex(index) + resolve(i) } catch (e) { reject(e); } From 513b3b3a5054c3bc5e4af2e3d37cf1a8766c62fc Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:52:21 -0300 Subject: [PATCH 0072/1965] chore:removed console.log in mongo_driver --- packages/mongo-async/mongo_driver.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index 14977cfa1e..aead47ab90 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -845,7 +845,6 @@ MongoConnection.prototype.createIndex = async function (collectionName, index, // We expect this function to be called at startup, not from within a method, // so we don't interact with the write fence. var collection = self.rawCollection(collectionName) - console.dir(index) const p = new Promise(async (resolve, reject) => { try { const i = await collection.createIndex(index, options) From d8e8014fc0ae3939cf5723f997a2f9f1acf08c24 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 22 Nov 2022 17:53:18 -0300 Subject: [PATCH 0073/1965] tests: all tests are on again :rocket: --- packages/mongo-async/package.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/mongo-async/package.js b/packages/mongo-async/package.js index e3687ef120..f31b7efe27 100644 --- a/packages/mongo-async/package.js +++ b/packages/mongo-async/package.js @@ -93,13 +93,13 @@ Package.onTest(function (api) { 'ddp', 'base64']); // XXX test order dependency: the allow_tests "partial allow" test // fails if it is run before mongo_livedata_tests. - // api.addFiles('mongo_livedata_tests.js', ['client', 'server']); - // api.addFiles('upsert_compatibility_test.js', 'server'); - // api.addFiles('allow_tests.js', ['client', 'server']); - // api.addFiles('collection_tests.js', ['client', 'server']); - // api.addFiles('collection_async_tests.js', ['client', 'server']); - // api.addFiles('observe_changes_tests.js', ['client', 'server']); + api.addFiles('mongo_livedata_tests.js', ['client', 'server']); + api.addFiles('upsert_compatibility_test.js', 'server'); + api.addFiles('allow_tests.js', ['client', 'server']); + api.addFiles('collection_tests.js', ['client', 'server']); + api.addFiles('collection_async_tests.js', ['client', 'server']); + api.addFiles('observe_changes_tests.js', ['client', 'server']); api.addFiles('oplog_tests.js', 'server'); - // api.addFiles('oplog_v2_converter_tests.js', 'server'); - // api.addFiles('doc_fetcher_tests.js', 'server'); + api.addFiles('oplog_v2_converter_tests.js', 'server'); + api.addFiles('doc_fetcher_tests.js', 'server'); }); From d7b58fdd617cf190115dc4983d8684ba2421d143 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 23 Nov 2022 10:32:14 -0300 Subject: [PATCH 0074/1965] fix: typo --- packages/mongo-async/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 9b4b1fb045..b4878e725f 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -830,7 +830,7 @@ Object.assign(Mongo.Collection.prototype, { * @summary Returns the [`Db`](http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html) object corresponding to this collection's database connection from the [npm `mongodb` driver module](https://www.npmjs.com/package/mongodb) which is wrapped by `Mongo.Collection`. * @locus Server * @memberof Mongo.Collection - * @ce + * @instance */ rawDatabase() { var self = this; From 2238df20acdff05251b6e99df87abdc8a7d3c6b0 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 23 Nov 2022 20:02:23 -0400 Subject: [PATCH 0075/1965] Running dpp-sever tests without Fibers --- .../common/livedata_connection.js | 41 ++++++++----- .../livedata_server_async_tests.js | 10 +++- .../ddp-server-async/livedata_server_tests.js | 60 +++++++++++-------- packages/meteor/dynamics_nodejs.js | 9 +++ 4 files changed, 79 insertions(+), 41 deletions(-) diff --git a/packages/ddp-client-async/common/livedata_connection.js b/packages/ddp-client-async/common/livedata_connection.js index c30ff6f48d..1855a0e0f5 100644 --- a/packages/ddp-client-async/common/livedata_connection.js +++ b/packages/ddp-client-async/common/livedata_connection.js @@ -606,14 +606,12 @@ export class Connection { DDP._CurrentMethodInvocation._set(); DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); return new Promise((resolve, reject) => { - this.applyAsync(name, args, { isFromCallAsync: true }, (err, result) => { - DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); - if (err) { - reject(err); - return; - } - resolve(result); - }); + this.applyAsync(name, args, { isFromCallAsync: true }) + .then(result => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + resolve(result); + }) + .catch(reject); }); } @@ -669,9 +667,8 @@ export class Connection { * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. - * @param {Function} [asyncCallback] Optional callback. */ - async applyAsync(name, args, options, callback) { + async applyAsync(name, args, options) { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); if (stubOptions.hasStub) { if ( @@ -703,7 +700,7 @@ export class Connection { stubOptions.exception = e; } } - return this._apply(name, stubOptions, args, options, callback); + return this._apply(name, stubOptions, args, options, null); } _apply(name, stubCallValue, args, options, callback) { @@ -803,8 +800,24 @@ export class Connection { } else { // On the server, make the function synchronous. Throw on // errors, return on success. - future = new Future(); - callback = future.resolver(); + // TODO fibers: before this was a future, now it's a promise. + // Do more tests around this. + + if (!options.isFromCallAsync) { + throw new Error("Can't create a future for Meteor.call()"); + } + + future = new Promise((resolve, reject) => { + callback = (...allArgs) => { + let args = Array.from(allArgs); + let err = args.shift(); + if (err) { + reject(err); + return; + } + resolve(...args); + } + }); } } @@ -849,7 +862,7 @@ export class Connection { // If we're using the default callback on the server, // block waiting for the result. if (future) { - return future.wait(); + return future; } return options.returnStubValue ? stubReturnValue : undefined; } diff --git a/packages/ddp-server-async/livedata_server_async_tests.js b/packages/ddp-server-async/livedata_server_async_tests.js index d145aeee92..ca00cc338b 100644 --- a/packages/ddp-server-async/livedata_server_async_tests.js +++ b/packages/ddp-server-async/livedata_server_async_tests.js @@ -23,6 +23,8 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); +// console.log('methodInvocation', methodInvocation); +// console.log('publicationInvocation', !!publicationInvocation); // Check the publish function's environment variables and context. if (callback) { callback.call(this, methodInvocation, publicationInvocation); @@ -33,6 +35,12 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + // console.log('onStopMethodInvocation', onStopMethodInvocation); + + + console.log('onStopPublicationInvocation', !!onStopPublicationInvocation, this.userId); + + callback.call( this, onStopMethodInvocation, @@ -45,7 +53,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.stop(); } else { this.ready(); - Meteor.call('livedata_server_test_setuserid', userId); + await Meteor.callAsync('livedata_server_test_setuserid', userId); } }); diff --git a/packages/ddp-server-async/livedata_server_tests.js b/packages/ddp-server-async/livedata_server_tests.js index cde56b6196..0e7c970b12 100644 --- a/packages/ddp-server-async/livedata_server_tests.js +++ b/packages/ddp-server-async/livedata_server_tests.js @@ -87,8 +87,8 @@ Meteor.methods({ return this.connection && this.connection.id; }, - livedata_server_test_outer: function () { - return Meteor.call('livedata_server_test_inner'); + livedata_server_test_outer: async function () { + return await Meteor.callAsync('livedata_server_test_inner'); }, livedata_server_test_setuserid: function (userId) { @@ -108,12 +108,17 @@ Tinytest.addAsync( }); makeTestConnection( - test, - function (clientConn, serverConn) { - clientConn.call('livedata_server_test_inner'); - clientConn.disconnect(); - }, - onComplete + test, + function(clientConn, serverConn) { + clientConn + .callAsync('livedata_server_test_inner') + .then(() => clientConn.disconnect()) + .catch(e => { + onComplete(); + throw new Meteor.Error(e); + }); + }, + onComplete ); } ); @@ -125,10 +130,11 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - var res = clientConn.call('livedata_server_test_inner'); - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); + clientConn.callAsync('livedata_server_test_inner').then(res => { + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }); }, onComplete ); @@ -142,10 +148,11 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - var res = clientConn.call('livedata_server_test_outer'); - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); + clientConn.callAsync('livedata_server_test_outer').then(res => { + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }); }, onComplete ); @@ -163,10 +170,10 @@ Meteor.publish("livedata_server_test_sub", function (connectionId) { this.stop(); }); -Meteor.publish("livedata_server_test_sub_method", function (connectionId) { +Meteor.publish("livedata_server_test_sub_method", async function (connectionId) { var callback = onSubscription[connectionId]; if (callback) { - var id = Meteor.call('livedata_server_test_inner'); + var id = await Meteor.callAsync('livedata_server_test_inner'); callback(id); } this.stop(); @@ -311,13 +318,13 @@ Tinytest.addAsync( ); Meteor.methods({ - testResolvedPromise(arg) { - const invocation1 = DDP._CurrentMethodInvocation.get(); + async testResolvedPromise(arg) { + const invocationRunningFromCallAsync1 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); return Promise.resolve(arg).then(result => { - const invocation2 = DDP._CurrentMethodInvocation.get(); - // This equality holds because Promise callbacks are bound to the - // dynamic environment where .then was called. - if (invocation1 !== invocation2) { + const invocationRunningFromCallAsync2 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); + // What matters here is that both invocations are coming from the same call, + // so both of them can be considered a simulation. + if (invocationRunningFromCallAsync1 !== invocationRunningFromCallAsync2) { throw new Meteor.Error("invocation mismatch"); } return result + " after waiting"; @@ -344,9 +351,10 @@ Meteor.methods({ Tinytest.addAsync( "livedata server - waiting for Promise", - (test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => { + (test, onComplete) => makeTestConnection(test, async (clientConn, serverConn) => { + const testResolvedPromiseResult = await clientConn.callAsync("testResolvedPromise", "clientConn.call"); test.equal( - clientConn.call("testResolvedPromise", "clientConn.call"), + testResolvedPromiseResult, "clientConn.call after waiting" ); diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 2a278acd5e..0d8ba74727 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -3,6 +3,7 @@ var Fiber = Meteor._isFibersEnabled && Npm.require('fibers'); let nextSlot = 0; +let callAsyncMethodRunning = false; Meteor._nodeCodeMustBeInFiber = function () { if (!Fiber.current) { @@ -111,6 +112,14 @@ class EnvironmentVariableAsync { this._set(value); return saved; } + + _isCallAsyncMethodRunning() { + return callAsyncMethodRunning; + } + + _setCallAsyncMethodRunning(value) { + callAsyncMethodRunning = value; + } } /** From 5ccfdd2fc9ec118eee015fe380982921adcb64f3 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Fri, 25 Nov 2022 07:52:09 -0300 Subject: [PATCH 0076/1965] fix: removed unecessary promise --- packages/mongo-async/mongo_driver.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/mongo-async/mongo_driver.js b/packages/mongo-async/mongo_driver.js index aead47ab90..0de93d0c5f 100644 --- a/packages/mongo-async/mongo_driver.js +++ b/packages/mongo-async/mongo_driver.js @@ -845,15 +845,7 @@ MongoConnection.prototype.createIndex = async function (collectionName, index, // We expect this function to be called at startup, not from within a method, // so we don't interact with the write fence. var collection = self.rawCollection(collectionName) - const p = new Promise(async (resolve, reject) => { - try { - const i = await collection.createIndex(index, options) - resolve(i) - } catch (e) { - reject(e); - } - }) - var indexName = await p + var indexName = await collection.createIndex(index, options) }; MongoConnection.prototype._ensureIndex = MongoConnection.prototype.createIndex; @@ -864,15 +856,7 @@ MongoConnection.prototype._dropIndex = async function (collectionName, index) { // This function is only used by test code, not within a method, so we don't // interact with the write fence. var collection = self.rawCollection(collectionName); - const p = new Promise(async (resolve, reject) => { - try { - const i = await collection.dropIndex(index) - resolve(i) - } catch (e) { - reject(e); - } - }) - var indexName = await p; + var indexName = await collection.dropIndex(index) }; // CURSORS From bd457fc724afd87b335038b8ffdf50a1fef45470 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Fri, 25 Nov 2022 07:52:23 -0300 Subject: [PATCH 0077/1965] tests: removed todo comment --- packages/mongo-async/oplog_tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mongo-async/oplog_tests.js b/packages/mongo-async/oplog_tests.js index 09bdbf35f6..8861d9cf3f 100644 --- a/packages/mongo-async/oplog_tests.js +++ b/packages/mongo-async/oplog_tests.js @@ -58,7 +58,6 @@ Tinytest.addAsync("mongo-livedata - oplog - cursorSupported", async function (te await supported(false, {}, { skip: 2 }); }); -// TODO -> Index here. process.env.MONGO_OPLOG_URL && testAsyncMulti( "mongo-livedata - oplog - entry skipping", [ async function (test, expect) { From 3939f77188bca61f08d00bf40c337ca36a19790c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Fri, 25 Nov 2022 11:32:29 -0300 Subject: [PATCH 0078/1965] docs: added a depracieation note --- packages/mongo-async/collection.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index b4878e725f..874e43821b 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -745,6 +745,19 @@ Object.assign(Mongo.Collection.prototype, { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. + /** + * @summary Creates the specified index on the collection. + * @locus server + * @deprecated in 3.0 + * @method createIndex + * @memberof Mongo.Collection + * @instance + * @param {Object} index A document that contains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of `1`; for descending index, specify a value of `-1`. Use `text` for text indexes. + * @param {Object} [options] All options are listed in [MongoDB documentation](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options) + * @param {String} options.name Name of the index + * @param {Boolean} options.unique Define that the index values must be unique, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-unique/) + * @param {Boolean} options.sparse Define that the index is sparse, more at [MongoDB documentation](https://docs.mongodb.com/manual/core/index-sparse/) + */ async _ensureIndex(index, options) { var self = this; if (!self._collection._ensureIndex || !self._collection.createIndex) From 63d34b38a37ce114c1e57f05cbf6aff8e99fe063 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Fri, 25 Nov 2022 11:33:48 -0300 Subject: [PATCH 0079/1965] fix: removed @ method sign --- packages/mongo-async/collection.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 874e43821b..48ae036433 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -749,7 +749,6 @@ Object.assign(Mongo.Collection.prototype, { * @summary Creates the specified index on the collection. * @locus server * @deprecated in 3.0 - * @method createIndex * @memberof Mongo.Collection * @instance * @param {Object} index A document that contains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of `1`; for descending index, specify a value of `-1`. Use `text` for text indexes. From 022a7e93036b3dc123d2e63cb8bd9b997a02582d Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 25 Nov 2022 20:15:37 -0300 Subject: [PATCH 0080/1965] Remove Fibers for meteor tools: - Changing package/compile steps to use async/await. --- packages/meteor/dynamics_nodejs.js | 3 +- tools/cli/commands.js | 19 ++- tools/cli/default-npm-deps.js | 6 +- tools/cli/main.js | 68 ++++---- tools/console/console.js | 15 +- tools/console/progress.ts | 2 +- tools/fs/files.ts | 19 +-- tools/isobuild/build-plugin.js | 12 +- tools/isobuild/builder.js | 16 +- tools/isobuild/bundler.js | 124 +++++++------- tools/isobuild/compiler-plugin.js | 101 ++++++----- tools/isobuild/compiler.js | 194 +++++++++++----------- tools/isobuild/isopack-cache.js | 99 +++++------ tools/isobuild/isopack.js | 73 ++++---- tools/isobuild/meteor-npm.js | 61 ++++--- tools/packaging/catalog/catalog-local.js | 28 ++-- tools/packaging/catalog/catalog-remote.js | 76 ++++----- tools/packaging/catalog/catalog.js | 16 +- tools/packaging/package-map.js | 9 +- tools/project-context.js | 48 +++--- tools/tool-env/isopackets.js | 56 +++---- tools/tool-env/profile.ts | 6 +- tools/utils/archinfo.ts | 12 +- tools/utils/buildmessage.js | 23 +-- tools/utils/fiber-helpers.js | 96 +++++------ tools/utils/utils.js | 34 ++-- 26 files changed, 625 insertions(+), 591 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 2a278acd5e..a39ce7ffb0 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -239,6 +239,7 @@ const bindEnvironmentAsync = (func, onException, _this) => { if (Meteor._getAslStore()) { return runWithEnvironment(); } - global.asyncLocalStorage.run({}, runWithEnvironment); + + return global.asyncLocalStorage.run({}, runWithEnvironment); }; }; diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 4a726cb3f3..832239b24f 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -541,7 +541,7 @@ main.registerCommand({ solid: { type: Boolean }, }, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { +}, async function (options) { // Creating a package is much easier than creating an app, so if that's what // we are doing, do that first. (For example, we don't springboard to the // latest release to create a package if we are inside an app) @@ -551,12 +551,12 @@ main.registerCommand({ if (options.list || options.example) { Console.error("No package examples exist at this time."); Console.error(); - throw new main.ShowUsage; + throw new main.ShowUsage(); } if (!packageName) { Console.error("Please specify the name of the package."); - throw new main.ShowUsage; + throw new main.ShowUsage(); } utils.validatePackageNameOrExit( @@ -666,7 +666,7 @@ main.registerCommand({ // at the end.) if (! release.current.isCheckout() && !release.forced) { if (release.current.name !== release.latestKnown()) { - throw new main.SpringboardToLatestRelease; + throw new main.SpringboardToLatestRelease(); } } @@ -712,7 +712,7 @@ main.registerCommand({ if (options.args.length === 1) { appPathAsEntered = options.args[0]; } else { - throw new main.ShowUsage; + throw new main.ShowUsage(); } var appPath = files.pathResolve(appPathAsEntered); @@ -811,13 +811,13 @@ main.registerCommand({ allowIncompatibleUpdate: true }); - main.captureAndExit("=> Errors while creating your project", function () { + await main.captureAndExit("=> Errors while creating your project", async function () { projectContext.readProjectMetadata(); if (buildmessage.jobHasMessages()) { return; } - projectContext.releaseFile.write( + await projectContext.releaseFile.write( release.current.isCheckout() ? "none" : release.current.name); if (buildmessage.jobHasMessages()) { return; @@ -837,7 +837,8 @@ main.registerCommand({ var upgraders = require('../upgraders.js'); projectContext.finishedUpgraders.appendUpgraders(upgraders.allUpgraders()); - projectContext.prepareProjectForBuild(); + console.log("1111111115555") + await projectContext.prepareProjectForBuild(); }); // No need to display the PackageMapDelta here, since it would include all of // the packages (or maybe an unpredictable subset based on what happens to be @@ -845,7 +846,7 @@ main.registerCommand({ // Since some of the project skeletons include npm `devDependencies`, we need // to make sure they're included when running `npm install`. - require("./default-npm-deps.js").install( + await require("./default-npm-deps.js").install( appPath, { includeDevDependencies: true } ); diff --git a/tools/cli/default-npm-deps.js b/tools/cli/default-npm-deps.js index d34c1a702e..9047a8f8d9 100644 --- a/tools/cli/default-npm-deps.js +++ b/tools/cli/default-npm-deps.js @@ -8,7 +8,7 @@ import { const INSTALL_JOB_MESSAGE = "installing npm dependencies"; -export function install(appDir, options) { +export async function install(appDir, options) { const packageJsonPath = pathJoin(appDir, "package.json"); const needTempPackageJson = ! statOrNull(packageJsonPath); @@ -25,14 +25,14 @@ export function install(appDir, options) { ); } - const ok = buildmessage.enterJob(INSTALL_JOB_MESSAGE, function () { + const ok = await buildmessage.enterJob(INSTALL_JOB_MESSAGE, async function () { const npmCommand = ["install"]; if (options && options.includeDevDependencies) { npmCommand.push("--production=false"); } const { runNpmCommand } = require("../isobuild/meteor-npm.js"); - const installResult = runNpmCommand(npmCommand, appDir); + const installResult = await runNpmCommand(npmCommand, appDir); if (! installResult.success) { buildmessage.error( "Could not install npm dependencies for test-packages: " + diff --git a/tools/cli/main.js b/tools/cli/main.js index 09a4873b3d..fe6c326e3b 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -5,7 +5,6 @@ if (showRequireProfile) { var assert = require("assert"); var _ = require('underscore'); -var Fiber = require('fibers'); var Console = require('../console/console.js').Console; var files = require('../fs/files'); var warehouse = require('../packaging/warehouse.js'); @@ -15,17 +14,12 @@ var projectContextModule = require('../project-context.js'); var catalog = require('../packaging/catalog/catalog.js'); var buildmessage = require('../utils/buildmessage.js'); var httpHelpers = require('../utils/http-helpers.js'); +const {asyncLocalStorage} = require("../utils/fiber-helpers"); const archinfo = require('../utils/archinfo'); import { isEmacs } from "../utils/utils.js"; var main = exports; -if (process.platform === 'darwin' && process.arch === 'arm64') { - // pool size needs to be bigger on m1 because arm64 builds are using - // fibers with CORO_PTHREAD set. - default on fibers is 120 - Fiber.poolSize = 250; -} - require('./flush-buffers-on-exit-in-windows.js'); // node (v8) defaults to only recording 10 lines of stack trace. This @@ -273,12 +267,12 @@ main.registerCommand = function (options, func) { target[nameParts[0]] = new Command(options); }; -main.captureAndExit = function (header, title, f) { +main.captureAndExit = async function (header, title, f) { var messages; if (f) { - messages = buildmessage.capture({ title: title }, f); + messages = await buildmessage.capture({ title: title }, f); } else { - messages = buildmessage.capture(title); // title is really f + messages = await buildmessage.capture(title); // title is really f } if (messages.hasMessages()) { Console.error(header); @@ -427,7 +421,7 @@ var longHelp = exports.longHelp = function (commandName) { // and will cause release.forced to be true). // - fromApp: this release was suggested because it is the app's // release. affects error messages. -var springboard = function (rel, options) { +var springboard = async function (rel, options) { options = options || {}; if (process.env.METEOR_DEBUG_SPRINGBOARD) { console.log("WILL SPRINGBOARD TO", rel.getToolsPackageAtVersion()); @@ -461,11 +455,11 @@ var springboard = function (rel, options) { }); // XXX split better - Console.withProgressDisplayVisible(function () { - var messages = buildmessage.capture({ + await Console.withProgressDisplayVisible(async function () { + var messages = await buildmessage.capture({ title: "downloading the command-line tool" }, function () { - catalog.runAndRetryWithRefreshIfHelpful(function () { + return catalog.runAndRetryWithRefreshIfHelpful(function () { tropohouse.default.downloadPackagesMissingFromMap(packageMap, { serverArchitectures, }); @@ -554,13 +548,13 @@ var springboard = function (rel, options) { ); if (isWindows) { - process.exit(new Promise(function (resolve) { + process.exit(await new Promise(function (resolve) { var execPath = files.convertToOSPath(executable); var child = require("child_process").spawn(execPath, newArgv, { env: process.env, stdio: 'inherit' }).on('exit', resolve); - }).await()); + })); } // Now exec; we're not coming back. @@ -596,8 +590,7 @@ var oldSpringboard = function (toolsVersion) { // finding the requested command in the commands table, and making // sure that you're using the version of the Meteor tools that match // your project. - -Fiber(function () { +asyncLocalStorage.run({}, async function () { // If running inside the Emacs shell, set stdin to be blocking, // reversing node's normal setting of O_NONBLOCK on the evaluation // of process.stdin (because Node unblocks stdio when forking). This @@ -873,7 +866,8 @@ Fiber(function () { appDir = files.pathResolve(appDir); } - require('../tool-env/isopackets.js').ensureIsopacketsLoadable(); + // TODO -> Maybe await here? + await require('../tool-env/isopackets.js').ensureIsopacketsLoadable(); // Initialize the server catalog. Among other things, this is where we get // release information (used by springboarding). We do not at this point talk @@ -988,8 +982,8 @@ Fiber(function () { // Somehow we have a catalog that doesn't have any releases on the // default track. Try syncing, at least. (This is a pretty unlikely // error case, since you should always start with a non-empty catalog.) - Console.withProgressDisplayVisible(function () { - catalog.refreshOrWarn(); + await Console.withProgressDisplayVisible(function () { + return catalog.refreshOrWarn(); }); releaseName = release.latestKnown(); } @@ -1028,7 +1022,7 @@ Fiber(function () { // mean we've downloaded the tool or any packages yet.) release.load just // does a single sqlite query; it doesn't refresh the catalog. try { - rel = release.load(releaseName); + rel = await release.load(releaseName); } catch (e) { if (!(e instanceof release.NoSuchReleaseError)) { throw e; @@ -1049,14 +1043,14 @@ Fiber(function () { } // ATTEMPT 3: modern release, troposphere sync needed. - Console.withProgressDisplayVisible(function () { - catalog.refreshOrWarn(); + await Console.withProgressDisplayVisible(function () { + return catalog.refreshOrWarn(); }); // Try to load the release even if the refresh failed, since it might // have failed on a later page than the one we needed. try { - rel = release.load(releaseName); + rel = await release.load(releaseName); } catch (e) { if (!(e instanceof release.NoSuchReleaseError)) { throw e; @@ -1162,13 +1156,13 @@ Fiber(function () { release.current.isProperRelease()) { if (files.getToolsVersion() !== release.current.getToolsPackageAtVersion()) { - springboard(release.current, { + await springboard(release.current, { fromApp: releaseFromApp, mayReturn: false, - }) + }); // Does not return! } else if (archinfo.canSwitchTo64Bit()) { - springboard(release.current, { + await springboard(release.current, { fromApp: releaseFromApp, // Switching to a 64-bit meteor-tool build may fail, in which case // we should continue on as usual. @@ -1525,14 +1519,14 @@ Fiber(function () { var catalogRefreshStrategy = command.catalogRefresh; if (! catalog.triedToRefreshRecently && catalogRefreshStrategy.beforeCommand) { - buildmessage.enterJob({title: 'updating package catalog'}, function () { - catalogRefreshStrategy.beforeCommand(); + await buildmessage.enterJob({title: 'updating package catalog'}, function () { + return catalogRefreshStrategy.beforeCommand(); }); } - var ret = Promise.resolve( + var ret = await Promise.resolve( command.func(options, { rawOptions }) - ).await(); + ); } catch (e) { Console.enableProgressDisplay(false); @@ -1550,15 +1544,15 @@ Fiber(function () { // Load the metadata for the latest release (or at least, the latest // release we know about locally). We should only do this if we know there // is some latest release on this track. - var latestRelease = release.load(release.latestKnown(e.track)); - springboard(latestRelease, { releaseOverride: latestRelease.name }); + var latestRelease = await release.load(release.latestKnown(e.track)); + await springboard(latestRelease, { releaseOverride: latestRelease.name }); // (does not return) } else if (e instanceof main.SpringboardToSpecificRelease) { // Springboard to a specific release. This is only throw by // publish-for-arch, which is catalog.Refresh.OnceAtStart, so we ought to // have decent knowledge of the latest release. - var nextRelease = release.load(e.fullReleaseName); - springboard(nextRelease, { releaseOverride: e.fullReleaseName }); + var nextRelease = await release.load(e.fullReleaseName); + await springboard(nextRelease, { releaseOverride: e.fullReleaseName }); // (does not return) } else if (e instanceof main.WaitForExit) { return; @@ -1580,4 +1574,4 @@ Fiber(function () { throw new Error("command returned non-number?"); } process.exit(ret); -}).run(); +}); diff --git a/tools/console/console.js b/tools/console/console.js index 9cad2b5cc1..804dbfd6e8 100644 --- a/tools/console/console.js +++ b/tools/console/console.js @@ -442,16 +442,16 @@ class StatusPoller { this._stop = false; } - _startPoller() { + async _startPoller() { if (this._pollPromise) { throw new Error("Already started"); } this._pollPromise = (async() => { - sleepMs(STATUS_INTERVAL_MS); + await sleepMs(STATUS_INTERVAL_MS); while (! this._stop) { this.statusPoll(); - sleepMs(STATUS_INTERVAL_MS); + await sleepMs(STATUS_INTERVAL_MS); } })(); } @@ -623,7 +623,7 @@ class Console extends ConsoleBase { // Runs f with the progress display visible (ie, with progress display enabled // and pretty). Resets both flags to their original values after f runs. - withProgressDisplayVisible(f) { + async withProgressDisplayVisible(f) { var originalPretty = this._pretty; var originalProgressDisplayEnabled = this._progressDisplayEnabled; @@ -636,7 +636,7 @@ class Console extends ConsoleBase { } try { - return f(); + return await f(); } finally { // Reset the flags. this._pretty = originalPretty; @@ -689,12 +689,13 @@ class Console extends ConsoleBase { // The caller should be OK with yielding --- it has to be in a Fiber and it can't be // anything that depends for correctness on not yielding. You can also call nudge(false) // if you just want to update the spinner and not yield, but you should avoid this. - nudge(canYield) { + // TODO -> Check here + async nudge(canYield) { if (this._statusPoller) { this._statusPoller.statusPoll(); } if (canYield === undefined || canYield === true) { - this._throttledYield.yield(); + await this._throttledYield.yield(); } } diff --git a/tools/console/progress.ts b/tools/console/progress.ts index 7b50bfa876..94ededdd31 100644 --- a/tools/console/progress.ts +++ b/tools/console/progress.ts @@ -15,7 +15,7 @@ type ProgressState = { /** * Utility class for computing the progress of complex tasks. - * + * * Watchers are invoked with a ProgressState object. */ export class Progress { diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 432b210def..c449257c35 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -344,14 +344,13 @@ export function rm_recursive_async(path: string) { } // Like rm -r. -export const rm_recursive = Profile("files.rm_recursive", (path: string) => { +export const rm_recursive = Profile("files.rm_recursive", async (path: string) => { try { rimraf.sync(convertToOSPath(path)); } catch (e: any) { if ((e.code === "ENOTEMPTY" || - e.code === "EPERM") && - canYield()) { - rm_recursive_async(path).await(); + e.code === "EPERM")) { + await rm_recursive_async(path); return; } throw e; @@ -920,7 +919,7 @@ export const createTarball = Profile(function (_: string, tarball: string) { // sitting around", but not "there's any time where toDir exists but // is in a state other than initial or final".) export const renameDirAlmostAtomically = -Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => { +Profile("files.renameDirAlmostAtomically", async (fromDir: string, toDir: string) => { const garbageDir = pathJoin( pathDirname(toDir), // Begin the base filename with a '.' character so that it can be @@ -932,7 +931,7 @@ Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => { let cleanupGarbage = false; let forceCopy = false; try { - rename(toDir, garbageDir); + await rename(toDir, garbageDir); cleanupGarbage = true; } catch (e: any) { if (e.code === 'EXDEV') { @@ -951,7 +950,7 @@ Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => { if (! forceCopy) { try { - rename(fromDir, toDir); + await rename(fromDir, toDir); } catch (e: any) { // It's possible that there may not have been a `toDir` to have // advanced warning about this, so we're prepared to handle it again. @@ -966,7 +965,7 @@ Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => { // If we've been forced to jeopardize our atomicity due to file-system // limitations, we'll resort to copying. if (forceCopy) { - rm_recursive(toDir); + await rm_recursive(toDir); cp_r(fromDir, toDir, { preserveSymlinks: true, }); @@ -975,7 +974,7 @@ Profile("files.renameDirAlmostAtomically", (fromDir: string, toDir: string) => { // ... and take out the trash. if (cleanupGarbage) { // We don't care about how long this takes, so we'll let it go async. - rm_recursive_async(garbageDir); + await rm_recursive_async(garbageDir); } }); @@ -1625,7 +1624,7 @@ export const rename = isWindowsLikeFilesystem() ? function (from: string, to: st } else { throw error; } - }).await(); + }); } : wrappedRename; // Warning: doesn't convert slashes in the second 'cache' arg diff --git a/tools/isobuild/build-plugin.js b/tools/isobuild/build-plugin.js index e67de09c6b..73850fadf3 100644 --- a/tools/isobuild/build-plugin.js +++ b/tools/isobuild/build-plugin.js @@ -25,23 +25,23 @@ Object.assign(exports.SourceProcessor.prototype, { // this immediately on evaluating Plugin.registerCompiler; we instead wait // until the whole plugin file has been evaluated (so that it can use things // defined later in the file). - instantiatePlugin: function () { + instantiatePlugin: async function () { var self = this; buildmessage.assertInCapture(); if (self.userPlugin) { throw Error("Called instantiatePlugin twice?"); } - buildmessage.enterJob( + await buildmessage.enterJob( `running ${self.methodName} callback in package ` + self.isopack.displayName(), - () => { + async () => { try { - self.userPlugin = buildmessage.markBoundary(self.factoryFunction) - .call(null); + const markedFactoryFunction = buildmessage.markBoundary(self.factoryFunction); + self.userPlugin = await markedFactoryFunction().call(null); // If we have a disk cache directory and the plugin wants it, use it. if (self.isopack.pluginCacheDir && self.userPlugin.setDiskCacheDirectory) { - buildmessage.markBoundary(function () { + await buildmessage.markBoundary(function () { self.userPlugin.setDiskCacheDirectory( files.convertToOSPath(self.isopack.pluginCacheDir) ); diff --git a/tools/isobuild/builder.js b/tools/isobuild/builder.js index 5e4e736915..8234257fa0 100644 --- a/tools/isobuild/builder.js +++ b/tools/isobuild/builder.js @@ -119,12 +119,12 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // Build the output from scratch if (resetBuildPath) { - files.rm_recursive(this.buildPath); - files.mkdir_p(this.buildPath, 0o755); + files.rm_recursive(this.buildPath).then(() => { + files.mkdir_p(this.buildPath, 0o755); + this.watchSet = new WatchSet(); + }); } - this.watchSet = new WatchSet(); - // XXX cleaner error handling. don't make the humans read an // exception (and, make suitable for use in automated systems) } @@ -419,7 +419,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // Serialize `data` as JSON and write it to `relPath` (a path to a // file relative to the bundle root), creating parent directories as // necessary. Throw an exception if the file already exists. - writeJson(relPath, data) { + async writeJson(relPath, data) { // Ensure no trailing slash if (relPath.slice(-1) === files.pathSep) { relPath = relPath.slice(0, -1); @@ -428,7 +428,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` this._ensureDirectory(files.pathDirname(relPath)); const absPath = files.pathJoin(this.buildPath, relPath); - atomicallyRewriteFile( + await atomicallyRewriteFile( absPath, Buffer.from(JSON.stringify(data, null, 2), 'utf8'), {mode: 0o444}); @@ -920,7 +920,7 @@ function jsToTs(path) { return path; } -function atomicallyRewriteFile(path, data, options) { +async function atomicallyRewriteFile(path, data, options) { // create a different file with a random name and then rename over atomically const rname = '.builder-tmp-file.' + Math.floor(Math.random() * 999999); const rpath = files.pathJoin(files.pathDirname(path), rname); @@ -932,7 +932,7 @@ function atomicallyRewriteFile(path, data, options) { // replacing a directory with a file; this is rare (so it can // be a slow path) but can legitimately happen if e.g. a developer // puts a file where there used to be a directory in their app. - files.rm_recursive(path); + await files.rm_recursive(path); files.rename(rpath, path); } else { throw e; diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 1f36259ba8..48d2674b57 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -844,27 +844,27 @@ class Target { // - addCacheBusters: if true, make all files cacheable by adding // unique query strings to their URLs. unlikely to be of much use // on server targets. - make({packages, minifyMode, addCacheBusters, minifiers, onJsOutputFiles = () => {}}) { + async make({packages, minifyMode, addCacheBusters, minifiers, onJsOutputFiles = () => {}}) { buildmessage.assertInCapture(); - buildmessage.enterJob("building for " + this.arch, () => { + await buildmessage.enterJob("building for " + this.arch, async () => { // Populate the list of unibuilds to load - this._determineLoadOrder({ + await this._determineLoadOrder({ packages: packages || [] }); - const sourceBatches = this._runCompilerPlugins({ + const sourceBatches = await this._runCompilerPlugins({ minifiers, minifyMode, }); // Link JavaScript and set up this.js, etc. - this._emitResources(sourceBatches, (outputFiles, sourceBatch, cacheKey) => { + await this._emitResources(sourceBatches, (outputFiles, sourceBatch, cacheKey) => { function getFileOutput(file) { return new LinkerFile(file).getPrelinkedOutput({}); }; - onJsOutputFiles( + return onJsOutputFiles( { arch: this.arch, name: sourceBatch.unibuild.pkg.name || null, @@ -878,7 +878,7 @@ class Target { // Add top-level Cordova dependencies, which override Cordova // dependencies from packages. - this._addDirectCordovaDependencies(); + await this._addDirectCordovaDependencies(); // Minify, with mode requested. // Why do we only minify in client targets? @@ -903,10 +903,10 @@ class Target { }); if (minifiersByExt.js) { - this.minifyJs(minifiersByExt.js, minifyMode); + await this.minifyJs(minifiersByExt.js, minifyMode); } if (minifiersByExt.css) { - this.minifyCss(minifiersByExt.css, minifyMode); + await this.minifyCss(minifiersByExt.css, minifyMode); } } @@ -928,15 +928,15 @@ class Target { // - packages: an array of packages (or, properly speaking, unibuilds) // to include. Each element should either be a Isopack object or a // package name as a string - _determineLoadOrder({packages}) { + async _determineLoadOrder({packages}) { buildmessage.assertInCapture(); const isopackCache = this.isopackCache; - buildmessage.enterJob('linking the program', () => { + await buildmessage.enterJob('linking the program', async () => { // Find the roots const rootUnibuilds = []; - packages.forEach((p) => { + for (let p of packages) { if (typeof p === 'string') { p = isopackCache.getIsopack(p); } @@ -952,9 +952,9 @@ class Target { if (p.testOnly && this.buildMode !== 'test') { return; } - const unibuild = p.getUnibuildAtArch(this.arch); + const unibuild = await p.getUnibuildAtArch(this.arch); unibuild && rootUnibuilds.push(unibuild); - }); + } if (buildmessage.jobHasMessages()) { return; @@ -971,7 +971,7 @@ class Target { // Phase 2. const usedUnibuilds = {}; // Map from unibuild.id to Unibuild. this.usedPackages = {}; // Map from package name to true; - const addToGetsUsed = function (unibuild) { + const addToGetsUsed = async function (unibuild) { if (_.has(usedUnibuilds, unibuild.id)) { return; } @@ -980,7 +980,7 @@ class Target { // Only track real packages, not plugin pseudo-packages. this.usedPackages[unibuild.pkg.name] = true; } - compiler.eachUsedUnibuild({ + await compiler.eachUsedUnibuild({ dependencies: unibuild.uses, arch: this.arch, isopackCache: isopackCache, @@ -992,7 +992,10 @@ class Target { }, addToGetsUsed); }.bind(this); - rootUnibuilds.forEach(addToGetsUsed); + // TODO -> We may need to keep the order here? + for (const unibuild of rootUnibuilds) { + await addToGetsUsed(unibuild); + } if (buildmessage.jobHasMessages()) { return; @@ -1019,7 +1022,7 @@ class Target { // This helper recursively adds unibuild's ordered dependencies to // this.unibuilds, then adds unibuild itself. - const add = function (unibuild) { + const add = async function (unibuild) { // If this has already been added, there's nothing to do. if (!_.has(needed, unibuild.id)) { return; @@ -1038,7 +1041,7 @@ class Target { var processUnibuild = function (usedUnibuild) { if (onStack[usedUnibuild.id]) { buildmessage.error( - "circular dependency between packages " + + "circular dependency between packages " + unibuild.pkg.name + " and " + usedUnibuild.pkg.name); // recover by not enforcing one of the depedencies return; @@ -1047,7 +1050,7 @@ class Target { add(usedUnibuild); delete onStack[usedUnibuild.id]; }; - compiler.eachUsedUnibuild({ + await compiler.eachUsedUnibuild({ dependencies: unibuild.uses, arch: this.arch, isopackCache: isopackCache, @@ -1070,11 +1073,11 @@ class Target { for (first in needed) { break; } - if (! first) { + if (!first) { break; } // Now add it, after its ordered dependencies. - add(needed[first]); + await add(needed[first]); } }); } @@ -1154,7 +1157,7 @@ class Target { // Process all of the sorted unibuilds (which includes running the JavaScript // linker). - _emitResources(sourceBatches, onJsOutputFiles = () => {}) { + async _emitResources(sourceBatches, onJsOutputFiles = () => {}) { buildmessage.assertInJob(); const isWeb = archinfo.matches(this.arch, 'web'); @@ -1398,7 +1401,7 @@ class Target { } // Minify the JS in this target - minifyJs(minifierDef, minifyMode) { + async minifyJs(minifierDef, minifyMode) { const staticFiles = []; const dynamicFiles = []; const { arch } = this; @@ -1421,14 +1424,14 @@ class Target { minifierDef.userPlugin ); - buildmessage.enterJob('minifying app code', function () { + await buildmessage.enterJob('minifying app code', async function () { try { - Promise.all([ + await Promise.all([ markedMinifier(staticFiles, { minifyMode }), ...dynamicFiles.map( file => markedMinifier([file], { minifyMode }) ), - ]).await(); + ]); } catch (e) { buildmessage.exception(e); } @@ -1694,8 +1697,8 @@ class ClientTarget extends Target { } // Minify the CSS in this target - minifyCss(minifierDef, minifyMode) { - this.css = minifyCssFiles(this.css, { + async minifyCss(minifierDef, minifyMode) { + this.css = await minifyCssFiles(this.css, { arch: this.arch, minifier: minifierDef, minifyMode, @@ -1905,7 +1908,7 @@ class ClientTarget extends Target { } } -function minifyCssFiles (files, { +async function minifyCssFiles (files, { arch, minifier, minifyMode, @@ -1922,9 +1925,9 @@ function minifyCssFiles (files, { minifier.userPlugin, ); - buildmessage.enterJob('minifying app stylesheet', function () { + await buildmessage.enterJob('minifying app stylesheet', async function () { try { - Promise.await(markedMinifier(sources, { minifyMode })); + await markedMinifier(sources, { minifyMode }); } catch (e) { buildmessage.exception(e); } @@ -3184,7 +3187,7 @@ exports.bundle = Profile("bundler.bundle", function (options) { return files.withCache(() => bundle(options)); }); -function bundle({ +async function bundle({ projectContext, outputPath, includeNodeModules, @@ -3238,14 +3241,14 @@ function bundle({ throw new Error('Unrecognized build mode: ' + buildMode); } - var messages = buildmessage.capture({ + var messages = await buildmessage.capture({ title: "building the application" - }, function () { - var packageSource = new PackageSource; + }, async function () { + var packageSource = new PackageSource(); packageSource.initFromAppDir(projectContext, exports.ignoreFiles); var makeClientTarget = Profile( - "bundler.bundle..makeClientTarget", function (app, webArch, options) { + "bundler.bundle..makeClientTarget", async function (app, webArch, options) { var client = new ClientTarget({ bundlerCacheDir, packageMap: projectContext.packageMap, @@ -3257,7 +3260,7 @@ function bundle({ buildMode: buildOptions.buildMode }); - client.make({ + await client.make({ packages: [app], minifyMode: minifyMode, minifiers: options.minifiers || [], @@ -3269,7 +3272,7 @@ function bundle({ }); var makeServerTarget = Profile( - "bundler.bundle..makeServerTarget", function (app, clientArchs) { + "bundler.bundle..makeServerTarget", async function (app, clientArchs) { const server = new ServerTarget({ bundlerCacheDir, packageMap: projectContext.packageMap, @@ -3282,7 +3285,7 @@ function bundle({ clientArchs, }); - server.make({ + await server.make({ packages: [app] }); @@ -3292,7 +3295,7 @@ function bundle({ // Create a Isopack object that represents the app // XXX should this be part of prepareProjectForBuild and get cached? // at the very least, would speed up deploy after build. - var app = compiler.compile(packageSource, { + var app = await compiler.compile(packageSource, { packageMap: projectContext.packageMap, isopackCache: projectContext.isopackCache, includeCordovaUnibuild: projectContext.platformList.usesCordova() @@ -3317,7 +3320,7 @@ function bundle({ } if (! buildmessage.jobHasMessages()) { - lintingMessages = lintBundle(projectContext, app, packageSource); + lintingMessages = await lintBundle(projectContext, app, packageSource); } // If while trying to lint, we got a compilation error (eg, an issue loading // plugins in one of the linter packages), restart on any relevant change, @@ -3330,7 +3333,7 @@ function bundle({ if (! ['development', 'production'].includes(minifyMode)) { throw new Error('Unrecognized minification mode: ' + minifyMode); } - minifiers = compiler.getMinifiers(packageSource, { + minifiers = await compiler.getMinifiers(packageSource, { isopackCache: projectContext.isopackCache, isopack: app }); @@ -3371,7 +3374,7 @@ function bundle({ } // Client - webArchs.forEach(arch => { + for (const arch of webArchs) { if (allowDelayedClientBuilds && hasOwn.call(previousBuilders, arch) && projectContext.platformList.canDelayBuildingArch(arch)) { @@ -3380,14 +3383,14 @@ function bundle({ // build later (e.g. web.browser.legacy), then schedule it to be // built after the server has started up. postStartupCallbacks.push(async ({ - pauseClient, - refreshClient, - runLog, - }) => { + pauseClient, + refreshClient, + runLog, + }) => { const start = +new Date; // Build the target first. - const target = makeClientTarget(app, arch, { minifiers }); + const target = await makeClientTarget(app, arch, { minifiers }); // Tell the webapp package to pause responding to requests from // clients that use this arch, because we're about to write a @@ -3410,20 +3413,19 @@ function bundle({ // should regenerate the client program for this arch. if (Profile.enabled) { runLog.log(`Finished delayed build of ${arch} in ${ - new Date - start + new Date - start }ms`, { arrow: true }); } }); - } else { // Otherwise make the client target now, and write it below. - targets[arch] = makeClientTarget(app, arch, {minifiers}); + targets[arch] = await makeClientTarget(app, arch, {minifiers}); } - }); + } // Server if (! hasCachedBundle) { - targets.server = makeServerTarget(app, webArchs); + targets.server = await makeServerTarget(app, webArchs); } if (outputPath !== null) { @@ -3476,14 +3478,14 @@ function ignoreHarmlessErrors(error) { // Returns null if there are no lint warnings and the app has no linters // defined. Returns an empty MessageSet if the app has a linter defined but // there are no lint warnings (on app or packages). -function lintBundle (projectContext, isopack, packageSource) { +async function lintBundle (projectContext, isopack, packageSource) { buildmessage.assertInJob(); let lintedAnything = false; const lintingMessages = new buildmessage._MessageSet(); if (projectContext.lintAppAndLocalPackages) { - const {warnings: appMessages, linted} = compiler.lint(packageSource, { + const {warnings: appMessages, linted} = await compiler.lint(packageSource, { isopack, isopackCache: projectContext.isopackCache }); @@ -3543,7 +3545,7 @@ function lintBundle (projectContext, isopack, packageSource) { // It would be nice to have a way to say "make this package anonymous" // without also saying "make its namespace the same as the global // namespace." It should be an easy refactor, -exports.buildJsImage = Profile("bundler.buildJsImage", function (options) { +exports.buildJsImage = Profile("bundler.buildJsImage", async function (options) { buildmessage.assertInCapture(); if (options.npmDependencies && ! options.npmDir) { throw new Error("Must indicate .npm directory to use"); @@ -3552,7 +3554,7 @@ exports.buildJsImage = Profile("bundler.buildJsImage", function (options) { throw new Error("Must provide a name"); } - var packageSource = new PackageSource; + var packageSource = new PackageSource(); packageSource.initFromOptions(options.name, { kind: "plugin", @@ -3567,7 +3569,7 @@ exports.buildJsImage = Profile("bundler.buildJsImage", function (options) { localNodeModulesDirs: options.localNodeModulesDirs, }); - var isopack = compiler.compile(packageSource, { + var isopack = await compiler.compile(packageSource, { packageMap: options.packageMap, isopackCache: options.isopackCache, // There's no web.cordova unibuild here anyway, just os. @@ -3583,9 +3585,9 @@ exports.buildJsImage = Profile("bundler.buildJsImage", function (options) { // cross-bundling, not cross-package-building, and this function is only // used to build plugins (during package build) and for isopack.load // (which always wants to build for the current host). - arch: archinfo.host() + arch: await archinfo.host() }); - target.make({ packages: [isopack] }); + await target.make({ packages: [isopack] }); return { image: target.toJsImage(), diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 9f58f39c33..31343c7cd7 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -137,25 +137,30 @@ export class CompilerPluginProcessor { } } - runCompilerPlugins() { + async runCompilerPlugins() { const self = this; buildmessage.assertInJob(); // plugin id -> {sourceProcessor, resourceSlots} var sourceProcessorsWithSlots = {}; - var sourceBatches = _.map(self.unibuilds, function (unibuild) { + const sourceBatches = []; + for (const unibuild of self.unibuilds) { const { pkg: { name }, arch } = unibuild; const sourceRoot = name - && self.isopackCache.getSourceRoot(name, arch) - || self.sourceRoot; + && self.isopackCache.getSourceRoot(name, arch) + || self.sourceRoot; - return new PackageSourceBatch(unibuild, self, { + const batch = new PackageSourceBatch(unibuild, self, { sourceRoot, linkerCacheDir: self.linkerCacheDir, scannerCacheDir: self.scannerCacheDir, }); - }); + + await batch.init(); + + sourceBatches.push(batch); + } // If we failed to match sources with processors, we're done. if (buildmessage.jobHasMessages()) { @@ -182,8 +187,8 @@ export class CompilerPluginProcessor { }); }); - // Now actually run the handlers. - _.each(sourceProcessorsWithSlots, function (data, id) { + // Now actually run the handlers + for (const [id, data] of Object.entries(sourceProcessorsWithSlots)) { var sourceProcessor = data.sourceProcessor; var resourceSlots = data.resourceSlots; @@ -193,27 +198,27 @@ export class CompilerPluginProcessor { " (for target ", self.arch, ")" ].join(''); - Profile.time("plugin "+sourceProcessor.isopack.name, () => { - buildmessage.enterJob({ + await Profile.time("plugin "+sourceProcessor.isopack.name, async () => { + await buildmessage.enterJob({ title: jobTitle - }, function () { + }, async function () { var inputFiles = _.map(resourceSlots, function (resourceSlot) { return new InputFile(resourceSlot); }); const markedMethod = buildmessage.markBoundary( - sourceProcessor.userPlugin.processFilesForTarget, - sourceProcessor.userPlugin + sourceProcessor.userPlugin.processFilesForTarget, + sourceProcessor.userPlugin ); try { - Promise.await(markedMethod(inputFiles)); + await markedMethod(inputFiles); } catch (e) { buildmessage.exception(e); } }); }); - }); + } return sourceBatches; } @@ -1061,12 +1066,6 @@ export class PackageSourceBatch { self._nodeModulesPaths = null; self.resourceSlots = []; - unibuild.resources.forEach(resource => { - const slot = self.makeResourceSlot(resource); - if (slot) { - self.resourceSlots.push(slot); - } - }); // Compute imports by merging the exports of all of the packages we // use. Note that in the case of conflicting symbols, later packages get @@ -1079,8 +1078,18 @@ export class PackageSourceBatch { // decision of whether or not an unrelated package in the target // depends on something). self.importedSymbolToPackageName = {}; // map from symbol to supplying package name + } - compiler.eachUsedUnibuild({ + async init() { + const self = this; + for (const resource of this.unibuild.resources) { + const slot = await self.makeResourceSlot(resource); + if (slot) { + self.resourceSlots.push(slot); + } + } + + await compiler.eachUsedUnibuild({ dependencies: self.unibuild.uses, arch: self.processor.arch, isopackCache: self.processor.isopackCache, @@ -1101,24 +1110,24 @@ export class PackageSourceBatch { }); self.useMeteorInstall = - _.isString(self.sourceRoot) && - self.processor.isopackCache.uses( - self.unibuild.pkg, - "modules", - self.unibuild.arch - ); + _.isString(self.sourceRoot) && + self.processor.isopackCache.uses( + self.unibuild.pkg, + "modules", + self.unibuild.arch + ); const isDevelopment = self.processor.buildMode === 'development'; const usesHMRPackage = self.unibuild.pkg.name !== "hot-module-replacement" && - self.processor.isopackCache.uses( - self.unibuild.pkg, - "hot-module-replacement", - self.unibuild.arch - ); + self.processor.isopackCache.uses( + self.unibuild.pkg, + "hot-module-replacement", + self.unibuild.arch + ); const supportedArch = archinfo.matches(self.unibuild.arch, 'web'); self.hmrAvailable = self.useMeteorInstall && isDevelopment - && usesHMRPackage && supportedArch; + && usesHMRPackage && supportedArch; // These are the options that should be passed as the second argument // to meteorInstall when modules in this source batch are installed. @@ -1127,8 +1136,8 @@ export class PackageSourceBatch { } : null; } - compileOneJsResource(resource) { - const slot = this.makeResourceSlot({ + async compileOneJsResource(resource) { + const slot = await this.makeResourceSlot({ type: "source", extension: "js", // Need { data, path, hash } here, at least. @@ -1144,7 +1153,7 @@ export class PackageSourceBatch { // added directly to slot.jsOutputResources by makeResourceSlot, // meaning we do not need to compile it. if (slot.jsOutputResources.length > 0) { - return slot.jsOutputResources + return slot.jsOutputResources; } const inputFile = new InputFile(slot); @@ -1158,7 +1167,7 @@ export class PackageSourceBatch { userPlugin ); try { - Promise.await(markedMethod([inputFile])); + await markedMethod([inputFile]); } catch (e) { buildmessage.exception(e); } @@ -1171,13 +1180,13 @@ export class PackageSourceBatch { return []; } - makeResourceSlot(resource) { + async makeResourceSlot(resource) { let sourceProcessor = null; if (resource.type === "source") { var extension = resource.extension; if (extension === null) { const filename = files.pathBasename(resource.path); - sourceProcessor = this._getSourceProcessorSet().getByFilename(filename); + sourceProcessor = (await this._getSourceProcessorSet()).getByFilename(filename); if (! sourceProcessor) { buildmessage.error( `no plugin found for ${ resource.path } in ` + @@ -1187,7 +1196,7 @@ export class PackageSourceBatch { // recover by ignoring } } else { - sourceProcessor = this._getSourceProcessorSet().getByExtension(extension); + sourceProcessor = (await this._getSourceProcessorSet()).getByExtension(extension); // If resource.extension === 'js', it's ok for there to be no // sourceProcessor, since we #HardcodeJs in ResourceSlot. if (! sourceProcessor && extension !== 'js') { @@ -1245,12 +1254,12 @@ export class PackageSourceBatch { return this._nodeModulesPaths; } - _getSourceProcessorSet() { + async _getSourceProcessorSet() { if (! this._sourceProcessorSet) { buildmessage.assertInJob(); const isopack = this.unibuild.pkg; - const activePluginPackages = compiler.getActivePluginPackages(isopack, { + const activePluginPackages = await compiler.getActivePluginPackages(isopack, { uses: this.unibuild.uses, isopackCache: this.processor.isopackCache }); @@ -1258,12 +1267,12 @@ export class PackageSourceBatch { this._sourceProcessorSet = new buildPluginModule.SourceProcessorSet( isopack.displayName(), { hardcodeJs: true }); - _.each(activePluginPackages, otherPkg => { - otherPkg.ensurePluginsInitialized(); + for (const otherPkg of activePluginPackages) { + await otherPkg.ensurePluginsInitialized(); this._sourceProcessorSet.merge(otherPkg.sourceProcessors.compiler, { arch: this.processor.arch, }); - }); + } } return this._sourceProcessorSet; diff --git a/tools/isobuild/compiler.js b/tools/isobuild/compiler.js index 729a2fb358..ffd9a181ae 100644 --- a/tools/isobuild/compiler.js +++ b/tools/isobuild/compiler.js @@ -47,7 +47,7 @@ compiler.ALL_ARCHES = [ compiler.compile = Profile(function (packageSource, options) { return `compiler.compile(${ packageSource.name || 'the app' })`; -}, function (packageSource, options) { +}, async function (packageSource, options) { buildmessage.assertInCapture(); var packageMap = options.packageMap; @@ -59,14 +59,14 @@ compiler.compile = Profile(function (packageSource, options) { var pluginProviderPackageNames = {}; - // Build plugins - _.each(packageSource.pluginInfo, function (info) { - buildmessage.enterJob({ + for (const info of Object.values(packageSource.pluginInfo)) { + // build plugins + await buildmessage.enterJob({ title: "building plugin `" + info.name + - "` in package `" + packageSource.name + "`", + "` in package `" + packageSource.name + "`", rootPath: packageSource.sourceRoot - }, function () { - var buildResult = buildJsImage({ + }, async function () { + var buildResult = await buildJsImage({ name: info.name, packageMap: packageMap, isopackCache: isopackCache, @@ -81,8 +81,8 @@ compiler.compile = Profile(function (packageSource, options) { // rest of the package, so they need their own separate npm // shrinkwrap and cache state. npmDir: files.pathResolve(files.pathJoin( - packageSource.sourceRoot, - '.npm', 'plugin', colonConverter.convert(info.name) + packageSource.sourceRoot, + '.npm', 'plugin', colonConverter.convert(info.name) )) }); // Add this plugin's dependencies to our "plugin dependency" @@ -109,7 +109,7 @@ compiler.compile = Profile(function (packageSource, options) { } plugins[info.name][buildResult.image.arch] = buildResult.image; }); - }); + } // Grab any npm dependencies. Keep them in a cache in the package // source directory so we don't have to do this from scratch on @@ -125,7 +125,7 @@ compiler.compile = Profile(function (packageSource, options) { // need to delete dependencies we used to have. var nodeModulesPath = null; if (packageSource.npmCacheDirectory) { - if (meteorNpm.updateDependencies(packageSource.name, + if (await meteorNpm.updateDependencies(packageSource.name, packageSource.npmCacheDirectory, packageSource.npmDependencies)) { nodeModulesPath = files.pathJoin( @@ -159,7 +159,7 @@ compiler.compile = Profile(function (packageSource, options) { }); isobuildFeatures = _.uniq(isobuildFeatures); - var isopk = new isopack.Isopack; + var isopk = new isopack.Isopack(); isopk.initFromOptions({ name: packageSource.name, metadata: packageSource.metadata, @@ -177,13 +177,15 @@ compiler.compile = Profile(function (packageSource, options) { isobuildFeatures }); - _.each(packageSource.architectures, function (architecture) { + for (const architecture of packageSource.architectures) { if (architecture.arch === 'web.cordova' && ! includeCordovaUnibuild) { - return; + continue; } - files.withCache(() => { - var unibuildResult = compileUnibuild({ + // TODO -> Maybe this withCache will bring some problems in other commands. + await files.withCache(async () => { + // TODO -> Check why the result is undefined... + var unibuildResult = await compileUnibuild({ isopack: isopk, sourceArch: architecture, isopackCache: isopackCache, @@ -191,9 +193,9 @@ compiler.compile = Profile(function (packageSource, options) { }); Object.assign(pluginProviderPackageNames, - unibuildResult.pluginProviderPackageNames); + unibuildResult.pluginProviderPackageNames); }); - }); + } if (options.includePluginProviderPackageMap) { isopk.setPluginProviderPackageMap( @@ -209,23 +211,24 @@ compiler.compile = Profile(function (packageSource, options) { // - includeCordovaUnibuild compiler.lint = Profile(function (packageSource, options) { return `compiler.lint(${ packageSource.name || 'the app' })`; -}, function (packageSource, options) { +}, async function (packageSource, options) { // Note: the buildmessage context of compiler.lint and lintUnibuild is a // normal error message context (eg, there might be errors from initializing // plugins in getLinterSourceProcessorSet). We return the linter warnings as // our return value. buildmessage.assertInJob(); - const warnings = new buildmessage._MessageSet; + const warnings = new buildmessage._MessageSet(); let linted = false; - _.each(packageSource.architectures, function (architecture) { + + for (const architecture of packageSource.architectures) { // skip Cordova if not required if (! options.includeCordovaUnibuild && architecture.arch === 'web.cordova') { - return; + continue; } - const unibuildWarnings = lintUnibuild({ + const unibuildWarnings = await lintUnibuild({ isopack: options.isopack, isopackCache: options.isopackCache, sourceArch: architecture @@ -234,28 +237,29 @@ compiler.lint = Profile(function (packageSource, options) { linted = true; warnings.merge(unibuildWarnings); } - }); + } + return {warnings, linted}; }); -compiler.getMinifiers = function (packageSource, options) { +compiler.getMinifiers = async function (packageSource, options) { buildmessage.assertInJob(); var minifiers = []; - _.each(packageSource.architectures, function (architecture) { - var activePluginPackages = getActivePluginPackages(options.isopack, { + for (const architecture of packageSource.architectures) { + var activePluginPackages = await getActivePluginPackages(options.isopack, { isopackCache: options.isopackCache, uses: architecture.uses }); - _.each(activePluginPackages, function (otherPkg) { - otherPkg.ensurePluginsInitialized(); + for (const otherPkg of activePluginPackages) { + await otherPkg.ensurePluginsInitialized(); _.each(otherPkg.sourceProcessors.minifier.allSourceProcessors, (sp) => { minifiers.push(sp); }); - }); - }); + } + } minifiers = _.uniq(minifiers); // check for extension-wise uniqness @@ -273,36 +277,36 @@ compiler.getMinifiers = function (packageSource, options) { return minifiers; }; -function getLinterSourceProcessorSet({isopack, activePluginPackages}) { +async function getLinterSourceProcessorSet({isopack, activePluginPackages}) { buildmessage.assertInJob(); const sourceProcessorSet = new SourceProcessorSet( isopack.displayName, { allowConflicts: true }); - _.each(activePluginPackages, function (otherPkg) { - otherPkg.ensurePluginsInitialized(); + for (const otherPkg of Object.values(activePluginPackages)) { + await otherPkg.ensurePluginsInitialized(); sourceProcessorSet.merge(otherPkg.sourceProcessors.linter); - }); + } return sourceProcessorSet; } -var lintUnibuild = function ({isopack, isopackCache, sourceArch}) { +var lintUnibuild = async function ({isopack, isopackCache, sourceArch}) { // Note: the buildmessage context of compiler.lint and lintUnibuild is a // normal error message context (eg, there might be errors from initializing // plugins in getLinterSourceProcessorSet). We return the linter warnings as // our return value. buildmessage.assertInJob(); - var activePluginPackages = getActivePluginPackages( + var activePluginPackages = await getActivePluginPackages( isopack, { isopackCache, uses: sourceArch.uses }); const sourceProcessorSet = - getLinterSourceProcessorSet({isopack, activePluginPackages}); + await getLinterSourceProcessorSet({isopack, activePluginPackages}); // bail out early if we had trouble loading plugins or if we're not // going to lint anything if (buildmessage.jobHasMessages() || sourceProcessorSet.isEmpty()) { @@ -320,8 +324,8 @@ var lintUnibuild = function ({isopack, isopackCache, sourceArch}) { const {sources} = sourceArch.getFiles(sourceProcessorSet, unibuild.watchSet); - const linterMessages = buildmessage.capture(() => { - runLinters({ + const linterMessages = await buildmessage.capture(() => { + return runLinters({ isopackCache, sources, sourceProcessorSet, @@ -339,7 +343,7 @@ var lintUnibuild = function ({isopack, isopackCache, sourceArch}) { // Returns a list of source files that were used in the compilation. var compileUnibuild = Profile(function (options) { return `compileUnibuild (${options.isopack.name || 'the app'})`; -}, function (options) { +}, async function (options) { buildmessage.assertInCapture(); const isopk = options.isopack; @@ -352,7 +356,7 @@ var compileUnibuild = Profile(function (options) { const watchSet = inputSourceArch.watchSet.clone(); // *** Determine and load active plugins - const activePluginPackages = getActivePluginPackages(isopk, { + const activePluginPackages = await getActivePluginPackages(isopk, { uses: inputSourceArch.uses, isopackCache: isopackCache, // If other package is built from source, then we need to rebuild this @@ -373,22 +377,23 @@ var compileUnibuild = Profile(function (options) { // handled by anything (which is an error unless explicitly declared // as a static asset). let sourceProcessorSet, linterSourceProcessorSet; - buildmessage.enterJob("determining active plugins", () => { + await buildmessage.enterJob("determining active plugins", async () => { sourceProcessorSet = new SourceProcessorSet( isopk.displayName(), { hardcodeJs: true}); - activePluginPackages.forEach((otherPkg) => { - otherPkg.ensurePluginsInitialized(); + for (const otherPkg of activePluginPackages) { + await otherPkg.ensurePluginsInitialized(); // Note that this may log a buildmessage if there are conflicts. sourceProcessorSet.merge(otherPkg.sourceProcessors.compiler); - }); + } // Used to excuse functions from the "undeclared static asset" check. - linterSourceProcessorSet = getLinterSourceProcessorSet({ + linterSourceProcessorSet = await getLinterSourceProcessorSet({ activePluginPackages, isopack: isopk }); + if (buildmessage.jobHasMessages()) { // Recover by not calling getFiles and pretending there are no // items. @@ -487,7 +492,7 @@ var compileUnibuild = Profile(function (options) { }); // Add and compile all source files - _.values(sources).forEach((source) => { + for (const source of sources) { const relPath = source.relPath; const fileOptions = _.clone(source.fileOptions) || {}; const absPath = files.pathResolve(inputSourceArch.sourceRoot, relPath); @@ -496,14 +501,14 @@ var compileUnibuild = Profile(function (options) { // Find the handler for source files with this extension let classification = null; classification = sourceProcessorSet.classifyFilename( - filename, inputSourceArch.arch); + filename, inputSourceArch.arch); if (classification.type === 'wrong-arch') { // This file is for a compiler plugin but not for this arch. Skip it, // and don't even watch it. (eg, skip CSS preprocessor files on the // server.) This `return` skips this source file and goes on to the next // one. - return; + continue; } if (classification.type === 'unmatched') { @@ -535,23 +540,23 @@ var compileUnibuild = Profile(function (options) { // removed while the Tool is running. Given that this is not a // common occurrence however, we'll ignore this situation and let the // Tool rebuild continue. - return; + continue; } const linterClassification = linterSourceProcessorSet.classifyFilename( - filename, inputSourceArch.arch); + filename, inputSourceArch.arch); if (linterClassification.type !== 'unmatched') { // The linter knows about this, so we'll just ignore it instead of // throwing an error. - return; + continue; } buildmessage.error( - `No plugin known to handle file '${ relPath }'. If you want this \ + `No plugin known to handle file '${ relPath }'. If you want this \ file to be a static asset, use addAssets instead of addFiles; eg, \ api.addAssets('${relPath}', 'client').`); // recover by ignoring - return; + continue; } const contents = optimisticReadFile(absPath); @@ -565,14 +570,13 @@ api.addAssets('${relPath}', 'client').`); } else { watchSet.addFile(absPath, hash); } - - Console.nudge(true); + await Console.nudge(true); if (classification.type === "meteor-ignore") { // Return after watching .meteorignore files but before adding them // as resources to be processed by compiler plugins. To see how // these files are handled, see PackageSource#_findSources. - return; + continue; } if (contents === null) { @@ -583,15 +587,15 @@ api.addAssets('${relPath}', 'client').`); // more. if (source.relPath.match(/:/)) { buildmessage.error( - "Couldn't build this package on Windows due to the following file " + - "with a colon -- " + source.relPath + ". Please rename and " + - "and re-publish the package."); + "Couldn't build this package on Windows due to the following file " + + "with a colon -- " + source.relPath + ". Please rename and " + + "and re-publish the package."); } else { buildmessage.error("File not found: " + source.relPath); } // recover by ignoring (but still watching the file) - return; + continue; } if (classification.isNonLegacySource()) { @@ -605,7 +609,7 @@ api.addAssets('${relPath}', 'client').`); hash, fileOptions })); - return; + continue; } if (classification.type !== 'legacy-handler') { @@ -614,16 +618,16 @@ api.addAssets('${relPath}', 'client').`); // OK, time to handle legacy handlers. var compileStep = compileStepModule.makeCompileStep( - source, file, inputSourceArch, { - resources: resources, - addAsset: addAsset - }); + source, file, inputSourceArch, { + resources: resources, + addAsset: addAsset + }); const handler = buildmessage.markBoundary(classification.legacyHandler); try { - Profile.time(`legacy handler (.${classification.extension})`, () => { - handler(compileStep); + await Profile.time(`legacy handler (.${classification.extension})`, () => { + return handler(compileStep); }); } catch (e) { e.message = e.message + " (compiling " + relPath + ")"; @@ -632,7 +636,7 @@ api.addAssets('${relPath}', 'client').`); // Recover by ignoring this source file (as best we can -- the // handler might already have emitted resources) } - }); + } // *** Determine captured variables var declaredExports = _.map(inputSourceArch.declaredExports, function (symbol) { @@ -651,16 +655,16 @@ api.addAssets('${relPath}', 'client').`); if (! process.env.METEOR_FORCE_PORTABLE) { // Make sure we've rebuilt these npm packages according to the current // process.{platform,arch,versions}. - _.each(nodeModulesDirectories, nmd => { + for (const nmd of Object.values(nodeModulesDirectories)) { if (nmd.local) { // Meteor never attempts to modify the contents of local // node_modules directories (such as the one in the root directory // of an application), so we call nmd.rebuildIfNonPortable() only // when nmd.local is false. } else { - nmd.rebuildIfNonPortable(); + await nmd.rebuildIfNonPortable(); } - }); + } if (process.env.METEOR_ALLOW_NON_PORTABLE || isopk.name === "meteor-tool") { @@ -698,7 +702,7 @@ api.addAssets('${relPath}', 'client').`); }; }); -function runLinters({inputSourceArch, isopackCache, sources, +async function runLinters({inputSourceArch, isopackCache, sources, sourceProcessorSet, watchSet}) { // The buildmessage context here is for linter warnings only! runLinters // should not do anything that can have a real build failure. @@ -730,7 +734,7 @@ function runLinters({inputSourceArch, isopackCache, sources, // exists instead of failing because a dependency does not have an 'os' // unibuild. const whichArch = inputSourceArch.arch === 'os' - ? archinfo.host() : inputSourceArch.arch; + ? await archinfo.host() : inputSourceArch.arch; // For linters, figure out what are the global imports from other packages // that we use directly, or are implied. @@ -740,7 +744,7 @@ function runLinters({inputSourceArch, isopackCache, sources, globalImports.push('Npm', 'Assets'); } - compiler.eachUsedUnibuild({ + await compiler.eachUsedUnibuild({ dependencies: inputSourceArch.uses, arch: whichArch, isopackCache: isopackCache, @@ -817,14 +821,14 @@ function runLinters({inputSourceArch, isopackCache, sources, }); // Run linters on files. This skips linters that don't have any files. - _.each(sourceItemsForLinter, ({sourceProcessor, sources}) => { + for (const {sourceProcessor, sources} of Object.values(sourceItemsForLinter)) { const sourcesToLint = sources.map( - wrappedSource => new linterPluginModule.LintingFile(wrappedSource) + wrappedSource => new linterPluginModule.LintingFile(wrappedSource) ); const markedLinter = buildmessage.markBoundary( - sourceProcessor.userPlugin.processFilesForPackage, - sourceProcessor.userPlugin + sourceProcessor.userPlugin.processFilesForPackage, + sourceProcessor.userPlugin ); function archToString(arch) { @@ -840,27 +844,27 @@ function runLinters({inputSourceArch, isopackCache, sources, throw new Error("Don't know how to display the arch: " + arch); } - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "linting files with " + - sourceProcessor.isopack.name + - " for " + - inputSourceArch.pkg.displayName() + - " (" + archToString(inputSourceArch.arch) + ")" - }, () => { + sourceProcessor.isopack.name + + " for " + + inputSourceArch.pkg.displayName() + + " (" + archToString(inputSourceArch.arch) + ")" + }, async () => { try { - Promise.await(markedLinter(sourcesToLint, { + await markedLinter(sourcesToLint, { globals: globalImports - })); + }); } catch (e) { buildmessage.exception(e); } }); - }); + } }; // takes an isopack and returns a list of packages isopack depends on, // containing at least one plugin -export function getActivePluginPackages(isopk, { +export async function getActivePluginPackages(isopk, { uses, isopackCache, pluginProviderPackageNames, @@ -892,9 +896,9 @@ export function getActivePluginPackages(isopk, { // // We pass archinfo.host here, not self.arch, because it may be more specific, // and because plugins always have to run on the host architecture. - compiler.eachUsedUnibuild({ + await compiler.eachUsedUnibuild({ dependencies: uses, - arch: archinfo.host(), + arch: await archinfo.host(), isopackCache: isopackCache, skipUnordered: true // implicitly skip weak deps by not specifying acceptableWeakPackages option @@ -923,7 +927,7 @@ export function getActivePluginPackages(isopk, { // options.isopackCache. // // Skips isobuild:* pseudo-packages. -compiler.eachUsedUnibuild = function ( +compiler.eachUsedUnibuild = async function ( options, callback) { buildmessage.assertInCapture(); var dependencies = options.dependencies; @@ -968,7 +972,7 @@ compiler.eachUsedUnibuild = function ( continue; } - var unibuild = usedPackage.getUnibuildAtArch(arch); + var unibuild = await usedPackage.getUnibuildAtArch(arch); if (!unibuild) { // The package exists but there's no unibuild for us. A buildmessage has // already been issued. Recover by skipping. @@ -980,7 +984,7 @@ compiler.eachUsedUnibuild = function ( } processedUnibuildId[unibuild.id] = true; - callback(unibuild, { + await callback(unibuild, { unordered: !!use.unordered, weak: !!use.weak }); diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index 371cd2f9cd..9b95079500 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -56,7 +56,7 @@ export class IsopackCache { self.allLoadedLocalPackagesWatchSet = new watch.WatchSet; } - buildLocalPackages(rootPackageNames) { + async buildLocalPackages(rootPackageNames) { var self = this; buildmessage.assertInCapture(); @@ -66,13 +66,13 @@ export class IsopackCache { var onStack = {}; if (rootPackageNames) { - _.each(rootPackageNames, function (name) { - self._ensurePackageLoaded(name, onStack); - }); + for (const name of rootPackageNames) { + await self._ensurePackageLoaded(name, onStack); + } } else { - self._packageMap.eachPackage(function (name, packageInfo) { - self._ensurePackageLoaded(name, onStack); - requestGarbageCollection(); + await self._packageMap.eachPackage(async function (name) { + await self._ensurePackageLoaded(name, onStack); + await requestGarbageCollection(); }); } } @@ -140,7 +140,7 @@ export class IsopackCache { return null; } - uses(isopack, name, arch) { + async uses(isopack, name, arch) { if (! isopack) { return false; } @@ -150,21 +150,23 @@ export class IsopackCache { return true; } - const unibuild = isopack.getUnibuildAtArch(arch); + const unibuild = await isopack.getUnibuildAtArch(arch); if (! unibuild) { return false; } - return _.some(unibuild.uses, use => { - return this.implies( - this._isopacks[use.package], - name, - arch, + for (const use of unibuild.uses) { + const implies = await this.implies( + this._isopacks[use.package], + name, + arch, ); - }); + + if (implies) return implies; + } } - implies(isopack, name, arch) { + async implies(isopack, name, arch) { if (! isopack) { return false; } @@ -174,28 +176,30 @@ export class IsopackCache { return true; } - const unibuild = isopack.getUnibuildAtArch(arch); + const unibuild = await isopack.getUnibuildAtArch(arch); if (! unibuild) { return false; } - return _.some(unibuild.implies, imp => { - return this.implies( - this._isopacks[imp.package], - name, - arch, + for (const imp of unibuild.implies) { + const implies = await this.implies( + this._isopacks[imp.package], + name, + arch, ); - }); + + if (implies) return implies; + } } - _ensurePackageLoaded(name, onStack) { + async _ensurePackageLoaded(name, onStack) { var self = this; buildmessage.assertInCapture(); if (_.has(self._isopacks, name)) { return; } - var ensureLoaded = function (depName) { + var ensureLoaded = async function (depName) { if (_.has(onStack, depName)) { buildmessage.error("circular dependency between packages " + name + " and " + depName); @@ -203,7 +207,7 @@ export class IsopackCache { return; } onStack[depName] = true; - self._ensurePackageLoaded(depName, onStack); + await self._ensurePackageLoaded(depName, onStack); delete onStack[depName]; }; @@ -229,17 +233,17 @@ export class IsopackCache { if (packageInfo.kind === 'local') { var packageNames = packageInfo.packageSource.getPackagesToLoadFirst(self._packageMap); - buildmessage.enterJob("preparing to build package " + name, function () { - _.each(packageNames, function (depName) { - ensureLoaded(depName); - }); + await buildmessage.enterJob("preparing to build package " + name, async function () { + for (const depName of packageNames) { + await ensureLoaded(depName); + } // If we failed to load something that this package depends on, don't // load it. if (buildmessage.jobHasMessages()) { return; } - Profile.time('IsopackCache Build local isopack', () => { - self._loadLocalPackage(name, packageInfo, previousIsopack); + await Profile.time('IsopackCache Build local isopack', () => { + return self._loadLocalPackage(name, packageInfo, previousIsopack); }); }); } else if (packageInfo.kind === 'versioned') { @@ -252,7 +256,7 @@ export class IsopackCache { var isopack = null, packagesToLoad = []; - Profile.time('IsopackCache Load local isopack', () => { + await Profile.time('IsopackCache Load local isopack', async () => { if (previousIsopack) { // We can always reuse a previous Isopack for a versioned package, since // we assume that it never changes. (Admittedly, this means we won't @@ -262,9 +266,9 @@ export class IsopackCache { } if (! isopack) { // Load the isopack from disk. - buildmessage.enterJob( + await buildmessage.enterJob( "loading package " + name + "@" + packageInfo.version, - function () { + async function () { var pluginCacheDir; if (self._pluginCacheDirRoot) { pluginCacheDir = self._pluginCacheDirForVersion( @@ -276,7 +280,7 @@ export class IsopackCache { var Isopack = isopackModule.Isopack; isopack = new Isopack(); - isopack.initFromPath(name, isopackPath, { + await isopack.initFromPath(name, isopackPath, { pluginCacheDir: pluginCacheDir }); // If loading the isopack fails, then we don't need to look for more @@ -285,7 +289,7 @@ export class IsopackCache { if (buildmessage.jobHasMessages()) { return; } - packagesToLoad = isopack.getStrongOrderedUsedAndImpliedPackages(); + packagesToLoad = await isopack.getStrongOrderedUsedAndImpliedPackages(); }); } }); @@ -302,10 +306,10 @@ export class IsopackCache { } } - _loadLocalPackage(name, packageInfo, previousIsopack) { + async _loadLocalPackage(name, packageInfo, previousIsopack) { var self = this; buildmessage.assertInCapture(); - buildmessage.enterJob("building package " + name, function () { + await buildmessage.enterJob("building package " + name, async function () { var isopack; if (previousIsopack && self._checkUpToDatePreloaded(previousIsopack)) { isopack = previousIsopack; @@ -327,7 +331,7 @@ export class IsopackCache { pluginCacheDir && files.mkdir_p(pluginCacheDir); isopack = new isopackModule.Isopack(); - isopack.initFromPath(name, self._isopackDir(name), { + await isopack.initFromPath(name, self._isopackDir(name), { isopackBuildInfoJson: isopackBuildInfoJson, pluginCacheDir: pluginCacheDir }); @@ -343,14 +347,15 @@ export class IsopackCache { // Because we don't save linter messages to disk, we have to relint // this package. // XXX save linter messages to disk? - self._lintLocalPackage(packageInfo.packageSource, isopack); + await self._lintLocalPackage(packageInfo.packageSource, isopack); } else { // Nope! Compile it again. Give it a fresh plugin cache. if (pluginCacheDir) { - files.rm_recursive(pluginCacheDir); + await files.rm_recursive(pluginCacheDir); files.mkdir_p(pluginCacheDir); } - isopack = compiler.compile(packageInfo.packageSource, { + + isopack = await compiler.compile(packageInfo.packageSource, { packageMap: self._packageMap, isopackCache: self, includeCordovaUnibuild: self._includeCordovaUnibuild, @@ -364,10 +369,10 @@ export class IsopackCache { if (! buildmessage.jobHasMessages()) { // Lint the package. We do this before saving so that the linter can // augment the saved-to-disk WatchSet with linter-specific files. - self._lintLocalPackage(packageInfo.packageSource, isopack); + await self._lintLocalPackage(packageInfo.packageSource, isopack); if (self.cacheDir) { // Save to disk, for next time! - isopack.saveToPath(self._isopackDir(name), { + await isopack.saveToPath(self._isopackDir(name), { includeIsopackBuildInfo: true, isopackCache: self, }); @@ -385,12 +390,12 @@ export class IsopackCache { // Runs appropriate linters on a package. It also augments their unibuilds' // WatchSets with files used by the linter. - _lintLocalPackage(packageSource, isopack) { + async _lintLocalPackage(packageSource, isopack) { buildmessage.assertInJob(); if (!this._shouldLintPackage(packageSource)) { return; } - const {warnings, linted} = compiler.lint(packageSource, { + const {warnings, linted} = await compiler.lint(packageSource, { isopackCache: this, isopack: isopack, includeCordovaUnibuild: this._includeCordovaUnibuild diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index ceb439f9a9..87c0b57003 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -401,7 +401,7 @@ Object.assign(Isopack.prototype, { // Return the unibuild of the package to use for a given target architecture // (eg, 'os.linux.x86_64' or 'web'), or throw an exception if that // packages can't be loaded under these circumstances. - getUnibuildAtArch: Profile("Isopack#getUnibuildAtArch", function (arch) { + getUnibuildAtArch: Profile("Isopack#getUnibuildAtArch", async function (arch) { var self = this; let chosenArch = archinfo.mostSpecificMatch( @@ -412,7 +412,7 @@ Object.assign(Isopack.prototype, { // are processing a local package with binary npm deps). Search // again for the host version, which might find the Mac version. chosenArch = - archinfo.mostSpecificMatch(archinfo.host(), _.pluck(self.unibuilds, 'arch')); + archinfo.mostSpecificMatch(await archinfo.host(), _.pluck(self.unibuilds, 'arch')); } if (! chosenArch) { buildmessage.error( @@ -435,7 +435,7 @@ Object.assign(Isopack.prototype, { // If this package has plugins, initialize them (run the startup // code in them so that they register their extensions). Idempotent. - ensurePluginsInitialized: Profile("Isopack#ensurePluginsInitialized", function () { + ensurePluginsInitialized: Profile("Isopack#ensurePluginsInitialized", async function () { var self = this; buildmessage.assertInJob(); @@ -451,31 +451,31 @@ Object.assign(Isopack.prototype, { self.sourceProcessors.minifier = new buildPluginModule.SourceProcessorSet( self.displayName(), { singlePackage: true }); - _.each(self.plugins, function (pluginsByArch, name) { + for (const [name, pluginsByArch] of Object.entries(self.plugins)) { var arch = archinfo.mostSpecificMatch( - archinfo.host(), Object.keys(pluginsByArch)); + await archinfo.host(), Object.keys(pluginsByArch)); if (! arch) { buildmessage.error("package `" + name + "` is built for incompatible " + - "architecture"); + "architecture"); // Recover by ignoring plugin // XXX does this recovery work? - return; + continue; } var plugin = pluginsByArch[arch]; - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "loading plugin `" + name + - "` from package `" + self.name + "`" + "` from package `" + self.name + "`" // don't necessarily have rootPath anymore // (XXX we do, if the isopack was locally built, which is // the important case for debugging. it'd be nice to get this // case right.) - }, function () { + }, async function () { // Make a new Plugin API object for this plugin. var Plugin = self._makePluginApi(name); - plugin.load({ Plugin: Plugin, Profile: Profile }); + await plugin.load({ Plugin: Plugin, Profile: Profile }); }); - }); + } // Instantiate each of the registered batch plugins. Note that we don't // do this directly in the registerCompiler (etc) call, because we want @@ -483,11 +483,11 @@ Object.assign(Isopack.prototype, { // Plugin.registerCompiler({...}, function () { return new C; }); // var C = function () {...} // and so we want to wait for C to be defined. - _.each(self.sourceProcessors, (sourceProcessorSet) => { - _.each(sourceProcessorSet.allSourceProcessors, (sourceProcessor) => { - sourceProcessor.instantiatePlugin(); - }); - }); + for (const sourceProcessorSet of Object.values(self.sourceProcessors)) { + for (const sourceProcessor of sourceProcessorSet.allSourceProcessors) { + await sourceProcessor.instantiatePlugin(); + } + } self._pluginsInitialized = true; }), @@ -828,7 +828,7 @@ Object.assign(Isopack.prototype, { return self._loadUnibuildsFromPath(name, dir, options); }), - _loadUnibuildsFromPath: function (name, dir, options) { + _loadUnibuildsFromPath: async function (name, dir, options) { var self = this; options = options || {}; @@ -838,7 +838,7 @@ Object.assign(Isopack.prototype, { // realpath'ing dir. dir = files.realpath(dir); - var {metadata: mainJson} = Isopack.readMetadataFromDirectory(dir); + var {metadata: mainJson} = await Isopack.readMetadataFromDirectory(dir); if (! mainJson) { throw new Error("No metadata files found for isopack at: " + dir); } @@ -1009,7 +1009,7 @@ Object.assign(Isopack.prototype, { // of this flag is allow us to optimize cases that never need to write the // older format, such as the per-app isopack cache.) // - isopackCache: isopack cache in which this isopack is registered - saveToPath: Profile("Isopack#saveToPath", function (outputDir, { + saveToPath: Profile("Isopack#saveToPath", async function (outputDir, { includePreCompilerPluginIsopackVersions, includeIsopackBuildInfo, isopackCache = null, @@ -1386,10 +1386,10 @@ Object.assign(Isopack.prototype, { } }), - _writeTool: Profile("Isopack#_writeTool", function (builder) { + _writeTool: Profile("Isopack#_writeTool", async function (builder) { var self = this; - var pathsToCopy = utils.runGitInCheckout( + var pathsToCopy = await utils.runGitInCheckout( 'ls-tree', '-r', '--name-only', @@ -1443,8 +1443,8 @@ Object.assign(Isopack.prototype, { }); // Set up builder to write to the correct directory - var toolPath = 'mt-' + archinfo.host(); - builder = builder.enter(toolPath); + var toolPath = 'mt-' + await archinfo.host(); + builder = await builder.enter(toolPath); const sourceRootDir = files.getCurrentToolsDir(); builder.copyTranspiledModules(pathsToTranspile, { @@ -1452,7 +1452,7 @@ Object.assign(Isopack.prototype, { needToTranspile: true, }); - var gitSha = utils.runGitInCheckout('rev-parse', 'HEAD'); + var gitSha = await utils.runGitInCheckout('rev-parse', 'HEAD'); builder.reserve('isopackets', {directory: true}); builder.write('.git_version.txt', {data: Buffer.from(gitSha, 'utf8')}); @@ -1478,30 +1478,31 @@ Object.assign(Isopack.prototype, { // Build all of the isopackets now, so that no build step is required when // you're actually running meteor from a release in order to load packages. - var isopacketBuildContext = makeIsopacketBuildContext(); + var isopacketBuildContext = await makeIsopacketBuildContext(); - var messages = buildmessage.capture(function () { + var messages = await buildmessage.capture(async function () { // We rebuild them in the order listed in ISOPACKETS. This is not strictly // necessary here, since any isopackets loaded as part of the build // process are going to be the current tool's isopackets, not the // isopackets that we're writing out. - _.each(ISOPACKETS, function (packages, isopacketName) { + for (const [isopacketName, packages] of ISOPACKETS) { + requestGarbageCollection(); - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "compiling " + isopacketName + " packages for the tool" - }, function () { - isopacketBuildContext.isopackCache.buildLocalPackages(packages); + }, async function () { + await isopacketBuildContext.isopackCache.buildLocalPackages(packages); if (buildmessage.jobHasMessages()) { return; } - var image = bundler.buildJsImage({ + var image = (await bundler.buildJsImage({ name: "isopacket-" + isopacketName, packageMap: isopacketBuildContext.packageMap, isopackCache: isopacketBuildContext.isopackCache, use: packages - }).image; + })).image; if (buildmessage.jobHasMessages()) { return; } @@ -1509,9 +1510,9 @@ Object.assign(Isopack.prototype, { requestGarbageCollection(); image.write( - builder.enter(files.pathJoin('isopackets', isopacketName))); + builder.enter(files.pathJoin('isopackets', isopacketName))); }); - }); + } }); // This is a build step ... but it's one that only happens in development, // and similar to a isopacket load failure, it can just crash the app @@ -1524,7 +1525,7 @@ Object.assign(Isopack.prototype, { return [{ name: 'meteor', - arch: archinfo.host(), + arch: await archinfo.host(), path: toolPath }]; }), diff --git a/tools/isobuild/meteor-npm.js b/tools/isobuild/meteor-npm.js index dbee6386fc..cbf39da843 100644 --- a/tools/isobuild/meteor-npm.js +++ b/tools/isobuild/meteor-npm.js @@ -58,7 +58,7 @@ var NpmFailure = function () {}; // @param npmDependencies {Object} dependencies that should be // installed, eg {tar: '0.1.6', gcd: '0.0.0'}. If falsey or empty, // will remove the .npm directory instead. -meteorNpm.updateDependencies = function (packageName, +meteorNpm.updateDependencies = async function (packageName, packageNpmDir, npmDependencies, quiet) { @@ -84,7 +84,7 @@ meteorNpm.updateDependencies = function (packageName, // It didn't exist, which is exactly what we wanted. return false; } - files.rm_recursive(newPackageNpmDir); + await files.rm_recursive(newPackageNpmDir); return false; } @@ -99,19 +99,19 @@ meteorNpm.updateDependencies = function (packageName, // proceed. if (files.exists(packageNpmDir) && ! files.exists(files.pathJoin(packageNpmDir, 'npm-shrinkwrap.json'))) { - files.rm_recursive(packageNpmDir); + await files.rm_recursive(packageNpmDir); } if (files.exists(packageNpmDir)) { // we already nave a .npm directory. update it appropriately with some // ceremony involving: // `npm install`, `npm install name@version`, `npm shrinkwrap` - updateExistingNpmDirectory( + await updateExistingNpmDirectory( packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet); } else { // create a fresh .npm directory with `npm install // name@version` and `npm shrinkwrap` - createFreshNpmDirectory( + await createFreshNpmDirectory( packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet); } } catch (e) { @@ -126,7 +126,7 @@ meteorNpm.updateDependencies = function (packageName, throw e; } finally { if (files.exists(newPackageNpmDir)) { - files.rm_recursive(newPackageNpmDir); + await files.rm_recursive(newPackageNpmDir); } tmpDirs = _.without(tmpDirs, newPackageNpmDir); } @@ -280,7 +280,7 @@ function isDirectory(path) { // Rebuilds any binary dependencies in the given node_modules directory, // and returns true iff anything was rebuilt. meteorNpm.rebuildIfNonPortable = -Profile("meteorNpm.rebuildIfNonPortable", function (nodeModulesDir) { +Profile("meteorNpm.rebuildIfNonPortable", async function (nodeModulesDir) { const dirsToRebuild = []; function scan(dir, scoped) { @@ -364,10 +364,10 @@ Profile("meteorNpm.rebuildIfNonPortable", function (nodeModulesDir) { // The `npm rebuild` command must be run in the parent directory of the // relevant node_modules directory, which in this case is tempDir. - const rebuildResult = runNpmCommand(getRebuildArgs(), tempDir); + const rebuildResult = await runNpmCommand(getRebuildArgs(), tempDir); if (! rebuildResult.success) { buildmessage.error(rebuildResult.error); - files.rm_recursive(tempDir); + await files.rm_recursive(tempDir); return false; } @@ -403,7 +403,7 @@ Profile("meteorNpm.rebuildIfNonPortable", function (nodeModulesDir) { files.renameDirAlmostAtomically(tempPkgDirs[pkgPath], pkgPath); }); - files.rm_recursive(tempDir); + await files.rm_recursive(tempDir); return true; }); @@ -594,7 +594,7 @@ var makeNewPackageNpmDir = function (newPackageNpmDir) { ''/*git diff complains without trailing newline*/].join('\n')); }; -var updateExistingNpmDirectory = function (packageName, newPackageNpmDir, +var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet) { // sanity check on contents of .npm directory @@ -679,7 +679,7 @@ var updateExistingNpmDirectory = function (packageName, newPackageNpmDir, } else { // Otherwise install npmTree.dependencies as if we were creating a new // .npm/package directory, and leave preservedShrinkwrap empty. - installNpmDependencies(npmDependencies, newPackageNpmDir); + await installNpmDependencies(npmDependencies, newPackageNpmDir); // Note: as of npm@4.0.0, npm-shrinkwrap.json files are regarded as // "canonical," meaning `npm install` (without a package argument) @@ -720,7 +720,7 @@ var updateExistingNpmDirectory = function (packageName, newPackageNpmDir, ); // `npm install` - installFromShrinkwrap(newPackageNpmDir); + await installFromShrinkwrap(newPackageNpmDir); files.unlink(newShrinkwrapFile); files.unlink(newPackageJsonFile); @@ -752,7 +752,7 @@ function isSubtreeOf(subsetTree, supersetTree, predicate) { return false; } -var createFreshNpmDirectory = function (packageName, newPackageNpmDir, +var createFreshNpmDirectory = async function (packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet) { if (! quiet) { logUpdateDependencies(packageName, npmDependencies); @@ -760,13 +760,13 @@ var createFreshNpmDirectory = function (packageName, newPackageNpmDir, makeNewPackageNpmDir(newPackageNpmDir); - installNpmDependencies(npmDependencies, newPackageNpmDir); + await installNpmDependencies(npmDependencies, newPackageNpmDir); completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir, npmDependencies); }; -function installNpmDependencies(dependencies, dir) { +async function installNpmDependencies(dependencies, dir) { const packageJsonPath = files.pathJoin(dir, "package.json"); const packageJsonExisted = files.exists(packageJsonPath); @@ -776,10 +776,10 @@ function installNpmDependencies(dependencies, dir) { ); try { - Object.keys(dependencies).forEach(name => { + for (const name of Object.keys(dependencies)) { const version = dependencies[name]; - installNpmModule(name, version, dir); - }); + await installNpmModule(name, version, dir); + } } finally { if (! packageJsonExisted) { files.unlink(packageJsonPath); @@ -881,7 +881,7 @@ Profile("meteorNpm.runNpmCommand", function (args, cwd) { return getEnv({ devBundle: devBundleDir - }).then(env => { + }).then(async env => { const opts = { env: env, maxBuffer: 10 * 1024 * 1024 @@ -894,7 +894,7 @@ Profile("meteorNpm.runNpmCommand", function (args, cwd) { // Make sure we don't honor any user-provided configuration files. env.npm_config_userconfig = npmUserConfigFile; - return new Promise(function (resolve) { + return await new Promise(function (resolve) { require('child_process').execFile( commandToRun, args, opts, function (err, stdout, stderr) { if (meteorNpm._printNpmCalls) { @@ -909,9 +909,8 @@ Profile("meteorNpm.runNpmCommand", function (args, cwd) { }); } ); - }).await(); - - }).await(); + }); + }); }); // Gets a JSON object from `npm ls --json` (getInstalledDependenciesTree) or @@ -1050,7 +1049,7 @@ var getShrinkwrappedDependencies = function (dir) { return treeToDependencies(getShrinkwrappedDependenciesTree(dir)); }; -const installNpmModule = meteorNpm.installNpmModule = (name, version, dir) => { +const installNpmModule = meteorNpm.installNpmModule = async (name, version, dir) => { const installArg = utils.isNpmUrl(version) ? version : `${name}@${version}`; @@ -1058,7 +1057,7 @@ const installNpmModule = meteorNpm.installNpmModule = (name, version, dir) => { // We don't use npm.commands.install since we couldn't figure out // how to silence all output (specifically the installed tree which // is printed out with `console.log`) - const result = runNpmCommand(["install", installArg], dir); + const result = await runNpmCommand(["install", installArg], dir); if (! result.success) { const pkgNotFound = @@ -1080,7 +1079,7 @@ const installNpmModule = meteorNpm.installNpmModule = (name, version, dir) => { } // Recover by returning false from updateDependencies - throw new NpmFailure; + throw new NpmFailure(); } const pkgDir = files.pathJoin(dir, "node_modules", name); @@ -1107,19 +1106,19 @@ const installNpmModule = meteorNpm.installNpmModule = (name, version, dir) => { "The following file paths in the NPM module '" + name + "' have colons, ':', which won't work on Windows:\n" + firstTen.join("\n")); - throw new NpmFailure; + throw new NpmFailure(); } } }; -var installFromShrinkwrap = function (dir) { +var installFromShrinkwrap = async function (dir) { if (! files.exists(files.pathJoin(dir, "npm-shrinkwrap.json"))) { throw new Error( "Can't call `npm install` without a npm-shrinkwrap.json file present"); } // `npm install`, which reads npm-shrinkwrap.json. - var result = runNpmCommand(["install"], dir); + var result = await runNpmCommand(["install"], dir); if (! result.success) { buildmessage.error( @@ -1128,7 +1127,7 @@ var installFromShrinkwrap = function (dir) { ); // Recover by returning false from updateDependencies - throw new NpmFailure; + throw new NpmFailure(); } const nodeModulesDir = files.pathJoin(dir, "node_modules"); diff --git a/tools/packaging/catalog/catalog-local.js b/tools/packaging/catalog/catalog-local.js index dda1ba5baa..de3b3e8c0a 100644 --- a/tools/packaging/catalog/catalog-local.js +++ b/tools/packaging/catalog/catalog-local.js @@ -135,7 +135,7 @@ Object.assign(LocalCatalog.prototype, { // are package source trees. Takes precedence over packages found // via localPackageSearchDirs. // - buildingIsopackets: true if we are building isopackets - initialize(options) { + async initialize(options) { var self = this; buildmessage.assertInCapture(); @@ -168,8 +168,8 @@ Object.assign(LocalCatalog.prototype, { self.explicitlyAddedLocalPackageDirs = [], ); - self._computeEffectiveLocalPackages(); - self._loadLocalPackages(options.buildingIsopackets); + await self._computeEffectiveLocalPackages(); + await self._loadLocalPackages(options.buildingIsopackets); self.initialized = true; }, @@ -313,7 +313,7 @@ Object.assign(LocalCatalog.prototype, { self.effectiveLocalPackageDirs = []; - buildmessage.enterJob("looking for packages", function () { + return buildmessage.enterJob("looking for packages", function () { _.each(self.explicitlyAddedLocalPackageDirs, (explicitDir) => { const packageJsPath = files.pathJoin(explicitDir, "package.js"); const packageJsHash = optimisticHashOrNull(packageJsPath); @@ -389,12 +389,12 @@ Object.assign(LocalCatalog.prototype, { // (note: this is the behavior that we want for overriding things in // checkout. It is not clear that you get good UX if you have two packages // with the same name in your app. We don't check that.) - var initSourceFromDir = function (packageDir, definiteName) { - var packageSource = new PackageSource; - buildmessage.enterJob({ + var initSourceFromDir = async function (packageDir, definiteName) { + var packageSource = new PackageSource(); + return buildmessage.enterJob({ title: "reading package from `" + packageDir + "`", rootPath: packageDir - }, function () { + }, async function () { var initFromPackageDirOptions = { buildingIsopackets: !! buildingIsopackets }; @@ -404,7 +404,7 @@ Object.assign(LocalCatalog.prototype, { if (definiteName) { initFromPackageDirOptions.name = definiteName; } - packageSource.initFromPackageDir(packageDir, initFromPackageDirOptions); + await packageSource.initFromPackageDir(packageDir, initFromPackageDirOptions); if (buildmessage.jobHasMessages()) return; // recover by ignoring @@ -454,17 +454,17 @@ Object.assign(LocalCatalog.prototype, { // marked as test packages by package source, so we will not recurse // infinitely), then process that too. if (!packageSource.isTest && packageSource.testName) { - initSourceFromDir(packageSource.sourceRoot, packageSource.testName); + await initSourceFromDir(packageSource.sourceRoot, packageSource.testName); } }); }; // Load the package sources for packages and their tests into // self.packages. - buildmessage.enterJob('initializing packages', function() { - _.each(self.effectiveLocalPackageDirs, function (dir) { - initSourceFromDir(dir); - }); + return buildmessage.enterJob('initializing packages', async function() { + for (const dir of self.effectiveLocalPackageDirs) { + await initSourceFromDir(dir); + } }); }, diff --git a/tools/packaging/catalog/catalog-remote.js b/tools/packaging/catalog/catalog-remote.js index 5ad6af79a3..005dff6f2f 100644 --- a/tools/packaging/catalog/catalog-remote.js +++ b/tools/packaging/catalog/catalog-remote.js @@ -39,7 +39,7 @@ var Mutex = function () { }; Object.assign(Mutex.prototype, { - lock: function () { + lock: async function () { var self = this; while (true) { @@ -48,13 +48,13 @@ Object.assign(Mutex.prototype, { return; } - new Promise(function (resolve) { + await new Promise(function (resolve) { self._resolvers.push(resolve); - }).await(); + }); } }, - unlock: function () { + unlock: async function () { var self = this; if (!self._locked) { @@ -64,7 +64,7 @@ Object.assign(Mutex.prototype, { self._locked = false; var resolve = self._resolvers.shift(); if (resolve) { - resolve(); + await resolve(); } } }); @@ -91,7 +91,7 @@ Object.assign(Txn.prototype, { }, // Start a transaction - begin: function (mode) { + begin: async function (mode) { var self = this; // XXX: Use DEFERRED mode? @@ -101,12 +101,12 @@ Object.assign(Txn.prototype, { throw new Error("Transaction already started"); } - self.db._execute("BEGIN " + mode + " TRANSACTION"); + await self.db._execute("BEGIN " + mode + " TRANSACTION"); self.started = true; }, // Releases resources from the transaction; Rollback if commit not already called. - close: function () { + close: async function () { var self = this; if (self.closed) { @@ -117,16 +117,16 @@ Object.assign(Txn.prototype, { return; } - self.db._execute("ROLLBACK TRANSACTION"); + await self.db._execute("ROLLBACK TRANSACTION"); self.committed = false; self.closed = true; }, // Commits the transaction. close() will then be a no-op - commit: function () { + commit: async function () { var self = this; - self.db._execute("END TRANSACTION"); + await self.db._execute("END TRANSACTION"); self.committed = true; self.closed = true; } @@ -175,14 +175,14 @@ Object.assign(Db.prototype, { }, // Runs functions serially, in a mutex - _serialize: function (f) { + _serialize: async function (f) { var self = this; try { - self._transactionMutex.lock(); - return f(); + await self._transactionMutex.lock(); + return await f(); } finally { - self._transactionMutex.unlock(); + await self._transactionMutex.unlock(); } }, @@ -199,10 +199,10 @@ Object.assign(Db.prototype, { }, // Runs the function inside a transaction block - runInTransaction: function (action) { + runInTransaction: async function (action) { var self = this; - var runOnce = Profile("sqlite query", function () { + var runOnce = Profile("sqlite query", async function () { var txn = new Txn(self); var t1 = Date.now(); @@ -211,15 +211,15 @@ Object.assign(Db.prototype, { var result = null; var resultError = null; - txn.begin(); + await txn.begin(); try { - result = action(txn); - txn.commit(); + result = await action(txn); + await txn.commit(); } catch (err) { resultError = err; } finally { try { - txn.close(); + await txn.close(); } catch (e) { // We don't have a lot of options here... Console.warn("Error closing transaction", e); @@ -243,7 +243,7 @@ Object.assign(Db.prototype, { for (var attempt = 0; ; attempt++) { try { - return self._serialize(runOnce); + return await self._serialize(runOnce); } catch (err) { var retry = false; // Grr... doesn't expose error code; must string-match @@ -261,7 +261,7 @@ Object.assign(Db.prototype, { // Wait on average BUSY_RETRY_INTERVAL, but randomize to avoid thundering herd var t = (Math.random() + 0.5) * BUSY_RETRY_INTERVAL; - utils.sleepMs(t); + await utils.sleepMs(t); } }, @@ -320,9 +320,9 @@ Object.assign(Db.prototype, { return rows; }, - // Runs a query synchronously, returning no rows + // Runs a query, returning no rows // Hidden to enforce transaction usage - _execute: function (sql, params) { + _execute: async function (sql, params) { var self = this; var prepared = null; @@ -335,7 +335,7 @@ Object.assign(Db.prototype, { // entirely.) var prepare = self._autoPrepare && !_.isEmpty(params); if (prepare) { - prepared = self._prepareWithCache(sql); + prepared = await self._prepareWithCache(sql); } if (DEBUG_SQL) { @@ -344,7 +344,7 @@ Object.assign(Db.prototype, { //Console.debug("Executing SQL ", sql, params); - var ret = new Promise(function (resolve, reject) { + var ret = await new Promise(function (resolve, reject) { function callback(err) { err ? reject(err) : resolve({ // Yes, lastID & changes are on this(!) @@ -358,7 +358,7 @@ Object.assign(Db.prototype, { } else { self._db.run(sql, params, callback); } - }).await(); + }); if (DEBUG_SQL) { var t2 = Date.now(); @@ -371,17 +371,17 @@ Object.assign(Db.prototype, { }, // Prepares the statement, caching the result - _prepareWithCache: function (sql) { + _prepareWithCache: async function (sql) { var self = this; var prepared = self._prepared[sql]; if (!prepared) { //Console.debug("Preparing statement: ", sql); - new Promise(function (resolve, reject) { + await new Promise(function (resolve, reject) { prepared = self._db.prepare(sql, function (err) { err ? reject(err) : resolve(); }); - }).await(); + }); self._prepared[sql] = prepared; } @@ -750,7 +750,7 @@ Object.assign(RemoteCatalog.prototype, { }); }, - refresh: function (options) { + refresh: async function (options) { var self = this; options = options || {}; @@ -766,7 +766,7 @@ Object.assign(RemoteCatalog.prototype, { return false; if (options.maxAge) { - var lastSync = self.getMetadata(METADATA_LAST_SYNC); + var lastSync = await self.getMetadata(METADATA_LAST_SYNC); Console.debug("lastSync = ", lastSync); if (lastSync && lastSync.timestamp) { if ((Date.now() - lastSync.timestamp) < options.maxAge) { @@ -778,12 +778,12 @@ Object.assign(RemoteCatalog.prototype, { var updateResult = {}; // XXX This buildmessage.enterJob only exists for showing progress. - buildmessage.enterJob({ title: 'updating package catalog' }, function () { - updateResult = packageClient.updateServerPackageData(self); + await buildmessage.enterJob({ title: 'updating package catalog' }, async function () { + updateResult = await packageClient.updateServerPackageData(self); }); if (updateResult.resetData) { - tropohouse.default.wipeAllPackages(); + await tropohouse.default.wipeAllPackages(); } return true; @@ -934,9 +934,9 @@ Object.assign(RemoteCatalog.prototype, { return result[0]; }, - getMetadata: function(key) { + getMetadata: async function(key) { var self = this; - var row = self.db.runInTransaction(function (txn) { + var row = await self.db.runInTransaction(function (txn) { return self.tableMetadata.find(txn, key); }); if (row) { diff --git a/tools/packaging/catalog/catalog.js b/tools/packaging/catalog/catalog.js index d60df551ae..1585ffbe1d 100644 --- a/tools/packaging/catalog/catalog.js +++ b/tools/packaging/catalog/catalog.js @@ -89,15 +89,16 @@ catalog.refreshOrWarn = function (options) { // Runs 'attempt'; if it fails in a way that can be fixed by refreshing the // official catalog, does that and tries again. -catalog.runAndRetryWithRefreshIfHelpful = function (attempt) { +catalog.runAndRetryWithRefreshIfHelpful = async function (attempt) { buildmessage.assertInJob(); var canRetry = ! (catalog.triedToRefreshRecently || catalog.official.offline); + console.log("11111") // Run `attempt` in a nested buildmessage context. - var messages = buildmessage.capture(function () { - attempt(canRetry); + var messages = await buildmessage.capture(function () { + return attempt(canRetry); }); // Did it work? Great. @@ -119,8 +120,9 @@ catalog.runAndRetryWithRefreshIfHelpful = function (attempt) { // catalog.refreshOrWarn, which is a higher-level function that's allowed to // log. catalog.triedToRefreshRecently = true; + console.log("adsdasd") try { - catalog.official.refresh(); + await catalog.official.refresh(); catalog.refreshFailed = false; } catch (err) { if (err.errorType !== 'DDP.ConnectionError') @@ -128,17 +130,17 @@ catalog.runAndRetryWithRefreshIfHelpful = function (attempt) { // First place the previous errors in the capture. buildmessage.mergeMessagesIntoCurrentJob(messages); // Then put an error representing this DDP error. - buildmessage.enterJob( + await buildmessage.enterJob( "refreshing package catalog to resolve previous errors", function () { - buildmessage.error(err.message); + return buildmessage.error(err.message); } ); return; } // Try again, this time directly in the current buildmessage job. - attempt(false); // canRetry = false + await attempt(false); // canRetry = false }; // As a work-around for [] !== [], we use a function to check whether values are acceptable diff --git a/tools/packaging/package-map.js b/tools/packaging/package-map.js index 3074338dae..83955e752f 100644 --- a/tools/packaging/package-map.js +++ b/tools/packaging/package-map.js @@ -44,9 +44,10 @@ exports.PackageMap = function (versions, options) { }; Object.assign(exports.PackageMap.prototype, { - eachPackage: function (iterator) { + eachPackage: async function (iterator) { var self = this; - _.each(self._map, function (info, packageName) { + + for (const [packageName, info] of Object.entries(self._map)) { // For reasons that are super unclear, if this `_.clone` is inlined into // the `iterator` call, the value produced can mysteriously turn into // undefined on the way into `iterator`. Presumably some sort of memory @@ -54,8 +55,8 @@ Object.assign(exports.PackageMap.prototype, { // exercise in nondeterminism. But this does seem to be a sure-fire way to // fix it, for now. Who knows why, and who knows when it will recur again. var infoClone = _.clone(info); - iterator(packageName, infoClone); - }); + await iterator(packageName, infoClone); + } }, getInfo: function (packageName) { var self = this; diff --git a/tools/project-context.js b/tools/project-context.js index 97786ea20e..d85392d27b 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -313,38 +313,38 @@ Object.assign(ProjectContext.prototype, { readProjectMetadata: function () { // don't generate a profiling report for this stage (Profile.run), // because all we do here is read a handful of files. - this._completeStagesThrough(STAGE.READ_PROJECT_METADATA); + return this._completeStagesThrough(STAGE.READ_PROJECT_METADATA); }, initializeCatalog: function () { - Profile.run('ProjectContext initializeCatalog', () => { - this._completeStagesThrough(STAGE.INITIALIZE_CATALOG); + return Profile.run('ProjectContext initializeCatalog', () => { + return this._completeStagesThrough(STAGE.INITIALIZE_CATALOG); }); }, resolveConstraints: function () { - Profile.run('ProjectContext resolveConstraints', () => { - this._completeStagesThrough(STAGE.RESOLVE_CONSTRAINTS); + return Profile.run('ProjectContext resolveConstraints', () => { + return this._completeStagesThrough(STAGE.RESOLVE_CONSTRAINTS); }); }, downloadMissingPackages: function () { - Profile.run('ProjectContext downloadMissingPackages', () => { - this._completeStagesThrough(STAGE.DOWNLOAD_MISSING_PACKAGES); + return Profile.run('ProjectContext downloadMissingPackages', () => { + return this._completeStagesThrough(STAGE.DOWNLOAD_MISSING_PACKAGES); }); }, buildLocalPackages: function () { - Profile.run('ProjectContext buildLocalPackages', () => { - this._completeStagesThrough(STAGE.BUILD_LOCAL_PACKAGES); + return Profile.run('ProjectContext buildLocalPackages', () => { + return this._completeStagesThrough(STAGE.BUILD_LOCAL_PACKAGES); }); }, saveChangedMetadata: function () { - Profile.run('ProjectContext saveChangedMetadata', () => { - this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); + return Profile.run('ProjectContext saveChangedMetadata', () => { + return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, prepareProjectForBuild: function () { // This is the same as saveChangedMetadata, but if we insert stages after // that one it will continue to mean "fully finished". - Profile.run('ProjectContext prepareProjectForBuild', () => { - this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); + return Profile.run('ProjectContext prepareProjectForBuild', () => { + return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, @@ -352,7 +352,7 @@ Object.assign(ProjectContext.prototype, { var self = this; buildmessage.assertInCapture(); - buildmessage.enterJob('preparing project', function () { + return buildmessage.enterJob('preparing project', async function () { while (self._completedStage !== targetStage) { // This error gets thrown if you request to go to a stage that's earlier // than where you started. Note that the error will be mildly confusing @@ -360,9 +360,11 @@ Object.assign(ProjectContext.prototype, { if (self.completedStage === STAGE.SAVE_CHANGED_METADATA) throw Error("can't find requested stage " + targetStage); + console.log("11111111") + console.log(self._completedStage) // The actual value of STAGE.FOO is the name of the method that takes // you to the next step after FOO. - self[self._completedStage](); + await self[self._completedStage](); if (buildmessage.jobHasMessages()) return; } @@ -384,11 +386,11 @@ Object.assign(ProjectContext.prototype, { // // This should be pretty fast --- for example, we shouldn't worry about // needing to wait for it to be done before we open the runner proxy. - _readProjectMetadata: Profile('_readProjectMetadata', function () { + _readProjectMetadata: Profile('_readProjectMetadata', async function () { var self = this; buildmessage.assertInCapture(); - buildmessage.enterJob('reading project metadata', function () { + await buildmessage.enterJob('reading project metadata', function () { // Ensure this is actually a project directory. self._ensureProjectDir(); if (buildmessage.jobHasMessages()) @@ -777,20 +779,20 @@ Object.assign(ProjectContext.prototype, { // but does not compile the packages. // // Must be run in a buildmessage context. On build error, returns null. - _initializeCatalog: Profile('_initializeCatalog', function () { + _initializeCatalog: Profile('_initializeCatalog', async function () { var self = this; buildmessage.assertInJob(); - catalog.runAndRetryWithRefreshIfHelpful(function () { - buildmessage.enterJob( + await catalog.runAndRetryWithRefreshIfHelpful(function () { + return buildmessage.enterJob( "scanning local packages", - function () { - self.localCatalog = new catalogLocal.LocalCatalog; + async function () { + self.localCatalog = new catalogLocal.LocalCatalog(); self.projectCatalog = new catalog.LayeredCatalog( self.localCatalog, self._officialCatalog); var searchDirs = self._localPackageSearchDirs(); - self.localCatalog.initialize({ + await self.localCatalog.initialize({ localPackageSearchDirs: searchDirs, explicitlyAddedLocalPackageDirs: self._explicitlyAddedLocalPackageDirs }); diff --git a/tools/tool-env/isopackets.js b/tools/tool-env/isopackets.js index a819428bb7..9e5113f77b 100644 --- a/tools/tool-env/isopackets.js +++ b/tools/tool-env/isopackets.js @@ -134,7 +134,7 @@ var isopacketPath = function (isopacketName) { // ensureIsopacketsLoadable is called at startup and ensures that all isopackets // exist on disk as up-to-date loadable programs. var calledEnsure = false; -export function ensureIsopacketsLoadable() { +export async function ensureIsopacketsLoadable() { if (calledEnsure) { throw Error("can't ensureIsopacketsLoadable twice!"); } @@ -155,16 +155,16 @@ export function ensureIsopacketsLoadable() { var failedPackageBuild = false; // Look at each isopacket. Check to see if it's on disk and up to date. If // not, build it. We rebuild them in the order listed in ISOPACKETS. - var messages = Console.withProgressDisplayVisible(function () { - return buildmessage.capture(function () { - _.each(ISOPACKETS, function (packages, isopacketName) { + var messages = await Console.withProgressDisplayVisible(function () { + return buildmessage.capture(async function () { + for (const [isopacketName, packages] of Object.entries(ISOPACKETS)) { if (failedPackageBuild) { - return; + continue; } var isopacketRoot = isopacketPath(isopacketName); var existingBuildinfo = files.readJSONOrNull( - files.pathJoin(isopacketRoot, 'isopacket-buildinfo.json')); + files.pathJoin(isopacketRoot, 'isopacket-buildinfo.json')); var needRebuild = !existingBuildinfo; if (!needRebuild && existingBuildinfo.builtBy !== compiler.BUILT_BY) { needRebuild = true; @@ -178,26 +178,26 @@ export function ensureIsopacketsLoadable() { if (!needRebuild) { // Great, it's loadable without a rebuild. loadedIsopackets[isopacketName] = null; - return; + continue; } // We're going to need to build! Make a catalog and loader if we haven't // yet. if (!isopacketBuildContext) { - isopacketBuildContext = makeIsopacketBuildContext(); + isopacketBuildContext = await makeIsopacketBuildContext(); } - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "bundling " + isopacketName + " packages for the tool" - }, function () { + }, async function () { // Build the packages into the in-memory IsopackCache. - isopacketBuildContext.isopackCache.buildLocalPackages(packages); + await isopacketBuildContext.isopackCache.buildLocalPackages(packages); if (buildmessage.jobHasMessages()) { return; } // Now bundle them into a program. - var built = bundler.buildJsImage({ + var built = await bundler.buildJsImage({ name: "isopacket-" + isopacketName, packageMap: isopacketBuildContext.packageMap, isopackCache: isopacketBuildContext.isopackCache, @@ -208,16 +208,16 @@ export function ensureIsopacketsLoadable() { } var builder = new Builder({ outputPath: isopacketRoot }); - builder.writeJson('isopacket-buildinfo.json', { + await builder.writeJson('isopacket-buildinfo.json', { builtBy: compiler.BUILT_BY, watchSet: built.watchSet.toJSON() }); - built.image.write(builder); - builder.complete(); + await built.image.write(builder); + await builder.complete(); // It's loadable now. loadedIsopackets[isopacketName] = null; }); - }); + } }); }); @@ -231,16 +231,16 @@ export function ensureIsopacketsLoadable() { } // Returns a new all-local-packages catalog to be used for building isopackets. -var newIsopacketBuildingCatalog = function () { +var newIsopacketBuildingCatalog = async function () { if (!files.inCheckout()) { throw Error("No need to build isopackets unless in checkout!"); } var catalogLocal = require('../packaging/catalog/catalog-local.js'); var isopacketCatalog = new catalogLocal.LocalCatalog; - var messages = buildmessage.capture( + var messages = await buildmessage.capture( { title: "scanning local core packages" }, - function () { + async function () { const packagesDir = files.pathJoin(files.getCurrentToolsDir(), 'packages'); @@ -249,7 +249,7 @@ var newIsopacketBuildingCatalog = function () { // $PACKAGE_DIRS packages. One side effect of this: we really really // expect them to all build, and we're fine with dying if they don't // (there's no worries about needing to springboard). - isopacketCatalog.initialize({ + await isopacketCatalog.initialize({ localPackageSearchDirs: [ packagesDir, files.pathJoin(packagesDir, "non-core", "*", "packages"), @@ -265,9 +265,9 @@ var newIsopacketBuildingCatalog = function () { return isopacketCatalog; }; -export function makeIsopacketBuildContext() { +export async function makeIsopacketBuildContext() { var context = {}; - var catalog = newIsopacketBuildingCatalog(); + var catalog = await newIsopacketBuildingCatalog(); var versions = {}; _.each(catalog.getAllPackageNames(), function (packageName) { versions[packageName] = catalog.getLatestVersion(packageName).version; @@ -285,14 +285,14 @@ export function makeIsopacketBuildContext() { // 'meteor' tool command invocations, we care more about startup time than // legibility, and the difference is actually observable (eg 25% speedup // loading constraint-solver). - noLineNumbers: true + noLineNumbers: true, }); return context; } // Loads a built isopacket from disk. Always loads (the cache is in 'load', not // this function). Does not run a build process; it must already be built. -var loadIsopacketFromDisk = function (isopacketName) { +var loadIsopacketFromDisk = async function (isopacketName) { var image = bundler.readJsImage( files.pathJoin(isopacketPath(isopacketName), 'program.json')); @@ -305,10 +305,10 @@ var loadIsopacketFromDisk = function (isopacketName) { env.Profile = Profile; var ret; - var messages = buildmessage.capture({ + var messages = await buildmessage.capture({ title: "loading isopacket `" + isopacketName + "`" - }, function () { - ret = image.load(env); + }, async function () { + ret = await image.load(env); }); // This is a build step ... but it's one that only happens in development, so @@ -322,7 +322,7 @@ var loadIsopacketFromDisk = function (isopacketName) { // Run any user startup hooks. while (env.__meteor_bootstrap__.startupHooks.length) { var hook = env.__meteor_bootstrap__.startupHooks.shift(); - hook(); + await hook(); } // Setting this to null tells Meteor.startup to call hooks immediately. env.__meteor_bootstrap__.startupHooks = null; diff --git a/tools/tool-env/profile.ts b/tools/tool-env/profile.ts index 3deb1aaa76..ddbcc9d842 100644 --- a/tools/tool-env/profile.ts +++ b/tools/tool-env/profile.ts @@ -284,7 +284,7 @@ export namespace Profile { return Profile(bucket, f)(); } - export function run(bucket: string, f: () => TResult) { + export async function run(bucket: string, f: () => TResult) { if (! Profile.enabled) { return f(); } @@ -293,14 +293,14 @@ export namespace Profile { // We've kept the calls to Profile.run in the tool disjoint so far, // and should probably keep doing so, but if we mess up, warn and continue. console.log("Warning: Nested Profile.run at " + bucket); - return time(bucket, f); + return await time(bucket, f); } runningName = bucket; print(`(#${reportNum}) Profiling: ${runningName}`); start(); try { - return time(bucket, f); + return await time(bucket, f); } finally { report(); reportNum++; diff --git a/tools/utils/archinfo.ts b/tools/utils/archinfo.ts index d454535355..df524781b9 100644 --- a/tools/utils/archinfo.ts +++ b/tools/utils/archinfo.ts @@ -141,10 +141,10 @@ export const VALID_ARCHITECTURES: Record = { // If you change this, also change scripts/admin/launch-meteor let _host: string | null = null; // memoize -export function host() { +export async function host() { if (!_host) { - const run = function (...args: Array) { - const result = utils.execFileSync(args[0], args.slice(1)).stdout; + const run = async function (...args: Array) { + const result = (await utils.execFile(args[0], args.slice(1))).stdout; if (! result) { throw new Error(`Can't get arch with ${args.join(" ")}?`); @@ -158,10 +158,10 @@ export function host() { if (platform === "darwin") { // Can't just test uname -m = x86_64, because Snow Leopard can // return other values. - const arch = run('uname', '-p'); + const arch = await run('uname', '-p'); if ((arch !== "i386" && arch !== "arm") || - run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") { + await run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") { throw new Error("Only 64-bit Intel and M1 processors are supported on OS X"); } if(arch === "arm"){ @@ -170,7 +170,7 @@ export function host() { _host = "os.osx.x86_64"; } } else if (platform === "linux") { - const machine = run('uname', '-m'); + const machine = await run('uname', '-m'); if (["x86_64", "amd64", "ia64"].includes(machine)) { _host = "os.linux.x86_64"; } else { diff --git a/tools/utils/buildmessage.js b/tools/utils/buildmessage.js index 703263ed82..a80a0f747f 100644 --- a/tools/utils/buildmessage.js +++ b/tools/utils/buildmessage.js @@ -239,9 +239,8 @@ var addChildTracker = function (title) { // begin capturing errors. Alternately you may pass `options` // (otherwise optional) and a job will be created for you based on // `options`. -function capture(options, f) { - var messageSet = new MessageSet; - var parentMessageSet = currentMessageSet.get(); +async function capture(options, f) { + var messageSet = new MessageSet(); var title; if (typeof options === "object" && options.title) { @@ -280,7 +279,9 @@ function capture(options, f) { } try { - f(); + await f(); + } catch (e) { + console.error(e); } finally { progress.reportProgressDone(); @@ -313,7 +314,7 @@ function capture(options, f) { // - rootPath: the absolute path relative to which paths in messages // in this job should be interpreted (omit if there is no way to map // files that this job talks about back to files on disk) -function enterJob(options, f) { +async function enterJob(options, f) { if (typeof options === "function") { f = options; options = {}; @@ -353,12 +354,12 @@ function enterJob(options, f) { resetFns.push(currentNestingLevel.set(nestingLevel + 1)); try { - return f(); + return await f(); } finally { progress.reportProgressDone(); while (resetFns.length) { - resetFns.pop()(); + await resetFns.pop()(); } if (debugBuild) { @@ -385,12 +386,12 @@ function enterJob(options, f) { } try { - return f(); + return await f(); } finally { progress.reportProgressDone(); while (resetFns.length) { - resetFns.pop()(); + await resetFns.pop()(); } if (debugBuild) { @@ -459,6 +460,8 @@ var markBoundary = function (f, context) { var error = function (message, options) { options = options || {}; + console.log("111") + console.trace("dasdsa") if (options.downcase) { message = message.slice(0,1).toLowerCase() + message.slice(1); } @@ -561,7 +564,7 @@ var assertInJob = function () { }; var assertInCapture = function () { - if (! currentMessageSet.get()) { + if (!currentMessageSet.get()) { throw new Error("Expected to be in a buildmessage capture"); } }; diff --git a/tools/utils/fiber-helpers.js b/tools/utils/fiber-helpers.js index 8b5bc2284d..53e7875745 100644 --- a/tools/utils/fiber-helpers.js +++ b/tools/utils/fiber-helpers.js @@ -1,11 +1,20 @@ var _ = require("underscore"); -var Fiber = require("fibers"); -exports.parallelEach = function (collection, callback, context) { +const { AsyncLocalStorage } = require('async_hooks'); +const {debug} = require("util"); +const asyncLocalStorage = new AsyncLocalStorage(); + +const getAslStore = () => asyncLocalStorage.getStore(); +const getValueFromAslStore = key => getAslStore()[key]; +const updateAslStore = (key, value) => getAslStore()[key] = value; + +exports.asyncLocalStorage = asyncLocalStorage; + +exports.parallelEach = async function (collection, callback, context) { const errors = []; context = context || null; - const results = Promise.all(_.map(collection, (...args) => { + const results = await Promise.all(_.map(collection, (...args) => { async function run() { return callback.apply(context, args); } @@ -15,7 +24,7 @@ exports.parallelEach = function (collection, callback, context) { // re-throw the first error after all iterations have completed. errors.push(error); }); - })).await(); + })); if (errors.length > 0) { throw errors[0]; @@ -31,26 +40,13 @@ function disallowedYield() { disallowedYield.disallowed = true; exports.noYieldsAllowed = function (f, context) { - var savedYield = Fiber.yield; - Fiber.yield = disallowedYield; - try { - return f.call(context || null); - } finally { - Fiber.yield = savedYield; - } + // no op since we don't use fibers anymore + return f.call(context || null); }; // Borrowed from packages/meteor/dynamics_nodejs.js // Used by buildmessage -exports.nodeCodeMustBeInFiber = function () { - if (!Fiber.current) { - throw new Error("Meteor code must always run within a Fiber. " + - "Try wrapping callbacks that you pass to non-Meteor " + - "libraries with Meteor.bindEnvironment."); - } -}; - var nextSlot = 0; exports.EnvironmentVariable = function (defaultValue) { var self = this; @@ -60,41 +56,38 @@ exports.EnvironmentVariable = function (defaultValue) { Object.assign(exports.EnvironmentVariable.prototype, { get() { - var self = this; - exports.nodeCodeMustBeInFiber(); + const self = this; + const currentValue = getValueFromAslStore("_meteor_dynamics"); + let returnValue = currentValue && currentValue[self.slot]; - if (!Fiber.current._meteorDynamics) { - return self.defaultValue; + if (!returnValue) { + returnValue = self.defaultValue; } - if (!_.has(Fiber.current._meteorDynamics, self.slot)) { - return self.defaultValue; - } - return Fiber.current._meteorDynamics[self.slot]; + + return returnValue; }, set(value) { - exports.nodeCodeMustBeInFiber(); + const self = this; + const currentValues = getValueFromAslStore("_meteor_dynamics") || {}; - const fiber = Fiber.current; - const currentValues = fiber._meteorDynamics || ( - fiber._meteorDynamics = {} - ); - - const saved = _.has(currentValues, this.slot) - ? currentValues[this.slot] + const saved = _.has(currentValues, self.slot) + ? currentValues[self.slot] : this.defaultValue; - currentValues[this.slot] = value; + currentValues[self.slot] = value; + updateAslStore("_meteor_dynamics", currentValues); return () => { - currentValues[this.slot] = saved; + currentValues[self.slot] = saved; + updateAslStore("_meteor_dynamics", currentValues); }; }, - withValue(value, func) { + async withValue(value, func) { const reset = this.set(value); try { - return func(); + return await func(); } finally { reset(); } @@ -104,29 +97,32 @@ Object.assign(exports.EnvironmentVariable.prototype, { // This is like Meteor.bindEnvironment. // Experimentally, we are NOT including onException or _this in this version. exports.bindEnvironment = function (func) { - exports.nodeCodeMustBeInFiber(); - - var boundValues = _.clone(Fiber.current._meteorDynamics || {}); + var dynamics = getValueFromAslStore("_meteor_dynamics"); + var boundValues = _.clone(dynamics || {}); return function (...args) { - var self = this; + const self = this; - var runWithEnvironment = function () { - var savedValues = Fiber.current._meteorDynamics; + var runWithEnvironment = async function () { + const savedValues = getValueFromAslStore("_meteor_dynamics"); + let ret; try { // Need to clone boundValues in case two fibers invoke this // function at the same time - Fiber.current._meteorDynamics = _.clone(boundValues); - return func.apply(self, args); + // TODO -> Probably not needed + updateAslStore("_meteor_dynamics", boundValues.slice()); + ret = await func.apply(self, args); } finally { - Fiber.current._meteorDynamics = savedValues; + updateAslStore("_meteor_dynamics", savedValues); } + return ret; }; - if (Fiber.current) { + if (getAslStore()) { return runWithEnvironment(); } - Fiber(runWithEnvironment).run(); + + return global.asyncLocalStorage.run({}, runWithEnvironment); }; }; diff --git a/tools/utils/utils.js b/tools/utils/utils.js index 86f3d4d624..7425b31ed8 100644 --- a/tools/utils/utils.js +++ b/tools/utils/utils.js @@ -1,3 +1,5 @@ +import child_process from "child_process"; + var _ = require('underscore'); var semver = require('semver'); var os = require('os'); @@ -194,9 +196,9 @@ exports.sleepMs = function (ms) { return; } - new Promise(function (resolve) { + return new Promise(function (resolve) { setTimeout(resolve, ms); - }).await(); + }); }; // Return a short, high entropy string without too many funny @@ -513,7 +515,7 @@ exports.isValidVersion = function (version, {forCordova}) { }; -exports.execFileSync = function (file, args, opts) { +exports.execFile = async function (file, args, opts) { var child_process = require('child_process'); var { eachline } = require('./eachline'); @@ -534,9 +536,9 @@ exports.execFileSync = function (file, args, opts) { }); return { - success: ! new Promise(function (resolve) { + success: await !new Promise(function (resolve) { p.on('exit', resolve); - }).await(), + }), stdout: "", stderr: "" }; @@ -550,7 +552,7 @@ exports.execFileSync = function (file, args, opts) { stderr: stderr }); }); - }).await(); + }); }; exports.execFileAsync = function (file, args, opts) { @@ -572,16 +574,28 @@ exports.execFileAsync = function (file, args, opts) { eachline(p.stdout, logOutput); eachline(p.stderr, logOutput); + if (!opts) { + return new Promise(function (resolve) { + child_process.execFile(file, args, opts, function (err, stdout, stderr) { + resolve({ + success: ! err, + stdout: stdout, + stderr: stderr + }); + }); + }); + } + return p; }; -exports.runGitInCheckout = function (...args) { +exports.runGitInCheckout = async function (...args) { args.unshift( '--git-dir=' + files.convertToOSPath(files.pathJoin(files.getCurrentToolsDir(), '.git'))); - return exports.execFileSync('git', args).stdout; + return (await exports.execFile('git', args)).stdout; }; exports.Throttled = function (options) { @@ -623,7 +637,7 @@ exports.ThrottledYield = function (options) { }; Object.assign(exports.ThrottledYield.prototype, { - yield: function () { + yield: async function () { var self = this; if (self._throttle.isAllowed()) { // setImmediate allows signals and IO to be processed but doesn't @@ -632,7 +646,7 @@ Object.assign(exports.ThrottledYield.prototype, { // setTimeout 1 (which adds a minimum of 1 ms and often more in delays). // XXX Actually, setImmediate is so fast that we might not even need // to use the throttler at all? - new Promise(setImmediate).await(); + await new Promise(setImmediate); } } }); From 4b31d2b352b46275ec18fc0de7851b6862325025 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 26 Nov 2022 00:09:33 -0300 Subject: [PATCH 0081/1965] Remove Fibers for meteor tools: - Package building is done, at least I hope so! --- tools/isobuild/builder.js | 17 +++++----- tools/isobuild/isopack.js | 61 ++++++++++++++++++------------------ tools/tool-env/isopackets.js | 1 + 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/tools/isobuild/builder.js b/tools/isobuild/builder.js index 8234257fa0..ed099aa395 100644 --- a/tools/isobuild/builder.js +++ b/tools/isobuild/builder.js @@ -117,18 +117,21 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` } } - // Build the output from scratch - if (resetBuildPath) { - files.rm_recursive(this.buildPath).then(() => { - files.mkdir_p(this.buildPath, 0o755); - this.watchSet = new WatchSet(); - }); - } + this.resetBuildPath = resetBuildPath; // XXX cleaner error handling. don't make the humans read an // exception (and, make suitable for use in automated systems) } + async init() { + // Build the output from scratch + if (this.resetBuildPath) { + await files.rm_recursive(this.buildPath); + files.mkdir_p(this.buildPath, 0o755); + this.watchSet = new WatchSet(); + } + } + // Like mkdir_p, but records in self.usedAsFile that we have created // the directories, and takes a path relative to the bundle // root. Throws an exception on failure. diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 87c0b57003..9be94473d5 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -1018,6 +1018,7 @@ Object.assign(Isopack.prototype, { var outputPath = outputDir; var builder = new Builder({ outputPath: outputPath }); + await builder.init(); try { var mainJson = { name: self.name, @@ -1098,13 +1099,13 @@ Object.assign(Isopack.prototype, { var unibuildInfos = []; // Unibuilds - _.each(self.unibuilds, function (unibuild) { + for (const unibuild of self.unibuilds) { // Make up a filename for this unibuild var baseUnibuildName = unibuild.arch; var unibuildDir = - builder.generateFilename(baseUnibuildName, { directory: true }); + builder.generateFilename(baseUnibuildName, { directory: true }); var unibuildJsonFile = - builder.generateFilename(baseUnibuildName + ".json"); + builder.generateFilename(baseUnibuildName + ".json"); mainJson.builds.push({ kind: unibuild.kind, arch: unibuild.arch, @@ -1115,11 +1116,11 @@ Object.assign(Isopack.prototype, { // too hard about how to encode pair (name, arch). if (isopackBuildInfoJson) { isopackBuildInfoJson.unibuildDependencies[unibuildJsonFile] = - unibuild.watchSet.toJSON(); + unibuild.watchSet.toJSON(); } const usesModules = ! isopackCache || - isopackCache.uses(self, "modules", unibuild.arch); + isopackCache.uses(self, "modules", unibuild.arch); const unibuildJson = unibuild.toJSON({ builder, @@ -1132,17 +1133,17 @@ Object.assign(Isopack.prototype, { // original form of the resource object (with the source in a // Buffer, etc) instead of the later version. #HardcodeJs const jsResourcesForLegacyPrelink = - writeLegacyBuilds ? unibuild.getLegacyJsResources() : []; + writeLegacyBuilds ? unibuild.getLegacyJsResources() : []; // Control file for unibuild - builder.writeJson(unibuildJsonFile, unibuildJson); + await builder.writeJson(unibuildJsonFile, unibuildJson); unibuildInfos.push({ unibuild, unibuildJson, jsResourcesForLegacyPrelink, }); - }); + } // If unibuilds included node_modules, copy them in. _.each(npmDirsToCopy, (bundlePath, sourcePath) => { @@ -1200,13 +1201,13 @@ Object.assign(Isopack.prototype, { mainLegacyJson = _.clone(mainJson); mainLegacyJson.builds = []; - _.each(unibuildInfos, function (unibuildInfo) { + for (const unibuildInfo of unibuildInfos) { var unibuild = unibuildInfo.unibuild; var unibuildJson = unibuildInfo.unibuildJson; var jsResourcesForLegacyPrelink = - unibuildInfo.jsResourcesForLegacyPrelink; + unibuildInfo.jsResourcesForLegacyPrelink; var legacyFilename = builder.generateFilename( - unibuild.arch + '-legacy.json'); + unibuild.arch + '-legacy.json'); var legacyDir = unibuild.arch + '-legacy'; mainLegacyJson.builds.push({ kind: unibuild.kind, @@ -1234,7 +1235,7 @@ Object.assign(Isopack.prototype, { // already, in the format that linker.prelink understands. } else { throw Error( - "shouldn't write legacy builds for non-JS/CSS source " + "shouldn't write legacy builds for non-JS/CSS source " + JSON.stringify(resource)); } }); @@ -1251,7 +1252,7 @@ Object.assign(Isopack.prototype, { // assignment differs from that below), ah well. prelinkData = prelinkFile.data; packageVariables = - jsResourcesForLegacyPrelink[0].legacyPrelink.packageVariables; + jsResourcesForLegacyPrelink[0].legacyPrelink.packageVariables; } else { // Determine captured variables, legacy way. First, start with the // exports. We'll add the package variables after running prelink. @@ -1276,15 +1277,15 @@ Object.assign(Isopack.prototype, { // combinedServePath is either [pkgname].js or [pluginName]:plugin.js. // XXX: If we change this, we can get rid of source arch names! combinedServePath: ( - "/packages/" + colonConverter.convert( - unibuild.pkg.name + - (unibuild.kind === "main" ? "" : (":" + unibuild.kind)) + - ".js")), + "/packages/" + colonConverter.convert( + unibuild.pkg.name + + (unibuild.kind === "main" ? "" : (":" + unibuild.kind)) + + ".js")), name: unibuild.pkg.name }); if (results.files.length !== 1) { throw Error("prelink should return 1 file, not " + - results.files.length); + results.files.length); } prelinkFile = results.files[0]; prelinkData = Buffer.from(prelinkFile.source, 'utf8'); @@ -1305,8 +1306,8 @@ Object.assign(Isopack.prototype, { var prelinkResource = { type: 'prelink', file: builder.writeToGeneratedFilename( - files.pathJoin(legacyDir, prelinkFile.servePath), - { data: prelinkData }), + files.pathJoin(legacyDir, prelinkFile.servePath), + { data: prelinkData }), length: prelinkData.length, offset: 0, servePath: prelinkFile.servePath || undefined @@ -1319,9 +1320,9 @@ Object.assign(Isopack.prototype, { // so here's some exhaustive checking of things buffer // _will_ accept. var acceptedByBuffer = _.isString(prelinkFile.sourceMap) - || _.isNumber(prelinkFile.sourceMap) - || _.isArray(prelinkFile.sourceMap) - || (prelinkFile.sourceMap instanceof Buffer); + || _.isNumber(prelinkFile.sourceMap) + || _.isArray(prelinkFile.sourceMap) + || (prelinkFile.sourceMap instanceof Buffer); if (!acceptedByBuffer) { prelinkFile.sourceMap = JSON.stringify(prelinkFile.sourceMap); } @@ -1332,8 +1333,8 @@ Object.assign(Isopack.prototype, { } prelinkResource.sourceMap = builder.writeToGeneratedFilename( - files.pathJoin(legacyDir, prelinkFile.servePath + '.map'), - { data: Buffer.from(prelinkFile.sourceMap, 'utf8') } + files.pathJoin(legacyDir, prelinkFile.servePath + '.map'), + { data: Buffer.from(prelinkFile.sourceMap, 'utf8') } ); } newResources.push(prelinkResource); @@ -1345,13 +1346,13 @@ Object.assign(Isopack.prototype, { unibuildJson.resources = newResources; delete unibuildJson.declaredExports; - builder.writeJson(legacyFilename, unibuildJson); - }); + await builder.writeJson(legacyFilename, unibuildJson); + } // old unipackage.json format/filename. no point to save this if // we can't even support isopack-1. // XXX COMPAT WITH 0.9.3 - builder.writeJson( + await builder.writeJson( "unipackage.json", Isopack.convertIsopackFormat( // Note that mainLegacyJson is isopack-1 (has no "source" resources) @@ -1374,10 +1375,10 @@ Object.assign(Isopack.prototype, { // isopack-1: {... data ...}, // isopack-2: {... data ...} // } - builder.writeJson("isopack.json", isopackJson); + await builder.writeJson("isopack.json", isopackJson); if (isopackBuildInfoJson) { - builder.writeJson("isopack-buildinfo.json", isopackBuildInfoJson); + await builder.writeJson("isopack-buildinfo.json", isopackBuildInfoJson); } builder.complete(); } catch (e) { diff --git a/tools/tool-env/isopackets.js b/tools/tool-env/isopackets.js index 9e5113f77b..652afc3852 100644 --- a/tools/tool-env/isopackets.js +++ b/tools/tool-env/isopackets.js @@ -208,6 +208,7 @@ export async function ensureIsopacketsLoadable() { } var builder = new Builder({ outputPath: isopacketRoot }); + await builder.init(); await builder.writeJson('isopacket-buildinfo.json', { builtBy: compiler.BUILT_BY, watchSet: built.watchSet.toJSON() From e2003c93a8b14ba7df559efad9e4d55d5b3f36b0 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 29 Nov 2022 09:48:00 -0300 Subject: [PATCH 0082/1965] fix: added instance in jsdocs --- packages/mongo-async/collection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mongo-async/collection.js b/packages/mongo-async/collection.js index 48ae036433..dc2d29c206 100644 --- a/packages/mongo-async/collection.js +++ b/packages/mongo-async/collection.js @@ -745,9 +745,10 @@ Object.assign(Mongo.Collection.prototype, { // We'll actually design an index API later. For now, we just pass through to // Mongo's, but make it synchronous. - /** + /** * @summary Creates the specified index on the collection. * @locus server + * @method _ensureIndex * @deprecated in 3.0 * @memberof Mongo.Collection * @instance From db03c7f6a25b6ee28008bd3c4be7f4bdc83bb4db Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 29 Nov 2022 18:22:20 -0400 Subject: [PATCH 0083/1965] using Meteor._runAsync to wrap _runHandler --- packages/ddp-server-async/livedata_server.js | 92 ++++++++++--------- .../livedata_server_async_tests.js | 5 - .../ddp-server-async/livedata_server_tests.js | 4 +- packages/meteor/asl-helpers.js | 19 ++-- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/packages/ddp-server-async/livedata_server.js b/packages/ddp-server-async/livedata_server.js index 533e01b701..b1dce0c369 100644 --- a/packages/ddp-server-async/livedata_server.js +++ b/packages/ddp-server-async/livedata_server.js @@ -1106,52 +1106,60 @@ var Subscription = function ( Object.assign(Subscription.prototype, { _runHandler: function() { - // XXX should we unblock() here? Either before running the publish - // function, or before running _publishCursor. - // - // Right now, each publish function blocks all future publishes and - // methods waiting on data from Mongo (or whatever else the function - // blocks on). This probably slows page load in common cases. + Meteor._runAsync( + () => { + // XXX should we unblock() here? Either before running the publish + // function, or before running _publishCursor. + // + // Right now, each publish function blocks all future publishes and + // methods waiting on data from Mongo (or whatever else the function + // blocks on). This probably slows page load in common cases. - if (!this.unblock) { - this.unblock = () => {}; - } + if (!this.unblock) { + this.unblock = () => {}; + } - const self = this; - let resultOrThenable = null; - try { - resultOrThenable = DDP._CurrentPublicationInvocation.withValue(self, () => - maybeAuditArgumentChecks( - self._handler, - self, - EJSON.clone(self._params), - // It's OK that this would look weird for universal subscriptions, - // because they have no arguments so there can never be an - // audit-argument-checks failure. - "publisher '" + self._name + "'" - ) - ); - } catch (e) { - self.error(e); - return; - } + const self = this; + let resultOrThenable = null; + try { + resultOrThenable = DDP._CurrentPublicationInvocation.withValue( + self, + () => + maybeAuditArgumentChecks( + self._handler, + self, + EJSON.clone(self._params), + // It's OK that this would look weird for universal subscriptions, + // because they have no arguments so there can never be an + // audit-argument-checks failure. + "publisher '" + self._name + "'" + ) + ); + } catch (e) { + self.error(e); + return; + } - // Did the handler call this.error or this.stop? - if (self._isDeactivated()) return; + // Did the handler call this.error or this.stop? + if (self._isDeactivated()) return; - // Both conventional and async publish handler functions are supported. - // If an object is returned with a then() function, it is either a promise - // or thenable and will be resolved asynchronously. - const isThenable = - resultOrThenable && typeof resultOrThenable.then === 'function'; - if (isThenable) { - Promise.resolve(resultOrThenable).then( - (...args) => self._publishHandlerResult.bind(self)(...args), - e => self.error(e) - ); - } else { - self._publishHandlerResult(resultOrThenable); - } + // Both conventional and async publish handler functions are supported. + // If an object is returned with a then() function, it is either a promise + // or thenable and will be resolved asynchronously. + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + Promise.resolve(resultOrThenable).then( + (...args) => self._publishHandlerResult.bind(self)(...args), + e => self.error(e) + ); + } else { + self._publishHandlerResult(resultOrThenable); + } + }, + this, + { callId: '_runHandler' } + ); }, _publishHandlerResult: function (res) { diff --git a/packages/ddp-server-async/livedata_server_async_tests.js b/packages/ddp-server-async/livedata_server_async_tests.js index ca00cc338b..d66a28f091 100644 --- a/packages/ddp-server-async/livedata_server_async_tests.js +++ b/packages/ddp-server-async/livedata_server_async_tests.js @@ -35,11 +35,6 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); - // console.log('onStopMethodInvocation', onStopMethodInvocation); - - - console.log('onStopPublicationInvocation', !!onStopPublicationInvocation, this.userId); - callback.call( this, diff --git a/packages/ddp-server-async/livedata_server_tests.js b/packages/ddp-server-async/livedata_server_tests.js index 0e7c970b12..101b1c2a93 100644 --- a/packages/ddp-server-async/livedata_server_tests.js +++ b/packages/ddp-server-async/livedata_server_tests.js @@ -179,7 +179,7 @@ Meteor.publish("livedata_server_test_sub_method", async function (connectionId) this.stop(); }); -Meteor.publish("livedata_server_test_sub_context", function (connectionId, userId) { +Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) { var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); @@ -201,7 +201,7 @@ Meteor.publish("livedata_server_test_sub_context", function (connectionId, userI this.stop(); } else { this.ready(); - Meteor.call('livedata_server_test_setuserid', userId); + await Meteor.callAsync('livedata_server_test_setuserid', userId); } }); diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js index 0d4ab7688a..859d767ee0 100644 --- a/packages/meteor/asl-helpers.js +++ b/packages/meteor/asl-helpers.js @@ -7,18 +7,21 @@ Meteor._getAslStore = getAslStore; Meteor._getValueFromAslStore = getValueFromAslStore; Meteor._updateAslStore = updateAslStore; -Meteor._runAsync = (fn, ctx) => { +Meteor._runAsync = (fn, ctx, store = {}) => { if (Meteor._isFibersEnabled) { - const Fiber = Npm.require('fibers'); + const Fiber = Npm.require('fibers'); - return Fiber(() => { - fn.call(ctx); - }).run(); + return Fiber(() => { + fn.call(ctx); + }).run(); } - return global.asyncLocalStorage.run(Meteor._getAslStore(), () => { - fn.call(ctx); - }); + return global.asyncLocalStorage.run( + { ...Meteor._getAslStore(), ...store }, + () => { + return fn.call(ctx); + } + ); }; Meteor._isPromise = (r) => { From 0c20daea4af2d61375b1a74e71be2351e6e4c91f Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 30 Nov 2022 10:47:14 -0400 Subject: [PATCH 0084/1965] creating function withValueExt e getExt --- .../.npm/package/npm-shrinkwrap.json | 746 +++++++++--------- packages/ddp-server-async/livedata_server.js | 96 ++- .../livedata_server_async_tests.js | 4 +- .../ddp-server-async/livedata_server_tests.js | 4 +- packages/meteor/dynamics_nodejs.js | 123 +-- 5 files changed, 485 insertions(+), 488 deletions(-) diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index 4453e83e92..3cdf6b8611 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -2,206 +2,213 @@ "lockfileVersion": 1, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==" }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==" }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dependencies": { "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" } } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + } + } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==" }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==" }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==" }, "@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==" }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==" }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==" }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==" }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==" - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==" }, "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==" }, "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==" }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==" }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==" }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==" + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==" }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==" }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==" }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==" }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==" }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==" }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==" }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -219,9 +226,9 @@ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" }, "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==" }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", @@ -249,139 +256,139 @@ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" }, "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==" }, "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==" }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==" }, "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz", + "integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==" }, "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==" }, "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==" }, "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==" }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==" }, "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==" + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==" }, "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==" }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==" }, "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==" }, "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz", + "integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==" }, "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==" }, "@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==" }, "@babel/plugin-transform-react-jsx": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", - "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==" }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==" }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==" }, "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==" }, "@babel/plugin-transform-runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", - "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==" }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==" }, "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==" }, "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==" }, "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==" }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==" }, "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==" }, "@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==" }, "@babel/runtime": { "version": "7.17.2", @@ -389,39 +396,49 @@ "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==" }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==" + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==" }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==" + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==" }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==" + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==" }, "@meteorjs/babel": { - "version": "7.16.0-beta.1", - "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.1.tgz", - "integrity": "sha512-PSyp2+oO2nrGMBTXd3VAP0EzHLW4bFqRIzmbTfHnr/s0dGhb7XaaGg3sOGAInewrFNCWfMHNe3hSiyOvC9bS2A==" + "version": "7.16.0-beta.7", + "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.7.tgz", + "integrity": "sha512-++ZngcRX51mu0fgijc7VYQnVlPr2tiq5p+sr1tOSQoe6gqH4toRCzVr4lzmPkrBXHl9IvSvkWcQYIGP5iSRsJw==" }, "@meteorjs/reify": { "version": "0.23.0", @@ -436,9 +453,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, "acorn": { "version": "6.4.2", @@ -463,38 +480,33 @@ "babel-helper-flip-expressions": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", - "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" + "integrity": "sha512-rSrkRW4YQ2ETCWww9gbsWk4N0x1BOtln349Tk0dlCS90oT68WMLyGR7WvaMp3eAnsVrCqdUtC19lo1avyGPejA==" }, "babel-helper-is-nodes-equiv": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", - "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" + "integrity": "sha512-ri/nsMFVRqXn7IyT5qW4/hIAGQxuYUFHa3qsxmPtbk6spZQcYlyDogfVpNm2XYOslH/ULS4VEJGUqQX5u7ACQw==" }, "babel-helper-is-void-0": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", - "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" + "integrity": "sha512-07rBV0xPRM3TM5NVJEOQEkECX3qnHDjaIbFvWYPv+T1ajpUiVLiqTfC+MmiZxY5KOL/Ec08vJdJD9kZiP9UkUg==" }, "babel-helper-mark-eval-scopes": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", - "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" + "integrity": "sha512-+d/mXPP33bhgHkdVOiPkmYoeXJ+rXRWi7OdhwpyseIqOS8CmzHQXHUp/+/Qr8baXsT0kjGpMHHofHs6C3cskdA==" }, "babel-helper-remove-or-void": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", - "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" + "integrity": "sha512-eYNceYtcGKpifHDir62gHJadVXdg9fAhuZEXiRQnJJ4Yi4oUTpqpNY//1pM4nVyjjDMPYaC2xSf0I+9IqVzwdA==" }, "babel-helper-to-multiple-sequence-expressions": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==" - }, "babel-plugin-minify-builtins": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", @@ -506,14 +518,14 @@ "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==" }, "babel-plugin-minify-dead-code-elimination": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", - "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.2.tgz", + "integrity": "sha512-krq9Lwi0QIzyAlcNBXTL4usqUvevB4BzktdEsb8srcXC1AaYqRJiAQw6vdKdJSaXbz6snBvziGr6ch/aoRCfpA==" }, "babel-plugin-minify-flip-comparisons": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", - "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=" + "integrity": "sha512-8hNwgLVeJzpeLVOVArag2DfTkbKodzOHU7+gAZ8mGBFGPQHK6uXVpg3jh5I/F6gfi5Q5usWU2OKcstn1YbAV7A==" }, "babel-plugin-minify-guarded-expressions": { "version": "0.4.4", @@ -523,17 +535,17 @@ "babel-plugin-minify-infinity": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", - "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" + "integrity": "sha512-X0ictxCk8y+NvIf+bZ1HJPbVZKMlPku3lgYxPmIp62Dp8wdtbMLSekczty3MzvUOlrk5xzWYpBpQprXUjDRyMA==" }, "babel-plugin-minify-mangle-names": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", - "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==" + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.1.tgz", + "integrity": "sha512-8KMichAOae2FHlipjNDTo2wz97MdEb2Q0jrn4NIRXzHH7SJ3c5TaNNBkeTHbk9WUsMnqpNUx949ugM9NFWewzw==" }, "babel-plugin-minify-numeric-literals": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", - "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" + "integrity": "sha512-5D54hvs9YVuCknfWywq0eaYDt7qYxlNwCqW9Ipm/kYeS9gYhJd0Rr/Pm2WhHKJ8DC6aIlDdqSBODSthabLSX3A==" }, "babel-plugin-minify-replace": { "version": "0.5.0", @@ -548,62 +560,62 @@ "babel-plugin-minify-type-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", - "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=" + "integrity": "sha512-4ADB0irJ/6BeXWHubjCJmrPbzhxDgjphBMjIjxCc25n4NGJ00NsYqwYt+F/OvE9RXx8KaSW7cJvp+iZX436tnQ==" }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==" }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==" }, "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==" }, "babel-plugin-transform-inline-consecutive-adds": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", - "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" + "integrity": "sha512-8D104wbzzI5RlxeVPYeQb9QsUyepiH1rAO5hpPpQ6NPRgQLpIVwkS/Nbx944pm4K8Z+rx7CgjPsFACz/VCBN0Q==" }, "babel-plugin-transform-member-expression-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", - "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" + "integrity": "sha512-Xq9/Rarpj+bjOZSl1nBbZYETsNEDDJSrb6Plb1sS3/36FukWFLLRysgecva5KZECjUJTrJoQqjJgtWToaflk5Q==" }, "babel-plugin-transform-merge-sibling-variables": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", - "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.5.tgz", + "integrity": "sha512-xj/KrWi6/uP+DrD844h66Qh2cZN++iugEIgH8QcIxhmZZPNP6VpOE9b4gP2FFW39xDAY43kCmYMM6U0QNKN8fw==" }, "babel-plugin-transform-minify-booleans": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", - "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" + "integrity": "sha512-9pW9ePng6DZpzGPalcrULuhSCcauGAbn8AeU3bE34HcDkGm8Ldt0ysjGkyb64f0K3T5ilV4mriayOVv5fg0ASA==" }, "babel-plugin-transform-property-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", - "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=" + "integrity": "sha512-Pf8JHTjTPxecqVyL6KSwD/hxGpoTZjiEgV7nCx0KFQsJYM0nuuoCajbg09KRmZWeZbJ5NGTySABYv8b/hY1eEA==" }, "babel-plugin-transform-regexp-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", - "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" + "integrity": "sha512-JjymDyEyRNhAoNFp09y/xGwYVYzT2nWTGrBrWaL6eCg2m+B24qH2jR0AA8V8GzKJTgC8NW6joJmc6nabvWBD/g==" }, "babel-plugin-transform-remove-console": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", - "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" + "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==" }, "babel-plugin-transform-remove-debugger": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", - "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" + "integrity": "sha512-Kd+eTBYlXfwoFzisburVwrngsrz4xh9I0ppoJnU/qlLysxVBRgI4Pj+dk3X8F5tDiehp3hhP8oarRMT9v2Z3lw==" }, "babel-plugin-transform-remove-undefined": { "version": "0.5.0", @@ -613,12 +625,12 @@ "babel-plugin-transform-simplify-comparison-operators": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", - "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" + "integrity": "sha512-GLInxhGAQWJ9YIdjwF6dAFlmh4U+kN8pL6Big7nkDzHoZcaDQOtBm28atEhQJq6m9GpAovbiGEShKqXv4BSp0A==" }, "babel-plugin-transform-undefined-to-void": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", - "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" + "integrity": "sha512-D2UbwxawEY1xVc9svYAUZQM2xarwSNXue2qDIx6CeV2EuMGaes/0su78zlIDIAgE7BvnMw4UpmSo9fDy+znghg==" }, "babel-preset-meteor": { "version": "7.10.0", @@ -626,24 +638,19 @@ "integrity": "sha512-bcdNfRCQAjTV42cUcmaG5/ltLZZQLpZajUcP+o0Lr+aLTY/XLNkGfASM5383wdXiAkEFl0sDOXeknnLlQtrmdg==" }, "babel-preset-minify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", - "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.2.tgz", + "integrity": "sha512-v4GL+kk0TfovbRIKZnC3HPbu2cAGmPAby7BsOmuPdMJfHV+4FVdsGXTH/OOGQRKYdjemBuL1+MsE6mobobhe9w==" }, "browserslist": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.2.tgz", - "integrity": "sha512-97XU1CTZ5TwU9Qy/Taj+RtiI6SQM1WIhZ9osT7EY0oO2aWXGABZT2OZeRL+6PfaQsiiMIjjwIoYFPq4APgspgQ==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" }, "caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==" + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" }, "chalk": { "version": "2.4.2", @@ -658,39 +665,27 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" }, "electron-to-chromium": { - "version": "1.4.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", - "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==" + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "escalade": { "version": "3.1.1", @@ -700,7 +695,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "estree-walker": { "version": "2.0.2", @@ -722,11 +717,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -740,17 +730,12 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==" }, "is-reference": { "version": "1.2.1", @@ -780,22 +765,22 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==" + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" }, "meteor-babel-helpers": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", - "integrity": "sha1-8uXZ+HlvvS6JAQI9dpnlsgLqn7A=" + "integrity": "sha512-PgfmiyT/HiBaxwGHxS4t3Qi0fpmEW3O0WW2VfrgekiMGz3aZPd9/4PRIaMMZsfyjQ1vyEm6dZqTAFZENbuoTxw==" }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "ms": { "version": "2.1.2", @@ -803,19 +788,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "path-parse": { "version": "1.0.7", @@ -838,62 +813,52 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==" }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==" + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==" }, "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==" + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==" }, "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" }, "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "dependencies": { "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" } } }, "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -912,12 +877,12 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" }, "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -930,14 +895,19 @@ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" }, "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" }, "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==" } } } diff --git a/packages/ddp-server-async/livedata_server.js b/packages/ddp-server-async/livedata_server.js index b1dce0c369..5677e82888 100644 --- a/packages/ddp-server-async/livedata_server.js +++ b/packages/ddp-server-async/livedata_server.js @@ -1106,60 +1106,56 @@ var Subscription = function ( Object.assign(Subscription.prototype, { _runHandler: function() { - Meteor._runAsync( - () => { - // XXX should we unblock() here? Either before running the publish - // function, or before running _publishCursor. - // - // Right now, each publish function blocks all future publishes and - // methods waiting on data from Mongo (or whatever else the function - // blocks on). This probably slows page load in common cases. + // XXX should we unblock() here? Either before running the publish + // function, or before running _publishCursor. + // + // Right now, each publish function blocks all future publishes and + // methods waiting on data from Mongo (or whatever else the function + // blocks on). This probably slows page load in common cases. - if (!this.unblock) { - this.unblock = () => {}; - } + if (!this.unblock) { + this.unblock = () => {}; + } - const self = this; - let resultOrThenable = null; - try { - resultOrThenable = DDP._CurrentPublicationInvocation.withValue( + const self = this; + let resultOrThenable = null; + try { + resultOrThenable = DDP._CurrentPublicationInvocation.withValueExt( + self, + () => + maybeAuditArgumentChecks( + self._handler, self, - () => - maybeAuditArgumentChecks( - self._handler, - self, - EJSON.clone(self._params), - // It's OK that this would look weird for universal subscriptions, - // because they have no arguments so there can never be an - // audit-argument-checks failure. - "publisher '" + self._name + "'" - ) - ); - } catch (e) { - self.error(e); - return; - } + EJSON.clone(self._params), + // It's OK that this would look weird for universal subscriptions, + // because they have no arguments so there can never be an + // audit-argument-checks failure. + "publisher '" + self._name + "'" + ), + { name: self._name } + ); + } catch (e) { + self.error(e); + return; + } - // Did the handler call this.error or this.stop? - if (self._isDeactivated()) return; + // Did the handler call this.error or this.stop? + if (self._isDeactivated()) return; + + // Both conventional and async publish handler functions are supported. + // If an object is returned with a then() function, it is either a promise + // or thenable and will be resolved asynchronously. + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + Promise.resolve(resultOrThenable).then( + (...args) => self._publishHandlerResult.bind(self)(...args), + e => self.error(e) + ); + } else { + self._publishHandlerResult(resultOrThenable); + } - // Both conventional and async publish handler functions are supported. - // If an object is returned with a then() function, it is either a promise - // or thenable and will be resolved asynchronously. - const isThenable = - resultOrThenable && typeof resultOrThenable.then === 'function'; - if (isThenable) { - Promise.resolve(resultOrThenable).then( - (...args) => self._publishHandlerResult.bind(self)(...args), - e => self.error(e) - ); - } else { - self._publishHandlerResult(resultOrThenable); - } - }, - this, - { callId: '_runHandler' } - ); }, _publishHandlerResult: function (res) { @@ -1811,7 +1807,7 @@ Object.assign(Server.prototype, { }; var connection = null; var currentMethodInvocation = DDP._CurrentMethodInvocation.get(); - var currentPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + var currentPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); var randomSeed = null; if (currentMethodInvocation) { userId = currentMethodInvocation.userId; diff --git a/packages/ddp-server-async/livedata_server_async_tests.js b/packages/ddp-server-async/livedata_server_async_tests.js index d66a28f091..65b8d3b8eb 100644 --- a/packages/ddp-server-async/livedata_server_async_tests.js +++ b/packages/ddp-server-async/livedata_server_async_tests.js @@ -21,7 +21,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( await sleep(50); var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); - var publicationInvocation = DDP._CurrentPublicationInvocation.get(); + var publicationInvocation = DDP._CurrentPublicationInvocation.getExt(); // console.log('methodInvocation', methodInvocation); // console.log('publicationInvocation', !!publicationInvocation); @@ -34,7 +34,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( // and that it runs with the same environment variables as this publish function. this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); - var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); callback.call( this, diff --git a/packages/ddp-server-async/livedata_server_tests.js b/packages/ddp-server-async/livedata_server_tests.js index 101b1c2a93..d4e40890d6 100644 --- a/packages/ddp-server-async/livedata_server_tests.js +++ b/packages/ddp-server-async/livedata_server_tests.js @@ -182,7 +182,7 @@ Meteor.publish("livedata_server_test_sub_method", async function (connectionId) Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) { var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); - var publicationInvocation = DDP._CurrentPublicationInvocation.get(); + var publicationInvocation = DDP._CurrentPublicationInvocation.getExt(); // Check the publish function's environment variables and context. if (callback) { @@ -193,7 +193,7 @@ Meteor.publish("livedata_server_test_sub_context", async function (connectionId, // and that it runs with the same environment variables as this publish function. this.onStop(function () { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); - var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); callback.call(this, onStopMethodInvocation, onStopPublicationInvocation, true); }); diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 0d8ba74727..2171240e51 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -5,11 +5,13 @@ var Fiber = Meteor._isFibersEnabled && Npm.require('fibers'); let nextSlot = 0; let callAsyncMethodRunning = false; -Meteor._nodeCodeMustBeInFiber = function () { +Meteor._nodeCodeMustBeInFiber = function() { if (!Fiber.current) { - throw new Error("Meteor code must always run within a Fiber. " + - "Try wrapping callbacks that you pass to non-Meteor " + - "libraries with Meteor.bindEnvironment."); + throw new Error( + 'Meteor code must always run within a Fiber. ' + + 'Try wrapping callbacks that you pass to non-Meteor ' + + 'libraries with Meteor.bindEnvironment.' + ); } }; @@ -21,21 +23,23 @@ class EnvironmentVariableFibers { get() { Meteor._nodeCodeMustBeInFiber(); - return Fiber.current._meteor_dynamics && - Fiber.current._meteor_dynamics[this.slot]; + return ( + Fiber.current._meteor_dynamics && + Fiber.current._meteor_dynamics[this.slot] + ); } getOrNullIfOutsideFiber() { - if (!Fiber.current) - return null; + if (!Fiber.current) return null; return this.get(); } withValue(value, func) { Meteor._nodeCodeMustBeInFiber(); - if (!Fiber.current._meteor_dynamics) + if (!Fiber.current._meteor_dynamics) { Fiber.current._meteor_dynamics = []; + } var currentValues = Fiber.current._meteor_dynamics; var saved = currentValues[this.slot]; @@ -69,16 +73,19 @@ class EnvironmentVariableAsync { } get() { - const currentValue = Meteor._getValueFromAslStore("_meteor_dynamics"); + const currentValue = Meteor._getValueFromAslStore('_meteor_dynamics'); return currentValue && currentValue[this.slot]; } + getExt() { + return Meteor._getValueFromAslStore('currentValue'); + } getOrNullIfOutsideFiber() { return this.get(); } async withValue(value, func) { - let currentValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + let currentValues = Meteor._getValueFromAslStore('_meteor_dynamics'); if (!currentValues) { currentValues = []; } @@ -87,23 +94,46 @@ class EnvironmentVariableAsync { let ret; try { currentValues[this.slot] = value; - Meteor._updateAslStore("_meteor_dynamics", currentValues); + Meteor._updateAslStore('_meteor_dynamics', currentValues); ret = await func(); } finally { currentValues[this.slot] = saved; - Meteor._updateAslStore("_meteor_dynamics", currentValues); + Meteor._updateAslStore('_meteor_dynamics', currentValues); } return ret; } + async withValueExt(value, func, storeOptions = {}) { + return Meteor._runAsync( + async () => { + let ret; + try { + Meteor._updateAslStore('currentValue', value); + ret = await func(); + } finally { + console.log( + `withValueExt finish running ${Meteor._getValueFromAslStore( + 'callId' + ) || 'no-id'} from met/sub ${Meteor._getValueFromAslStore('name') || + 'no-name'}` + ); + } + return ret; + }, + this, + { callId: `withValueExt-${this.slot}`, ...storeOptions } + ); + } + _set(context) { - const _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics") || []; + const _meteor_dynamics = + Meteor._getValueFromAslStore('_meteor_dynamics') || []; _meteor_dynamics[this.slot] = context; } _setNewContextAndGetCurrent(value) { - let _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + let _meteor_dynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); if (!_meteor_dynamics) { _meteor_dynamics = []; } @@ -128,7 +158,9 @@ class EnvironmentVariableAsync { * @locus Anywhere * @class */ -Meteor.EnvironmentVariable = Meteor._isFibersEnabled ? EnvironmentVariableFibers : EnvironmentVariableAsync; +Meteor.EnvironmentVariable = Meteor._isFibersEnabled + ? EnvironmentVariableFibers + : EnvironmentVariableAsync; // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from @@ -160,8 +192,10 @@ Meteor.EnvironmentVariable = Meteor._isFibersEnabled ? EnvironmentVariableFibers * @param {Object} _this Optional `this` object against which the original function will be invoked * @return {Function} The wrapped function */ -Meteor.bindEnvironment = function (func, onException, _this) { - return Meteor._isFibersEnabled ? bindEnvironmentFibers(func, onException, _this) : bindEnvironmentAsync(func, onException, _this); +Meteor.bindEnvironment = function(func, onException, _this) { + return Meteor._isFibersEnabled + ? bindEnvironmentFibers(func, onException, _this) + : bindEnvironmentAsync(func, onException, _this); }; const bindEnvironmentFibers = (func, onException, _this) => { @@ -170,22 +204,21 @@ const bindEnvironmentFibers = (func, onException, _this) => { var dynamics = Fiber.current._meteor_dynamics; var boundValues = dynamics ? dynamics.slice() : []; - if (!onException || typeof(onException) === 'string') { - var description = onException || "callback of async function"; - onException = function (error) { - Meteor._debug( - "Exception in " + description + ":", - error - ); + if (!onException || typeof onException === 'string') { + var description = onException || 'callback of async function'; + onException = function(error) { + Meteor._debug('Exception in ' + description + ':', error); }; - } else if (typeof(onException) !== 'function') { - throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } else if (typeof onException !== 'function') { + throw new Error( + 'onException argument must be a function, string or undefined for Meteor.bindEnvironment().' + ); } - return function (/* arguments */) { + return function(/* arguments */) { var args = Array.prototype.slice.call(arguments); - var runWithEnvironment = function () { + var runWithEnvironment = function() { var savedValues = Fiber.current._meteor_dynamics; try { // Need to clone boundValues in case two fibers invoke this @@ -203,44 +236,42 @@ const bindEnvironmentFibers = (func, onException, _this) => { return ret; }; - if (Fiber.current) - return runWithEnvironment(); + if (Fiber.current) return runWithEnvironment(); Fiber(runWithEnvironment).run(); }; }; const bindEnvironmentAsync = (func, onException, _this) => { - var dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + var dynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); var boundValues = Array.isArray(dynamics) ? dynamics.slice() : []; - if (!onException || typeof(onException) === 'string') { - var description = onException || "callback of async function"; - onException = function (error) { - Meteor._debug( - "Exception in " + description + ":", - error - ); + if (!onException || typeof onException === 'string') { + var description = onException || 'callback of async function'; + onException = function(error) { + Meteor._debug('Exception in ' + description + ':', error); }; - } else if (typeof(onException) !== 'function') { - throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } else if (typeof onException !== 'function') { + throw new Error( + 'onException argument must be a function, string or undefined for Meteor.bindEnvironment().' + ); } - return function (/* arguments */) { + return function(/* arguments */) { var args = Array.prototype.slice.call(arguments); - var runWithEnvironment = async function () { - const savedValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + var runWithEnvironment = async function() { + const savedValues = Meteor._getValueFromAslStore('_meteor_dynamics'); let ret; try { // Need to clone boundValues in case two fibers invoke this // function at the same time // TODO -> Probably not needed - Meteor._updateAslStore("_meteor_dynamics", boundValues.slice()); + Meteor._updateAslStore('_meteor_dynamics', boundValues.slice()); ret = await func.apply(_this, args); } catch (e) { onException(e); } finally { - Meteor._updateAslStore("_meteor_dynamics", savedValues); + Meteor._updateAslStore('_meteor_dynamics', savedValues); } return ret; }; From b60d7d44f505f383e9090f6ef5ce206210cbf434 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 30 Nov 2022 17:15:05 -0400 Subject: [PATCH 0085/1965] changing EV.withValue, EV.get, and bindEv to work with ALS without relaying on global array --- packages/ddp-server-async/livedata_server.js | 53 ++++++----- .../livedata_server_async_tests.js | 4 +- .../ddp-server-async/livedata_server_tests.js | 4 +- packages/meteor/asl-helpers.js | 2 +- packages/meteor/dynamics_nodejs.js | 88 ++++++++----------- tools/static-assets/server/boot.js | 4 +- 6 files changed, 70 insertions(+), 85 deletions(-) diff --git a/packages/ddp-server-async/livedata_server.js b/packages/ddp-server-async/livedata_server.js index 5677e82888..9cadd4d4ee 100644 --- a/packages/ddp-server-async/livedata_server.js +++ b/packages/ddp-server-async/livedata_server.js @@ -762,33 +762,32 @@ Object.assign(Session.prototype, { } } - const getCurrentMethodInvocationResult = () => { - const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( - invocation + const getCurrentMethodInvocationResult = () => + DDP._CurrentPublicationInvocation.withValue( + invocation, + () => + maybeAuditArgumentChecks( + handler, + invocation, + msg.params, + "call to '" + msg.method + "'" + ), + { + name: 'getCurrentMethodInvocationResult', + keyName: 'getCurrentMethodInvocationResult', + } ); - try { - let result; - const resultOrThenable = maybeAuditArgumentChecks( - handler, - invocation, - msg.params, - "call to '" + msg.method + "'" - ); - const isThenable = - resultOrThenable && typeof resultOrThenable.then === 'function'; - if (isThenable) { - result = Meteor._isFibersEnabled ? Promise.await(resultOrThenable) : resultOrThenable; - } else { - result = resultOrThenable; + resolve( + DDPServer._CurrentWriteFence.withValue( + fence, + getCurrentMethodInvocationResult, + { + name: 'DDPServer._CurrentWriteFence', + keyName: '_CurrentWriteFence', } - return result; - } finally { - DDP._CurrentMethodInvocation._set(currentContext); - } - }; - - resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult)); + ) + ); }); function finish() { @@ -899,7 +898,7 @@ Object.assign(Session.prototype, { // subs. self._dontStartNewUniversalSubs = false; self.startUniversalSubs(); - }); + }, { name: '_setUserId' }); // Start sending messages again, beginning with the diff from the previous // state of the world to the current state. No yields are allowed during @@ -1120,7 +1119,7 @@ Object.assign(Subscription.prototype, { const self = this; let resultOrThenable = null; try { - resultOrThenable = DDP._CurrentPublicationInvocation.withValueExt( + resultOrThenable = DDP._CurrentPublicationInvocation.withValue( self, () => maybeAuditArgumentChecks( @@ -1807,7 +1806,7 @@ Object.assign(Server.prototype, { }; var connection = null; var currentMethodInvocation = DDP._CurrentMethodInvocation.get(); - var currentPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); + var currentPublicationInvocation = DDP._CurrentPublicationInvocation.get(); var randomSeed = null; if (currentMethodInvocation) { userId = currentMethodInvocation.userId; diff --git a/packages/ddp-server-async/livedata_server_async_tests.js b/packages/ddp-server-async/livedata_server_async_tests.js index 65b8d3b8eb..d66a28f091 100644 --- a/packages/ddp-server-async/livedata_server_async_tests.js +++ b/packages/ddp-server-async/livedata_server_async_tests.js @@ -21,7 +21,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( await sleep(50); var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); - var publicationInvocation = DDP._CurrentPublicationInvocation.getExt(); + var publicationInvocation = DDP._CurrentPublicationInvocation.get(); // console.log('methodInvocation', methodInvocation); // console.log('publicationInvocation', !!publicationInvocation); @@ -34,7 +34,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( // and that it runs with the same environment variables as this publish function. this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); - var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); callback.call( this, diff --git a/packages/ddp-server-async/livedata_server_tests.js b/packages/ddp-server-async/livedata_server_tests.js index d4e40890d6..101b1c2a93 100644 --- a/packages/ddp-server-async/livedata_server_tests.js +++ b/packages/ddp-server-async/livedata_server_tests.js @@ -182,7 +182,7 @@ Meteor.publish("livedata_server_test_sub_method", async function (connectionId) Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) { var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); - var publicationInvocation = DDP._CurrentPublicationInvocation.getExt(); + var publicationInvocation = DDP._CurrentPublicationInvocation.get(); // Check the publish function's environment variables and context. if (callback) { @@ -193,7 +193,7 @@ Meteor.publish("livedata_server_test_sub_context", async function (connectionId, // and that it runs with the same environment variables as this publish function. this.onStop(function () { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); - var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.getExt(); + var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); callback.call(this, onStopMethodInvocation, onStopPublicationInvocation, true); }); diff --git a/packages/meteor/asl-helpers.js b/packages/meteor/asl-helpers.js index 859d767ee0..24e460385e 100644 --- a/packages/meteor/asl-helpers.js +++ b/packages/meteor/asl-helpers.js @@ -17,7 +17,7 @@ Meteor._runAsync = (fn, ctx, store = {}) => { } return global.asyncLocalStorage.run( - { ...Meteor._getAslStore(), ...store }, + store || Meteor._getAslStore(), () => { return fn.call(ctx); } diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 2171240e51..62ea44205c 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -5,6 +5,9 @@ var Fiber = Meteor._isFibersEnabled && Npm.require('fibers'); let nextSlot = 0; let callAsyncMethodRunning = false; +const CURRENT_VALUE_KEY_NAME = 'currentValue'; +const SLOT_CALL_KEY = 'slotCall'; + Meteor._nodeCodeMustBeInFiber = function() { if (!Fiber.current) { throw new Error( @@ -73,56 +76,34 @@ class EnvironmentVariableAsync { } get() { - const currentValue = Meteor._getValueFromAslStore('_meteor_dynamics'); - return currentValue && currentValue[this.slot]; - } - getExt() { - return Meteor._getValueFromAslStore('currentValue'); + if (this.slot !== Meteor._getValueFromAslStore(SLOT_CALL_KEY)) { + return; + } + return Meteor._getValueFromAslStore(CURRENT_VALUE_KEY_NAME); } getOrNullIfOutsideFiber() { return this.get(); } - async withValue(value, func) { - let currentValues = Meteor._getValueFromAslStore('_meteor_dynamics'); - if (!currentValues) { - currentValues = []; - } - - const saved = currentValues[this.slot]; - let ret; - try { - currentValues[this.slot] = value; - Meteor._updateAslStore('_meteor_dynamics', currentValues); - ret = await func(); - } finally { - currentValues[this.slot] = saved; - Meteor._updateAslStore('_meteor_dynamics', currentValues); - } - - return ret; - } - - async withValueExt(value, func, storeOptions = {}) { + async withValue(value, func, options = {}) { return Meteor._runAsync( async () => { let ret; try { - Meteor._updateAslStore('currentValue', value); + Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, value); ret = await func(); } finally { - console.log( - `withValueExt finish running ${Meteor._getValueFromAslStore( - 'callId' - ) || 'no-id'} from met/sub ${Meteor._getValueFromAslStore('name') || - 'no-name'}` - ); + Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, undefined); } return ret; }, this, - { callId: `withValueExt-${this.slot}`, ...storeOptions } + { + callId: `${this.slot}-${Math.random()}`, + [SLOT_CALL_KEY]: this.slot, + ...options, + } ); } @@ -242,8 +223,8 @@ const bindEnvironmentFibers = (func, onException, _this) => { }; const bindEnvironmentAsync = (func, onException, _this) => { - var dynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); - var boundValues = Array.isArray(dynamics) ? dynamics.slice() : []; + const dynamics = Meteor._getValueFromAslStore(CURRENT_VALUE_KEY_NAME); + const currentSlot = Meteor._getValueFromAslStore(SLOT_CALL_KEY); if (!onException || typeof onException === 'string') { var description = onException || 'callback of async function'; @@ -259,21 +240,26 @@ const bindEnvironmentAsync = (func, onException, _this) => { return function(/* arguments */) { var args = Array.prototype.slice.call(arguments); - var runWithEnvironment = async function() { - const savedValues = Meteor._getValueFromAslStore('_meteor_dynamics'); - let ret; - try { - // Need to clone boundValues in case two fibers invoke this - // function at the same time - // TODO -> Probably not needed - Meteor._updateAslStore('_meteor_dynamics', boundValues.slice()); - ret = await func.apply(_this, args); - } catch (e) { - onException(e); - } finally { - Meteor._updateAslStore('_meteor_dynamics', savedValues); - } - return ret; + var runWithEnvironment = function() { + return Meteor._runAsync( + async () => { + let ret; + try { + Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, dynamics); + ret = await func.apply(_this, args); + } catch (e) { + onException(e); + } finally { + Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, undefined); + } + return ret; + }, + _this, + { + callId: `bindEnvironment-${Math.random()}`, + [SLOT_CALL_KEY]: currentSlot, + } + ); }; if (Meteor._getAslStore()) { diff --git a/tools/static-assets/server/boot.js b/tools/static-assets/server/boot.js index 68ae408045..ac69eca005 100644 --- a/tools/static-assets/server/boot.js +++ b/tools/static-assets/server/boot.js @@ -484,13 +484,13 @@ function startServerProcess() { global.asyncLocalStorage = new AsyncLocalStorage(); Profile.run('Server startup', function() { - // TODO the if around loadServerBundles should be enough + // TODO[FIBERS] the if around loadServerBundles should be enough if (IS_FIBERS_ENABLED) { loadServerBundles(); callStartupHooks(); runMain(); } else { - global.asyncLocalStorage.run({}, () => { + global.asyncLocalStorage.run({ level: 'boot' }, () => { loadServerBundles(); callStartupHooks(); runMain(); From 5ed7e4d18df3fc2aafcf5f5ba8c555b3249fabd1 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 30 Nov 2022 18:16:57 -0400 Subject: [PATCH 0086/1965] calling Meteor.callAsync to set userId in test --- packages/ddp-client-async/test/livedata_test_service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ddp-client-async/test/livedata_test_service.js b/packages/ddp-client-async/test/livedata_test_service.js index db32cf3cc4..9f0c7d1fe4 100644 --- a/packages/ddp-client-async/test/livedata_test_service.js +++ b/packages/ddp-client-async/test/livedata_test_service.js @@ -202,10 +202,10 @@ if (Meteor.isServer) { /// Helper for "livedata - setUserId fails when called on server" if (Meteor.isServer) { - Meteor.startup(function() { + Meteor.startup(async function() { errorThrownWhenCallingSetUserIdDirectlyOnServer = null; try { - Meteor.call('setUserId', '1000'); + await Meteor.callAsync('setUserId', '1000'); } catch (e) { errorThrownWhenCallingSetUserIdDirectlyOnServer = e; } From de04cfe6766cab08e74a97dbeb6dd5997221c408 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 1 Dec 2022 10:43:48 -0300 Subject: [PATCH 0087/1965] Update Node Version, NPM Version and tools to work without Fibers - Update node version to 18.12.1 - Update npm version to 7.24.2 (last before 8) - Update commands to use async --- NPM 8 break changes Error: The programmatic API was removed in npm v8.0.0 https://github.com/npm/cli/blob/latest/index.js --- meteor | 2 +- .../scripts/build-dev-bundle-common.sh | 4 +- .../scripts/dev-bundle-tool-package.js | 4 +- .../scripts/generate-dev-bundle.sh | 3 +- .../.npm/package/npm-shrinkwrap.json | 746 +++++++++--------- scripts/build-dev-bundle-common.sh | 2 +- scripts/dev-bundle-server-package.js | 1 - scripts/dev-bundle-tool-package.js | 5 +- scripts/generate-dev-bundle.sh | 2 +- tools/cli/commands-packages-query.js | 57 +- tools/cli/commands-packages.js | 188 ++--- tools/cli/commands.js | 14 +- tools/cli/main.js | 4 +- tools/fs/files.ts | 11 +- tools/isobuild/bundler.js | 10 +- tools/meteor-services/auth.js | 12 +- tools/meteor-services/deploy.js | 8 +- tools/meteor-services/service-connection.js | 14 +- tools/meteor-services/stats.js | 11 +- tools/project-context.js | 8 +- tools/runners/run-all.js | 4 +- tools/runners/run-app.js | 28 +- tools/runners/run-mongo.js | 309 ++++---- tools/runners/run-proxy.js | 4 +- tools/runners/run-selenium.js | 6 +- tools/tool-env/install-promise.js | 10 +- tools/tool-env/install-runtime.js | 2 +- tools/tool-env/profile.ts | 10 +- tools/utils/buildmessage.js | 4 +- 29 files changed, 734 insertions(+), 749 deletions(-) diff --git a/meteor b/meteor index dff5d789b0..df6c76bdb9 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=14.20.1.0 +BUNDLE_VERSION=18.12.1.0 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh b/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh index 941a01eabb..5998f245af 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh @@ -5,10 +5,10 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=14.17.6 +NODE_VERSION=18.12.1 MONGO_VERSION_64BIT=4.4.4 MONGO_VERSION_32BIT=3.2.22 -NPM_VERSION=6.14.15 +NPM_VERSION=7.24.2 if [ "$UNAME" == "Linux" ] ; then if [ "$ARCH" != "i686" -a "$ARCH" != "x86_64" ] ; then diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js index 17b63e201f..61f444253e 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js @@ -10,10 +10,10 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "6.14.15", + npm: "7.24.2", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", "node-gyp": "8.0.0", - "node-pre-gyp": "0.15.0", + "@mapbox/node-pre-gyp": "1.0.10", typescript: "4.5.4", "@meteorjs/babel": "7.16.0-beta.7", // Keep the versions of these packages consistent with the versions diff --git a/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh b/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh index 4d75328eae..52c84c8333 100755 --- a/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh @@ -164,8 +164,9 @@ then mv pacote npm/node_modules/ fi +pwd delete sqlite3/deps -delete sqlite3/node_modules/node-pre-gyp +delete sqlite3/node_modules/@mapbox/node-pre-gyp delete wordwrap/test delete moment/min diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index 4453e83e92..810798d033 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -2,206 +2,213 @@ "lockfileVersion": 1, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==" }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==" }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", "dependencies": { "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" } } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + } + } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==" }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==" }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==" }, "@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==" }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==" }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==" }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==" }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==" - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==" }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==" }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==" }, "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==" }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==" }, "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==" }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==" }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==" + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==" }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==" }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==" + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==" }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==" + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==" }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==" }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==" }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==" }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==" }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==" }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -219,9 +226,9 @@ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" }, "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==" }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", @@ -249,139 +256,139 @@ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" }, "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==" }, "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==" }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==" }, "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz", + "integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==" }, "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==" }, "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==" }, "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==" + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==" }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==" }, "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==" + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==" }, "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==" }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==" }, "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==" }, "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz", + "integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==" }, "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==" }, "@babel/plugin-transform-react-display-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", - "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==" }, "@babel/plugin-transform-react-jsx": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", - "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==" }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", - "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==" }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", - "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==" }, "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==" }, "@babel/plugin-transform-runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", - "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==" + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==" }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==" }, "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==" }, "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==" }, "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==" }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==" }, "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==" }, "@babel/preset-react": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", - "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==" }, "@babel/runtime": { "version": "7.17.2", @@ -389,39 +396,49 @@ "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==" }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==" + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==" }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==" }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==" + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==" }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==" + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==" }, "@meteorjs/babel": { - "version": "7.16.0-beta.1", - "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.1.tgz", - "integrity": "sha512-PSyp2+oO2nrGMBTXd3VAP0EzHLW4bFqRIzmbTfHnr/s0dGhb7XaaGg3sOGAInewrFNCWfMHNe3hSiyOvC9bS2A==" + "version": "7.16.0-beta.7", + "resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.16.0-beta.7.tgz", + "integrity": "sha512-++ZngcRX51mu0fgijc7VYQnVlPr2tiq5p+sr1tOSQoe6gqH4toRCzVr4lzmPkrBXHl9IvSvkWcQYIGP5iSRsJw==" }, "@meteorjs/reify": { "version": "0.23.0", @@ -436,9 +453,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==" }, "acorn": { "version": "6.4.2", @@ -463,38 +480,33 @@ "babel-helper-flip-expressions": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", - "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" + "integrity": "sha512-rSrkRW4YQ2ETCWww9gbsWk4N0x1BOtln349Tk0dlCS90oT68WMLyGR7WvaMp3eAnsVrCqdUtC19lo1avyGPejA==" }, "babel-helper-is-nodes-equiv": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", - "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" + "integrity": "sha512-ri/nsMFVRqXn7IyT5qW4/hIAGQxuYUFHa3qsxmPtbk6spZQcYlyDogfVpNm2XYOslH/ULS4VEJGUqQX5u7ACQw==" }, "babel-helper-is-void-0": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", - "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" + "integrity": "sha512-07rBV0xPRM3TM5NVJEOQEkECX3qnHDjaIbFvWYPv+T1ajpUiVLiqTfC+MmiZxY5KOL/Ec08vJdJD9kZiP9UkUg==" }, "babel-helper-mark-eval-scopes": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", - "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" + "integrity": "sha512-+d/mXPP33bhgHkdVOiPkmYoeXJ+rXRWi7OdhwpyseIqOS8CmzHQXHUp/+/Qr8baXsT0kjGpMHHofHs6C3cskdA==" }, "babel-helper-remove-or-void": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", - "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" + "integrity": "sha512-eYNceYtcGKpifHDir62gHJadVXdg9fAhuZEXiRQnJJ4Yi4oUTpqpNY//1pM4nVyjjDMPYaC2xSf0I+9IqVzwdA==" }, "babel-helper-to-multiple-sequence-expressions": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==" - }, "babel-plugin-minify-builtins": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", @@ -506,14 +518,14 @@ "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==" }, "babel-plugin-minify-dead-code-elimination": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", - "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.2.tgz", + "integrity": "sha512-krq9Lwi0QIzyAlcNBXTL4usqUvevB4BzktdEsb8srcXC1AaYqRJiAQw6vdKdJSaXbz6snBvziGr6ch/aoRCfpA==" }, "babel-plugin-minify-flip-comparisons": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", - "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=" + "integrity": "sha512-8hNwgLVeJzpeLVOVArag2DfTkbKodzOHU7+gAZ8mGBFGPQHK6uXVpg3jh5I/F6gfi5Q5usWU2OKcstn1YbAV7A==" }, "babel-plugin-minify-guarded-expressions": { "version": "0.4.4", @@ -523,17 +535,17 @@ "babel-plugin-minify-infinity": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", - "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" + "integrity": "sha512-X0ictxCk8y+NvIf+bZ1HJPbVZKMlPku3lgYxPmIp62Dp8wdtbMLSekczty3MzvUOlrk5xzWYpBpQprXUjDRyMA==" }, "babel-plugin-minify-mangle-names": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", - "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==" + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.1.tgz", + "integrity": "sha512-8KMichAOae2FHlipjNDTo2wz97MdEb2Q0jrn4NIRXzHH7SJ3c5TaNNBkeTHbk9WUsMnqpNUx949ugM9NFWewzw==" }, "babel-plugin-minify-numeric-literals": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", - "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" + "integrity": "sha512-5D54hvs9YVuCknfWywq0eaYDt7qYxlNwCqW9Ipm/kYeS9gYhJd0Rr/Pm2WhHKJ8DC6aIlDdqSBODSthabLSX3A==" }, "babel-plugin-minify-replace": { "version": "0.5.0", @@ -548,62 +560,62 @@ "babel-plugin-minify-type-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", - "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=" + "integrity": "sha512-4ADB0irJ/6BeXWHubjCJmrPbzhxDgjphBMjIjxCc25n4NGJ00NsYqwYt+F/OvE9RXx8KaSW7cJvp+iZX436tnQ==" }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==" }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==" }, "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==" }, "babel-plugin-transform-inline-consecutive-adds": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", - "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" + "integrity": "sha512-8D104wbzzI5RlxeVPYeQb9QsUyepiH1rAO5hpPpQ6NPRgQLpIVwkS/Nbx944pm4K8Z+rx7CgjPsFACz/VCBN0Q==" }, "babel-plugin-transform-member-expression-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", - "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" + "integrity": "sha512-Xq9/Rarpj+bjOZSl1nBbZYETsNEDDJSrb6Plb1sS3/36FukWFLLRysgecva5KZECjUJTrJoQqjJgtWToaflk5Q==" }, "babel-plugin-transform-merge-sibling-variables": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", - "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.5.tgz", + "integrity": "sha512-xj/KrWi6/uP+DrD844h66Qh2cZN++iugEIgH8QcIxhmZZPNP6VpOE9b4gP2FFW39xDAY43kCmYMM6U0QNKN8fw==" }, "babel-plugin-transform-minify-booleans": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", - "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" + "integrity": "sha512-9pW9ePng6DZpzGPalcrULuhSCcauGAbn8AeU3bE34HcDkGm8Ldt0ysjGkyb64f0K3T5ilV4mriayOVv5fg0ASA==" }, "babel-plugin-transform-property-literals": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", - "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=" + "integrity": "sha512-Pf8JHTjTPxecqVyL6KSwD/hxGpoTZjiEgV7nCx0KFQsJYM0nuuoCajbg09KRmZWeZbJ5NGTySABYv8b/hY1eEA==" }, "babel-plugin-transform-regexp-constructors": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", - "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" + "integrity": "sha512-JjymDyEyRNhAoNFp09y/xGwYVYzT2nWTGrBrWaL6eCg2m+B24qH2jR0AA8V8GzKJTgC8NW6joJmc6nabvWBD/g==" }, "babel-plugin-transform-remove-console": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", - "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" + "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==" }, "babel-plugin-transform-remove-debugger": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", - "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" + "integrity": "sha512-Kd+eTBYlXfwoFzisburVwrngsrz4xh9I0ppoJnU/qlLysxVBRgI4Pj+dk3X8F5tDiehp3hhP8oarRMT9v2Z3lw==" }, "babel-plugin-transform-remove-undefined": { "version": "0.5.0", @@ -613,12 +625,12 @@ "babel-plugin-transform-simplify-comparison-operators": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", - "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" + "integrity": "sha512-GLInxhGAQWJ9YIdjwF6dAFlmh4U+kN8pL6Big7nkDzHoZcaDQOtBm28atEhQJq6m9GpAovbiGEShKqXv4BSp0A==" }, "babel-plugin-transform-undefined-to-void": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", - "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" + "integrity": "sha512-D2UbwxawEY1xVc9svYAUZQM2xarwSNXue2qDIx6CeV2EuMGaes/0su78zlIDIAgE7BvnMw4UpmSo9fDy+znghg==" }, "babel-preset-meteor": { "version": "7.10.0", @@ -626,24 +638,19 @@ "integrity": "sha512-bcdNfRCQAjTV42cUcmaG5/ltLZZQLpZajUcP+o0Lr+aLTY/XLNkGfASM5383wdXiAkEFl0sDOXeknnLlQtrmdg==" }, "babel-preset-minify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", - "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.2.tgz", + "integrity": "sha512-v4GL+kk0TfovbRIKZnC3HPbu2cAGmPAby7BsOmuPdMJfHV+4FVdsGXTH/OOGQRKYdjemBuL1+MsE6mobobhe9w==" }, "browserslist": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.2.tgz", - "integrity": "sha512-97XU1CTZ5TwU9Qy/Taj+RtiI6SQM1WIhZ9osT7EY0oO2aWXGABZT2OZeRL+6PfaQsiiMIjjwIoYFPq4APgspgQ==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==" }, "caniuse-lite": { - "version": "1.0.30001312", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", - "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==" + "version": "1.0.30001435", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001435.tgz", + "integrity": "sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==" }, "chalk": { "version": "2.4.2", @@ -658,39 +665,27 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" }, "electron-to-chromium": { - "version": "1.4.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", - "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==" + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "escalade": { "version": "3.1.1", @@ -700,7 +695,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "estree-walker": { "version": "2.0.2", @@ -722,11 +717,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -740,17 +730,12 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==" }, "is-reference": { "version": "1.2.1", @@ -780,22 +765,22 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==" + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==" }, "meteor-babel-helpers": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/meteor-babel-helpers/-/meteor-babel-helpers-0.0.3.tgz", - "integrity": "sha1-8uXZ+HlvvS6JAQI9dpnlsgLqn7A=" + "integrity": "sha512-PgfmiyT/HiBaxwGHxS4t3Qi0fpmEW3O0WW2VfrgekiMGz3aZPd9/4PRIaMMZsfyjQ1vyEm6dZqTAFZENbuoTxw==" }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "ms": { "version": "2.1.2", @@ -803,19 +788,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "path-parse": { "version": "1.0.7", @@ -838,62 +813,52 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==" }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==" + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==" }, "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==" + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==" }, "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" }, "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "dependencies": { "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" } } }, "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -912,12 +877,12 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" }, "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -930,14 +895,19 @@ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" }, "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" }, "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==" } } } diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index d7b4cfaa10..4f38bd243a 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,7 +5,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=14.20.1 +NODE_VERSION=18.12.1 MONGO_VERSION_64BIT=5.0.5 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.17 diff --git a/scripts/dev-bundle-server-package.js b/scripts/dev-bundle-server-package.js index 6218244bad..29e81d8802 100644 --- a/scripts/dev-bundle-server-package.js +++ b/scripts/dev-bundle-server-package.js @@ -10,7 +10,6 @@ var packageJson = { dependencies: { // Keep the versions of these packages consistent with the versions // found in dev-bundle-tool-package.js. - fibers: "5.0.1", "meteor-promise": "0.9.0", promise: "8.1.0", "@meteorjs/reify": "0.24.0", diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index e0f21db047..e48ca2acf8 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -12,14 +12,13 @@ var packageJson = { // and we want to make sure there are no dependencies on a higher version npm: "6.14.17", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", - "node-gyp": "8.0.0", - "node-pre-gyp": "0.15.0", + "node-gyp": "9.3.0", + "@mapbox/node-pre-gyp": "1.0.10", typescript: "4.5.4", "@meteorjs/babel": "7.16.0-beta.7", // Keep the versions of these packages consistent with the versions // found in dev-bundle-server-package.js. "meteor-promise": "0.9.0", - fibers: "5.0.1", "@meteorjs/reify": "0.24.0", // So that Babel can emit require("@babel/runtime/helpers/...") calls. "@babel/runtime": "7.15.3", diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 5a6d10eeb6..38a1fbdc63 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -171,7 +171,7 @@ then fi delete sqlite3/deps -delete sqlite3/node_modules/node-pre-gyp +#delete sqlite3/node_modules/@mapbox/node-pre-gyp delete wordwrap/test delete moment/min diff --git a/tools/cli/commands-packages-query.js b/tools/cli/commands-packages-query.js index aea3fe0e59..b8db0b8fca 100644 --- a/tools/cli/commands-packages-query.js +++ b/tools/cli/commands-packages-query.js @@ -137,7 +137,7 @@ var padLongformDate = function (dateStr) { // - packageDir: If we are running in a package directory, this will contain // the source root of that package. If we are running from inside a package, // we want that package to show up in our results. -var getTempContext = function (options) { +var getTempContext = async function (options) { var projectContext; // If we are running in an app, we will use it to create a // (mostly immutable) projectContext. @@ -161,8 +161,8 @@ var getTempContext = function (options) { // packages if we can't read them. If this turns out to be a frequent problem, // we can give a warning, instead of failing in the future. For now, we want // to err on the side of consistency. - main.captureAndExit("=> Errors while reading local packages:", function () { - projectContext.initializeCatalog(); + await main.captureAndExit("=> Errors while reading local packages:", async function () { + await projectContext.initializeCatalog(); }); return projectContext; }; @@ -415,18 +415,29 @@ var PackageQuery = function (options) { // Collect the data for this package, including looking up any specific // package version that we care about. - if (options.version) { - var versionRecord = self._getVersionRecord(options.version); - if (! versionRecord) { - self.data = null; - return; + return new Promise(resolve => { + console.log('init .....'); + if (options.version) { + var versionRecord = self._getVersionRecord(options.version); + if (! versionRecord) { + self.data = null; + return; + } + if (versionRecord.local) { + self._getLocalVersion(versionRecord).then(res => { + self.data = res; + console.log('init .....', {res}); + resolve(); + }); + } else { + self.data = self._getOfficialVersion(versionRecord); + } + + } else { + self.data = self._collectPackageData(); + resolve(); } - self.data = versionRecord.local ? - self._getLocalVersion(versionRecord) : - self._getOfficialVersion(versionRecord); - } else { - self.data = self._collectPackageData(); - } + }); }; Object.assign(PackageQuery.prototype, { @@ -496,7 +507,7 @@ Object.assign(PackageQuery.prototype, { // per-version information that is relevant to the package as a whole, such // as git, description,etc. // - versions: an array of objects representing versions of this package. - _collectPackageData: function () { + _collectPackageData: async function () { var self = this; var data = { name: self.metaRecord.name, @@ -558,7 +569,7 @@ Object.assign(PackageQuery.prototype, { var localVersion = self.localCatalog.getLatestVersion(self.name); var local; if (localVersion) { - local = self._getLocalVersion(localVersion); + local = await self._getLocalVersion(localVersion); data["versions"].push(local); totalVersions++; } @@ -707,7 +718,7 @@ Object.assign(PackageQuery.prototype, { // - packageName: name of the dependency // - constraint: constraint for that dependency // - weak: true if this is a weak dependency. - _getLocalVersion: function (localRecord) { + _getLocalVersion: async function (localRecord) { var self = this; var data = { name: self.name, @@ -739,7 +750,7 @@ Object.assign(PackageQuery.prototype, { } var readmeInfo; - main.captureAndExit( + await main.captureAndExit( "=> Errors while reading local packages:", "reading " + data["directory"], function () { @@ -1384,13 +1395,13 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.OnceAtStart( { maxAge: DEFAULT_MAX_AGE_MS, ignoreErrors: true }) -}, function (options) { +}, async function (options) { var fullName; var name; var version; // Because of the new projectContext interface, we need to initialize the // project context in order to load the local catalog. This is not ideal. - var projectContext = getTempContext(options); + var projectContext = await getTempContext(options); // If the user specified a query, process it. if (! _.isEmpty(options.args)) { @@ -1434,7 +1445,7 @@ main.registerCommand({ catalog.official.getPackage(name) || projectContext.localCatalog.getPackage(name); if (packageRecord) { - query = new PackageQuery({ + query = await new PackageQuery({ metaRecord: packageRecord, version: version, projectContext: projectContext, @@ -1491,7 +1502,7 @@ main.registerCommand({ catalogRefresh: new catalog.Refresh.OnceAtStart( { maxAge: DEFAULT_MAX_AGE_MS, ignoreErrors: true }) -}, function (options) { +}, async function (options) { if (options.args.length === 0) { Console.info( "To show all packages, do", Console.command("meteor search .")); @@ -1500,7 +1511,7 @@ main.registerCommand({ // Because of the new projectContext interface, we need to initialize the // project context in order to load the local catalog. - var projectContext = getTempContext(options); + var projectContext = await getTempContext(options); // XXX We should push the queries into SQLite! var allPackages = _.union( diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 001bfa9a8c..2d706df315 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -93,7 +93,7 @@ main.registerCommand({ options: { 'allow-incompatible-update': { type: Boolean } } -}, function (options) { +}, async function (options) { // If we're in an app, make sure that we can build the current app. Otherwise // just make sure that we can build some fake app. @@ -103,8 +103,8 @@ main.registerCommand({ neverWritePackageMap: true, allowIncompatibleUpdate: options['allow-incompatible-update'] }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.initializeCatalog(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.initializeCatalog(); }); // Add every local package (including tests) and every release package to this @@ -122,8 +122,8 @@ main.registerCommand({ } // Now finish building and downloading. - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); // We don't display package changes because they'd include all these packages // not actually in the app! @@ -143,14 +143,14 @@ main.registerCommand({ options: { 'allow-incompatible-update': { type: Boolean } } -}, function (options) { +}, async function (options) { var projectContext = new projectContextModule.ProjectContext({ projectDir: options.appDir, allowIncompatibleUpdate: options['allow-incompatible-update'] }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); projectContext.packageMapDelta.displayOnConsole(); @@ -167,7 +167,7 @@ main.registerCommand({ // // Takes in a packageSource and a connection to the package server. Returns 0 on // success and an exit code on failure. -var updatePackageMetadata = function (packageSource, conn) { +var updatePackageMetadata = async function (packageSource, conn) { var name = packageSource.name; var version = packageSource.version; @@ -184,7 +184,7 @@ var updatePackageMetadata = function (packageSource, conn) { // Load in the user's documentation, and check that it isn't blank. var readmeInfo; - main.captureAndExit( + await main.captureAndExit( "=> Errors while publishing:", "reading documentation", function () { readmeInfo = packageSource.processReadme(); @@ -200,11 +200,11 @@ var updatePackageMetadata = function (packageSource, conn) { }; // Finally, call to the server. - main.captureAndExit( + await main.captureAndExit( "=> Errors while publishing:", "updating package metadata", - function () { - packageClient.updatePackageMetadata({ + async function () { + await packageClient.updatePackageMetadata({ packageSource: packageSource, readmeInfo: readmeInfo, connection: conn @@ -244,7 +244,7 @@ main.registerCommand({ // obviously incorrect submissions before they ever hit the wire. catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }), 'allow-incompatible-update': { type: Boolean } -}, function (options) { +}, async function (options) { if (options.create && options['existing-version']) { // Make up your mind! Console.error( @@ -301,10 +301,10 @@ main.registerCommand({ }); } - main.captureAndExit("=> Errors while initializing project:", function () { + await main.captureAndExit("=> Errors while initializing project:", async function () { // Just get up to initializing the catalog. We're going to mutate the // constraints file a bit before we prepare the build. - projectContext.initializeCatalog(); + await projectContext.initializeCatalog(); }); if (!process.env.METEOR_TEST_NO_PUBLISH) { @@ -353,7 +353,7 @@ main.registerCommand({ // need. Don't bother building the package, just update the metadata and // return the result. if (options.update) { - return updatePackageMetadata(packageSource, conn); + return await updatePackageMetadata(packageSource, conn); } // Fail early if the package record exists, but we don't think that it does @@ -402,8 +402,8 @@ main.registerCommand({ }); // Now resolve constraints and build packages. - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); // We don't display the package map delta here, because it includes adding the // package's test and all the test's dependencies. @@ -438,11 +438,11 @@ main.registerCommand({ // We have initialized everything, so perform the publish operation. var binary = isopack.platformSpecific(); - main.captureAndExit( + await main.captureAndExit( "=> Errors while publishing:", "publishing the package", - function () { - packageClient.publishPackage({ + async function () { + await packageClient.publishPackage({ projectContext: projectContext, packageSource: packageSource, connection: conn, @@ -502,7 +502,7 @@ main.registerCommand({ // publish-for-arch you want to reproduce the exact same setup as when // you ran 'publish', but support the option in case it comes up. 'allow-incompatible-update': { type: Boolean } -}, function (options) { +}, async function (options) { // argument processing var all = options.args[0].split('@'); if (all.length !== 2) { @@ -641,13 +641,13 @@ main.registerCommand({ }); // Just get up to initializing the catalog. We're going to mutate the // constraints file a bit before we prepare the build. - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.initializeCatalog(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.initializeCatalog(); }); projectContext.projectConstraintsFile.addConstraints( [utils.parsePackageConstraint(name + "@=" + versionString)]); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); projectContext.packageMapDelta.displayOnConsole({ title: "Some package versions changed since this package was published!" @@ -666,12 +666,12 @@ main.registerCommand({ return 1; } - main.captureAndExit( + await main.captureAndExit( "=> Errors while publishing build:", ("publishing package " + name + " for architecture " + isopk.buildArchitectures()), - function () { - packageClient.createAndPublishBuiltPackage( + async function () { + await packageClient.createAndPublishBuiltPackage( conn, isopk, projectContext.isopackCache); } ); @@ -704,7 +704,7 @@ main.registerCommand({ 'skip-tree-hashing': { type: Boolean }, }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { try { var conn = packageClient.loggedInPackagesConnection(); } catch (err) { @@ -726,7 +726,7 @@ main.registerCommand({ } // Fill in the order key and any other generated release.json fields. - main.captureAndExit( + await main.captureAndExit( "=> Errors in release schema:", "double-checking release schema", function () { @@ -858,8 +858,8 @@ main.registerCommand({ }); // Read metadata and initialize catalog. - main.captureAndExit("=> Errors while building for release:", function () { - projectContext.initializeCatalog(); + await main.captureAndExit("=> Errors while building for release:", async function () { + await projectContext.initializeCatalog(); }); // Ensure that all packages and their tests are built. (We need to build @@ -875,8 +875,8 @@ main.registerCommand({ ); // Build! - main.captureAndExit("=> Errors while building for release:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while building for release:", async function () { + await projectContext.prepareProjectForBuild(); }); // No need to display the PackageMapDelta here, since it would include all // of the packages! @@ -884,7 +884,7 @@ main.registerCommand({ relConf.packages = {}; var toPublish = []; - main.captureAndExit("=> Errors in release packages:", function () { + await main.captureAndExit("=> Errors in release packages:", function () { _.each(allPackages, function (packageName) { buildmessage.enterJob("checking consistency of " + packageName, function () { var packageSource = projectContext.localCatalog.getPackageSource( @@ -988,7 +988,7 @@ main.registerCommand({ }); if (options['dry-run']) { - main.captureAndExit("=> Dry run", function () { + await main.captureAndExit("=> Dry run", function () { buildmessage.error( "This is not an error but it was just a validation" + " and nothing was published. Remove --dry-run to publish."); @@ -999,8 +999,8 @@ main.registerCommand({ // We now have an object of packages that have new versions on disk that // don't exist in the server catalog. Publish them. var unfinishedBuilds = {}; - _.each(toPublish, function (packageName) { - main.captureAndExit( + _.each(toPublish, async function (packageName) { + await main.captureAndExit( "=> Errors while publishing:", "publishing package " + packageName, function () { @@ -1042,15 +1042,15 @@ main.registerCommand({ delete relConf.packages["meteor-tool"]; } - main.captureAndExit( + await main.captureAndExit( "=> Errors while publishing release:", "publishing release", - function () { + async function () { // Create the new track, if we have been told to. if (options['create-track']) { // XXX maybe this job title should be left on the screen too? some sort // of enterJob/progress option that lets you do that? - buildmessage.enterJob("creating a new release track", function () { + await buildmessage.enterJob("creating a new release track", function () { packageClient.callPackageServerBM( conn, 'createReleaseTrack', { name: relConf.track } ); }); @@ -1059,7 +1059,7 @@ main.registerCommand({ } } - buildmessage.enterJob("creating a new release version", function () { + await buildmessage.enterJob("creating a new release version", function () { var record = { track: relConf.track, version: relConf.version, @@ -1156,13 +1156,13 @@ main.registerCommand({ 'allow-incompatible-update': { type: Boolean } }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: true }) -}, function (options) { +}, async function (options) { var projectContext = new projectContextModule.ProjectContext({ projectDir: options.appDir, allowIncompatibleUpdate: options['allow-incompatible-update'] }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); // No need to display the PackageMapDelta here, since we're about to list all // of the packages anyway! @@ -1464,7 +1464,7 @@ var getNewerVersion = function (packageName, curVersion, whichCatalog) { // decided not to with good reason. Returns something other than 0, if it is not // safe to proceed (ex: our release track is fundamentally unsafe or there is // weird catalog corruption). -var maybeUpdateRelease = function (options) { +var maybeUpdateRelease = async function (options) { // We are only updating packages, so we are not updating the release. if (options["packages-only"]) { return 0; @@ -1606,8 +1606,8 @@ var maybeUpdateRelease = function (options) { alwaysWritePackageMap: true, allowIncompatibleUpdate: true // disregard `.meteor/versions` if necessary }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.readProjectMetadata(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.readProjectMetadata(); }); if (projectContext.releaseFile.fullReleaseName === release.current.name) { @@ -1717,8 +1717,8 @@ var maybeUpdateRelease = function (options) { // to upgrade packages and have to do it again. Maybe we shouldn't? Note // that if we change this, that changes the upgraders interface, which // expects a projectContext that is fully prepared for build. - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); projectContext.writeReleaseFileAndDevBundleLink(releaseName); @@ -1767,7 +1767,7 @@ main.registerCommand({ minArgs: 0, maxArgs: Infinity, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: true }) -}, function (options) { +}, async function (options) { // If you are specifying packages individually, you probably don't want to // update the release. if (options.args.length > 0) { @@ -1791,7 +1791,7 @@ main.registerCommand({ return 1; } - var releaseUpdateStatus = maybeUpdateRelease(options); + var releaseUpdateStatus = await maybeUpdateRelease(options); // If we encountered an error and cannot proceed, return. if (releaseUpdateStatus !== 0) { return releaseUpdateStatus; @@ -1812,8 +1812,8 @@ main.registerCommand({ alwaysWritePackageMap: true, allowIncompatibleUpdate: options["allow-incompatible-update"] }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.readProjectMetadata(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.readProjectMetadata(); }); // If no packages have been specified, then we will send in a request to @@ -1905,10 +1905,10 @@ main.registerCommand({ upgradePackageNames: upgradePackageNames, upgradeIndirectDepPatchVersions: upgradeIndirectDepPatchVersions }); - main.captureAndExit( - "=> Errors while upgrading packages:", "upgrading packages", function () { - projectContext.resolveConstraints(); - if (buildmessage.jobHasMessages()) { + await main.captureAndExit( + "=> Errors while upgrading packages:", "upgrading packages", async function () { + await projectContext.resolveConstraints(); + if (await buildmessage.jobHasMessages()) { return; } @@ -1921,12 +1921,12 @@ main.registerCommand({ } }); } - if (buildmessage.jobHasMessages()) { + if (await buildmessage.jobHasMessages()) { return; } // Finish preparing the project. - projectContext.prepareProjectForBuild(); + await projectContext.prepareProjectForBuild(); } ); @@ -2022,13 +2022,13 @@ main.registerCommand({ requiresApp: true, catalogRefresh: new catalog.Refresh.Never(), 'allow-incompatible-update': { type: Boolean } -}, function (options) { +}, async function (options) { var projectContext = new projectContextModule.ProjectContext({ projectDir: options.appDir, allowIncompatibleUpdate: options['allow-incompatible-update'] }); - main.captureAndExit("=> Errors while initializing project:", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors while initializing project:", async function () { + await projectContext.prepareProjectForBuild(); }); projectContext.packageMapDelta.displayOnConsole(); @@ -2049,7 +2049,7 @@ main.registerCommand({ name: 'admin run-background-updater', hidden: true, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { +}, async function (options) { updater.tryToDownloadUpdate({ showBanner: true, printErrors: true @@ -2065,7 +2065,7 @@ main.registerCommand({ name: 'admin wipe-all-packages', hidden: true, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { +}, async function (options) { tropohouse.default.wipeAllPackages(); }); @@ -2079,7 +2079,7 @@ main.registerCommand({ name: 'admin check-package-versions', hidden: true, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { if (!files.inCheckout()) { Console.error("Must run from checkout."); return 1; @@ -2097,8 +2097,8 @@ main.registerCommand({ }); // Read metadata and initialize catalog. - main.captureAndExit("=> Errors while building for release:", function () { - projectContext.initializeCatalog(); + await main.captureAndExit("=> Errors while building for release:", async function () { + await projectContext.initializeCatalog(); }); var allPackages = projectContext.localCatalog.getAllNonTestPackageNames(); @@ -2129,16 +2129,16 @@ main.registerCommand({ maxArgs: Infinity, requiresApp: true, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: true }) -}, function (options) { +}, async function (options) { var projectContext = new projectContextModule.ProjectContext({ projectDir: options.appDir, allowIncompatibleUpdate: options["allow-incompatible-update"] }); - main.captureAndExit("=> Errors while initializing project:", function () { + await main.captureAndExit("=> Errors while initializing project:", async function () { // We're just reading metadata here --- we're not going to resolve // constraints until after we've made our changes. - projectContext.initializeCatalog(); + await projectContext.initializeCatalog(); }); let exitCode = 0; @@ -2192,7 +2192,7 @@ main.registerCommand({ // constraints. Don't run the constraint solver until you have added all of // them -- add should be an atomic operation regardless of the package // order. - var messages = buildmessage.capture(function () { + var messages = await buildmessage.capture(function () { _.each(packagesToAdd, function (packageReq) { buildmessage.enterJob("adding package " + packageReq, function () { var constraint = utils.parsePackageConstraint(packageReq, { @@ -2286,8 +2286,8 @@ main.registerCommand({ projectContext.projectConstraintsFile.addConstraints(constraintsToAdd); // Run the constraint solver, download packages, etc. - messages = buildmessage.capture(function () { - projectContext.prepareProjectForBuild(); + messages = await buildmessage.capture(async function () { + await projectContext.prepareProjectForBuild(); }); if (messages.hasMessages()) { Console.arrowError("Errors while adding packages:", 1); @@ -2337,15 +2337,15 @@ main.registerCommand({ maxArgs: Infinity, requiresApp: true, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { +}, async function (options) { var projectContext = new projectContextModule.ProjectContext({ projectDir: options.appDir, allowIncompatibleUpdate: options["allow-incompatible-update"] }); - main.captureAndExit("=> Errors while initializing project:", function () { + await main.captureAndExit("=> Errors while initializing project:", async function () { // We're just reading metadata here --- we're not going to resolve // constraints until after we've made our changes. - projectContext.readProjectMetadata(); + await projectContext.readProjectMetadata(); }); let exitCode = 0; @@ -2418,8 +2418,8 @@ main.registerCommand({ // Run the constraint solver, rebuild local packages, etc. This will write // our changes to .meteor/packages if it succeeds. - main.captureAndExit("=> Errors after removing packages", function () { - projectContext.prepareProjectForBuild(); + await main.captureAndExit("=> Errors after removing packages", async function () { + await projectContext.prepareProjectForBuild(); }); projectContext.packageMapDelta.displayOnConsole(); @@ -2441,7 +2441,7 @@ main.registerCommand({ main.registerCommand({ name: 'refresh', catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { // We already did it! return 0; }); @@ -2466,7 +2466,7 @@ main.registerCommand({ list: { type: Boolean } }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { var name = options.args[0]; // Yay, checking that options are correct. @@ -2569,7 +2569,7 @@ main.registerCommand({ // we assume that all packages have been published (along with the release // obviously) and we want to be sure to only bundle the published versions. catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { var releaseNameAndVersion = options.args[0]; // We get this as an argument, so it is an OS path. Make it a standard path. @@ -2640,10 +2640,10 @@ main.registerCommand({ // Before downloading anything, check that the catalog contains everything we // need for the OSes that the tool is built for. - main.captureAndExit("=> Errors finding builds:", function () { + await main.captureAndExit("=> Errors finding builds:", async function () { _.each(osArches, function (osArch) { - _.each(releaseRecord.packages, function (pkgVersion, pkgName) { - buildmessage.enterJob({ + _.each(releaseRecord.packages, async function (pkgVersion, pkgName) { + await buildmessage.enterJob({ title: "looking up " + pkgName + "@" + pkgVersion + " on " + osArch }, function () { if (!catalog.official.getBuildsForArches(pkgName, pkgVersion, [osArch])) { @@ -2686,7 +2686,7 @@ main.registerCommand({ var packageMap = packageMapModule.PackageMap.fromReleaseVersion(releaseRecord); - _.each(osArches, function (osArch) { + for (const osArche in osArches) { var tmpdir = files.mkdtemp(); Console.info("Building tarball for " + osArch); @@ -2702,7 +2702,7 @@ main.registerCommand({ files.pathJoin(tmpdir, '.meteor'), { platform: targetPlatform }); - main.captureAndExit( + await main.captureAndExit( "=> Errors downloading packages for " + osArch + ":", function () { tmpTropo.downloadPackagesMissingFromMap(packageMap, { @@ -2743,7 +2743,7 @@ main.registerCommand({ files.pathJoin(outputDirectory, 'meteor-bootstrap-' + osArch + '.tar.gz')); } - }); + } return 0; }); @@ -2755,7 +2755,7 @@ main.registerCommand({ maxArgs: 1, hidden: true, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { var bannersFile = options.args[0]; try { var bannersData = files.readFile(bannersFile, 'utf8'); @@ -2802,7 +2802,7 @@ main.registerCommand({ unrecommend: { type: Boolean, short: "u" } }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { // We want the most recent information. //refreshOfficialCatalogOrDie(); @@ -2858,7 +2858,7 @@ main.registerCommand({ minArgs: 2, maxArgs: 2, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { // We want the most recent information. //refreshOfficialCatalogOrDie(); @@ -2906,7 +2906,7 @@ main.registerCommand({ }, hidden: true, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: false }) -}, function (options) { +}, async function (options) { // We don't care about having the most recent information, but we do want the // option to either unmigrate a specific version, or to unmigrate an entire diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 832239b24f..c4a0149d13 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -194,7 +194,7 @@ main.registerCommand({ requiresRelease: false, pretty: false, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { +}, async function (options) { if (release.current === null) { if (! options.appDir) { throw new Error("missing release, but not in an app?"); @@ -208,9 +208,9 @@ main.registerCommand({ } if (release.current.isCheckout()) { - var gitLog = utils.runGitInCheckout( + var gitLog = (await utils.runGitInCheckout( 'log', - '--format=%h%d', '-n 1').trim(); + '--format=%h%d', '-n 1')).trim(); Console.error("Unreleased, running from a checkout at " + gitLog); return 1; } @@ -335,7 +335,7 @@ main.registerCommand(Object.assign( runCommandOptions ), doRunCommand); -function doRunCommand(options) { +async function doRunCommand(options) { Console.setVerbose(!!options.verbose); // Additional args are interpreted as run targets @@ -434,7 +434,7 @@ function doRunCommand(options) { } var runAll = require('../runners/run-all.js'); - return runAll.run({ + return await runAll.run({ projectContext: projectContext, proxyPort: parsedServerUrl.port, proxyHost: parsedServerUrl.hostname, @@ -465,9 +465,9 @@ function doRunCommand(options) { main.registerCommand(Object.assign( { name: 'debug' }, runCommandOptions -), function (options) { +), async function (options) { options["inspect-brk"] = options["inspect-brk"] || "9229"; - return doRunCommand(options); + return await doRunCommand(options); }); /////////////////////////////////////////////////////////////////////////////// diff --git a/tools/cli/main.js b/tools/cli/main.js index fe6c326e3b..84bffcadee 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -1524,9 +1524,7 @@ asyncLocalStorage.run({}, async function () { }); } - var ret = await Promise.resolve( - command.func(options, { rawOptions }) - ); + var ret = await command.func(options, { rawOptions }); } catch (e) { Console.enableProgressDisplay(false); diff --git a/tools/fs/files.ts b/tools/fs/files.ts index c449257c35..a5ca8be4a7 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -12,7 +12,6 @@ import { Slot } from "@wry/context"; import { dep } from "optimism"; const _ = require('underscore'); -const Fiber = require("fibers"); const rimraf = require('rimraf'); const sourcemap = require('source-map'); @@ -62,11 +61,11 @@ function useParsedSourceMap(pathForSourceMap: string) { // Try this source map first sourceMapRetrieverStack.push(useParsedSourceMap); -function canYield() { - return Fiber.current && - Fiber.yield && - ! Fiber.yield.disallowed; -} +// function canYield() { +// return Fiber.current && +// Fiber.yield && +// ! Fiber.yield.disallowed; +// } // given a predicate function and a starting path, traverse upwards // from the path until we find a path that satisfies the predicate. diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 48d2674b57..476b37aa10 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -149,7 +149,7 @@ // wait until later. var assert = require('assert'); -var Fiber = require('fibers'); +//var Fiber = require('fibers'); var _ = require('underscore'); var compiler = require('./compiler.js'); @@ -2043,10 +2043,6 @@ class JsImage { assetPath = files.convertToStandardPath(assetPath); var promise; if (! callback) { - if (! Fiber.current) { - throw new Error("The synchronous Assets API can " + - "only be called from within a Fiber."); - } promise = new Promise(function (resolve, reject) { callback = function (err, res) { @@ -2729,8 +2725,8 @@ class ServerTarget extends JsImageTarget { serverPkgJson.dependencies["node-gyp"] = require("node-gyp/package.json").version; - serverPkgJson.dependencies["node-pre-gyp"] = - require("node-pre-gyp/package.json").version; + serverPkgJson.dependencies["@mapbox/node-pre-gyp"] = + require("@mapbox/node-pre-gyp/package.json").version; builder.write('package.json', { data: Buffer.from( diff --git a/tools/meteor-services/auth.js b/tools/meteor-services/auth.js index 2ae814965c..7d529a40c3 100644 --- a/tools/meteor-services/auth.js +++ b/tools/meteor-services/auth.js @@ -46,12 +46,12 @@ var withAccountsConnection = function (f) { // // XXX if we reconnect we won't reauthenticate. Fix that before using // this for long-lived connections. -var loggedInAccountsConnection = function (token) { +var loggedInAccountsConnection = async function (token) { var connection = loadDDP().connect( config.getAuthDDPUrl() ); - return new Promise(function (resolve, reject) { + return await new Promise(function (resolve, reject) { connection.apply( 'login', [{ resume: token }], @@ -74,7 +74,7 @@ var loggedInAccountsConnection = function (token) { // Something else went wrong throw err; - }).await(); + }); }; // The accounts server has some wrapped methods that take and return @@ -133,7 +133,7 @@ var sessionMethodCaller = function (methodName, options) { cleanUp(); throw err; - }).await(); + }); }; }; @@ -554,7 +554,7 @@ exports.doUsernamePasswordLogin = function (options) { exports.doInteractivePasswordLogin = doInteractivePasswordLogin; -exports.loginCommand = withAccountsConnection(function (options, +exports.loginCommand = withAccountsConnection(async function (options, connection) { var data = readSessionData(); @@ -581,7 +581,7 @@ exports.loginCommand = withAccountsConnection(function (options, } } - tryRevokeOldTokens({ firstTry: true, connection: connection }); + await tryRevokeOldTokens({ firstTry: true, connection: connection }); data = readSessionData(); Console.error(); diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 84203ea283..9d03503dd6 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -82,7 +82,7 @@ const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // derived from either a transport-level exception, the response // body, or a generic 'try again later' message, as appropriate -function deployRpc(options) { +async function deployRpc(options) { options = Object.assign({}, options); options.headers = Object.assign({}, options.headers || {}); if (options.headers.cookie) { @@ -100,7 +100,7 @@ function deployRpc(options) { options.qs.capabilities.push('willPollVersionStatus'); } - const deployURLBase = getDeployURL(options.site).await(); + const deployURLBase = await getDeployURL(options.site); if (options.printDeployURL) { Console.info("Talking to Galaxy servers at " + deployURLBase); @@ -193,13 +193,13 @@ function deployRpc(options) { // accounts server but our authentication actually fails, then prompt // the user to log in with a username and password and then resend the // RPC. -function authedRpc(options) { +async function authedRpc(options) { var rpcOptions = Object.assign({}, options); var preflight = rpcOptions.preflight; delete rpcOptions.preflight; // Fetch auth info - var infoResult = deployRpc({ + var infoResult = await deployRpc({ operation: 'info', site: rpcOptions.site, expectPayload: [], diff --git a/tools/meteor-services/service-connection.js b/tools/meteor-services/service-connection.js index 6ac6791fd8..af916b1674 100644 --- a/tools/meteor-services/service-connection.js +++ b/tools/meteor-services/service-connection.js @@ -95,15 +95,15 @@ var ServiceConnection = function (endpointUrl, options) { } }); - connectPromise.await(); + return connectPromise; }; Object.assign(ServiceConnection.prototype, { - call: function (name, ...args) { - return this.apply(name, args); + call: async function (name, ...args) { + return await this.apply(name, args); }, - apply: function (...args) { + apply: async function (...args) { var self = this; if (self.currentPromise) { @@ -128,12 +128,12 @@ Object.assign(ServiceConnection.prototype, { self.connection.apply(...args); - return self.currentPromise.await(); + return await self.currentPromise; }, // XXX derived from _subscribeAndWait in ddp_connection.js // -- but with a different signature.. - subscribeAndWait: function (...args) { + subscribeAndWait: async function (...args) { var self = this; if (self.currentPromise) { @@ -165,7 +165,7 @@ Object.assign(ServiceConnection.prototype, { }); var sub = self.connection.subscribe(...args); - subPromise.await(); + await subPromise; return sub; }, diff --git a/tools/meteor-services/stats.js b/tools/meteor-services/stats.js index bf02f116eb..4fc075ba78 100644 --- a/tools/meteor-services/stats.js +++ b/tools/meteor-services/stats.js @@ -1,4 +1,4 @@ -var Fiber = require("fibers"); +//var Fiber = require("fibers"); var _ = require("underscore"); var config = require('./config.js'); @@ -7,6 +7,7 @@ var auth = require('./auth.js'); var ServiceConnection = require('./service-connection.js'); var httpHelpers = require('../utils/http-helpers.js'); var Console = require('../console/console.js').Console; +const { asyncLocalStorage } = require('../utils/fiber-helpers'); // The name of the package that you add to your app to opt out of // sending stats. @@ -56,7 +57,7 @@ var recordPackages = function (options) { // This also gives it its own buildmessage state. // However, we do make sure to have already extracted the package list from // projectContext, since it might mutate out from under us otherwise. - Fiber(function () { + asyncLocalStorage.run({}, function () { var details = { what: options.what, @@ -64,9 +65,9 @@ var recordPackages = function (options) { sessionId: auth.getSessionId(config.getAccountsDomain()), site: options.site }; - + var conn; try { - var conn = connectToPackagesStatsServer(); + conn = connectToPackagesStatsServer(); var accountsConfiguration = auth.getAccountsConfiguration(conn); if (auth.isLoggedIn()) { @@ -111,7 +112,7 @@ var recordPackages = function (options) { } finally { conn && conn.close(); } - }).run(); + }); }; var logErrorIfInCheckout = function (err) { diff --git a/tools/project-context.js b/tools/project-context.js index d85392d27b..70e6736964 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -315,8 +315,8 @@ Object.assign(ProjectContext.prototype, { // because all we do here is read a handful of files. return this._completeStagesThrough(STAGE.READ_PROJECT_METADATA); }, - initializeCatalog: function () { - return Profile.run('ProjectContext initializeCatalog', () => { + initializeCatalog: async function () { + return await Profile.run('ProjectContext initializeCatalog', () => { return this._completeStagesThrough(STAGE.INITIALIZE_CATALOG); }); }, @@ -340,10 +340,10 @@ Object.assign(ProjectContext.prototype, { return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, - prepareProjectForBuild: function () { + prepareProjectForBuild: async function () { // This is the same as saveChangedMetadata, but if we insert stages after // that one it will continue to mean "fully finished". - return Profile.run('ProjectContext prepareProjectForBuild', () => { + return await Profile.run('ProjectContext prepareProjectForBuild', () => { return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, diff --git a/tools/runners/run-all.js b/tools/runners/run-all.js index 04fa462eff..1f973f6838 100644 --- a/tools/runners/run-all.js +++ b/tools/runners/run-all.js @@ -331,7 +331,7 @@ class Runner { // - recordPackageUsage: (default true) if set to false, don't send // information about packages used by this app to the package stats // server. -exports.run = function (options) { +exports.run = async function (options) { var runOptions = _.clone(options); var once = runOptions.once; @@ -396,7 +396,7 @@ exports.run = function (options) { var runner = new Runner(runOptions); runner.start(); - var result = promise.await(); + var result = await promise; runner.stop(); if (result.outcome === "conflicting-versions") { diff --git a/tools/runners/run-app.js b/tools/runners/run-app.js index fbc3b03af5..d58601f4b6 100644 --- a/tools/runners/run-app.js +++ b/tools/runners/run-app.js @@ -1,5 +1,5 @@ var _ = require('underscore'); -var Fiber = require('fibers'); +var { asyncLocalStorage } = require('../utils/fiber-helpers'); var files = require('../fs/files'); var watch = require('../fs/watch'); var bundler = require('../isobuild/bundler.js'); @@ -395,7 +395,7 @@ var AppRunner = function (options) { Object.assign(AppRunner.prototype, { // Start the app running, and restart it as necessary. Returns // immediately. - start: function () { + start: async function () { var self = this; if (self.fiber) { @@ -404,10 +404,11 @@ Object.assign(AppRunner.prototype, { self.startPromise = self._makePromise("start"); - self.fiber = Fiber(function () { - self._fiber(); + //TODO Check + self.fiber = await asyncLocalStorage.run({}, async function () { + return await self._fiber(); }); - self.fiber.run(); + //self.fiber.run(); self.startPromise.await(); self.startPromise = null; @@ -480,7 +481,7 @@ Object.assign(AppRunner.prototype, { // Run the program once, wait for it to exit, and then return. The // return value is same as onRunEnd. - _runOnce: function (options) { + _runOnce: async function (options) { var self = this; options = options || {}; var firstRun = options.firstRun; @@ -497,7 +498,7 @@ Object.assign(AppRunner.prototype, { // a single invocation of _runOnce(). var cachedServerWatchSet; - var bundleApp = function () { + var bundleApp = async function () { if (! firstRun) { // If the build fails in a way that could be fixed by a refresh, allow // it even if we refreshed previously, since that might have been a @@ -524,7 +525,8 @@ Object.assign(AppRunner.prototype, { // shown from the previous solution. preservePackageMap: true }); - var messages = buildmessage.capture(function () { + var messages = await buildmessage.capture(function () { + console.log(self.projectContext.packageMapDelta); self.projectContext.readProjectMetadata(); }); if (messages.hasMessages()) { @@ -550,9 +552,10 @@ Object.assign(AppRunner.prototype, { }; } - messages = buildmessage.capture(function () { + messages = await buildmessage.capture(function () { self.projectContext.prepareProjectForBuild(); }); + console.log({messages}); if (messages.hasMessages()) { return { runResult: { @@ -565,6 +568,7 @@ Object.assign(AppRunner.prototype, { // Show package changes... unless it's the first time in test-packages. if (!(self.omitPackageMapDeltaDisplayOnFirstRun && firstRun)) { + console.log(self.projectContext.packageMapDelta); self.projectContext.packageMapDelta.displayOnConsole(); } @@ -622,7 +626,7 @@ Object.assign(AppRunner.prototype, { }; var bundleResult; - var bundleResultOrRunResult = bundleApp(); + var bundleResultOrRunResult = await bundleApp(); if (bundleResultOrRunResult.runResult) { return bundleResultOrRunResult.runResult; } @@ -939,12 +943,12 @@ Object.assign(AppRunner.prototype, { return ret; }, - _fiber: function () { + _fiber: async function () { var self = this; var firstRun = true; while (true) { - var runResult = self._runOnce({ + var runResult = await self._runOnce({ onListen: function () { if (! self.noRestartBanner && ! firstRun) { runLog.logRestart(self); diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index c1e2450e1b..4c8942e0bc 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -117,179 +117,184 @@ if (process.platform === 'win32') { // Windows doesn't have a ps equivalent that (reliably) includes the command // line, so approximate using the combined output of tasklist and netstat. findMongoPids = function(dbDir_unused, port) { - var promise = fiberHelpers.makeFulfillablePromise(); + return new Promise((resolve, reject) => { - child_process.exec('tasklist /fi "IMAGENAME eq mongod.exe"', function( - error, - stdout, - stderr - ) { - if (error) { - var additionalInfo = JSON.stringify(error); - if (error.code === 'ENOENT') { - additionalInfo = - "tasklist wasn't found on your system, it usually can be found at C:\\Windows\\System32\\."; - } - promise.reject( - new Error("Couldn't run tasklist.exe: " + additionalInfo) - ); - return; - } else { - // Find the pids of all mongod processes - var mongo_pids = []; - stdout.split('\n').forEach(function(line) { - var m = line.match(/^mongod.exe\s+(\d+) /); - if (m) { - mongo_pids[m[1]] = true; + child_process.exec('tasklist /fi "IMAGENAME eq mongod.exe"', function( + error, + stdout, + stderr + ) { + if (error) { + var additionalInfo = JSON.stringify(error); + if (error.code === 'ENOENT') { + additionalInfo = + "tasklist wasn't found on your system, it usually can be found at C:\\Windows\\System32\\."; } - }); + reject( + new Error("Couldn't run tasklist.exe: " + additionalInfo) + ); + return; + } else { + // Find the pids of all mongod processes + var mongo_pids = []; + stdout.split('\n') + .forEach(function(line) { + var m = line.match(/^mongod.exe\s+(\d+) /); + if (m) { + mongo_pids[m[1]] = true; + } + }); - // Now get the corresponding port numbers - child_process.exec( - 'netstat -ano', - { maxBuffer: 1024 * 1024 * 10 }, - function(error, stdout, stderr) { - if (error) { - promise.reject( - new Error("Couldn't run netstat -ano: " + JSON.stringify(error)) - ); - return; - } else { - var pids = []; - stdout.split('\n').forEach(function(line) { - var m = line.match( - /^\s*TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)/ + // Now get the corresponding port numbers + child_process.exec( + 'netstat -ano', + { maxBuffer: 1024 * 1024 * 10 }, + function(error, stdout, stderr) { + if (error) { + promise.reject( + new Error("Couldn't run netstat -ano: " + JSON.stringify(error)) ); - if (m) { - var found_pid = parseInt(m[2], 10); - var found_port = parseInt(m[1], 10); + return; + } else { + var pids = []; + stdout.split('\n') + .forEach(function(line) { + var m = line.match( + /^\s*TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)/ + ); + if (m) { + var found_pid = parseInt(m[2], 10); + var found_port = parseInt(m[1], 10); - // We can't check the path app_dir so assume it always matches - if (mongo_pids[found_pid] && (!port || port === found_port)) { - // Note that if the mongo rest interface is enabled the - // initial port + 1000 is also likely to be open. - // So remove the pid so we only match it once. - delete mongo_pids[found_pid]; - pids.push({ - pid: found_pid, - port: found_port, - app_dir: null, - }); - } - } - }); + // We can't check the path app_dir so assume it always matches + if (mongo_pids[found_pid] && (!port || port === found_port)) { + // Note that if the mongo rest interface is enabled the + // initial port + 1000 is also likely to be open. + // So remove the pid so we only match it once. + delete mongo_pids[found_pid]; + pids.push({ + pid: found_pid, + port: found_port, + app_dir: null, + }); + } + } + }); - promise.resolve(pids); + resolve(pids); + } } - } - ); - } + ); + } + }); }); - - return promise.await(); }; } else { findMongoPids = function(dbDir, port) { - var promise = fiberHelpers.makeFulfillablePromise(); + return new Promise((resolve, reject) => { - // 'ps ax' should be standard across all MacOS and Linux. - // However, ps on OS X corrupts some non-ASCII characters in arguments, - // such as т (CYRILLIC SMALL LETTER TE), leading to this function - // failing to properly match pathnames with those characters. #3999 - // - // pgrep appears to do a better job (and has output that is roughly - // similar; it lacks a few fields that we don't care about). Plus, - // it can do some of the grepping for us. - // - // However, 'pgrep' only started shipping with OS X 10.8 (and may be less - // common on Linux too), so we check to see if it exists and fall back to - // 'ps' if we can't find it. - // - // We avoid using pgrep on Linux, because some versions of Linux pgrep - // require you to pass -a/--list-full to include the arguments in the - // output, and other versions fail if you pass that option. We have not - // observed the Unicode corruption on Linux, so using ps ax there is fine. - var psScript = 'ps ax'; - if (process.platform === 'darwin') { - psScript = - 'if type pgrep >/dev/null 2>&1; then ' + - // -lf means to display and match against full argument lists. - // pgrep exits 1 if no processes match the argument; we're OK - // considering this as a success, but we don't want other errors - // to be ignored. Note that this is sh not bash, so we can't use - // [[. - 'pgrep -lf mongod; test "$?" -eq 0 -o "$?" -eq 1;' + - 'else ps ax; fi'; - } + // 'ps ax' should be standard across all MacOS and Linux. + // However, ps on OS X corrupts some non-ASCII characters in arguments, + // such as т (CYRILLIC SMALL LETTER TE), leading to this function + // failing to properly match pathnames with those characters. #3999 + // + // pgrep appears to do a better job (and has output that is roughly + // similar; it lacks a few fields that we don't care about). Plus, + // it can do some of the grepping for us. + // + // However, 'pgrep' only started shipping with OS X 10.8 (and may be less + // common on Linux too), so we check to see if it exists and fall back to + // 'ps' if we can't find it. + // + // We avoid using pgrep on Linux, because some versions of Linux pgrep + // require you to pass -a/--list-full to include the arguments in the + // output, and other versions fail if you pass that option. We have not + // observed the Unicode corruption on Linux, so using ps ax there is fine. + var psScript = 'ps ax'; + if (process.platform === 'darwin') { + psScript = + 'if type pgrep >/dev/null 2>&1; then ' + + // -lf means to display and match against full argument lists. + // pgrep exits 1 if no processes match the argument; we're OK + // considering this as a success, but we don't want other errors + // to be ignored. Note that this is sh not bash, so we can't use + // [[. + 'pgrep -lf mongod; test "$?" -eq 0 -o "$?" -eq 1;' + + 'else ps ax; fi'; + } - // If the child process output includes unicode, make sure it's - // handled properly. - const { - LANG = 'en_US.UTF-8', - LC_ALL = LANG, - LANGUAGE = LANG, - // Remainder of process.env without above properties. - ...env - } = process.env; + // If the child process output includes unicode, make sure it's + // handled properly. + const { + LANG = 'en_US.UTF-8', + LC_ALL = LANG, + LANGUAGE = LANG, + // Remainder of process.env without above properties. + ...env + } = process.env; - // Make sure all three properties are set to the same value, which - // defaults to "en_US.UTF-8" or whatever LANG was already set to. - Object.assign(env, { LANG, LC_ALL, LANGUAGE }); + // Make sure all three properties are set to the same value, which + // defaults to "en_US.UTF-8" or whatever LANG was already set to. + Object.assign(env, { + LANG, + LC_ALL, + LANGUAGE + }); - child_process.exec( - psScript, - { - env, - // we don't want this to randomly fail just because you're running - // lots of processes. 10MB should be more than ps ax will ever - // spit out; the default is 200K, which at least one person hit - // (#2158). - maxBuffer: 1024 * 1024 * 10, - }, - function(error, stdout, stderr) { - if (error) { - promise.reject( - new Error( - "Couldn't run ps ax: " + + child_process.exec( + psScript, + { + env, + // we don't want this to randomly fail just because you're running + // lots of processes. 10MB should be more than ps ax will ever + // spit out; the default is 200K, which at least one person hit + // (#2158). + maxBuffer: 1024 * 1024 * 10, + }, + function(error, stdout, stderr) { + if (error) { + reject( + new Error( + "Couldn't run ps ax: " + JSON.stringify(error) + '; ' + error.message - ) - ); - return; - } - - var ret = []; - stdout.split('\n').forEach(function(line) { - // Matches mongos we start. Note that this matches - // 'fake-mongod' (our mongod stub for automated tests) as well - // as 'mongod'. - var m = line.match( - /^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+(?:\/|\\)db)/ - ); - if (m && m.length === 4) { - var foundPid = parseInt(m[1], 10); - var foundPort = parseInt(m[2], 10); - var foundPath = m[3]; - - if ( - (!port || port === foundPort) && - (!dbDir || dbDir === foundPath) - ) { - ret.push({ - pid: foundPid, - port: foundPort, - dbDir: foundPath, - }); - } + ) + ); + return; } - }); - promise.resolve(ret); - } - ); + var ret = []; + stdout.split('\n') + .forEach(function(line) { + // Matches mongos we start. Note that this matches + // 'fake-mongod' (our mongod stub for automated tests) as well + // as 'mongod'. + var m = line.match( + /^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+(?:\/|\\)db)/ + ); + if (m && m.length === 4) { + var foundPid = parseInt(m[1], 10); + var foundPort = parseInt(m[2], 10); + var foundPath = m[3]; - return promise.await(); + if ( + (!port || port === foundPort) && + (!dbDir || dbDir === foundPath) + ) { + ret.push({ + pid: foundPid, + port: foundPort, + dbDir: foundPath, + }); + } + } + }); + + resolve(ret); + } + ); + }); }; } diff --git a/tools/runners/run-proxy.js b/tools/runners/run-proxy.js index d591054232..62b8c53849 100644 --- a/tools/runners/run-proxy.js +++ b/tools/runners/run-proxy.js @@ -26,7 +26,7 @@ Object.assign(Proxy.prototype, { // Start the proxy server, block (yield) until it is ready to go // (actively listening on outer and proxying to inner), and then // return. - start: function () { + start: async function () { var self = this; if (self.server) { @@ -142,7 +142,7 @@ Object.assign(Proxy.prototype, { allowStart(); }); - promise.await(); + await promise; }, // Idempotent. diff --git a/tools/runners/run-selenium.js b/tools/runners/run-selenium.js index 8bbaaaf2ac..1590bc48b3 100644 --- a/tools/runners/run-selenium.js +++ b/tools/runners/run-selenium.js @@ -1,4 +1,4 @@ -var Fiber = require('fibers'); +var { asyncLocalStorage } = require('../utils/fiber-helpers'); var files = require('../fs/files'); var runLog = require('./run-log.js'); var utils = require('../utils/utils.js'); @@ -69,13 +69,13 @@ Object.assign(Selenium.prototype, { Promise.await(self.driver.getSession()); Promise.await(self.driver.get(self.url)); - Fiber(function () { + asyncLocalStorage.run({}, function () { try { self._pollLogs(); } catch (err) { runLog.log("Log polling exited unexpectedly: " + err); } - }).run(); + }); }, stop: function () { diff --git a/tools/tool-env/install-promise.js b/tools/tool-env/install-promise.js index cd4dac6267..2df5076153 100644 --- a/tools/tool-env/install-promise.js +++ b/tools/tool-env/install-promise.js @@ -2,7 +2,7 @@ // methods before we call makeCompatible, because the meteor-promise // implementation captures Fiber.yield and keeps calling the captured // version, which ignores any wrapping that happens later. -require("./wrap-fibers.js"); +//require("./wrap-fibers.js"); // Ensure the global Promise constructor knows how to run all its // callbacks in Fibers. @@ -10,10 +10,10 @@ require("./wrap-fibers.js"); const { Promise } = global; function makeCompatible(newPromise) { - require("meteor-promise").makeCompatible( - newPromise, - require("fibers") - ); + // require("meteor-promise").makeCompatible( + // newPromise, + // require("fibers") + // ); } makeCompatible(Promise); diff --git a/tools/tool-env/install-runtime.js b/tools/tool-env/install-runtime.js index d8237cdc0e..f6f4264a09 100644 --- a/tools/tool-env/install-runtime.js +++ b/tools/tool-env/install-runtime.js @@ -2,7 +2,7 @@ // This module gets imported again in install-promise.js, but we might as // well import it here as well, in case we ever stop using meteor-promise. -require("./wrap-fibers.js"); +//require("./wrap-fibers.js"); // Install ES2015-complaint polyfills for Object, Array, String, Function, // Symbol, Map, Set, and Promise, patching the native implementations when diff --git a/tools/tool-env/profile.ts b/tools/tool-env/profile.ts index ddbcc9d842..1ba2e70d59 100644 --- a/tools/tool-env/profile.ts +++ b/tools/tool-env/profile.ts @@ -161,7 +161,6 @@ // // In both reports the grand total is 600ms. -const Fiber = require('fibers'); const filter = parseFloat(process.env.METEOR_PROFILE || "100"); // ms @@ -254,9 +253,12 @@ export function Profile< ? bucketName.apply(this, arguments as any) : bucketName; - const currentEntry = Fiber.current - ? Fiber.current.profilerEntry || (Fiber.current.profilerEntry = []) - : globalEntry; + // TODO Test with Profile / use asyncLocalStorage + //const currentStore = asyncLo + // const currentEntry = Fiber.current + // ? Fiber.current.profilerEntry || (Fiber.current.profilerEntry = []) + // : globalEntry; + const currentEntry = globalEntry; currentEntry.push(name); const key = encodeEntryKey(currentEntry); diff --git a/tools/utils/buildmessage.js b/tools/utils/buildmessage.js index a80a0f747f..e105dbd1fb 100644 --- a/tools/utils/buildmessage.js +++ b/tools/utils/buildmessage.js @@ -460,8 +460,8 @@ var markBoundary = function (f, context) { var error = function (message, options) { options = options || {}; - console.log("111") - console.trace("dasdsa") + //console.log("111") + //console.trace("dasdsa") if (options.downcase) { message = message.slice(0,1).toLowerCase() + message.slice(1); } From 3004ac0fd2e1aa2455a006313817061715934594 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 1 Dec 2022 11:32:51 -0300 Subject: [PATCH 0088/1965] Back to node 14 --- meteor | 2 +- .../eslint-plugin-meteor/scripts/build-dev-bundle-common.sh | 4 ++-- .../eslint-plugin-meteor/scripts/dev-bundle-tool-package.js | 4 ++-- .../eslint-plugin-meteor/scripts/generate-dev-bundle.sh | 3 +-- scripts/build-dev-bundle-common.sh | 2 +- scripts/dev-bundle-tool-package.js | 4 ++-- scripts/generate-dev-bundle.sh | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/meteor b/meteor index df6c76bdb9..dff5d789b0 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=18.12.1.0 +BUNDLE_VERSION=14.20.1.0 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh b/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh index 5998f245af..941a01eabb 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/build-dev-bundle-common.sh @@ -5,10 +5,10 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=18.12.1 +NODE_VERSION=14.17.6 MONGO_VERSION_64BIT=4.4.4 MONGO_VERSION_32BIT=3.2.22 -NPM_VERSION=7.24.2 +NPM_VERSION=6.14.15 if [ "$UNAME" == "Linux" ] ; then if [ "$ARCH" != "i686" -a "$ARCH" != "x86_64" ] ; then diff --git a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js index 61f444253e..17b63e201f 100644 --- a/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js +++ b/npm-packages/eslint-plugin-meteor/scripts/dev-bundle-tool-package.js @@ -10,10 +10,10 @@ var packageJson = { dependencies: { // Explicit dependency because we are replacing it with a bundled version // and we want to make sure there are no dependencies on a higher version - npm: "7.24.2", + npm: "6.14.15", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", "node-gyp": "8.0.0", - "@mapbox/node-pre-gyp": "1.0.10", + "node-pre-gyp": "0.15.0", typescript: "4.5.4", "@meteorjs/babel": "7.16.0-beta.7", // Keep the versions of these packages consistent with the versions diff --git a/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh b/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh index 52c84c8333..4d75328eae 100755 --- a/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh +++ b/npm-packages/eslint-plugin-meteor/scripts/generate-dev-bundle.sh @@ -164,9 +164,8 @@ then mv pacote npm/node_modules/ fi -pwd delete sqlite3/deps -delete sqlite3/node_modules/@mapbox/node-pre-gyp +delete sqlite3/node_modules/node-pre-gyp delete wordwrap/test delete moment/min diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index 4f38bd243a..d7b4cfaa10 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,7 +5,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -NODE_VERSION=18.12.1 +NODE_VERSION=14.20.1 MONGO_VERSION_64BIT=5.0.5 MONGO_VERSION_32BIT=3.2.22 NPM_VERSION=6.14.17 diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index e48ca2acf8..f593b8b83c 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -12,8 +12,8 @@ var packageJson = { // and we want to make sure there are no dependencies on a higher version npm: "6.14.17", pacote: "https://github.com/meteor/pacote/tarball/a81b0324686e85d22c7688c47629d4009000e8b8", - "node-gyp": "9.3.0", - "@mapbox/node-pre-gyp": "1.0.10", + "node-gyp": "8.0.0", + "node-pre-gyp": "0.15.0", typescript: "4.5.4", "@meteorjs/babel": "7.16.0-beta.7", // Keep the versions of these packages consistent with the versions diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 38a1fbdc63..5a6d10eeb6 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -171,7 +171,7 @@ then fi delete sqlite3/deps -#delete sqlite3/node_modules/@mapbox/node-pre-gyp +delete sqlite3/node_modules/node-pre-gyp delete wordwrap/test delete moment/min From 7bda967e2d3266a547c18a0396e6f2b0275526e8 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 29 Nov 2022 13:29:24 -0300 Subject: [PATCH 0089/1965] wip: started working on accounts base --- packages/accounts-base/accounts_client.js | 21 +++++----- packages/accounts-base/accounts_server.js | 49 ++++++++++++----------- packages/accounts-base/server_main.js | 5 ++- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 842e927ad9..6f3314ad04 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -119,18 +119,21 @@ export class AccountsClient extends AccountsCommon { */ logout(callback) { this._loggingOut.set(true); - this.connection.apply('logout', [], { + + this.connection.applyAsync('logout', [], { + // TODO[FIBERS]: Look this { wait: true } later. wait: true - }, (error, result) => { - this._loggingOut.set(false); - this._loginCallbacksCalled = false; - if (error) { - callback && callback(error); - } else { + }) + .then((result) => { + this._loggingOut.set(false); + this._loginCallbacksCalled = false; this.makeClientLoggedOut(); callback && callback(); - } - }); + }) + .catch((e) => { + this._loggingOut.set(false); + callback && callback(e); + }); } /** diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 2fd0a6d41b..2ad5a2cb83 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -14,6 +14,7 @@ const NonEmptyString = Match.Where(x => { return x.length > 0; }); + /** * @summary Constructor for the `Accounts` namespace on the server. * @locus Server @@ -71,8 +72,6 @@ export class AccountsServer extends AccountsCommon { // list of all registered handlers. this._loginHandlers = []; - - setupUsersCollection(this.users); setupDefaultLoginHandlers(this); setExpireTokensInterval(this); @@ -126,6 +125,10 @@ export class AccountsServer extends AccountsCommon { return currentInvocation.userId; } + async init() { + await setupUsersCollection(this.users); + } + /// /// LOGIN HOOKS /// @@ -259,11 +262,11 @@ export class AccountsServer extends AccountsCommon { }); }; - _successfulLogout(connection, userId) { + async _successfulLogout(connection, userId) { // don't fetch the user object unless there are some callbacks registered let user; - this._onLogoutHook.each(callback => { - if (!user && userId) user = this.users.findOne(userId, {fields: this._options.defaultFieldSelector}); + await this._onLogoutHook.forEachAsync(async callback => { + if (!user && userId) user = await this.users.findOne(userId, { fields: this._options.defaultFieldSelector }); callback({ user, connection }); return true; }); @@ -615,8 +618,8 @@ export class AccountsServer extends AccountsCommon { // Any connections associated with old-style unhashed tokens will be // in the process of becoming associated with hashed tokens and then // they'll get closed. - destroyToken(userId, loginToken) { - this.users.update(userId, { + async destroyToken(userId, loginToken) { + await this.users.update(userId, { $pull: { "services.resume.loginTokens": { $or: [ @@ -653,13 +656,13 @@ export class AccountsServer extends AccountsCommon { return await accounts._attemptLogin(this, "login", arguments, result); }; - methods.logout = function () { + methods.logout = async function () { const token = accounts._getLoginToken(this.connection.id); accounts._setLoginToken(this.userId, this.connection, null); if (token && this.userId) { - accounts.destroyToken(this.userId, token); + await accounts.destroyToken(this.userId, token); } - accounts._successfulLogout(this.connection, this.userId); + await accounts._successfulLogout(this.connection, this.userId); this.setUserId(null); }; @@ -671,8 +674,8 @@ export class AccountsServer extends AccountsCommon { // @returns Object // If successful, returns { token: , id: , // tokenExpires: }. - methods.getNewToken = function () { - const user = accounts.users.findOne(this.userId, { + methods.getNewToken = async function () { + const user = await accounts.users.findOne(this.userId, { fields: { "services.resume.loginTokens": 1 } }); if (! this.userId || ! user) { @@ -972,7 +975,7 @@ export class AccountsServer extends AccountsCommon { // already -- in this case we just clean up the observe that we started). const myObserveNumber = ++this._nextUserObserveNumber; this._userObservesForConnections[connection.id] = myObserveNumber; - Meteor.defer(() => { + Meteor.defer(async () => { // If something else happened on this connection in the meantime (it got // closed, or another call to _setLoginToken happened), just do // nothing. We don't need to start an observe for an old connection or old @@ -985,7 +988,7 @@ export class AccountsServer extends AccountsCommon { // Because we upgrade unhashed login tokens to hashed tokens at // login time, sessions will only be logged in with a hashed // token. Thus we only need to observe hashed tokens here. - const observe = this.users.find({ + const observe = await this.users.find({ _id: userId, 'services.resume.loginTokens.hashedToken': newToken }, { fields: { _id: 1 } }).observeChanges({ @@ -1747,7 +1750,7 @@ function defaultValidateNewUserHook(user) { } } -const setupUsersCollection = users => { +const setupUsersCollection = async users => { /// /// RESTRICTING WRITES TO USER OBJECTS /// @@ -1773,21 +1776,21 @@ const setupUsersCollection = users => { }); /// DEFAULT INDEXES ON USERS - users.createIndex('username', { unique: true, sparse: true }); - users.createIndex('emails.address', { unique: true, sparse: true }); - users.createIndex('services.resume.loginTokens.hashedToken', + await users.createIndex('username', { unique: true, sparse: true }); + await users.createIndex('emails.address', { unique: true, sparse: true }); + await users.createIndex('services.resume.loginTokens.hashedToken', { unique: true, sparse: true }); - users.createIndex('services.resume.loginTokens.token', + await users.createIndex('services.resume.loginTokens.token', { unique: true, sparse: true }); // For taking care of logoutOtherClients calls that crashed before the // tokens were deleted. - users.createIndex('services.resume.haveLoginTokensToDelete', + await users.createIndex('services.resume.haveLoginTokensToDelete', { sparse: true }); // For expiring login tokens - users.createIndex("services.resume.loginTokens.when", { sparse: true }); + await users.createIndex("services.resume.loginTokens.when", { sparse: true }); // For expiring password tokens - users.createIndex('services.password.reset.when', { sparse: true }); - users.createIndex('services.password.enroll.when', { sparse: true }); + await users.createIndex('services.password.reset.when', { sparse: true }); + await users.createIndex('services.password.enroll.when', { sparse: true }); }; diff --git a/packages/accounts-base/server_main.js b/packages/accounts-base/server_main.js index db5020fed5..05ec621117 100644 --- a/packages/accounts-base/server_main.js +++ b/packages/accounts-base/server_main.js @@ -5,7 +5,8 @@ import { AccountsServer } from "./accounts_server.js"; * @summary The namespace for all server-side accounts-related methods. */ Accounts = new AccountsServer(Meteor.server); - +// TODO[FIBERS]: I need TLA +Accounts.init().then() // Users table. Don't use the normal autopublish, since we want to hide // some fields. Code to autopublish this is in accounts_server.js. // XXX Allow users to configure this collection name. @@ -15,7 +16,7 @@ Accounts = new AccountsServer(Meteor.server); * @locus Anywhere * @type {Mongo.Collection} * @importFromPackage meteor -*/ + */ Meteor.users = Accounts.users; export { From b1a6cc07615a875847068c4df16bcc7b39286e1e Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sun, 4 Dec 2022 00:27:41 -0300 Subject: [PATCH 0090/1965] Remove Fibers for meteor tools: turns out the package building is still not done. Still need to check why it's failing to require node_modules when evaluating/loading the plugin. --- packages/caching-compiler/caching-compiler.js | 8 +- packages/constraint-solver/solver.js | 4 +- packages/logic-solver/optimize.js | 10 +- tools/cli/commands-packages.js | 6 +- tools/cli/commands.js | 5 +- tools/cli/main.js | 8 +- tools/fs/files.ts | 8 +- tools/isobuild/build-plugin.js | 2 +- tools/isobuild/builder.js | 34 +-- tools/isobuild/bundler.js | 282 +++++++++--------- tools/isobuild/compiler-plugin.js | 30 +- tools/isobuild/compiler.js | 1 - tools/isobuild/isopack-cache.js | 14 +- tools/isobuild/isopack.js | 66 ++-- tools/isobuild/linker.js | 144 ++++----- tools/isobuild/meteor-npm.js | 58 ++-- tools/isobuild/package-api.js | 4 +- tools/isobuild/package-source.js | 11 +- tools/isobuild/unibuild.js | 50 ++-- tools/meteor-services/auth-client.js | 13 +- tools/meteor-services/auth.js | 4 +- tools/meteor-services/service-connection.js | 144 ++++----- tools/meteor-services/stats.js | 11 +- tools/packaging/catalog/catalog-local.js | 6 +- tools/packaging/catalog/catalog-remote.js | 120 ++++---- tools/packaging/catalog/catalog.js | 6 +- tools/packaging/package-client.js | 22 +- tools/project-context.js | 45 +-- tools/tool-env/isopackets.js | 24 +- tools/utils/buildmessage.js | 2 - 30 files changed, 577 insertions(+), 565 deletions(-) diff --git a/packages/caching-compiler/caching-compiler.js b/packages/caching-compiler/caching-compiler.js index d0c3fe36a8..0f31547662 100644 --- a/packages/caching-compiler/caching-compiler.js +++ b/packages/caching-compiler/caching-compiler.js @@ -121,10 +121,10 @@ CachingCompilerBase = class CachingCompilerBase { // Called by the compiler plugins system after all linking and lazy // compilation has finished. - afterLink() { - this._afterLinkCallbacks.splice(0).forEach(callback => { - callback(); - }); + async afterLink() { + for (const callback of this._afterLinkCallbacks.splice(0)) { + await callback(); + } } // Borrowed from another MIT-licensed project that benjamn wrote: diff --git a/packages/constraint-solver/solver.js b/packages/constraint-solver/solver.js index c92bb67843..4876109848 100644 --- a/packages/constraint-solver/solver.js +++ b/packages/constraint-solver/solver.js @@ -419,9 +419,9 @@ CS.Solver.prototype.minimize = function (step, options) { self.setSolution(logic.minimizeWeightedSum( self.solution, optimized.costTerms, optimized.costWeights, { - progress: function (status, cost) { + progress: async function (status, cost) { if (self.options.nudge) { - self.options.nudge(); + await self.options.nudge(); } if (DEBUG) { if (status === 'improving') { diff --git a/packages/logic-solver/optimize.js b/packages/logic-solver/optimize.js index 454f7e8b2f..658b28291b 100644 --- a/packages/logic-solver/optimize.js +++ b/packages/logic-solver/optimize.js @@ -13,7 +13,7 @@ var getNonZeroWeightedTerms = function (costTerms, costWeights) { }; // See comments on minimizeWeightedSum and maximizeWeightedSum. -var minMaxWS = function (solver, solution, costTerms, costWeights, options, +var minMaxWS = async function (solver, solution, costTerms, costWeights, options, isMin) { var curSolution = solution; var curCost = curSolution.getWeightedSum(costTerms, costWeights); @@ -31,7 +31,7 @@ var minMaxWS = function (solver, solution, costTerms, costWeights, options, // try to skip straight to 0 cost, because if it works, it could // save us some time if (progress) { - progress('trying', 0); + await progress('trying', 0); } var zeroSolution = null; nonZeroTerms = getNonZeroWeightedTerms(costTerms, costWeights); @@ -45,7 +45,7 @@ var minMaxWS = function (solver, solution, costTerms, costWeights, options, if (isMin && strategy === 'bottom-up') { for (var trialCost = 1; trialCost < curCost; trialCost++) { if (progress) { - progress('trying', trialCost); + await progress('trying', trialCost); } var costIsTrialCost = Logic.equalBits( weightedSum, Logic.constantBits(trialCost)); @@ -67,7 +67,7 @@ var minMaxWS = function (solver, solution, costTerms, costWeights, options, // count up. while (isMin ? curCost > 0 : true) { if (progress) { - progress('improving', curCost); + await progress('improving', curCost); } var improvement = (isMin ? Logic.lessThan : Logic.greaterThan)( weightedSum, Logic.constantBits(curCost)); @@ -93,7 +93,7 @@ var minMaxWS = function (solver, solution, costTerms, costWeights, options, } if (progress) { - progress('finished', curCost); + await progress('finished', curCost); } return curSolution; diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 001bfa9a8c..50fba3ef92 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1421,7 +1421,7 @@ main.registerCommand({ return 0; }); -var getNewerVersion = function (packageName, curVersion, whichCatalog) { +var getNewerVersion = async function (packageName, curVersion, whichCatalog) { // Check to see if there are later versions available, returning the // latest version if there are. // @@ -1434,9 +1434,9 @@ var getNewerVersion = function (packageName, curVersion, whichCatalog) { // that we'll find something when we look in the catalog. var latest; if (/-/.test(curVersion)) { - latest = whichCatalog.getLatestVersion(packageName); + latest = await whichCatalog.getLatestVersion(packageName); } else { - latest = whichCatalog.getLatestMainlineVersion(packageName); + latest = await whichCatalog.getLatestMainlineVersion(packageName); } if (! latest) { // Shouldn't happen: we've chosen a published version of this package, diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 832239b24f..430f7a262b 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -811,8 +811,10 @@ main.registerCommand({ allowIncompatibleUpdate: true }); + await projectContext.init(); + await main.captureAndExit("=> Errors while creating your project", async function () { - projectContext.readProjectMetadata(); + await projectContext.readProjectMetadata(); if (buildmessage.jobHasMessages()) { return; } @@ -837,7 +839,6 @@ main.registerCommand({ var upgraders = require('../upgraders.js'); projectContext.finishedUpgraders.appendUpgraders(upgraders.allUpgraders()); - console.log("1111111115555") await projectContext.prepareProjectForBuild(); }); // No need to display the PackageMapDelta here, since it would include all of diff --git a/tools/cli/main.js b/tools/cli/main.js index fe6c326e3b..82d85ecad0 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -487,7 +487,7 @@ var springboard = async function (rel, options) { const isopack = require('../isobuild/isopack.js'); const packagePath = tropohouse.default.packagePath(toolsPkg, toolsVersion); const toolIsopack = new isopack.Isopack; - toolIsopack.initFromPath(toolsPkg, packagePath); + await toolIsopack.initFromPath(toolsPkg, packagePath); let toolRecord = null; serverArchitectures.some(arch => { @@ -495,7 +495,7 @@ var springboard = async function (rel, options) { }); if (!toolRecord) { - throw Error("missing tool for " + archinfo.host() + " in " + + throw Error("missing tool for " + await archinfo.host() + " in " + toolsPkg + "@" + toolsVersion); } @@ -539,7 +539,7 @@ var springboard = async function (rel, options) { // Release our connection to the sqlite catalog database for the current // process, so that the springboarded process can reestablish it. - catalog.official.closePermanently(); + await catalog.official.closePermanently(); const isWindows = process.platform === "win32"; const executable = files.pathJoin( @@ -872,7 +872,7 @@ asyncLocalStorage.run({}, async function () { // Initialize the server catalog. Among other things, this is where we get // release information (used by springboarding). We do not at this point talk // to the server and refresh it. - catalog.official.initialize({ + await catalog.official.initialize({ offline: !!process.env.METEOR_OFFLINE_CATALOG }); diff --git a/tools/fs/files.ts b/tools/fs/files.ts index c449257c35..b88de888b4 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -1033,7 +1033,7 @@ export function runJavaScript(code: string, { sourceMap?: object; sourceMapRoot?: string; }) { - return Profile.time('runJavaScript ' + filename, () => { + return Profile.time('runJavaScript ' + filename, async () => { const keys: string[] = [], values: any[] = []; // don't assume that _.keys and _.values are guaranteed to // enumerate in the same order @@ -1053,7 +1053,7 @@ export function runJavaScript(code: string, { const header = "(function(" + keys.join(',') + "){"; chunks.push(header); if (sourceMap) { - const sourcemapConsumer = Promise.await(new sourcemap.SourceMapConsumer(sourceMap)); + const sourcemapConsumer = await new sourcemap.SourceMapConsumer(sourceMap); chunks.push(sourcemap.SourceNode.fromStringWithSourceMap( code, sourcemapConsumer)); sourcemapConsumer.destroy(); @@ -1125,7 +1125,7 @@ export function runJavaScript(code: string, { if (parsedSourceMap) { // XXX this duplicates code in computeGlobalReferences - var consumer2 = Promise.await(new sourcemap.SourceMapConsumer(parsedSourceMap)); + var consumer2 = await new sourcemap.SourceMapConsumer(parsedSourceMap); var original = consumer2.originalPositionFor(parseError.loc); consumer2.destroy(); if (original.source) { @@ -1155,7 +1155,7 @@ export function runJavaScript(code: string, { } return buildmessage.markBoundary( - script.runInThisContext() + await script.runInThisContext() ).apply(null, values); }); } diff --git a/tools/isobuild/build-plugin.js b/tools/isobuild/build-plugin.js index 73850fadf3..281bf6e78e 100644 --- a/tools/isobuild/build-plugin.js +++ b/tools/isobuild/build-plugin.js @@ -37,7 +37,7 @@ Object.assign(exports.SourceProcessor.prototype, { async () => { try { const markedFactoryFunction = buildmessage.markBoundary(self.factoryFunction); - self.userPlugin = await markedFactoryFunction().call(null); + self.userPlugin = await markedFactoryFunction.call(null); // If we have a disk cache directory and the plugin wants it, use it. if (self.isopack.pluginCacheDir && self.userPlugin.setDiskCacheDirectory) { diff --git a/tools/isobuild/builder.js b/tools/isobuild/builder.js index ed099aa395..c8853e3abe 100644 --- a/tools/isobuild/builder.js +++ b/tools/isobuild/builder.js @@ -289,7 +289,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // Returns the final canonicalize relPath that was written to. // // If `file` is used then it will be added to the builder's WatchSet. - write(relPath, {data, file, hash, sanitize, executable, symlink}) { + async write(relPath, {data, file, hash, sanitize, executable, symlink}) { relPath = this._normalizeFilePath(relPath, sanitize); let getData = null; @@ -326,7 +326,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` if (this.buildPath === this.outputPath || this.writtenHashes[relPath]) { // atomicallyRewriteFile handles overwriting files that have already been created - atomicallyRewriteFile(absPath, getData(), { + await atomicallyRewriteFile(absPath, getData(), { mode }); } else { @@ -335,7 +335,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // it is not important to write atomically. files.writeFile(absPath, getData(), { mode - }) + }); } } @@ -346,7 +346,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` return relPath; } - copyTranspiledModules(relativePaths, { + async copyTranspiledModules(relativePaths, { sourceRootDir, targetRootDir = this.outputPath, needToTranspile = files.inCheckout(), @@ -355,14 +355,14 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // If these files have already been transpiled, copy the transpiled files // (both .js and .js.map) directly to the builder output directory, without // recompiling them. - relativePaths.forEach(relPath => { + for (const relPath of relativePaths) { const jsPath = jsToTs(relPath); - [jsPath, jsPath + ".map"].forEach(path => { - this.write(path, { + for (const path of [jsPath, jsPath + ".map"]) { + await this.write(path, { file: files.pathJoin(sourceRootDir, path), }); - }); - }); + } + } return; } @@ -521,9 +521,9 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // is patched through directly rather than rewriting its inputs and // outputs. This is only valid because it does nothing with its inputs // and outputs other than send pass them to other methods.) - writeToGeneratedFilename(relPath, writeOptions) { + async writeToGeneratedFilename(relPath, writeOptions) { const generated = this.generateFilename(relPath); - this.write(generated, writeOptions); + await this.write(generated, writeOptions); return generated; } @@ -858,13 +858,13 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` } // Move the completed bundle into its final location (outputPath) - complete() { + async complete() { if (this.previousUsedAsFile) { // delete files and folders left-over from previous runs and not // re-used in this run const removed = {}; const paths = Object.keys(this.previousUsedAsFile); - paths.forEach((path) => { + for (const path of paths) { // if the same path was re-used, leave it if (this.usedAsFile.hasOwnProperty(path)) { return; } @@ -880,7 +880,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` removed[path] = true; } else { // directory - files.rm_recursive(absPath); + await files.rm_recursive(absPath); // mark all sub-paths as removed, too paths.forEach((anotherPath) => { @@ -889,7 +889,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` } }); } - }); + } } // XXX Alternatively, we could just keep buildPath around, and make @@ -897,13 +897,13 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // case of renameDirAlmostAtomically since that one is constructing files to // be checked in to version control, but here we could get away with it. if (this.buildPath !== this.outputPath) { - files.renameDirAlmostAtomically(this.buildPath, this.outputPath); + await files.renameDirAlmostAtomically(this.buildPath, this.outputPath); } } // Delete the partially-completed bundle. Do not disturb outputPath. abort() { - files.rm_recursive(this.buildPath); + return files.rm_recursive(this.buildPath); } // Returns a WatchSet representing all files that were read from disk by the diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 48d2674b57..1cc115ea3f 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -351,7 +351,7 @@ class NodeModulesDirectory { // objects returned by the toJSON method above. Note that this works // even if the node_modules parameter is a string, though that will only // be the case for bundles built before Meteor 1.3. - static readDirsFromJSON(node_modules, { + static async readDirsFromJSON(node_modules, { rebuildBinaries = false, // Options consumed by readDirsFromJSON are listed above. Any other // options will be passed on to NodeModulesDirectory constructor via @@ -422,9 +422,9 @@ class NodeModulesDirectory { } if (rebuildBinaries) { - _.each(nodeModulesDirectories, (info, path) => { - meteorNpm.rebuildIfNonPortable(path); - }); + for (const path of Object.keys(nodeModulesDirectories)) { + await meteorNpm.rebuildIfNonPortable(path); + } } return nodeModulesDirectories; @@ -944,13 +944,13 @@ class Target { // `debugOnly` packages work with "debug" and "test" build // modes. if (p.debugOnly && this.buildMode === 'production') { - return; + continue; } if (p.prodOnly && this.buildMode !== 'production') { - return; + continue; } if (p.testOnly && this.buildMode !== 'test') { - return; + continue; } const unibuild = await p.getUnibuildAtArch(this.arch); unibuild && rootUnibuilds.push(unibuild); @@ -992,7 +992,6 @@ class Target { }, addToGetsUsed); }.bind(this); - // TODO -> We may need to keep the order here? for (const unibuild of rootUnibuilds) { await addToGetsUsed(unibuild); } @@ -1038,7 +1037,7 @@ class Target { // eachUsedUnibuild does follow weak edges (ie, they affect the // ordering), but only if they point to a package in usedPackages (ie, a // package that SOMETHING uses strongly). - var processUnibuild = function (usedUnibuild) { + var processUnibuild = async function (usedUnibuild) { if (onStack[usedUnibuild.id]) { buildmessage.error( "circular dependency between packages " + @@ -1047,7 +1046,7 @@ class Target { return; } onStack[usedUnibuild.id] = true; - add(usedUnibuild); + await add(usedUnibuild); delete onStack[usedUnibuild.id]; }; await compiler.eachUsedUnibuild({ @@ -1084,7 +1083,7 @@ class Target { // Run all the compiler plugins on all source files in the project. Returns an // array of PackageSourceBatches which contain the results of this processing. - _runCompilerPlugins({ + async _runCompilerPlugins({ minifiers = [], minifyMode = "development", }) { @@ -1120,7 +1119,7 @@ class Target { // Takes a CssOutputResource and returns a string of minified CSS, // or null to indicate no minification occurred. - minifyCssResource: (resource) => { + minifyCssResource: async (resource) => { if (! minifiersByExt.css) { // Indicates the caller should use the original resource.data // without minification. @@ -1143,12 +1142,14 @@ class Target { file.setTargetPathFromRelPath( stripLeadingSlash(resource.servePath)); - return minifyCssFiles([file], { + const results = await minifyCssFiles([file], { arch: target.arch, minifier: minifiersByExt.css, minifyMode, watchSet: this.watchSet - }).map(file => file.contents("utf8")).join("\n"); + }); + + return results.map(file => file.contents("utf8")).join("\n"); } }); @@ -1193,27 +1194,26 @@ class Target { const dynamicImportFiles = new Set; // Copy their resources into the bundle in order - sourceBatches.forEach((sourceBatch) => { + for (const sourceBatch of sourceBatches) { const unibuild = sourceBatch.unibuild; if (this.cordovaDependencies) { _.each(unibuild.pkg.cordovaDependencies, (version, name) => { this._addCordovaDependency( - name, - version, - // use newer version if another version has already been added - false + name, + version, + // use newer version if another version has already been added + false ); }); } const name = unibuild.pkg.name || null; - const isApp = ! name; // Emit the resources - const resources = sourceBatch.getResources( - jsOutputFilesMap.get(name).files, - (linkCacheKey, jsResources) => onJsOutputFiles(jsResources, sourceBatch, linkCacheKey) + const resources = await sourceBatch.getResources( + jsOutputFilesMap.get(name).files, + (linkCacheKey, jsResources) => onJsOutputFiles(jsResources, sourceBatch, linkCacheKey) ); // First, find all the assets, so that we can associate them with each js @@ -1246,8 +1246,8 @@ class Target { assetFiles.forEach(f => { const relPath = isOs - ? files.pathJoin('assets', resource.servePath) - : stripLeadingSlash(resource.servePath); + ? files.pathJoin('assets', resource.servePath) + : stripLeadingSlash(resource.servePath); f.setTargetPathFromRelPath(relPath); @@ -1353,7 +1353,7 @@ class Target { addToTree(file.hash(), file.targetPath, versions); } }); - }); + } dynamicImportFiles.forEach(file => { file.setContents( @@ -1367,15 +1367,15 @@ class Target { // Call any plugin.afterLink callbacks defined by compiler plugins, // and update the watch set's list of potentially unused files // now that all compilation (including lazy compilation) is finished. - sourceBatches.forEach(batch => { - batch.resourceSlots.forEach(slot => { + for (const batch of sourceBatches) { + for (const slot of batch.resourceSlots) { const plugin = - slot.sourceProcessor && - slot.sourceProcessor.userPlugin; + slot.sourceProcessor && + slot.sourceProcessor.userPlugin; if (plugin && typeof plugin.afterLink === "function") { - plugin.afterLink(); + await plugin.afterLink(); } - }); + } // Any source resource that the content or hash was accessed for are marked // as definitely used. @@ -1389,15 +1389,14 @@ class Target { } assert.strictEqual( - typeof resource._dataUsed, - "boolean" + typeof resource._dataUsed, + "boolean" ); let absPath = files.pathJoin(batch.sourceRoot, resource.path); this.watchSet.addFile(absPath, resource.hash); }); - }); - + } } // Minify the JS in this target @@ -1711,23 +1710,23 @@ class ClientTarget extends Target { // Returns an object with the following keys: // - controlFile: the path (relative to 'builder') of the control file for // the target - write(builder, {minifyMode, buildMode}) { + async write(builder, {minifyMode, buildMode}) { builder.reserve("program.json"); // Helper to iterate over all resources that we serve over HTTP. - const eachResource = function (f) { - ["js", "css", "asset"].forEach((type) => { - this[type].forEach((file) => { - f(file, file.type || type); - }); - }); + const eachResource = async function (f) { + for (const type of ["js", "css", "asset"]) { + for (const file of this[type]) { + await f(file, file.type || type); + } + } }.bind(this); // Reserve all file names from the manifest, so that interleaved // generateFilename calls don't overlap with them. const targetPathToHash = new Map; - eachResource((file, type) => { + await eachResource((file, type) => { const hash = targetPathToHash.get(file.targetPath); if (hash) { // When we add assets that have a URL prefix like /__cordova, we @@ -1750,7 +1749,7 @@ class ClientTarget extends Target { // Build up a manifest of all resources served via HTTP. const manifest = []; - eachResource((file, type) => { + await eachResource(async (file, type) => { const manifestItem = { path: file.targetPath, where: "client", @@ -1781,7 +1780,7 @@ class ClientTarget extends Target { mapData = Buffer.from(JSON.stringify(file.sourceMap), 'utf8'); } - manifestItem.sourceMap = builder.writeToGeneratedFilename( + manifestItem.sourceMap = await builder.writeToGeneratedFilename( file.targetPath + '.map', {data: mapData}); // Use a SHA to make this cacheable. @@ -1796,7 +1795,7 @@ class ClientTarget extends Target { manifestItem.sri = file.sri(); if (! file.targetPath.startsWith("dynamic/")) { - writeFile(file, builder, { + await writeFile(file, builder, { leaveSourceMapUrls: type === 'asset' }); manifest.push(manifestItem); @@ -1822,7 +1821,7 @@ class ClientTarget extends Target { // source maps can be very large), but rather include a normal URL // referring to the source map (as a comment), so that it can be // loaded from the web server when needed. - writeFile(file, builder, { + await writeFile(file, builder, { sourceMapUrl: manifestItem.sourceMapUrl, }); @@ -1847,16 +1846,16 @@ class ClientTarget extends Target { } else { // If the dynamic module does not have a source map, just write it // normally. - writeFile(file, builder); + await writeFile(file, builder); } }); - ['head', 'body'].forEach((type) => { + for (const type of ['head', 'body']) { const data = this[type].join('\n'); if (data) { const dataBuffer = Buffer.from(data, 'utf8'); - const dataFile = builder.writeToGeneratedFilename( - type + '.html', { data: dataBuffer }); + const dataFile = await builder.writeToGeneratedFilename( + type + '.html', { data: dataBuffer }); manifest.push({ path: dataFile, where: 'internal', @@ -1864,7 +1863,7 @@ class ClientTarget extends Target { hash: watch.sha1(dataBuffer) }); } - }); + } // Control file const program = { @@ -1874,7 +1873,7 @@ class ClientTarget extends Target { if (this.arch === 'web.cordova') { import { CORDOVA_PLATFORM_VERSIONS } from '../cordova'; - const { WebAppHashing } = loadIsopackage('webapp-hashing'); + const { WebAppHashing } = await loadIsopackage('webapp-hashing'); const cordovaCompatibilityVersions = _.object(_.map(CORDOVA_PLATFORM_VERSIONS, (version, platform) => { @@ -1899,8 +1898,7 @@ class ClientTarget extends Target { if (buildMode === 'development') { program.hmrVersion = Date.now(); } - - builder.writeJson('program.json', program); + await builder.writeJson('program.json', program); return { controlFile: "program.json" @@ -2032,7 +2030,7 @@ class JsImage { // XXX throw an error if the image includes any "app-style" code // that is built to put symbols in the global namespace rather than // in a compartment of Package - load(bindings) { + async load(bindings) { var self = this; var ret = new PackageRegistry(); @@ -2043,11 +2041,6 @@ class JsImage { assetPath = files.convertToStandardPath(assetPath); var promise; if (! callback) { - if (! Fiber.current) { - throw new Error("The synchronous Assets API can " + - "only be called from within a Fiber."); - } - promise = new Promise(function (resolve, reject) { callback = function (err, res) { err ? reject(err) : resolve(res); @@ -2072,7 +2065,7 @@ class JsImage { } if (promise) { - return promise.await(); + return promise; } }; @@ -2114,9 +2107,9 @@ class JsImage { // packages, and an 'Assets' symbol to help the package find its // static assets. var failed = false; - _.each(self.jsToLoad, function (item) { + for (const item of self.jsToLoad) { if (failed) { - return; + continue; } var env = Object.assign({ @@ -2152,13 +2145,13 @@ class JsImage { } var nodeModulesTopDir = files.pathJoin( - nodeModulesPath, - name.split("/", 1)[0] + nodeModulesPath, + name.split("/", 1)[0] ); if (files.exists(nodeModulesTopDir)) { return fullPath = files.convertToOSPath( - files.pathJoin(nodeModulesPath, name) + files.pathJoin(nodeModulesPath, name) ); } } @@ -2184,7 +2177,7 @@ class JsImage { // // in the parent package (e.g. ecmascript, coffeescript). const nmdSourcePaths = - nodeModulesDirsByPackageName.get(bindings.Plugin.name); + nodeModulesDirsByPackageName.get(bindings.Plugin.name); if (Array.isArray(nmdSourcePaths)) { found = _.some(nmdSourcePaths, sourcePath => { return tryLookup(sourcePath, name); @@ -2201,7 +2194,7 @@ class JsImage { if (appDir && resolved) { const isOutsideAppDir = - files.pathRelative(appDir, resolved).startsWith(".."); + files.pathRelative(appDir, resolved).startsWith(".."); if (! isOutsideAppDir) { return require(resolved); @@ -2209,7 +2202,7 @@ class JsImage { } throw error || new Error( - "Cannot find module " + JSON.stringify(name) + "Cannot find module " + JSON.stringify(name) ); }) }, @@ -2254,7 +2247,7 @@ class JsImage { // XXX XXX Get the actual source file path -- item.targetPath // is not actually correct (it's the path in the bundle rather // than in the source tree). - files.runJavaScript(item.source.toString('utf8'), { + await files.runJavaScript(item.source.toString('utf8'), { filename: item.targetPath, symbols: env, sourceMap: item.sourceMap, @@ -2266,7 +2259,7 @@ class JsImage { failed = true; return; } - }); + } return ret; } @@ -2350,7 +2343,7 @@ class JsImage { // Returns an object with the following keys: // - controlFile: the path (relative to 'builder') of the control file for // the image - write(builder, { + async write(builder, { buildMode, // falsy or 'symlink', documented on exports.bundle includeNodeModules, @@ -2389,7 +2382,7 @@ class JsImage { // JavaScript sources var load = []; - _.each(self.jsToLoad, function (item) { + for (const item of self.jsToLoad) { if (! item.targetPath) { throw new Error("No targetPath?"); } @@ -2398,7 +2391,7 @@ class JsImage { node_modules: {} }; - _.each(item.nodeModulesDirectories, nmd => { + for (const nmd of Object.values(item.nodeModulesDirectories)) { // We need to make sure to use the directory name we got from // builder.generateFilename here. // XXX these two parallel data structures of self.jsToLoad and @@ -2406,14 +2399,14 @@ class JsImage { const generatedNMD = nodeModulesDirectories[nmd.sourcePath]; if (generatedNMD) { assert.strictEqual( - typeof generatedNMD.preferredBundlePath, - "string" + typeof generatedNMD.preferredBundlePath, + "string" ); loadItem.node_modules[generatedNMD.preferredBundlePath] = - generatedNMD.toJSON(); + await generatedNMD.toJSON(); } - }); + } const preferredPaths = Object.keys(loadItem.node_modules); if (preferredPaths.length === 1) { @@ -2432,23 +2425,23 @@ class JsImage { if (item.sourceMap) { const sourceMapBuffer = - Buffer.from(JSON.stringify(item.sourceMap), "utf8"); + Buffer.from(JSON.stringify(item.sourceMap), "utf8"); - loadItem.sourceMap = builder.writeToGeneratedFilename( - item.targetPath + ".map", - { data: sourceMapBuffer } + loadItem.sourceMap = await builder.writeToGeneratedFilename( + item.targetPath + ".map", + { data: sourceMapBuffer } ); const sourceMappingURL = - "data:application/json;charset=utf8;base64," + - sourceMapBuffer.toString("base64"); + "data:application/json;charset=utf8;base64," + + sourceMapBuffer.toString("base64"); // Remove any existing sourceMappingURL line. (eg, if roundtripping // through JsImage.readFromDisk, don't end up with two!) sourceBuffer = addSourceMappingURL( - item.source, - sourceMappingURL, - item.targetPath, + item.source, + sourceMappingURL, + item.targetPath, ); if (item.sourceMapRoot) { @@ -2461,9 +2454,9 @@ class JsImage { sourceBuffer = removeSourceMappingURLs(item.source); } - loadItem.path = builder.writeToGeneratedFilename( - item.targetPath, - { data: sourceBuffer } + loadItem.path = await builder.writeToGeneratedFilename( + item.targetPath, + { data: sourceBuffer } ); if (!_.isEmpty(item.assets)) { @@ -2471,7 +2464,7 @@ class JsImage { // assets/packages specific to this package. Application assets (e.g. those // inside private/) go in assets/app/. // XXX same hack as setTargetPathFromRelPath - var assetBundlePath; + var assetBundlePath; if (item.targetPath.match(/^packages\//)) { var dir = files.pathDirname(item.targetPath); var base = files.pathBasename(item.targetPath, ".js"); @@ -2481,22 +2474,22 @@ class JsImage { } loadItem.assets = {}; - _.each(item.assets, function (data, relPath) { + for (const [relPath, data] of Object.entries(item.assets)) { var sha = watch.sha1(data); if (_.has(assetFilesBySha, sha)) { loadItem.assets[relPath] = assetFilesBySha[sha]; } else { loadItem.assets[relPath] = assetFilesBySha[sha] = - builder.writeToGeneratedFilename( - files.pathJoin(assetBundlePath, relPath), { data: data }); + await builder.writeToGeneratedFilename( + files.pathJoin(assetBundlePath, relPath), { data: data }); } - }); + } } if (! item.targetPath.startsWith("dynamic/")) { load.push(loadItem); } - }); + } const rebuildDirs = Object.create(null); @@ -2554,7 +2547,7 @@ class JsImage { // This JSON file will be read by npm-rebuild.js, which is executed to // trigger rebuilds for all non-portable npm packages. - builder.write("npm-rebuilds.json", { + await builder.write("npm-rebuilds.json", { data: Buffer.from( JSON.stringify(Object.keys(rebuildDirs), null, 2) + "\n", "utf8" @@ -2562,7 +2555,7 @@ class JsImage { }); // Control file - builder.writeJson('program.json', { + await builder.writeJson('program.json', { format: "javascript-image-pre1", arch: self.arch, load: load @@ -2576,8 +2569,8 @@ class JsImage { // Create a JsImage by loading a bundle of format // 'javascript-image-pre1' from disk (eg, previously written out with // write()). `dir` is the path to the control file. - static readFromDisk (controlFilePath) { - var ret = new JsImage; + static async readFromDisk (controlFilePath) { + var ret = new JsImage(); var json = JSON.parse(files.readFile(controlFilePath)); var dir = files.pathDirname(controlFilePath); @@ -2589,20 +2582,20 @@ class JsImage { ret.arch = json.arch; // Rebuild binary npm packages if host arch matches image arch. - const rebuildBinaries = archinfo.matches(archinfo.host(), ret.arch); + const rebuildBinaries = archinfo.matches(await archinfo.host(), ret.arch); - _.each(json.load, function (item) { + for (const item of json.load) { rejectBadPath(item.path); let nodeModulesDirectories; if (item.node_modules) { Object.assign( - ret.nodeModulesDirectories, - nodeModulesDirectories = - NodeModulesDirectory.readDirsFromJSON(item.node_modules, { - sourceRoot: dir, - rebuildBinaries, - }) + ret.nodeModulesDirectories, + nodeModulesDirectories = + await NodeModulesDirectory.readDirsFromJSON(item.node_modules, { + sourceRoot: dir, + rebuildBinaries, + }) ); } @@ -2616,7 +2609,7 @@ class JsImage { // XXX this is the same code as isopack.initFromPath rejectBadPath(item.sourceMap); loadItem.sourceMap = JSON.parse(files.readFile( - files.pathJoin(dir, item.sourceMap), 'utf8')); + files.pathJoin(dir, item.sourceMap), 'utf8')); loadItem.sourceMapRoot = item.sourceMapRoot; } @@ -2628,7 +2621,7 @@ class JsImage { } ret.jsToLoad.push(loadItem); - }); + } return ret; } @@ -2699,7 +2692,7 @@ class ServerTarget extends JsImageTarget { // // Returns the path (relative to 'builder') of the control file for // the plugin and the required NODE_PATH. - write(builder, { + async write(builder, { buildMode, // falsy or 'symlink', documented in exports.bundle includeNodeModules, @@ -2711,7 +2704,7 @@ class ServerTarget extends JsImageTarget { // We will write out config.json, the dependency kit, and the // server driver alongside the JsImage - builder.writeJson("config.json", { + await builder.writeJson("config.json", { meteorRelease: self.releaseName || undefined, appId: self.appIdentifier || undefined, clientArchs: self.clientArchs || undefined, @@ -2732,14 +2725,14 @@ class ServerTarget extends JsImageTarget { serverPkgJson.dependencies["node-pre-gyp"] = require("node-pre-gyp/package.json").version; - builder.write('package.json', { + await builder.write('package.json', { data: Buffer.from( JSON.stringify(serverPkgJson, null, 2) + "\n", "utf8" ) }); - builder.write('npm-shrinkwrap.json', { + await builder.write('npm-shrinkwrap.json', { file: files.pathJoin(files.getDevBundle(), 'etc', 'npm-shrinkwrap.json') }); @@ -2747,7 +2740,7 @@ class ServerTarget extends JsImageTarget { // install' using the above package.json and npm-shrinkwrap.json on every // rebuild). if (includeNodeModules === 'symlink') { - builder.write('node_modules', { + await builder.write('node_modules', { symlink: files.pathJoin(files.getDevBundle(), 'server-lib', 'node_modules') }); } else if (includeNodeModules) { @@ -2760,7 +2753,7 @@ class ServerTarget extends JsImageTarget { // Linked JavaScript image (including static assets, assuming that there are // any JS files at all) var jsImage = self.toJsImage(); - jsImage.write(builder, { + await jsImage.write(builder, { buildMode, includeNodeModules, }); @@ -2768,14 +2761,14 @@ class ServerTarget extends JsImageTarget { const toolsDir = files.pathDirname( files.convertToStandardPath(__dirname)); - builder.copyTranspiledModules([ + await builder.copyTranspiledModules([ "profile.ts" ], { sourceRootDir: files.pathJoin(toolsDir, "tool-env"), }); // Server bootstrap - builder.copyTranspiledModules([ + await builder.copyTranspiledModules([ "boot.js", "boot-utils.js", "debug.ts", @@ -2823,7 +2816,7 @@ class ServerTarget extends JsImageTarget { ServerTarget.prototype[method] = Profile(`ServerTarget#${method}`, ServerTarget.prototype[method]); }); -var writeFile = Profile("bundler writeFile", function (file, builder, options) { +var writeFile = Profile("bundler writeFile", async function (file, builder, options) { if (! file.targetPath) { throw new Error("No targetPath?"); } @@ -2850,7 +2843,7 @@ var writeFile = Profile("bundler writeFile", function (file, builder, options) { data = removeSourceMappingURLs(data); } - builder.write(file.targetPath, { data, hash }); + await builder.write(file.targetPath, { data, hash }); }); // Takes a Buffer or string and returns a Buffer. If it looks like there @@ -2909,7 +2902,7 @@ function addSourceMappingURL(data, url, targetPath) { // Writes a target a path in 'programs' var writeTargetToPath = Profile( "bundler writeTargetToPath", - function (name, target, outputPath, { + async function (name, target, outputPath, { includeNodeModules, previousBuilder = null, buildMode, @@ -2928,13 +2921,15 @@ var writeTargetToPath = Profile( forceInPlaceBuild }); - var targetBuild = target.write(builder, { + await builder.init(); + + var targetBuild = await target.write(builder, { includeNodeModules, buildMode, minifyMode, }); - builder.complete(); + await builder.complete(); return { name, @@ -2969,7 +2964,7 @@ var writeTargetToPath = Profile( // - builtBy: vanity identification string to write into metadata // - releaseName: The Meteor release version // - previousBuilder: previous Builder object used in previous iteration -var writeSiteArchive = Profile("bundler writeSiteArchive", function ( +var writeSiteArchive = Profile("bundler writeSiteArchive", async function ( targets, outputPath, { includeNodeModules, builtBy, @@ -3016,22 +3011,22 @@ var writeSiteArchive = Profile("bundler writeSiteArchive", function ( // symlinked a node_modules, since that's probably enough for it to work in // spite of the presence of node_modules for the wrong arch). The place we // stash this is grody for temporary reasons of backwards compatibility. - builder.write(files.pathJoin('server', '.bundle_version.txt'), { + await builder.write(files.pathJoin('server', '.bundle_version.txt'), { file: files.pathJoin(files.getDevBundle(), '.bundle_version.txt') }); - builder.write('.node_version.txt', { + await builder.write('.node_version.txt', { data: Buffer.from(process.version + '\n', 'utf8') }); // Affordances for standalone use if (targets.server) { // add program.json as the first argument after "node main.js" to the boot script. - builder.write('main.js', { + await builder.write('main.js', { data: Buffer.from(exports._mainJsContents, 'utf8') }); - builder.write('README', { data: Buffer.from( + await builder.write('README', { data: Buffer.from( `This is a Meteor application bundle. It has only one external dependency: Node.js ${process.version}. To run the application: @@ -3062,12 +3057,12 @@ Find out more about Meteor at meteor.com. } }); - Object.keys(targets).forEach(name => { + for (const name of Object.keys(targets)) { const target = targets[name]; const { arch, path, cordovaDependencies, builder: targetBuilder - } = writeTargetToPath(name, target, builder.buildPath, { + } = await writeTargetToPath(name, target, builder.buildPath, { includeNodeModules, builtBy, releaseName, @@ -3082,13 +3077,13 @@ Find out more about Meteor at meteor.com. json.programs.push({ name, arch, path, cordovaDependencies }); - }); + } // Control file - builder.writeJson('star.json', json); + await builder.writeJson('star.json', json); // We did it! - builder.complete(); + await builder.complete(); // Now, go and "fix up" the outputPath properties of the sub-builders. // Since the sub-builders originally were targetted at a temporary @@ -3107,7 +3102,7 @@ Find out more about Meteor at meteor.com. builders, }; } catch (e) { - builder.abort(); + await builder.abort(); throw e; } }); @@ -3200,7 +3195,7 @@ async function bundle({ }) { buildOptions = buildOptions || {}; - var serverArch = buildOptions.serverArch || archinfo.host(); + var serverArch = buildOptions.serverArch || await archinfo.host(); var webArchs; if (buildOptions.webArchs) { // Don't attempt to build web.cordova when platforms have been removed @@ -3362,9 +3357,9 @@ async function bundle({ forceInPlaceBuild, }; - function writeClientTarget(target) { + async function writeClientTarget(target) { const { arch } = target; - const written = writeTargetToPath(arch, target, outputPath, { + const written = await writeTargetToPath(arch, target, outputPath, { buildMode: buildOptions.buildMode, previousBuilder: previousBuilders[arch], ...writeOptions, @@ -3402,7 +3397,7 @@ async function bundle({ // Now write the target to disk. Note that we are rewriting the // bundle in place, so this work is not atomic by any means, // which is why we needed to pause the client. - writeClientTarget(target); + await writeClientTarget(target); // Refresh and unpause the client, now that writing is finished. // If the child process exited for some reason, don't worry if @@ -3432,7 +3427,9 @@ async function bundle({ if (hasCachedBundle) { // If we already have a cached bundle, just recreate the new targets. // XXX This might make the contents of "star.json" out of date. - _.each(targets, writeClientTarget); + for (const target of targets) { + await writeClientTarget(target); + } } else { starResult = writeSiteArchive(targets, outputPath, { buildMode: buildOptions.buildMode, @@ -3556,7 +3553,7 @@ exports.buildJsImage = Profile("bundler.buildJsImage", async function (options) var packageSource = new PackageSource(); - packageSource.initFromOptions(options.name, { + await packageSource.initFromOptions(options.name, { kind: "plugin", use: options.use || [], sourceRoot: options.sourceRoot, @@ -3587,6 +3584,7 @@ exports.buildJsImage = Profile("bundler.buildJsImage", async function (options) // (which always wants to build for the current host). arch: await archinfo.host() }); + await target.make({ packages: [isopack] }); return { diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 31343c7cd7..f97c4d4e8d 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -202,9 +202,7 @@ export class CompilerPluginProcessor { await buildmessage.enterJob({ title: jobTitle }, async function () { - var inputFiles = _.map(resourceSlots, function (resourceSlot) { - return new InputFile(resourceSlot); - }); + var inputFiles = resourceSlots.map(resourceSlot => new InputFile(resourceSlot)); const markedMethod = buildmessage.markBoundary( sourceProcessor.userPlugin.processFilesForTarget, @@ -1111,7 +1109,7 @@ export class PackageSourceBatch { self.useMeteorInstall = _.isString(self.sourceRoot) && - self.processor.isopackCache.uses( + await self.processor.isopackCache.uses( self.unibuild.pkg, "modules", self.unibuild.arch @@ -1660,7 +1658,7 @@ export class PackageSourceBatch { // that end up in the program for this package. By this point, it knows what // its dependencies are and what their exports are, so it can set up // linker-style imports and exports. - getResources(jsResources, onCacheKey) { + async getResources(jsResources, onCacheKey) { buildmessage.assertInJob(); const resources = []; @@ -1669,12 +1667,12 @@ export class PackageSourceBatch { resources.push(...slot.outputResources); }); - resources.push(...this._linkJS(jsResources, onCacheKey)); + resources.push(...await this._linkJS(jsResources, onCacheKey)); return resources; } - _linkJS(jsResources, onCacheKey = () => {}) { + async _linkJS(jsResources, onCacheKey = () => {}) { const self = this; buildmessage.assertInJob(); @@ -1723,7 +1721,7 @@ export class PackageSourceBatch { fileHashes })); const cacheKey = `${cacheKeyPrefix}_${cacheKeySuffix}`; - onCacheKey(cacheKey, jsResources); + await onCacheKey(cacheKey, jsResources); if (LINKER_CACHE.has(cacheKey)) { if (CACHE_DEBUG) { @@ -1779,8 +1777,8 @@ export class PackageSourceBatch { // mutate anything from it. let canCache = true; let linkedFiles = null; - buildmessage.enterJob('linking', () => { - linkedFiles = linker.fullLink(jsResources, linkerOptions); + await buildmessage.enterJob('linking', async () => { + linkedFiles = await linker.fullLink(jsResources, linkerOptions); if (buildmessage.jobHasMessages()) { canCache = false; } @@ -1813,13 +1811,11 @@ export class PackageSourceBatch { LINKER_CACHE.set(cacheKey, ret); if (cacheFilename) { // Write asynchronously. - Promise.resolve().then(() => { - try { - files.rm_recursive(wildcardCacheFilename); - } finally { - files.writeFileAtomically(cacheFilename, retAsJSON); - } - }); + try { + await files.rm_recursive(wildcardCacheFilename); + } finally { + await files.writeFileAtomically(cacheFilename, retAsJSON); + } } } diff --git a/tools/isobuild/compiler.js b/tools/isobuild/compiler.js index ffd9a181ae..eb12e945fe 100644 --- a/tools/isobuild/compiler.js +++ b/tools/isobuild/compiler.js @@ -184,7 +184,6 @@ compiler.compile = Profile(function (packageSource, options) { // TODO -> Maybe this withCache will bring some problems in other commands. await files.withCache(async () => { - // TODO -> Check why the result is undefined... var unibuildResult = await compileUnibuild({ isopack: isopk, sourceArch: architecture, diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index 9b95079500..e7c1d75016 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -77,25 +77,25 @@ export class IsopackCache { } } - wipeCachedPackages(packages) { + async wipeCachedPackages(packages) { var self = this; if (packages) { // Wipe specific packages. - _.each(packages, function (packageName) { + for (const packageName of packages) { if (self.cacheDir) { - files.rm_recursive(self._isopackDir(packageName)); + await files.rm_recursive(self._isopackDir(packageName)); } if (self._pluginCacheDirRoot) { - files.rm_recursive(self._pluginCacheDirForPackage(packageName)); + await files.rm_recursive(self._pluginCacheDirForPackage(packageName)); } - }); + } } else { // Wipe all packages. if (self.cacheDir) { - files.rm_recursive(self.cacheDir); + await files.rm_recursive(self.cacheDir); } if (self._pluginCacheDirRoot) { - files.rm_recursive(self._pluginCacheDirRoot); + await files.rm_recursive(self._pluginCacheDirRoot); } } } diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 9be94473d5..40c2a91264 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -790,7 +790,7 @@ Object.assign(Isopack.prototype, { }, nudge: function () { - Console.nudge(true); + return Console.nudge(true); }, convertToOSPath: files.convertToOSPath, @@ -816,7 +816,7 @@ Object.assign(Isopack.prototype, { // - isopackBuildInfoJson: parsed isopack-buildinfo.json object, // if loading from an IsopackCache. initFromPath: Profile( - "Isopack#initFromPath", function (name, dir, options) { + "Isopack#initFromPath", async function (name, dir, options) { var self = this; options = _.clone(options || {}); options.firstIsopack = true; @@ -825,7 +825,7 @@ Object.assign(Isopack.prototype, { self.pluginCacheDir = options.pluginCacheDir; } - return self._loadUnibuildsFromPath(name, dir, options); + await self._loadUnibuildsFromPath(name, dir, options); }), _loadUnibuildsFromPath: async function (name, dir, options) { @@ -891,10 +891,10 @@ Object.assign(Isopack.prototype, { self.prodOnly = !!mainJson.prodOnly; self.testOnly = !!mainJson.testOnly; } - _.each(mainJson.plugins, function (pluginMeta) { + for (const pluginMeta of mainJson.plugins) { rejectBadPath(pluginMeta.path); - var plugin = bundler.readJsImage(files.pathJoin(dir, pluginMeta.path)); + var plugin = await bundler.readJsImage(files.pathJoin(dir, pluginMeta.path)); if (!_.has(self.plugins, pluginMeta.name)) { self.plugins[pluginMeta.name] = {}; @@ -903,9 +903,9 @@ Object.assign(Isopack.prototype, { if (!_.has(self.plugins[pluginMeta.name], plugin.arch)) { self.plugins[pluginMeta.name][plugin.arch] = plugin; } - }); + } self.pluginsBuilt = true; - _.each(mainJson.builds, function (unibuildMeta) { + for (const unibuildMeta of mainJson.builds) { // aggressively sanitize path (don't let it escape to parent // directory) rejectBadPath(unibuildMeta.path); @@ -915,17 +915,17 @@ Object.assign(Isopack.prototype, { return unibuild.arch === unibuildMeta.arch; }); if (alreadyHaveUnibuild) { - return; + continue; } - const unibuild = Unibuild.fromJSON(JSON.parse( - files.readFile(files.pathJoin(dir, unibuildMeta.path)) + const unibuild = await Unibuild.fromJSON(JSON.parse( + files.readFile(files.pathJoin(dir, unibuildMeta.path)) ), { isopack: self, kind: unibuildMeta.kind, arch: unibuildMeta.arch, unibuildBasePath: files.pathDirname( - files.pathJoin(dir, unibuildMeta.path)), + files.pathJoin(dir, unibuildMeta.path)), watchSet: unibuildWatchSets[unibuildMeta.path], }); @@ -940,7 +940,7 @@ Object.assign(Isopack.prototype, { } self.unibuilds.push(unibuild); - }); + } self.cordovaDependencies = mainJson.cordovaDependencies || null; @@ -1089,7 +1089,7 @@ Object.assign(Isopack.prototype, { // to see if package.js exists instead of just looking for the package // directory.) // XXX Remove this once we can. - builder.write("package.js", { + await builder.write("package.js", { data: Buffer.from( ("// This file is included for compatibility with the Meteor " + "0.6.4 package downloader.\n"), @@ -1146,14 +1146,14 @@ Object.assign(Isopack.prototype, { } // If unibuilds included node_modules, copy them in. - _.each(npmDirsToCopy, (bundlePath, sourcePath) => { - builder.copyNodeModulesDirectory({ + for (const [sourcePath, bundlePath] of Object.entries(npmDirsToCopy)) { + await builder.copyNodeModulesDirectory({ from: sourcePath, to: bundlePath, npmDiscards: self.npmDiscards, symlink: false }); - }); + } // Plugins _.each(self.plugins, function (pluginsByArch, name) { @@ -1176,16 +1176,16 @@ Object.assign(Isopack.prototype, { // Tools // First, are we supposed to include our own source as a tool? if (self.includeTool) { - var toolsJson = self._writeTool(builder); + var toolsJson = await self._writeTool(builder); mainJson.tools = toolsJson; } // Next, what about other tools we may be merging from other isopacks? // XXX check for overlap - _.each(self.toolsOnDisk, function (toolMeta) { + for (const toolMeta of self.toolsOnDisk) { toolMeta = _.clone(toolMeta); var rootDir = toolMeta.rootDir; delete toolMeta.rootDir; - builder.copyDirectory({ + await builder.copyDirectory({ from: files.pathJoin(rootDir, toolMeta.path), to: toolMeta.path, symlink: false @@ -1194,7 +1194,7 @@ Object.assign(Isopack.prototype, { mainJson.tools = []; } mainJson.tools.push(toolMeta); - }); + } var mainLegacyJson = null; if (writeLegacyBuilds) { @@ -1271,7 +1271,7 @@ Object.assign(Isopack.prototype, { if (jsResourcesForLegacyPrelink.length) { // Not originally legacy; let's run prelink to make it legacy. - var results = linker.prelink({ + var results = await linker.prelink({ inputFiles: jsResourcesForLegacyPrelink, // I was confused about this, so I am leaving a comment -- the // combinedServePath is either [pkgname].js or [pluginName]:plugin.js. @@ -1305,7 +1305,7 @@ Object.assign(Isopack.prototype, { if (prelinkFile && prelinkData) { var prelinkResource = { type: 'prelink', - file: builder.writeToGeneratedFilename( + file: await builder.writeToGeneratedFilename( files.pathJoin(legacyDir, prelinkFile.servePath), { data: prelinkData }), length: prelinkData.length, @@ -1332,7 +1332,7 @@ Object.assign(Isopack.prototype, { prelinkFile.sourceMap = JSON.stringify(prelinkFile.sourceMap); } - prelinkResource.sourceMap = builder.writeToGeneratedFilename( + prelinkResource.sourceMap = await builder.writeToGeneratedFilename( files.pathJoin(legacyDir, prelinkFile.servePath + '.map'), { data: Buffer.from(prelinkFile.sourceMap, 'utf8') } ); @@ -1380,9 +1380,9 @@ Object.assign(Isopack.prototype, { if (isopackBuildInfoJson) { await builder.writeJson("isopack-buildinfo.json", isopackBuildInfoJson); } - builder.complete(); + await builder.complete(); } catch (e) { - builder.abort(); + await builder.abort(); throw e; } }), @@ -1448,16 +1448,16 @@ Object.assign(Isopack.prototype, { builder = await builder.enter(toolPath); const sourceRootDir = files.getCurrentToolsDir(); - builder.copyTranspiledModules(pathsToTranspile, { + await builder.copyTranspiledModules(pathsToTranspile, { sourceRootDir, needToTranspile: true, }); var gitSha = await utils.runGitInCheckout('rev-parse', 'HEAD'); builder.reserve('isopackets', {directory: true}); - builder.write('.git_version.txt', {data: Buffer.from(gitSha, 'utf8')}); + await builder.write('.git_version.txt', {data: Buffer.from(gitSha, 'utf8')}); - builder.copyDirectory({ + await builder.copyDirectory({ from: files.getCurrentToolsDir(), to: '', specificFiles: pathsToCopyStraight, @@ -1468,7 +1468,7 @@ Object.assign(Isopack.prototype, { // self-test (which isn't supported from release). var devBundleIgnore = _.clone(bundler.ignoreFiles); devBundleIgnore.push(/BrowserStackLocal/, /browserstack-webdriver/); - builder.copyDirectory({ + await builder.copyDirectory({ from: files.pathJoin(files.getDevBundle()), to: 'dev_bundle', ignore: devBundleIgnore, @@ -1486,10 +1486,8 @@ Object.assign(Isopack.prototype, { // necessary here, since any isopackets loaded as part of the build // process are going to be the current tool's isopackets, not the // isopackets that we're writing out. - for (const [isopacketName, packages] of ISOPACKETS) { - + for (const [isopacketName, packages] of Object.entries(ISOPACKETS)) { requestGarbageCollection(); - await buildmessage.enterJob({ title: "compiling " + isopacketName + " packages for the tool" }, async function () { @@ -1510,8 +1508,8 @@ Object.assign(Isopack.prototype, { requestGarbageCollection(); - image.write( - builder.enter(files.pathJoin('isopackets', isopacketName))); + await image.write( + await builder.enter(files.pathJoin('isopackets', isopacketName))); }); } }); diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index 8edd4fc117..a6814ea421 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -1,3 +1,5 @@ +import {debug} from "util"; + var _ = require('underscore'); var sourcemap = require('source-map'); var buildmessage = require('../utils/buildmessage.js'); @@ -86,7 +88,7 @@ Object.assign(Module.prototype, { // Figure out which vars need to be specifically put in the module // scope. - computeAssignedVariables: Profile("linker Module#computeAssignedVariables", function () { + computeAssignedVariables: Profile("linker Module#computeAssignedVariables", async function () { var self = this; // The assigned variables in the app aren't actually used for anything: @@ -99,10 +101,10 @@ Object.assign(Module.prototype, { // Find all global references in any files var assignedVariables = []; - _.each(self.files, function (file) { + for (const file of self.files) { assignedVariables = assignedVariables.concat( - file.computeAssignedVariables()); - }); + await file.computeAssignedVariables()); + } assignedVariables = _.uniq(assignedVariables); return assignedVariables; @@ -110,7 +112,7 @@ Object.assign(Module.prototype, { // Output is a list of objects with keys 'source', 'servePath', 'sourceMap', // 'sourcePath' - getPrelinkedFiles: Profile("linker Module#getPrelinkedFiles", function () { + getPrelinkedFiles: Profile("linker Module#getPrelinkedFiles", async function () { var self = this; const haveMeteorInstallOptions = @@ -124,7 +126,8 @@ Object.assign(Module.prototype, { // Ignore lazy files unless we have a module system. const eagerFiles = _.filter(self.files, file => ! file.lazy); - return _.map(eagerFiles, function (file) { + const ret = []; + for (const file of eagerFiles) { const cacheKey = JSON.stringify([ file._inputHash, file.bare, @@ -132,16 +135,16 @@ Object.assign(Module.prototype, { ]); if (APP_PRELINK_CACHE.has(cacheKey)) { - return APP_PRELINK_CACHE.get(cacheKey); + ret.push(APP_PRELINK_CACHE.get(cacheKey)); } - const node = file.getPrelinkedOutput({ preserveLineNumbers: true }); - const results = Profile.time( - "toStringWithSourceMap (app)", () => { - return node.toStringWithSourceMap({ - file: file.servePath - }); // results has 'code' and 'map' attributes - } + const node = await file.getPrelinkedOutput({ preserveLineNumbers: true }); + const results = await Profile.time( + "toStringWithSourceMap (app)", () => { + return node.toStringWithSourceMap({ + file: file.servePath + }); // results has 'code' and 'map' attributes + } ); let sourceMap = results.map.toJSON(); @@ -158,8 +161,10 @@ Object.assign(Module.prototype, { }; APP_PRELINK_CACHE.set(cacheKey, prelinked); - return prelinked; - }); + ret.push(prelinked); + } + + return ret; } // Otherwise.. @@ -178,40 +183,37 @@ Object.assign(Module.prototype, { }; const results = [result]; - // An array of strings and SourceNode objects. let chunks = []; let fileCount = 0; // Emit each file if (haveMeteorInstallOptions) { - const trees = self._buildModuleTrees(results, sourceWidth); - fileCount = self._chunkifyModuleTrees(trees, chunks, sourceWidth); - result.exportsName = - self._chunkifyEagerRequires(chunks, fileCount, sourceWidth); - + const trees = await self._buildModuleTrees(results, sourceWidth); + fileCount = await self._chunkifyModuleTrees(trees, chunks, sourceWidth); + result.exportsName = await self._chunkifyEagerRequires(chunks, fileCount, sourceWidth); } else { - _.each(self.files, function (file) { + for (const file of self.files) { if (file.lazy) { // Ignore lazy files unless we have a module system. - return; + continue; } if (!_.isEmpty(chunks)) { chunks.push("\n\n\n\n\n\n"); } - chunks.push(file.getPrelinkedOutput({ + chunks.push(await file.getPrelinkedOutput({ sourceWidth: sourceWidth, })); ++fileCount; - }); + } } var node = new sourcemap.SourceNode(null, null, null, chunks); - Profile.time( + await Profile.time( 'getPrelinkedFiles toStringWithSourceMap', function () { if (fileCount > 0) { @@ -239,10 +241,10 @@ Object.assign(Module.prototype, { // files or directories, and the values are either nested objects // (representing directories) or File objects (representing modules). // Bare files and lazy files that are never imported are ignored. - _buildModuleTrees(results, sourceWidth) { + async _buildModuleTrees(results, sourceWidth) { // Map from meteorInstallOptions objects to trees of File objects for // all non-dynamic modules. - const trees = new Map; + const trees = new Map(); function getTree({ meteorInstallOptions }) { if (! trees.has(meteorInstallOptions)) { @@ -251,31 +253,31 @@ Object.assign(Module.prototype, { return trees.get(meteorInstallOptions); } - _.each(this.files, file => { + for (const file of this.files) { if (file.bare) { // Bare files will be added before the synchronous require calls // in _chunkifyEagerRequires. - return; + continue; } if (file.lazy && ! file.imported) { // If the file is not eagerly evaluated, and no other files // import or require it, then it need not be included in the // bundle. - return; + continue; } const tree = getTree(file); if (file.aliasId) { addToTree(file.aliasId, file.absModuleId, tree); - return; + continue; } if (file.isDynamic()) { const servePath = files.pathJoin("dynamic", file.absModuleId); const { code: source, map } = - getOutputWithSourceMapCached(file, servePath, { sourceWidth }) + await getOutputWithSourceMapCached(file, servePath, { sourceWidth }) results.push({ source, @@ -312,7 +314,7 @@ Object.assign(Module.prototype, { // initial bundle, so we add it to the static tree. addToTree(file, file.absModuleId, tree); } - }); + } return trees; }, @@ -320,7 +322,7 @@ Object.assign(Module.prototype, { // Take the tree generated in getPrelinkedFiles and populate the chunks // array with strings and SourceNode objects that can be combined into a // single SourceNode object. Return the count of modules in the tree. - _chunkifyModuleTrees(trees, chunks, sourceWidth) { + async _chunkifyModuleTrees(trees, chunks, sourceWidth) { const self = this; assert.ok(_.isArray(chunks)); @@ -328,11 +330,10 @@ Object.assign(Module.prototype, { let moduleCount = 0; - function walk(t) { + async function walk(t) { if (Array.isArray(t)) { ++moduleCount; chunks.push(JSON.stringify(t, null, 2)); - } else if (typeof t === "string") { // This case can happen if a package.json file has an // object-valued "browser" field that aliases this module to a @@ -344,31 +345,28 @@ Object.assign(Module.prototype, { // are meant to be resolved relative to the package.json file. ++moduleCount; chunks.push(JSON.stringify(t)); - } else if (t === false) { // This case can happen if a package.json file has an // object-valued "browser" field that maps this module to `false`, // indicating it should be replaced by an empty stub. ++moduleCount; chunks.push("function(){}"); - } else if (t instanceof File) { ++moduleCount; - chunks.push(t.getPrelinkedOutput({ + chunks.push(await t.getPrelinkedOutput({ sourceWidth, })); - } else if (_.isObject(t)) { chunks.push("{"); const keys = Object.keys(t); - _.each(keys, (key, i) => { + for (const [i, key] of keys.entries()) { chunks.push(JSON.stringify(key), ":"); - walk(t[key]); + await walk(t[key]); if (i < keys.length - 1) { chunks.push(","); } - }); + } chunks.push("}"); } } @@ -382,11 +380,11 @@ Object.assign(Module.prototype, { // Emit one meteorInstall call per distinct meteorInstallOptions // object, since the options apply to all modules installed by a given // call to meteorInstall. - trees.forEach((tree, options) => { + for (const [tree, options] of trees) { chunks.push("meteorInstall("); - walk(tree); + await walk(tree); chunks.push(",", self._stringifyInstallOptions(options), ");\n"); - }); + } if (moduleCount === 0) { // If no files were actually added to the chunks array, roll back @@ -434,7 +432,7 @@ Object.assign(Module.prototype, { // eagerly evaluated, and also includes any bare files before the // require calls. Returns the name of the variable that holds the main // exports object, if api.mainModule was used to define a main module. - _chunkifyEagerRequires(chunks, moduleCount, sourceWidth) { + async _chunkifyEagerRequires(chunks, moduleCount, sourceWidth) { assert.ok(_.isArray(chunks)); assert.ok(_.isNumber(moduleCount)); assert.ok(_.isNumber(sourceWidth)); @@ -447,29 +445,29 @@ Object.assign(Module.prototype, { const eagerModuleFiles = []; - _.each(this.files, file => { + for (const file of this.files) { if (file.bare) { - chunks.push("\n", file.getPrelinkedOutput({ + chunks.push("\n", await file.getPrelinkedOutput({ sourceWidth, })); } else if (moduleCount > 0 && ! file.lazy) { eagerModuleFiles.push(file); } - }); + } if (eagerModuleFiles.length > 0) { - _.each(eagerModuleFiles, file => { + for (const file of eagerModuleFiles) { if (file.mainModule) { exportsName = "exports"; } chunks.push( - file.mainModule ? "\nvar " + exportsName + " = " : "\n", - "require(", - JSON.stringify(file.absModuleId), - ");" + file.mainModule ? "\nvar " + exportsName + " = " : "\n", + "require(", + JSON.stringify(file.absModuleId), + ");" ); - }); + } } return exportsName; @@ -630,7 +628,7 @@ Object.assign(File.prototype, { // example: if the code references 'Foo.bar.baz' and 'Quux', and // neither are declared in a scope enclosing the point where they're // referenced, then globalReferences would include ["Foo", "Quux"]. - computeAssignedVariables: Profile("linker File#computeAssignedVariables", function () { + computeAssignedVariables: Profile("linker File#computeAssignedVariables", async function () { var self = this; if (self.absModuleId) { @@ -657,7 +655,7 @@ Object.assign(File.prototype, { column: e.column }; if (self.sourceMap) { - var parsed = Promise.await(new sourcemap.SourceMapConsumer(self.sourceMap)); + var parsed = await new sourcemap.SourceMapConsumer(self.sourceMap); var original = parsed.originalPositionFor( {line: e.lineNumber, column: e.column - 1}); if (original.source) { @@ -726,7 +724,7 @@ Object.assign(File.prototype, { }); const getPrelinkedOutputCached = require("optimism").wrap( - function (file, options) { + async function (file, options) { var width = options.sourceWidth || 70; var bannerWidth = width + 3; var preserveLineNumbers = options.preserveLineNumbers; @@ -780,7 +778,7 @@ const getPrelinkedOutputCached = require("optimism").wrap( let chunk = result.code; if (result.map) { - const sourcemapConsumer = Promise.await(new sourcemap.SourceMapConsumer(result.map)); + const sourcemapConsumer = await new sourcemap.SourceMapConsumer(result.map); chunk = sourcemap.SourceNode.fromStringWithSourceMap( result.code, sourcemapConsumer, @@ -836,7 +834,7 @@ const getPrelinkedOutputCached = require("optimism").wrap( } ); -function getOutputWithSourceMapCached(file, servePath, options) { +async function getOutputWithSourceMapCached(file, servePath, options) { const key = JSON.stringify({ hash: file._inputHash, arch: file.bundleArch, @@ -850,10 +848,12 @@ function getOutputWithSourceMapCached(file, servePath, options) { return DYNAMIC_PRELINKED_OUTPUT_CACHE.get(key); } - const result = file.getPrelinkedOutput({ + const linkedOutput = await file.getPrelinkedOutput({ ...options, disableCache: true - }).toStringWithSourceMap({ + }); + + const result = linkedOutput.toStringWithSourceMap({ file: servePath, }); @@ -932,7 +932,7 @@ var bannerPadding = function (bannerWidth) { // sourceMap (a string) (XXX) // - assignedPackageVariables: an array of variables assigned to without // being declared -export var prelink = Profile("linker.prelink", function (options) { +export var prelink = Profile("linker.prelink", async function (options) { var module = new Module({ name: options.name, combinedServePath: options.combinedServePath, @@ -945,8 +945,8 @@ export var prelink = Profile("linker.prelink", function (options) { // Do static analysis to compute module-scoped variables. Error recovery from // the static analysis mutates the sources, so this has to be done before // concatenation. - var assignedVariables = module.computeAssignedVariables(); - var files = module.getPrelinkedFiles(); + var assignedVariables = await module.computeAssignedVariables(); + var files = await module.getPrelinkedFiles(); return { files: files, @@ -1064,7 +1064,7 @@ var getFooter = function ({ // // Output is an array of output files: objects with keys source, servePath, // sourceMap. -export var fullLink = Profile("linker.fullLink", function (inputFiles, { +export var fullLink = Profile("linker.fullLink", async function (inputFiles, { // True if we're linking the application (as opposed to a // package). Among other consequences, this makes the top level // namespace be the same as the global namespace, so that symbols are @@ -1101,7 +1101,7 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, { _.each(inputFiles, file => module.addFile(file)); - var prelinkedFiles = module.getPrelinkedFiles(); + var prelinkedFiles = await module.getPrelinkedFiles(); // If we're in the app, then we just add the import code as its own file in // the front. @@ -1123,8 +1123,8 @@ export var fullLink = Profile("linker.fullLink", function (inputFiles, { // the static analysis mutates the sources, so this has to be done before // concatenation. let assignedVariables; - const failed = buildmessage.enterJob('computing assigned variables', () => { - assignedVariables = module.computeAssignedVariables(); + const failed = await buildmessage.enterJob('computing assigned variables', async () => { + assignedVariables = await module.computeAssignedVariables(); return buildmessage.jobHasMessages(); }); if (failed) { diff --git a/tools/isobuild/meteor-npm.js b/tools/isobuild/meteor-npm.js index cbf39da843..e6f292e0ac 100644 --- a/tools/isobuild/meteor-npm.js +++ b/tools/isobuild/meteor-npm.js @@ -375,12 +375,12 @@ Profile("meteorNpm.rebuildIfNonPortable", async function (nodeModulesDir) { // If the `npm rebuild` command succeeded, overwrite the original // package directories with the rebuilt package directories. - dirsToRebuild.forEach(function (pkgPath) { + for (const pkgPath of dirsToRebuild) { const actualNodeModulesDir = - files.pathJoin(pkgPath, "node_modules"); + files.pathJoin(pkgPath, "node_modules"); const actualNodeModulesStat = - files.statOrNull(actualNodeModulesDir); + files.statOrNull(actualNodeModulesDir); if (actualNodeModulesStat && actualNodeModulesStat.isDirectory()) { @@ -392,16 +392,16 @@ Profile("meteorNpm.rebuildIfNonPortable", async function (nodeModulesDir) { // directory that contains real packages rather than symlinks. const symlinkNodeModulesDir = - files.pathJoin(tempPkgDirs[pkgPath], "node_modules"); + files.pathJoin(tempPkgDirs[pkgPath], "node_modules"); - files.renameDirAlmostAtomically( - actualNodeModulesDir, - symlinkNodeModulesDir + await files.renameDirAlmostAtomically( + actualNodeModulesDir, + symlinkNodeModulesDir ); } - files.renameDirAlmostAtomically(tempPkgDirs[pkgPath], pkgPath); - }); + await files.renameDirAlmostAtomically(tempPkgDirs[pkgPath], pkgPath); + } await files.rm_recursive(tempDir); @@ -726,7 +726,7 @@ var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir, files.unlink(newPackageJsonFile); } - completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir, + await completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir, npmDependencies); }; @@ -762,7 +762,7 @@ var createFreshNpmDirectory = async function (packageName, newPackageNpmDir, await installNpmDependencies(npmDependencies, newPackageNpmDir); - completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir, + await completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir, npmDependencies); }; @@ -788,7 +788,7 @@ async function installNpmDependencies(dependencies, dir) { } // Shared code for updateExistingNpmDirectory and createFreshNpmDirectory. -function completeNpmDirectory( +async function completeNpmDirectory( packageName, newPackageNpmDir, packageNpmDir, @@ -805,7 +805,7 @@ function completeNpmDirectory( createReadme(newPackageNpmDir); createNodeVersion(newPackageNpmDir); - files.renameDirAlmostAtomically(newPackageNpmDir, packageNpmDir); + await files.renameDirAlmostAtomically(newPackageNpmDir, packageNpmDir); dirtyNodeModulesDirectory(files.pathJoin(packageNpmDir, "node_modules")); } @@ -850,7 +850,7 @@ const npmUserConfigFile = files.pathJoin( ); var runNpmCommand = meteorNpm.runNpmCommand = -Profile("meteorNpm.runNpmCommand", function (args, cwd) { +Profile("meteorNpm.runNpmCommand", async function (args, cwd) { import { getEnv } from "../cli/dev-bundle-bin-helpers.js"; const devBundleDir = files.getDevBundle(); @@ -879,23 +879,22 @@ Profile("meteorNpm.runNpmCommand", function (args, cwd) { args.join(' ') + ' ...\n'); } - return getEnv({ - devBundle: devBundleDir - }).then(async env => { - const opts = { - env: env, - maxBuffer: 10 * 1024 * 1024 - }; + const env = getEnv({devBundle: devBundleDir}); - if (cwd) { - opts.cwd = files.convertToOSPath(cwd); - } + const opts = { + env: env, + maxBuffer: 10 * 1024 * 1024 + }; - // Make sure we don't honor any user-provided configuration files. - env.npm_config_userconfig = npmUserConfigFile; + if (cwd) { + opts.cwd = files.convertToOSPath(cwd); + } - return await new Promise(function (resolve) { - require('child_process').execFile( + // Make sure we don't honor any user-provided configuration files. + env.npm_config_userconfig = npmUserConfigFile; + + return await new Promise(function (resolve) { + require('child_process').execFile( commandToRun, args, opts, function (err, stdout, stderr) { if (meteorNpm._printNpmCalls) { process.stdout.write(err ? 'failed\n' : 'done\n'); @@ -908,8 +907,7 @@ Profile("meteorNpm.runNpmCommand", function (args, cwd) { stderr: stderr }); } - ); - }); + ); }); }); diff --git a/tools/isobuild/package-api.js b/tools/isobuild/package-api.js index 1571ba5b34..c43445c35b 100644 --- a/tools/isobuild/package-api.js +++ b/tools/isobuild/package-api.js @@ -514,7 +514,7 @@ export class PackageAPI { * track@version. Just 'version' (e.g. `"0.9.0"`) is sufficient if using the * default release track `METEOR`. Can be an array of specifications. */ - versionsFrom(releases) { + async versionsFrom(releases) { var self = this; // Packages in isopackets really ought to be in the core release, by @@ -548,7 +548,7 @@ export class PackageAPI { { useMyCaller: true }); return; } - var releaseRecord = catalog.official.getReleaseVersion( + var releaseRecord = await catalog.official.getReleaseVersion( relInf[0], relInf[1]); if (!releaseRecord) { buildmessage.error("Unknown release "+ release, diff --git a/tools/isobuild/package-source.js b/tools/isobuild/package-source.js index 77f66c79f5..2fd7a885ea 100644 --- a/tools/isobuild/package-source.js +++ b/tools/isobuild/package-source.js @@ -485,7 +485,7 @@ Object.assign(PackageSource.prototype, { // Initialize a PackageSource from a package.js-style package directory. Uses // the name field provided and the name/test fields in the package.js file to - // figre out if this is a test package (load from onTest) or a use package + // figure out if this is a test package (load from onTest) or a use package // (load from onUse). // // name: name of the package. @@ -498,7 +498,7 @@ Object.assign(PackageSource.prototype, { return `PackageSource#initFromPackageDir for ${ options?.name || dir.split(files.pathSep).pop() }`; - }, function (dir, options) { + }, async function (dir, options) { var self = this; buildmessage.assertInCapture(); var isPortable = true; @@ -588,7 +588,7 @@ Object.assign(PackageSource.prototype, { const Cordova = new PackageCordova(); try { - files.runJavaScript(packageJsCode.toString('utf8'), { + await files.runJavaScript(packageJsCode.toString('utf8'), { filename: 'package.js', symbols: { Package, Npm, Cordova } }); @@ -663,7 +663,8 @@ Object.assign(PackageSource.prototype, { if (Package._fileAndDepLoader) { try { - buildmessage.markBoundary(Package._fileAndDepLoader)(api); + const marked = buildmessage.markBoundary(Package._fileAndDepLoader) + await marked(api); } catch (e) { console.log(e.stack); // XXX should we keep this here -- or do we want broken // packages to fail silently? @@ -962,7 +963,7 @@ Object.assign(PackageSource.prototype, { // then sources will not be the same files used to bundle the app. let missingMainModule = !! mainModule && !sourceProcessorSet.isConflictsAllowed(); - + // Similar to the main module, when conflicts are allowed // these sources won't be used to build the app so the order // isn't important, and is difficult to accurately create when diff --git a/tools/isobuild/unibuild.js b/tools/isobuild/unibuild.js index fafb7a8543..5ecf5c40a9 100644 --- a/tools/isobuild/unibuild.js +++ b/tools/isobuild/unibuild.js @@ -93,7 +93,7 @@ export class Unibuild { }); } - static fromJSON(unibuildJson, { + static async fromJSON(unibuildJson, { isopack, // At some point we stopped writing 'kind's to the metadata file, so // default to main. @@ -209,7 +209,7 @@ export class Unibuild { } const nodeModulesDirectories = - NodeModulesDirectory.readDirsFromJSON(unibuildJson.node_modules, { + await NodeModulesDirectory.readDirsFromJSON(unibuildJson.node_modules, { packageName: isopack.name, sourceRoot: unibuildBasePath, // Rebuild binary npm packages if unibuild arch matches host arch. @@ -228,7 +228,7 @@ export class Unibuild { }); } - toJSON({ + async toJSON({ builder, unibuildDir, usesModules, @@ -252,14 +252,14 @@ export class Unibuild { // Figure out where the npm dependencies go. let node_modules = {}; - _.each(unibuild.nodeModulesDirectories, nmd => { + for (const nmd of unibuild.nodeModulesDirectories) { const bundlePath = _.has(npmDirsToCopy, nmd.sourcePath) - // We already have this npm directory from another unibuild. - ? npmDirsToCopy[nmd.sourcePath] - : npmDirsToCopy[nmd.sourcePath] = - nmd.getPreferredBundlePath("isopack"); - node_modules[bundlePath] = nmd.toJSON(); - }); + // We already have this npm directory from another unibuild. + ? npmDirsToCopy[nmd.sourcePath] + : npmDirsToCopy[nmd.sourcePath] = + nmd.getPreferredBundlePath("isopack"); + node_modules[bundlePath] = await nmd.toJSON(); + } const preferredPaths = Object.keys(node_modules); if (preferredPaths.length === 1) { @@ -307,19 +307,19 @@ export class Unibuild { } }); - _.each(concat, function (parts, type) { + for (const [type, parts] of Object.entries(offset)) { if (parts.length) { - builder.write(files.pathJoin(unibuildDir, type), { + await builder.write(files.pathJoin(unibuildDir, type), { data: Buffer.concat(concat[type], offset[type]) }); } - }); + } // Output other resources each to their own file - _.each(unibuild.resources, function (resource) { + for (const resource of unibuild.resources) { if (["head", "body"].includes(resource.type)) { // already did this one - return; + continue; } let data; @@ -330,20 +330,20 @@ export class Unibuild { } const generatedFilename = - builder.writeToGeneratedFilename( - files.pathJoin( - unibuildDir, - resource.servePath || resource.path, - ), - { data } - ); + await builder.writeToGeneratedFilename( + files.pathJoin( + unibuildDir, + resource.servePath || resource.path, + ), + { data } + ); if (! usesModules && resource.fileOptions && resource.fileOptions.lazy) { // Omit lazy resources from the unibuild JSON file, but only after // they are copied into the bundle (immediately above). - return; + continue; } unibuildJson.resources.push({ @@ -353,13 +353,13 @@ export class Unibuild { length: data.length, offset: 0, usesDefaultSourceProcessor: - resource.usesDefaultSourceProcessor || undefined, + resource.usesDefaultSourceProcessor || undefined, servePath: resource.servePath || undefined, path: resource.path || undefined, hash: resource._hash || resource.hash || undefined, fileOptions: resource.fileOptions || undefined }); - }); + } return unibuildJson; } diff --git a/tools/meteor-services/auth-client.js b/tools/meteor-services/auth-client.js index e9ee8bc411..ef751671c4 100644 --- a/tools/meteor-services/auth-client.js +++ b/tools/meteor-services/auth-client.js @@ -8,11 +8,14 @@ exports.AlreadyPrintedMessageError = function () {}; // Opens a DDP connection to a package server. Loads the packages needed for a // DDP connection, then calls DDP connect to the package server URL in config, // using a current user-agent header composed by http-helpers.js. -exports.openServiceConnection = function (serverUrl) { - return new ServiceConnection( - serverUrl, - {headers: {"User-Agent": httpHelpers.getUserAgent()}, - _dontPrintErrors: true}); +exports.openServiceConnection = async function (serverUrl) { + const connection = new ServiceConnection( + serverUrl, + {headers: {"User-Agent": httpHelpers.getUserAgent()}, + _dontPrintErrors: true}); + + await connection.init(); + return connection; }; diff --git a/tools/meteor-services/auth.js b/tools/meteor-services/auth.js index 2ae814965c..917743cbd6 100644 --- a/tools/meteor-services/auth.js +++ b/tools/meteor-services/auth.js @@ -895,7 +895,7 @@ exports.loggedInUsername = function () { return loggedIn(data) ? currentUsername(data) : false; }; -exports.getAccountsConfiguration = function (conn) { +exports.getAccountsConfiguration = async function (conn) { // Subscribe to the package server's service configurations so that we // can get the OAuth client ID to kick off the OAuth flow. var accountsConfiguration = null; @@ -914,7 +914,7 @@ exports.getAccountsConfiguration = function (conn) { } }); - var serviceConfigurationsSub = conn.subscribeAndWait( + var serviceConfigurationsSub = await conn.subscribeAndWait( 'meteor.loginServiceConfiguration'); if (! accountsConfiguration || ! accountsConfiguration.clientId) { throw new Error('no-accounts-configuration'); diff --git a/tools/meteor-services/service-connection.js b/tools/meteor-services/service-connection.js index 6ac6791fd8..98e6731815 100644 --- a/tools/meteor-services/service-connection.js +++ b/tools/meteor-services/service-connection.js @@ -1,4 +1,5 @@ import { loadIsopackage } from '../tool-env/isopackets.js'; +import {clearScreenDown} from "readline"; var files = require('../fs/files'); var fiberHelpers = require("../utils/fiber-helpers.js"); @@ -24,81 +25,88 @@ var fiberHelpers = require("../utils/fiber-helpers.js"); // var ServiceConnection = function (endpointUrl, options) { const self = this; - const ddpClient = loadIsopackage('ddp-client'); // ServiceConnection never should retry connections: just one TCP connection - // is enough, and any errors on it should be detected promptly. - options = Object.assign({}, options, { - // We found that this was likely to time out with the DDP default of 10s, - // especially if the CPU is churning on bundling (eg, for the stats - // connection which we start in parallel with bundling). - connectTimeoutMs: 30000, - // Disable client->server heartbeats for service connections. Users with - // slow internet connections were seeing heartbeat timeouts because the - // heartbeats were buried behind large responses (eg - // https://github.com/meteor/meteor/issues/2777). - heartbeatInterval: 0, - retry: false, - onConnected: function () { - self.connected = true; - if (! self.currentPromise) { - throw Error("nobody waiting for connection?"); - } - if (self.currentPromise !== connectPromise) { - throw Error("waiting for something that isn't connection?"); - } - self.currentPromise = null; - connectPromise.resolve(); - connectPromise.resolve = null; - } - }); + // is enough, and any errors on it should be detected promptly.] + self.options = options; + self.endpointUrl = endpointUrl; + if (process.env.CAFILE) { options.npmFayeOptions = { ca: files.readFile(process.env.CAFILE) - } + }; } - - self.connection = ddpClient.DDP.connect(endpointUrl, options); - - // Wait until we have some sort of initial connection or error (including the - // 10-second timeout built into our DDP client). - - var connectPromise = self.currentPromise = - fiberHelpers.makeFulfillablePromise(); - - self.connection._stream.on('disconnect', function (error) { - self.connected = false; - if (error && error.errorType === "DDP.ForcedReconnectError") { - // OK, we requested this, probably due to version negotation failure. - // - // This ought to have happened before we successfully connect, unless - // somebody adds other calls to forced reconnect to Meteor... - if (! connectPromise.resolve) { - throw Error("disconnect before connect?"); - } - // Otherwise, ignore this error. We're going to reconnect! - return; - } - // Are we waiting to connect or for the result of a method apply or a - // subscribeAndWait? If so, disconnecting is a problem. - if (self.currentPromise) { - var promise = self.currentPromise; - self.currentPromise = null; - promise.reject( - error || new ddpClient.DDP.ConnectionError( - "DDP disconnected while connection in progress") - ); - } else if (error) { - // We got some sort of error with nobody listening for it; handle it. - // XXX probably have a better way to handle it than this - throw error; - } - }); - - connectPromise.await(); }; Object.assign(ServiceConnection.prototype, { + init: async function() { + const self = this; + const ddpClient = await loadIsopackage('ddp-client'); + + const options = Object.assign({}, self.options, { + // We found that this was likely to time out with the DDP default of 10s, + // especially if the CPU is churning on bundling (eg, for the stats + // connection which we start in parallel with bundling). + connectTimeoutMs: 30000, + // Disable client->server heartbeats for service connections. Users with + // slow internet connections were seeing heartbeat timeouts because the + // heartbeats were buried behind large responses (eg + // https://github.com/meteor/meteor/issues/2777). + heartbeatInterval: 0, + retry: false, + onConnected: function () { + self.connected = true; + if (! self.currentPromise) { + throw Error("nobody waiting for connection?"); + } + if (self.currentPromise !== connectPromise) { + throw Error("waiting for something that isn't connection?"); + } + self.currentPromise = null; + connectPromise.resolve(); + connectPromise.resolve = null; + } + }); + + self.connection = ddpClient.DDP.connect(self.endpointUrl, options); + + // Wait until we have some sort of initial connection or error (including the + // 10-second timeout built into our DDP client). + + var connectPromise = self.currentPromise = + fiberHelpers.makeFulfillablePromise(); + + self.connection._stream.on('disconnect', function (error) { + self.connected = false; + if (error && error.errorType === "DDP.ForcedReconnectError") { + // OK, we requested this, probably due to version negotation failure. + // + // This ought to have happened before we successfully connect, unless + // somebody adds other calls to forced reconnect to Meteor... + if (! connectPromise.resolve) { + throw Error("disconnect before connect?"); + } + // Otherwise, ignore this error. We're going to reconnect! + return; + } + // Are we waiting to connect or for the result of a method apply or a + // subscribeAndWait? If so, disconnecting is a problem. + if (self.currentPromise) { + var promise = self.currentPromise; + self.currentPromise = null; + promise.reject( + error || new ddpClient.DDP.ConnectionError( + "DDP disconnected while connection in progress") + ); + } else if (error) { + // We got some sort of error with nobody listening for it; handle it. + // XXX probably have a better way to handle it than this + throw error; + } + }); + + await connectPromise; + }, call: function (name, ...args) { return this.apply(name, args); }, @@ -133,7 +141,7 @@ Object.assign(ServiceConnection.prototype, { // XXX derived from _subscribeAndWait in ddp_connection.js // -- but with a different signature.. - subscribeAndWait: function (...args) { + subscribeAndWait: async function (...args) { var self = this; if (self.currentPromise) { @@ -165,7 +173,7 @@ Object.assign(ServiceConnection.prototype, { }); var sub = self.connection.subscribe(...args); - subPromise.await(); + await subPromise; return sub; }, diff --git a/tools/meteor-services/stats.js b/tools/meteor-services/stats.js index bf02f116eb..369fc00b2f 100644 --- a/tools/meteor-services/stats.js +++ b/tools/meteor-services/stats.js @@ -147,11 +147,14 @@ var getPackagesForAppIdInTest = function (projectContext) { return result; }; -var connectToPackagesStatsServer = function () { - return new ServiceConnection( - config.getPackageStatsServerUrl(), - {_dontPrintErrors: true} +var connectToPackagesStatsServer = async function () { + const connection = new ServiceConnection( + config.getPackageStatsServerUrl(), + {_dontPrintErrors: true} ); + + await connection.init(); + return connection; }; exports.recordPackages = recordPackages; diff --git a/tools/packaging/catalog/catalog-local.js b/tools/packaging/catalog/catalog-local.js index de3b3e8c0a..f930fefabb 100644 --- a/tools/packaging/catalog/catalog-local.js +++ b/tools/packaging/catalog/catalog-local.js @@ -375,7 +375,7 @@ Object.assign(LocalCatalog.prototype, { }); }, - _loadLocalPackages(buildingIsopackets) { + async _loadLocalPackages(buildingIsopackets) { var self = this; buildmessage.assertInCapture(); @@ -391,7 +391,7 @@ Object.assign(LocalCatalog.prototype, { // with the same name in your app. We don't check that.) var initSourceFromDir = async function (packageDir, definiteName) { var packageSource = new PackageSource(); - return buildmessage.enterJob({ + await buildmessage.enterJob({ title: "reading package from `" + packageDir + "`", rootPath: packageDir }, async function () { @@ -461,7 +461,7 @@ Object.assign(LocalCatalog.prototype, { // Load the package sources for packages and their tests into // self.packages. - return buildmessage.enterJob('initializing packages', async function() { + await buildmessage.enterJob('initializing packages', async function() { for (const dir of self.effectiveLocalPackageDirs) { await initSourceFromDir(dir); } diff --git a/tools/packaging/catalog/catalog-remote.js b/tools/packaging/catalog/catalog-remote.js index 005dff6f2f..de32fb6118 100644 --- a/tools/packaging/catalog/catalog-remote.js +++ b/tools/packaging/catalog/catalog-remote.js @@ -141,25 +141,26 @@ var Db = function (dbFile, options) { self._prepared = {}; self._transactionMutex = new Mutex(); - - self._db = self._retry(function () { - return self.open(dbFile); - }); - - self._retry(function () { - self._execute(`PRAGMA journal_mode=${JOURNAL_MODE}`); - }); }; Object.assign(Db.prototype, { + init: async function() { + const self = this; + self._db = await self._retry(function () { + return self.open(self._dbFile); + }); + await self._retry(function () { + return self._execute(`PRAGMA journal_mode=${JOURNAL_MODE}`); + }); + }, // TODO: Move to utils? - _retry: function (f, options) { + _retry: async function (f, options) { options = Object.assign({ maxAttempts: 3, delay: 500}, options || {}); for (var attempt = 1; attempt <= options.maxAttempts; attempt++) { try { - return f(); + return await f(); } catch (err) { if (attempt < options.maxAttempts) { Console.warn("Retrying after error", err); @@ -169,7 +170,7 @@ Object.assign(Db.prototype, { } if (options.delay) { - utils.sleepMs(options.delay); + await utils.sleepMs(options.delay); } } }, @@ -188,14 +189,14 @@ Object.assign(Db.prototype, { // Do not call any other methods on this object after calling this one. // This yields. - closePermanently: function () { + closePermanently: async function () { var self = this; - self._closePreparedStatements(); + await self._closePreparedStatements(); var db = self._db; self._db = null; - new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { db.close(err => err ? reject(err) : resolve()); - }).await(); + }); }, // Runs the function inside a transaction block @@ -282,13 +283,13 @@ Object.assign(Db.prototype, { // Runs a query synchronously, returning all rows // Hidden to enforce transaction usage - _query: function (sql, params) { + _query: async function (sql, params) { var self = this; var prepared = null; var prepare = self._autoPrepare && !_.isEmpty(params); if (prepare) { - prepared = self._prepareWithCache(sql); + prepared = await self._prepareWithCache(sql); } if (DEBUG_SQL) { @@ -297,7 +298,7 @@ Object.assign(Db.prototype, { //Console.debug("Executing SQL ", sql, params); - var rows = new Promise((resolve, reject) => { + var rows = await new Promise((resolve, reject) => { function callback(err, rows) { err ? reject(err) : resolve(rows); } @@ -307,7 +308,7 @@ Object.assign(Db.prototype, { } else { self._db.all(sql, params, callback); } - }).await(); + }); if (DEBUG_SQL) { var t2 = Date.now(); @@ -391,25 +392,24 @@ Object.assign(Db.prototype, { // Close any cached prepared statements - _closePreparedStatements: function () { + _closePreparedStatements: async function () { var self = this; var prepared = self._prepared; self._prepared = {}; - _.each(prepared, function (statement) { - var err = new Promise(function (resolve) { + for (const statement of prepared) { + var err = await new Promise(function (resolve) { // We resolve the promise with an error instead of rejecting it, // because we don't want to throw. statement.finalize(resolve); - }).await(); + }); if (err) { Console.warn("Error finalizing statement ", err); } - }); + } } - }); @@ -441,9 +441,9 @@ Object.assign(Table.prototype, { return "(" + _.times(n, function () { return "?" }).join(",") + ")"; }, - find: function (txn, id) { + find: async function (txn, id) { var self = this; - var rows = txn.query(self._selectQuery, [ id ]); + var rows = await txn.query(self._selectQuery, [ id ]); if (rows.length !== 0) { if (rows.length !== 1) { throw new Error("Corrupt database (PK violation)"); @@ -453,17 +453,17 @@ Object.assign(Table.prototype, { return undefined; }, - upsert: function (txn, objects) { + upsert: async function (txn, objects) { var self = this; // XXX: Use sqlite upsert // XXX: Speculative insert // XXX: Fix transaction logic so we always roll back - _.each(objects, function (o) { + for (const o of objects) { var id = o._id; - var rows = txn.query(self._selectQuery, [ id ]); + var rows = await txn.query(self._selectQuery, [ id ]); if (rows.length !== 0) { - var deleteResults = txn.execute(self._deleteQuery, [ id ]); + var deleteResults = await txn.execute(self._deleteQuery, [ id ]); if (deleteResults.changes !== 1) { throw new Error("Unable to delete row: " + id); } @@ -475,11 +475,11 @@ Object.assign(Table.prototype, { if (! self.noContentColumn) { row.push(JSON.stringify(o)); } - txn.execute(self._insertQuery, row); - }); + await txn.execute(self._insertQuery, row); + } }, - createTable: function (txn) { + createTable: async function (txn) { var self = this; var sql = 'CREATE TABLE IF NOT EXISTS ' + self.name + '('; @@ -496,7 +496,7 @@ Object.assign(Table.prototype, { sql += ", content STRING"; } sql += ")"; - txn.execute(sql); + await txn.execute(sql); //sql = "CREATE INDEX IF NOT EXISTS idx_" + self.name + "_id ON " + self.name + "(_id)"; //txn.execute(sql); @@ -530,14 +530,14 @@ Object.assign(RemoteCatalog.prototype, { // are closed (eg to ensure that all writes have been flushed from the '-wal' // file to the main DB file). Most methods on this class will stop working // after you call this method. Note that this yields. - closePermanently: function () { + closePermanently: async function () { var self = this; - self.db.closePermanently(); + await self.db.closePermanently(); self.db = null; }, - getVersion: function (packageName, version) { - var result = this._contentQuery( + getVersion: async function (packageName, version) { + var result = await this._contentQuery( "SELECT content FROM versions WHERE packageName=? AND version=?", [packageName, version]); return filterExactRows(result, { packageName, version }); @@ -552,9 +552,9 @@ Object.assign(RemoteCatalog.prototype, { return self.getVersion(name, _.last(versions)); }, - getSortedVersions: function (name) { + getSortedVersions: async function (name) { var self = this; - var match = this._columnsQuery( + var match = await this._columnsQuery( "SELECT version FROM versions WHERE packageName=?", name); if (match === null) return []; @@ -658,9 +658,9 @@ Object.assign(RemoteCatalog.prototype, { return filterExactRows(result, { name }); }, - getReleaseVersion: function (track, version) { + getReleaseVersion: async function (track, version) { var self = this; - var result = self._contentQuery( + var result = await self._contentQuery( "SELECT content FROM releaseVersions WHERE track=? AND version=?", [track, version]); return filterExactRows(result, { track, version }); @@ -688,7 +688,7 @@ Object.assign(RemoteCatalog.prototype, { return _.pluck(this._columnsQuery("SELECT name FROM packages"), 'name'); }, - initialize: function (options) { + initialize: async function (options) { var self = this; options = options || {}; @@ -698,6 +698,8 @@ Object.assign(RemoteCatalog.prototype, { var dbFile = options.packageStorage || config.getPackageStorage(); self.db = new Db(dbFile); + await self.db.init(); + self.tableVersions = new Table('versions', ['packageName', 'version', '_id']); self.tableBuilds = new Table('builds', ['versionId', '_id']); self.tableReleaseTracks = new Table('releaseTracks', ['name', '_id']); @@ -718,24 +720,24 @@ Object.assign(RemoteCatalog.prototype, { self.tableMetadata, self.tableBannersShown ]; - return self.db.runInTransaction(function(txn) { - _.each(self.allTables, function (table) { - table.createTable(txn); - }); + return self.db.runInTransaction(async function(txn) { + for (const table of self.allTables) { + await table.createTable(txn); + } // Extra indexes for the most expensive queries // These are non-unique indexes // XXX We used to have a versionsNamesIdx here on versions(packageName); // we no longer create it but we don't waste time dropping it either. - txn.execute("CREATE INDEX IF NOT EXISTS versionsIdx ON " + + await txn.execute("CREATE INDEX IF NOT EXISTS versionsIdx ON " + "versions(packageName, version)"); - txn.execute("CREATE INDEX IF NOT EXISTS buildsVersionsIdx ON " + + await txn.execute("CREATE INDEX IF NOT EXISTS buildsVersionsIdx ON " + "builds(versionId)"); - txn.execute("CREATE INDEX IF NOT EXISTS packagesIdx ON " + + await txn.execute("CREATE INDEX IF NOT EXISTS packagesIdx ON " + "packages(name)"); - txn.execute("CREATE INDEX IF NOT EXISTS releaseTracksIdx ON " + + await txn.execute("CREATE INDEX IF NOT EXISTS releaseTracksIdx ON " + "releaseTracks(name)"); - txn.execute("CREATE INDEX IF NOT EXISTS releaseVersionsIdx ON " + + await txn.execute("CREATE INDEX IF NOT EXISTS releaseVersionsIdx ON " + "releaseVersions(track, version)"); }); }, @@ -871,9 +873,9 @@ Object.assign(RemoteCatalog.prototype, { }, // Executes a query, returning an array of each content column parsed as JSON - _contentQuery: function (query, params) { + _contentQuery: async function (query, params) { var self = this; - var rows = self._columnsQuery(query, params); + var rows = await self._columnsQuery(query, params); return _.map(rows, function(entity) { return JSON.parse(entity.content); }); @@ -881,9 +883,9 @@ Object.assign(RemoteCatalog.prototype, { // Executes a query, returning an array of maps from column name to data. // No JSON parsing is performed. - _columnsQuery: function (query, params) { + _columnsQuery: async function (query, params) { var self = this; - var rows = self.db.runInTransaction(function (txn) { + var rows = await self.db.runInTransaction(function (txn) { return txn.query(query, params); }); return rows; @@ -918,9 +920,9 @@ Object.assign(RemoteCatalog.prototype, { }); }, - getSyncToken: function() { + getSyncToken: async function() { var self = this; - var result = self._contentQuery("SELECT content FROM syncToken WHERE _id=?", + var result = await self._contentQuery("SELECT content FROM syncToken WHERE _id=?", [ SYNCTOKEN_ID ]); if (!result || result.length === 0) { Console.debug("No sync token found"); diff --git a/tools/packaging/catalog/catalog.js b/tools/packaging/catalog/catalog.js index 1585ffbe1d..8e2fe31b1c 100644 --- a/tools/packaging/catalog/catalog.js +++ b/tools/packaging/catalog/catalog.js @@ -95,7 +95,6 @@ catalog.runAndRetryWithRefreshIfHelpful = async function (attempt) { var canRetry = ! (catalog.triedToRefreshRecently || catalog.official.offline); - console.log("11111") // Run `attempt` in a nested buildmessage context. var messages = await buildmessage.capture(function () { return attempt(canRetry); @@ -120,7 +119,6 @@ catalog.runAndRetryWithRefreshIfHelpful = async function (attempt) { // catalog.refreshOrWarn, which is a higher-level function that's allowed to // log. catalog.triedToRefreshRecently = true; - console.log("adsdasd") try { await catalog.official.refresh(); catalog.refreshFailed = false; @@ -220,10 +218,10 @@ Object.assign(LayeredCatalog.prototype, { // As getVersion, but returns info on the latest version of the // package, or null if the package doesn't exist or has no versions. // It does not include prereleases (with dashes in the version); - getLatestMainlineVersion: function (name) { + getLatestMainlineVersion: async function (name) { var self = this; - var versions = self.getSortedVersions(name); + var versions = await self.getSortedVersions(name); versions.reverse(); var latest = versions.find(function (version) { return !/-/.test(version); diff --git a/tools/packaging/package-client.js b/tools/packaging/package-client.js index 78af222cfd..8c4dbf52be 100644 --- a/tools/packaging/package-client.js +++ b/tools/packaging/package-client.js @@ -84,7 +84,7 @@ var callPackageServerBM = exports.callPackageServerBM = function (...args) { // - syncToken: a new syncToken object, that we can pass to the server in the future. // - collections: an object keyed by the name of server collections, with the // records as an array of javascript objects. -var loadRemotePackageData = function (conn, syncToken, options) { +var loadRemotePackageData = async function (conn, syncToken, options) { options = options || {}; // Did we get disconnected between retries somehow? Then we should open a new @@ -92,7 +92,7 @@ var loadRemotePackageData = function (conn, syncToken, options) { // since we don't need to authenticate. if (!conn.connected) { conn.close(); - conn = openPackageServerConnection(); + conn = await openPackageServerConnection(); } var syncOpts = {}; @@ -130,7 +130,7 @@ exports.updateServerPackageData = function (dataStore, options) { }); }; -var _updateServerPackageData = function (dataStore, options) { +var _updateServerPackageData = async function (dataStore, options) { var self = this; options = options || {}; if (dataStore === null) { @@ -148,15 +148,15 @@ var _updateServerPackageData = function (dataStore, options) { var state = { current: 0, end: 60 * 60 * 1000, done: false}; useProgressbar && buildmessage.reportProgress(state); - var conn = openPackageServerConnection(options.packageServerUrl); + var conn = await openPackageServerConnection(options.packageServerUrl); // Provide some progress indication for connection // XXX though it is just a hack state.current = 1; useProgressbar && buildmessage.reportProgress(state); - var getSomeData = function () { - var syncToken = dataStore.getSyncToken() || {format: "1.1"}; + var getSomeData = async function () { + var syncToken = (await dataStore.getSyncToken()) || {format: "1.1"}; if (!start) { start = {}; @@ -173,7 +173,7 @@ var _updateServerPackageData = function (dataStore, options) { var compress = !!process.env.METEOR_CATALOG_COMPRESS_RPCS; // (loadRemotePackageData may throw) - var remoteData = loadRemotePackageData(conn, syncToken, { + var remoteData = await loadRemotePackageData(conn, syncToken, { useShortPages: options.useShortPages, compressCollections: compress }); @@ -191,11 +191,11 @@ var _updateServerPackageData = function (dataStore, options) { var zlib = require('zlib'); var colsGzippedBuffer = Buffer.from( remoteData.collectionsCompressed, 'base64'); - var colsJSON = new Promise((resolve, reject) => { + var colsJSON = await new Promise((resolve, reject) => { zlib.gunzip(colsGzippedBuffer, (err, res) => { err ? reject(err) : resolve(res); }); - }).await(); + }); remoteData.collections = JSON.parse(colsJSON); delete remoteData.collectionsCompressed; } @@ -204,7 +204,7 @@ var _updateServerPackageData = function (dataStore, options) { // data! e.g. the last-refresh timestamp var syncComplete = _.isEqual(remoteData.collections, {}) || remoteData.upToDate; - dataStore.insertData(remoteData, syncComplete); + await dataStore.insertData(remoteData, syncComplete); // If there is no new data from the server, don't bother writing things to // disk (unless we were just told to reset everything). @@ -220,7 +220,7 @@ var _updateServerPackageData = function (dataStore, options) { try { while (!done) { - getSomeData(); + await getSomeData(); requestGarbageCollection(); } } finally { diff --git a/tools/project-context.js b/tools/project-context.js index d85392d27b..79d9e765bd 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -120,7 +120,6 @@ function ProjectContext(options) { throw Error("missing projectDir!"); self.originalOptions = options; - self.reset(); } exports.ProjectContext = ProjectContext; @@ -136,7 +135,11 @@ var STAGE = { }; Object.assign(ProjectContext.prototype, { - reset: function (moreOptions, resetOptions) { + init: function () { + const self = this; + return self.reset(); + }, + reset: async function (moreOptions, resetOptions) { var self = this; // Allow overriding some options until the next call to reset; var options = Object.assign({}, self.originalOptions, moreOptions); @@ -154,7 +157,7 @@ Object.assign(ProjectContext.prototype, { self._serverArchitectures = options.serverArchitectures || []; // We always need to download host versions of packages, at least for // plugins. - self._serverArchitectures.push(archinfo.host()); + self._serverArchitectures.push(await archinfo.host()); self._serverArchitectures = _.uniq(self._serverArchitectures); // test-packages overrides this to load local packages from your real app @@ -360,8 +363,6 @@ Object.assign(ProjectContext.prototype, { if (self.completedStage === STAGE.SAVE_CHANGED_METADATA) throw Error("can't find requested stage " + targetStage); - console.log("11111111") - console.log(self._completedStage) // The actual value of STAGE.FOO is the name of the method that takes // you to the next step after FOO. await self[self._completedStage](); @@ -556,7 +557,7 @@ Object.assign(ProjectContext.prototype, { self.appIdentifier = appId; }, - _resolveConstraints: Profile('_resolveConstraints', function () { + _resolveConstraints: Profile('_resolveConstraints', async function () { var self = this; buildmessage.assertInJob(); @@ -591,9 +592,9 @@ Object.assign(ProjectContext.prototype, { // Nothing before this point looked in the official or project catalog! // However, the resolver does, so it gets run in the retry context. - catalog.runAndRetryWithRefreshIfHelpful(function (canRetry) { - buildmessage.enterJob("selecting package versions", function () { - var resolver = self._buildResolver(); + await catalog.runAndRetryWithRefreshIfHelpful(function (canRetry) { + return buildmessage.enterJob("selecting package versions", async function () { + var resolver = await self._buildResolver(); var resolveOptions = { previousSolution: cachedVersions, @@ -620,7 +621,7 @@ Object.assign(ProjectContext.prototype, { var solution; try { - Profile.time( + await Profile.time( "Select Package Versions" + (resolverRunCount > 1 ? (" (Try " + resolverRunCount + ")") : ""), function () { @@ -900,27 +901,27 @@ Object.assign(ProjectContext.prototype, { return anticipatedPrereleases; }, - _buildResolver: function () { - const { ConstraintSolver } = loadIsopackage('constraint-solver'); + _buildResolver: async function () { + const { ConstraintSolver } = await loadIsopackage('constraint-solver'); return new ConstraintSolver.PackagesResolver(this.projectCatalog, { nudge() { - Console.nudge(true); + return Console.nudge(true); }, Profile: Profile, resultCache: this._resolverResultCache }); }, - _downloadMissingPackages: Profile('_downloadMissingPackages', function () { + _downloadMissingPackages: Profile('_downloadMissingPackages', async function () { var self = this; buildmessage.assertInJob(); if (!self.packageMap) throw Error("which packages to download?"); - catalog.runAndRetryWithRefreshIfHelpful(function () { - buildmessage.enterJob("downloading missing packages", function () { - self.tropohouse.downloadPackagesMissingFromMap(self.packageMap, { + await catalog.runAndRetryWithRefreshIfHelpful(function () { + return buildmessage.enterJob("downloading missing packages", async function () { + await self.tropohouse.downloadPackagesMissingFromMap(self.packageMap, { serverArchitectures: self._serverArchitectures }); if (buildmessage.jobHasMessages()) @@ -930,12 +931,12 @@ Object.assign(ProjectContext.prototype, { }); }), - _buildLocalPackages: Profile('_buildLocalPackages', function () { + _buildLocalPackages: Profile('_buildLocalPackages', async function () { var self = this; buildmessage.assertInCapture(); - self.packageMap.eachPackage((name, packageInfo) => { + await self.packageMap.eachPackage((name, packageInfo) => { if (packageInfo.kind === 'local') { addWatchRoot(packageInfo.packageSource.sourceRoot) } @@ -954,13 +955,13 @@ Object.assign(ProjectContext.prototype, { }); if (self._forceRebuildPackages) { - self.isopackCache.wipeCachedPackages( + await self.isopackCache.wipeCachedPackages( self._forceRebuildPackages === true ? null : self._forceRebuildPackages); } - buildmessage.enterJob('building local packages', function () { - self.isopackCache.buildLocalPackages(); + await buildmessage.enterJob('building local packages', function () { + return self.isopackCache.buildLocalPackages(); }); self._completedStage = STAGE.BUILD_LOCAL_PACKAGES; }), diff --git a/tools/tool-env/isopackets.js b/tools/tool-env/isopackets.js index 652afc3852..1b4d22ffec 100644 --- a/tools/tool-env/isopackets.js +++ b/tools/tool-env/isopackets.js @@ -1,3 +1,5 @@ +import {debug} from "util"; + var assert = require('assert'); var _ = require('underscore'); @@ -93,14 +95,14 @@ var loadedIsopackets = {}; // dependency, complaining if the package does not exist. Note that // ensureIsopacketsLoadable must be called first, as this function does // not trigger any building. -export function loadIsopackage(packageName, isopacketName = "combined") { +export async function loadIsopackage(packageName, isopacketName = "combined") { // Small but necessary hack: because archinfo.host() calls execFileSync, // it yields the first time we call it, which is a problem for the // fiberHelpers.noYieldsAllowed block below. Calling it here ensures the // result is cached, so no yielding occurs later. - assert.strictEqual(archinfo.host().split(".", 1)[0], "os"); + assert.strictEqual((await archinfo.host()).split(".", 1)[0], "os"); - const isopacket = function () { + async function load() { if (_.has(loadedIsopackets, isopacketName)) { if (loadedIsopackets[isopacketName]) { return loadedIsopackets[isopacketName]; @@ -108,8 +110,10 @@ export function loadIsopackage(packageName, isopacketName = "combined") { // This is the case where the isopacket is up to date on disk but not // loaded. - return loadedIsopackets[isopacketName] = - loadIsopacketFromDisk(isopacketName); + const loaded = await loadIsopacketFromDisk(isopacketName); + loadedIsopackets[isopacketName] = loaded; + + return loaded; } if (_.has(ISOPACKETS, isopacketName)) { @@ -118,8 +122,12 @@ export function loadIsopackage(packageName, isopacketName = "combined") { } throw Error("Unknown isopacket: " + isopacketName); - }(); + } + const isopacket = await load(); + console.log("Isopacket: ", isopacket); + console.log("PackageName: ", packageName); + console.log("***********"); if (!_.has(isopacket, packageName)) { throw new Error("Unknown isopacket dependency: " + packageName); } @@ -165,7 +173,7 @@ export async function ensureIsopacketsLoadable() { var isopacketRoot = isopacketPath(isopacketName); var existingBuildinfo = files.readJSONOrNull( files.pathJoin(isopacketRoot, 'isopacket-buildinfo.json')); - var needRebuild = !existingBuildinfo; + var needRebuild = true; if (!needRebuild && existingBuildinfo.builtBy !== compiler.BUILT_BY) { needRebuild = true; } @@ -294,7 +302,7 @@ export async function makeIsopacketBuildContext() { // Loads a built isopacket from disk. Always loads (the cache is in 'load', not // this function). Does not run a build process; it must already be built. var loadIsopacketFromDisk = async function (isopacketName) { - var image = bundler.readJsImage( + var image = await bundler.readJsImage( files.pathJoin(isopacketPath(isopacketName), 'program.json')); // An incredibly minimalist version of the environment from diff --git a/tools/utils/buildmessage.js b/tools/utils/buildmessage.js index a80a0f747f..f75efc2851 100644 --- a/tools/utils/buildmessage.js +++ b/tools/utils/buildmessage.js @@ -460,8 +460,6 @@ var markBoundary = function (f, context) { var error = function (message, options) { options = options || {}; - console.log("111") - console.trace("dasdsa") if (options.downcase) { message = message.slice(0,1).toLowerCase() + message.slice(1); } From ee695996d649252c457c480f3eeb9a89c61b2778 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sun, 4 Dec 2022 00:50:44 -0300 Subject: [PATCH 0091/1965] Fix issue with module not found. --- tools/isobuild/linker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index a6814ea421..8400190afc 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -380,7 +380,7 @@ Object.assign(Module.prototype, { // Emit one meteorInstall call per distinct meteorInstallOptions // object, since the options apply to all modules installed by a given // call to meteorInstall. - for (const [tree, options] of trees) { + for (const [options, tree] of trees) { chunks.push("meteorInstall("); await walk(tree); chunks.push(",", self._stringifyInstallOptions(options), ");\n"); From fb40d960b8b806f599830ed4049dd1300752f50f Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 5 Dec 2022 10:11:41 -0400 Subject: [PATCH 0092/1965] ddp-server and ddp-client are now ddp-server-legacy and ddp-client-server ddp-server-async and ddp-client-async are now ddp-server and ddp-client --- .../ddp-client-async/.npm/package/.gitignore | 1 - packages/ddp-client-async/.npm/package/README | 7 - .../.npm/package/npm-shrinkwrap.json | 25 -- .../README.md | 0 .../client/client.js | 0 .../client/client_convenience.js | 0 .../common/MethodInvoker.js | 0 .../common/livedata_connection.js | 50 ++-- .../common/namespace.js | 0 .../package.js | 2 +- .../server/server.js | 0 .../test/livedata_connection_tests.js | 0 .../test/livedata_test_service.js | 4 +- .../test/livedata_tests.js | 0 .../test/random_stream_tests.js | 0 .../test/stub_stream.js | 0 .../ddp-client/common/livedata_connection.js | 50 ++-- packages/ddp-client/package.js | 7 +- .../ddp-client/test/livedata_test_service.js | 4 +- .../ddp-server-async/.npm/package/.gitignore | 1 - packages/ddp-server-async/.npm/package/README | 7 - .../.npm/package/npm-shrinkwrap.json | 45 ---- packages/ddp-server-async/writefence.js | 125 ---------- .../README.md | 0 .../crossbar.js | 0 .../crossbar_tests.js | 0 .../livedata_server.js | 149 +++++------ .../livedata_server_async_tests.js | 5 +- .../livedata_server_tests.js | 64 +++-- .../package.js | 0 .../server_convenience.js | 0 .../session_view_tests.js | 0 .../stream_server.js | 0 packages/ddp-server-legacy/writefence.js | 131 ++++++++++ packages/ddp-server/livedata_server.js | 149 ++++++----- .../ddp-server/livedata_server_async_tests.js | 5 +- packages/ddp-server/livedata_server_tests.js | 64 ++--- packages/ddp-server/package.js | 5 - packages/ddp-server/writefence.js | 232 +++++++++--------- 39 files changed, 518 insertions(+), 614 deletions(-) delete mode 100644 packages/ddp-client-async/.npm/package/.gitignore delete mode 100644 packages/ddp-client-async/.npm/package/README delete mode 100644 packages/ddp-client-async/.npm/package/npm-shrinkwrap.json rename packages/{ddp-client-async => ddp-client-legacy}/README.md (100%) rename packages/{ddp-client-async => ddp-client-legacy}/client/client.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/client/client_convenience.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/common/MethodInvoker.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/common/livedata_connection.js (98%) rename packages/{ddp-client-async => ddp-client-legacy}/common/namespace.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/package.js (98%) rename packages/{ddp-client-async => ddp-client-legacy}/server/server.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/test/livedata_connection_tests.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/test/livedata_test_service.js (99%) rename packages/{ddp-client-async => ddp-client-legacy}/test/livedata_tests.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/test/random_stream_tests.js (100%) rename packages/{ddp-client-async => ddp-client-legacy}/test/stub_stream.js (100%) delete mode 100644 packages/ddp-server-async/.npm/package/.gitignore delete mode 100644 packages/ddp-server-async/.npm/package/README delete mode 100644 packages/ddp-server-async/.npm/package/npm-shrinkwrap.json delete mode 100644 packages/ddp-server-async/writefence.js rename packages/{ddp-server-async => ddp-server-legacy}/README.md (100%) rename packages/{ddp-server-async => ddp-server-legacy}/crossbar.js (100%) rename packages/{ddp-server-async => ddp-server-legacy}/crossbar_tests.js (100%) rename packages/{ddp-server-async => ddp-server-legacy}/livedata_server.js (96%) rename packages/{ddp-server-async => ddp-server-legacy}/livedata_server_async_tests.js (96%) rename packages/{ddp-server-async => ddp-server-legacy}/livedata_server_tests.js (84%) rename packages/{ddp-server-async => ddp-server-legacy}/package.js (100%) rename packages/{ddp-server-async => ddp-server-legacy}/server_convenience.js (100%) rename packages/{ddp-server-async => ddp-server-legacy}/session_view_tests.js (100%) rename packages/{ddp-server-async => ddp-server-legacy}/stream_server.js (100%) create mode 100644 packages/ddp-server-legacy/writefence.js diff --git a/packages/ddp-client-async/.npm/package/.gitignore b/packages/ddp-client-async/.npm/package/.gitignore deleted file mode 100644 index 3c3629e647..0000000000 --- a/packages/ddp-client-async/.npm/package/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/packages/ddp-client-async/.npm/package/README b/packages/ddp-client-async/.npm/package/README deleted file mode 100644 index 3d492553a4..0000000000 --- a/packages/ddp-client-async/.npm/package/README +++ /dev/null @@ -1,7 +0,0 @@ -This directory and the files immediately inside it are automatically generated -when you change this package's NPM dependencies. Commit the files in this -directory (npm-shrinkwrap.json, .gitignore, and this README) to source control -so that others run the same versions of sub-dependencies. - -You should NOT check in the node_modules directory that Meteor automatically -creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/ddp-client-async/.npm/package/npm-shrinkwrap.json b/packages/ddp-client-async/.npm/package/npm-shrinkwrap.json deleted file mode 100644 index 28feb731a6..0000000000 --- a/packages/ddp-client-async/.npm/package/npm-shrinkwrap.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "lockfileVersion": 1, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==" - }, - "@sinonjs/fake-timers": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", - "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==" - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - } - } -} diff --git a/packages/ddp-client-async/README.md b/packages/ddp-client-legacy/README.md similarity index 100% rename from packages/ddp-client-async/README.md rename to packages/ddp-client-legacy/README.md diff --git a/packages/ddp-client-async/client/client.js b/packages/ddp-client-legacy/client/client.js similarity index 100% rename from packages/ddp-client-async/client/client.js rename to packages/ddp-client-legacy/client/client.js diff --git a/packages/ddp-client-async/client/client_convenience.js b/packages/ddp-client-legacy/client/client_convenience.js similarity index 100% rename from packages/ddp-client-async/client/client_convenience.js rename to packages/ddp-client-legacy/client/client_convenience.js diff --git a/packages/ddp-client-async/common/MethodInvoker.js b/packages/ddp-client-legacy/common/MethodInvoker.js similarity index 100% rename from packages/ddp-client-async/common/MethodInvoker.js rename to packages/ddp-client-legacy/common/MethodInvoker.js diff --git a/packages/ddp-client-async/common/livedata_connection.js b/packages/ddp-client-legacy/common/livedata_connection.js similarity index 98% rename from packages/ddp-client-async/common/livedata_connection.js rename to packages/ddp-client-legacy/common/livedata_connection.js index 1855a0e0f5..868201c43d 100644 --- a/packages/ddp-client-async/common/livedata_connection.js +++ b/packages/ddp-client-legacy/common/livedata_connection.js @@ -606,12 +606,14 @@ export class Connection { DDP._CurrentMethodInvocation._set(); DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); return new Promise((resolve, reject) => { - this.applyAsync(name, args, { isFromCallAsync: true }) - .then(result => { - DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); - resolve(result); - }) - .catch(reject); + this.applyAsync(name, args, { isFromCallAsync: true }, (err, result) => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + if (err) { + reject(err); + return; + } + resolve(result); + }); }); } @@ -667,8 +669,9 @@ export class Connection { * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. + * @param {Function} [asyncCallback] Optional callback. */ - async applyAsync(name, args, options) { + async applyAsync(name, args, options, callback) { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); if (stubOptions.hasStub) { if ( @@ -692,7 +695,14 @@ export class Connection { invocation ); try { - stubOptions.stubReturnValue = await stubInvocation(); + const resultOrThenable = stubInvocation(); + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + stubOptions.stubReturnValue = await resultOrThenable; + } else { + stubOptions.stubReturnValue = resultOrThenable; + } } finally { DDP._CurrentMethodInvocation._set(currentContext); } @@ -700,7 +710,7 @@ export class Connection { stubOptions.exception = e; } } - return this._apply(name, stubOptions, args, options, null); + return this._apply(name, stubOptions, args, options, callback); } _apply(name, stubCallValue, args, options, callback) { @@ -800,24 +810,8 @@ export class Connection { } else { // On the server, make the function synchronous. Throw on // errors, return on success. - // TODO fibers: before this was a future, now it's a promise. - // Do more tests around this. - - if (!options.isFromCallAsync) { - throw new Error("Can't create a future for Meteor.call()"); - } - - future = new Promise((resolve, reject) => { - callback = (...allArgs) => { - let args = Array.from(allArgs); - let err = args.shift(); - if (err) { - reject(err); - return; - } - resolve(...args); - } - }); + future = new Future(); + callback = future.resolver(); } } @@ -862,7 +856,7 @@ export class Connection { // If we're using the default callback on the server, // block waiting for the result. if (future) { - return future; + return future.wait(); } return options.returnStubValue ? stubReturnValue : undefined; } diff --git a/packages/ddp-client-async/common/namespace.js b/packages/ddp-client-legacy/common/namespace.js similarity index 100% rename from packages/ddp-client-async/common/namespace.js rename to packages/ddp-client-legacy/common/namespace.js diff --git a/packages/ddp-client-async/package.js b/packages/ddp-client-legacy/package.js similarity index 98% rename from packages/ddp-client-async/package.js rename to packages/ddp-client-legacy/package.js index 0cdc77a953..5a0b31737f 100644 --- a/packages/ddp-client-async/package.js +++ b/packages/ddp-client-legacy/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.6.0', + version: '2.6.1', documentation: null }); diff --git a/packages/ddp-client-async/server/server.js b/packages/ddp-client-legacy/server/server.js similarity index 100% rename from packages/ddp-client-async/server/server.js rename to packages/ddp-client-legacy/server/server.js diff --git a/packages/ddp-client-async/test/livedata_connection_tests.js b/packages/ddp-client-legacy/test/livedata_connection_tests.js similarity index 100% rename from packages/ddp-client-async/test/livedata_connection_tests.js rename to packages/ddp-client-legacy/test/livedata_connection_tests.js diff --git a/packages/ddp-client-async/test/livedata_test_service.js b/packages/ddp-client-legacy/test/livedata_test_service.js similarity index 99% rename from packages/ddp-client-async/test/livedata_test_service.js rename to packages/ddp-client-legacy/test/livedata_test_service.js index 9f0c7d1fe4..db32cf3cc4 100644 --- a/packages/ddp-client-async/test/livedata_test_service.js +++ b/packages/ddp-client-legacy/test/livedata_test_service.js @@ -202,10 +202,10 @@ if (Meteor.isServer) { /// Helper for "livedata - setUserId fails when called on server" if (Meteor.isServer) { - Meteor.startup(async function() { + Meteor.startup(function() { errorThrownWhenCallingSetUserIdDirectlyOnServer = null; try { - await Meteor.callAsync('setUserId', '1000'); + Meteor.call('setUserId', '1000'); } catch (e) { errorThrownWhenCallingSetUserIdDirectlyOnServer = e; } diff --git a/packages/ddp-client-async/test/livedata_tests.js b/packages/ddp-client-legacy/test/livedata_tests.js similarity index 100% rename from packages/ddp-client-async/test/livedata_tests.js rename to packages/ddp-client-legacy/test/livedata_tests.js diff --git a/packages/ddp-client-async/test/random_stream_tests.js b/packages/ddp-client-legacy/test/random_stream_tests.js similarity index 100% rename from packages/ddp-client-async/test/random_stream_tests.js rename to packages/ddp-client-legacy/test/random_stream_tests.js diff --git a/packages/ddp-client-async/test/stub_stream.js b/packages/ddp-client-legacy/test/stub_stream.js similarity index 100% rename from packages/ddp-client-async/test/stub_stream.js rename to packages/ddp-client-legacy/test/stub_stream.js diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index 868201c43d..1855a0e0f5 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -606,14 +606,12 @@ export class Connection { DDP._CurrentMethodInvocation._set(); DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); return new Promise((resolve, reject) => { - this.applyAsync(name, args, { isFromCallAsync: true }, (err, result) => { - DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); - if (err) { - reject(err); - return; - } - resolve(result); - }); + this.applyAsync(name, args, { isFromCallAsync: true }) + .then(result => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + resolve(result); + }) + .catch(reject); }); } @@ -669,9 +667,8 @@ export class Connection { * @param {Boolean} options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback an error with the error code 'invocation-failed'. * @param {Boolean} options.throwStubExceptions (Client only) If true, exceptions thrown by method stubs will be thrown instead of logged, and the method will not be invoked on the server. * @param {Boolean} options.returnStubValue (Client only) If true then in cases where we would have otherwise discarded the stub's return value and returned undefined, instead we go ahead and return it. Specifically, this is any time other than when (a) we are already inside a stub or (b) we are in Node and no callback was provided. Currently we require this flag to be explicitly passed to reduce the likelihood that stub return values will be confused with server return values; we may improve this in future. - * @param {Function} [asyncCallback] Optional callback. */ - async applyAsync(name, args, options, callback) { + async applyAsync(name, args, options) { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); if (stubOptions.hasStub) { if ( @@ -695,14 +692,7 @@ export class Connection { invocation ); try { - const resultOrThenable = stubInvocation(); - const isThenable = - resultOrThenable && typeof resultOrThenable.then === 'function'; - if (isThenable) { - stubOptions.stubReturnValue = await resultOrThenable; - } else { - stubOptions.stubReturnValue = resultOrThenable; - } + stubOptions.stubReturnValue = await stubInvocation(); } finally { DDP._CurrentMethodInvocation._set(currentContext); } @@ -710,7 +700,7 @@ export class Connection { stubOptions.exception = e; } } - return this._apply(name, stubOptions, args, options, callback); + return this._apply(name, stubOptions, args, options, null); } _apply(name, stubCallValue, args, options, callback) { @@ -810,8 +800,24 @@ export class Connection { } else { // On the server, make the function synchronous. Throw on // errors, return on success. - future = new Future(); - callback = future.resolver(); + // TODO fibers: before this was a future, now it's a promise. + // Do more tests around this. + + if (!options.isFromCallAsync) { + throw new Error("Can't create a future for Meteor.call()"); + } + + future = new Promise((resolve, reject) => { + callback = (...allArgs) => { + let args = Array.from(allArgs); + let err = args.shift(); + if (err) { + reject(err); + return; + } + resolve(...args); + } + }); } } @@ -856,7 +862,7 @@ export class Connection { // If we're using the default callback on the server, // block waiting for the result. if (future) { - return future.wait(); + return future; } return options.returnStubValue ? stubReturnValue : undefined; } diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 8c41b99c1c..0cdc77a953 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.6.1', + version: '2.6.0', documentation: null }); @@ -9,11 +9,6 @@ Npm.depends({ }); Package.onUse((api) => { - if (process.env.DISABLE_FIBERS) { - api.use('ddp-client-async'); - api.export('DDP', 'server'); - return; - } api.use([ 'check', 'random', diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index db32cf3cc4..9f0c7d1fe4 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -202,10 +202,10 @@ if (Meteor.isServer) { /// Helper for "livedata - setUserId fails when called on server" if (Meteor.isServer) { - Meteor.startup(function() { + Meteor.startup(async function() { errorThrownWhenCallingSetUserIdDirectlyOnServer = null; try { - Meteor.call('setUserId', '1000'); + await Meteor.callAsync('setUserId', '1000'); } catch (e) { errorThrownWhenCallingSetUserIdDirectlyOnServer = e; } diff --git a/packages/ddp-server-async/.npm/package/.gitignore b/packages/ddp-server-async/.npm/package/.gitignore deleted file mode 100644 index 3c3629e647..0000000000 --- a/packages/ddp-server-async/.npm/package/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/packages/ddp-server-async/.npm/package/README b/packages/ddp-server-async/.npm/package/README deleted file mode 100644 index 3d492553a4..0000000000 --- a/packages/ddp-server-async/.npm/package/README +++ /dev/null @@ -1,7 +0,0 @@ -This directory and the files immediately inside it are automatically generated -when you change this package's NPM dependencies. Commit the files in this -directory (npm-shrinkwrap.json, .gitignore, and this README) to source control -so that others run the same versions of sub-dependencies. - -You should NOT check in the node_modules directory that Meteor automatically -creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/ddp-server-async/.npm/package/npm-shrinkwrap.json b/packages/ddp-server-async/.npm/package/npm-shrinkwrap.json deleted file mode 100644 index b9b024674c..0000000000 --- a/packages/ddp-server-async/.npm/package/npm-shrinkwrap.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "lockfileVersion": 1, - "dependencies": { - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==" - }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" - }, - "permessage-deflate": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.7.tgz", - "integrity": "sha512-EUNi/RIsyJ1P1u9QHFwMOUWMYetqlE22ZgGbad7YP856WF4BFF0B7DuNy6vEGsgNNud6c/SkdWzkne71hH8MjA==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==" - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - } - } -} diff --git a/packages/ddp-server-async/writefence.js b/packages/ddp-server-async/writefence.js deleted file mode 100644 index d85f028ff8..0000000000 --- a/packages/ddp-server-async/writefence.js +++ /dev/null @@ -1,125 +0,0 @@ -// A write fence collects a group of writes, and provides a callback -// when all of the writes are fully committed and propagated (all -// observers have been notified of the write and acknowledged it.) -// -DDPServer._WriteFence = class { - constructor() { - this.armed = false; - this.fired = false; - this.retired = false; - this.outstanding_writes = 0; - this.before_fire_callbacks = []; - this.completion_callbacks = []; - } - - // Start tracking a write, and return an object to represent it. The - // object has a single method, committed(). This method should be - // called when the write is fully committed and propagated. You can - // continue to add writes to the WriteFence up until it is triggered - // (calls its callbacks because all writes have committed.) - beginWrite() { - if (this.retired) - return { committed: function () {} }; - - if (this.fired) - throw new Error("fence has already activated -- too late to add writes"); - - this.outstanding_writes++; - let committed = false; - const _committedFn = async () => { - if (committed) - throw new Error("committed called twice on the same write"); - committed = true; - this.outstanding_writes--; - await this._maybeFire(); - }; - - const self = this; - return { - committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, - }; - } - - // Arm the fence. Once the fence is armed, and there are no more - // uncommitted writes, it will activate. - arm() { - if (this === DDPServer._CurrentWriteFence.get()) - throw Error("Can't arm the current fence"); - this.armed = true; - return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); - } - - // Register a function to be called once before firing the fence. - // Callback function can add new writes to the fence, in which case - // it won't fire until those writes are done as well. - onBeforeFire(func) { - if (this.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - this.before_fire_callbacks.push(func); - } - - // Register a function to be called when the fence fires. - onAllCommitted(func) { - if (this.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - this.completion_callbacks.push(func); - } - - _armAndWait() { - let resolver; - const returnValue = new Promise(r => resolver = r); - this.onAllCommitted(resolver); - this.arm(); - - return returnValue; - } - // Convenience function. Arms the fence, then blocks until it fires. - armAndWait() { - return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait(); - } - - async _maybeFire() { - if (this.fired) - throw new Error("write fence already activated?"); - if (this.armed && !this.outstanding_writes) { - const invokeCallback = async (func) => { - try { - await func(this); - } catch (err) { - Meteor._debug("exception in write fence callback:", err); - } - }; - - this.outstanding_writes++; - while (this.before_fire_callbacks.length > 0) { - const cb = this.before_fire_callbacks.shift(); - await invokeCallback(cb); - } - this.outstanding_writes--; - - if (!this.outstanding_writes) { - this.fired = true; - while (this.completion_callbacks.length > 0) { - const cb = this.completion_callbacks.shift(); - await invokeCallback(cb); - } - } - } - } - - // Deactivate this fence so that adding more writes has no effect. - // The fence must have already fired. - retire() { - if (!this.fired) - throw new Error("Can't retire a fence that hasn't fired."); - this.retired = true; - } -}; - -// The current write fence. When there is a current write fence, code -// that writes to databases should register their writes with it using -// beginWrite(). -// -DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; diff --git a/packages/ddp-server-async/README.md b/packages/ddp-server-legacy/README.md similarity index 100% rename from packages/ddp-server-async/README.md rename to packages/ddp-server-legacy/README.md diff --git a/packages/ddp-server-async/crossbar.js b/packages/ddp-server-legacy/crossbar.js similarity index 100% rename from packages/ddp-server-async/crossbar.js rename to packages/ddp-server-legacy/crossbar.js diff --git a/packages/ddp-server-async/crossbar_tests.js b/packages/ddp-server-legacy/crossbar_tests.js similarity index 100% rename from packages/ddp-server-async/crossbar_tests.js rename to packages/ddp-server-legacy/crossbar_tests.js diff --git a/packages/ddp-server-async/livedata_server.js b/packages/ddp-server-legacy/livedata_server.js similarity index 96% rename from packages/ddp-server-async/livedata_server.js rename to packages/ddp-server-legacy/livedata_server.js index 9cadd4d4ee..3b4853c39e 100644 --- a/packages/ddp-server-async/livedata_server.js +++ b/packages/ddp-server-legacy/livedata_server.js @@ -1,5 +1,7 @@ DDPServer = {}; +var Fiber = Npm.require('fibers'); + // Publication strategies define how we handle data from published cursors at the collection level // This allows someone to: // - Choose a trade-off between client-server bandwidth and server memory usage @@ -328,9 +330,9 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - Meteor._runAsync(function() { + Fiber(function () { self.startUniversalSubs(); - }); + }).run(); if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -555,10 +557,10 @@ Object.assign(Session.prototype, { // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. if (self.heartbeat) { - Meteor._runAsync(function() { + Fiber(function () { self.heartbeat.messageReceived(); - }); - }; + }).run(); + } if (self.version !== 'pre1' && msg_in.msg === 'ping') { if (self._respondToPings) @@ -582,7 +584,7 @@ Object.assign(Session.prototype, { return; } - function runHandlers() { + Fiber(function () { var blocked = true; var unblock = function () { @@ -602,9 +604,7 @@ Object.assign(Session.prototype, { else self.sendError('Bad request', msg); unblock(); // in case the handler didn't already do it - } - - Meteor._runAsync(runHandlers); + }).run(); }; processNext(); @@ -762,32 +762,33 @@ Object.assign(Session.prototype, { } } - const getCurrentMethodInvocationResult = () => - DDP._CurrentPublicationInvocation.withValue( - invocation, - () => - maybeAuditArgumentChecks( - handler, - invocation, - msg.params, - "call to '" + msg.method + "'" - ), - { - name: 'getCurrentMethodInvocationResult', - keyName: 'getCurrentMethodInvocationResult', - } + const getCurrentMethodInvocationResult = () => { + const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( + invocation ); - resolve( - DDPServer._CurrentWriteFence.withValue( - fence, - getCurrentMethodInvocationResult, - { - name: 'DDPServer._CurrentWriteFence', - keyName: '_CurrentWriteFence', + try { + let result; + const resultOrThenable = maybeAuditArgumentChecks( + handler, + invocation, + msg.params, + "call to '" + msg.method + "'" + ); + const isThenable = + resultOrThenable && typeof resultOrThenable.then === 'function'; + if (isThenable) { + result = Promise.await(resultOrThenable); + } else { + result = resultOrThenable; } - ) - ); + return result; + } finally { + DDP._CurrentMethodInvocation._set(currentContext); + } + }; + + resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult)); }); function finish() { @@ -898,7 +899,7 @@ Object.assign(Session.prototype, { // subs. self._dontStartNewUniversalSubs = false; self.startUniversalSubs(); - }, { name: '_setUserId' }); + }); // Start sending messages again, beginning with the diff from the previous // state of the world to the current state. No yields are allowed during @@ -1119,19 +1120,16 @@ Object.assign(Subscription.prototype, { const self = this; let resultOrThenable = null; try { - resultOrThenable = DDP._CurrentPublicationInvocation.withValue( - self, - () => - maybeAuditArgumentChecks( - self._handler, - self, - EJSON.clone(self._params), - // It's OK that this would look weird for universal subscriptions, - // because they have no arguments so there can never be an - // audit-argument-checks failure. - "publisher '" + self._name + "'" - ), - { name: self._name } + resultOrThenable = DDP._CurrentPublicationInvocation.withValue(self, () => + maybeAuditArgumentChecks( + self._handler, + self, + EJSON.clone(self._params), + // It's OK that this would look weird for universal subscriptions, + // because they have no arguments so there can never be an + // audit-argument-checks failure. + "publisher '" + self._name + "'" + ) ); } catch (e) { self.error(e); @@ -1154,7 +1152,6 @@ Object.assign(Subscription.prototype, { } else { self._publishHandlerResult(resultOrThenable); } - }, _publishHandlerResult: function (res) { @@ -1180,21 +1177,15 @@ Object.assign(Subscription.prototype, { return c && c._publishCursor; }; if (isCursor(res)) { - if (Meteor._isFibersEnabled) { - try { - res._publishCursor(self); - } catch (e) { - self.error(e); - return; - } - // _publishCursor only returns after the initial added callbacks have run. - // mark subscription as ready. - self.ready(); - } else { - res._publishCursor(self).then(() => { - self.ready(); - }).catch((e) => self.error(e)); + try { + res._publishCursor(self); + } catch (e) { + self.error(e); + return; } + // _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)) { @@ -1216,21 +1207,15 @@ Object.assign(Subscription.prototype, { collectionNames[collectionName] = true; }; - if (Meteor._isFibersEnabled) { - try { - _.each(res, function (cur) { - cur._publishCursor(self); - }); - } catch (e) { - self.error(e); - return; - } - self.ready(); - } else { - Promise.all(res.map((c) => c._publishCursor(self))).then(() => { - self.ready(); - }).catch((e) => self.error(e)); + try { + _.each(res, function (cur) { + cur._publishCursor(self); + }); + } catch (e) { + self.error(e); + return; } + self.ready(); } else if (res) { // Truthy values other than cursors or arrays are probably a // user mistake (possible returning a Mongo document via, say, @@ -1507,11 +1492,9 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - - Meteor._runAsync(function() { + Fiber(function () { self._handleConnect(socket, msg); - }) - + }).run(); return; } @@ -1528,9 +1511,9 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - Meteor._runAsync(function() { + Fiber(function () { socket._meteorSession.close(); - }); + }).run(); } }); }); @@ -1708,9 +1691,9 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - Meteor._runAsync(function() { + Fiber(function() { session._startSubscription(handler); - }); + }).run(); } }); } diff --git a/packages/ddp-server-async/livedata_server_async_tests.js b/packages/ddp-server-legacy/livedata_server_async_tests.js similarity index 96% rename from packages/ddp-server-async/livedata_server_async_tests.js rename to packages/ddp-server-legacy/livedata_server_async_tests.js index d66a28f091..d145aeee92 100644 --- a/packages/ddp-server-async/livedata_server_async_tests.js +++ b/packages/ddp-server-legacy/livedata_server_async_tests.js @@ -23,8 +23,6 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); -// console.log('methodInvocation', methodInvocation); -// console.log('publicationInvocation', !!publicationInvocation); // Check the publish function's environment variables and context. if (callback) { callback.call(this, methodInvocation, publicationInvocation); @@ -35,7 +33,6 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); - callback.call( this, onStopMethodInvocation, @@ -48,7 +45,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.stop(); } else { this.ready(); - await Meteor.callAsync('livedata_server_test_setuserid', userId); + Meteor.call('livedata_server_test_setuserid', userId); } }); diff --git a/packages/ddp-server-async/livedata_server_tests.js b/packages/ddp-server-legacy/livedata_server_tests.js similarity index 84% rename from packages/ddp-server-async/livedata_server_tests.js rename to packages/ddp-server-legacy/livedata_server_tests.js index 101b1c2a93..cde56b6196 100644 --- a/packages/ddp-server-async/livedata_server_tests.js +++ b/packages/ddp-server-legacy/livedata_server_tests.js @@ -87,8 +87,8 @@ Meteor.methods({ return this.connection && this.connection.id; }, - livedata_server_test_outer: async function () { - return await Meteor.callAsync('livedata_server_test_inner'); + livedata_server_test_outer: function () { + return Meteor.call('livedata_server_test_inner'); }, livedata_server_test_setuserid: function (userId) { @@ -108,17 +108,12 @@ Tinytest.addAsync( }); makeTestConnection( - test, - function(clientConn, serverConn) { - clientConn - .callAsync('livedata_server_test_inner') - .then(() => clientConn.disconnect()) - .catch(e => { - onComplete(); - throw new Meteor.Error(e); - }); - }, - onComplete + test, + function (clientConn, serverConn) { + clientConn.call('livedata_server_test_inner'); + clientConn.disconnect(); + }, + onComplete ); } ); @@ -130,11 +125,10 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - clientConn.callAsync('livedata_server_test_inner').then(res => { - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); - }); + var res = clientConn.call('livedata_server_test_inner'); + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); }, onComplete ); @@ -148,11 +142,10 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - clientConn.callAsync('livedata_server_test_outer').then(res => { - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); - }); + var res = clientConn.call('livedata_server_test_outer'); + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); }, onComplete ); @@ -170,16 +163,16 @@ Meteor.publish("livedata_server_test_sub", function (connectionId) { this.stop(); }); -Meteor.publish("livedata_server_test_sub_method", async function (connectionId) { +Meteor.publish("livedata_server_test_sub_method", function (connectionId) { var callback = onSubscription[connectionId]; if (callback) { - var id = await Meteor.callAsync('livedata_server_test_inner'); + var id = Meteor.call('livedata_server_test_inner'); callback(id); } this.stop(); }); -Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) { +Meteor.publish("livedata_server_test_sub_context", function (connectionId, userId) { var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); @@ -201,7 +194,7 @@ Meteor.publish("livedata_server_test_sub_context", async function (connectionId, this.stop(); } else { this.ready(); - await Meteor.callAsync('livedata_server_test_setuserid', userId); + Meteor.call('livedata_server_test_setuserid', userId); } }); @@ -318,13 +311,13 @@ Tinytest.addAsync( ); Meteor.methods({ - async testResolvedPromise(arg) { - const invocationRunningFromCallAsync1 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); + testResolvedPromise(arg) { + const invocation1 = DDP._CurrentMethodInvocation.get(); return Promise.resolve(arg).then(result => { - const invocationRunningFromCallAsync2 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); - // What matters here is that both invocations are coming from the same call, - // so both of them can be considered a simulation. - if (invocationRunningFromCallAsync1 !== invocationRunningFromCallAsync2) { + const invocation2 = DDP._CurrentMethodInvocation.get(); + // This equality holds because Promise callbacks are bound to the + // dynamic environment where .then was called. + if (invocation1 !== invocation2) { throw new Meteor.Error("invocation mismatch"); } return result + " after waiting"; @@ -351,10 +344,9 @@ Meteor.methods({ Tinytest.addAsync( "livedata server - waiting for Promise", - (test, onComplete) => makeTestConnection(test, async (clientConn, serverConn) => { - const testResolvedPromiseResult = await clientConn.callAsync("testResolvedPromise", "clientConn.call"); + (test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => { test.equal( - testResolvedPromiseResult, + clientConn.call("testResolvedPromise", "clientConn.call"), "clientConn.call after waiting" ); diff --git a/packages/ddp-server-async/package.js b/packages/ddp-server-legacy/package.js similarity index 100% rename from packages/ddp-server-async/package.js rename to packages/ddp-server-legacy/package.js diff --git a/packages/ddp-server-async/server_convenience.js b/packages/ddp-server-legacy/server_convenience.js similarity index 100% rename from packages/ddp-server-async/server_convenience.js rename to packages/ddp-server-legacy/server_convenience.js diff --git a/packages/ddp-server-async/session_view_tests.js b/packages/ddp-server-legacy/session_view_tests.js similarity index 100% rename from packages/ddp-server-async/session_view_tests.js rename to packages/ddp-server-legacy/session_view_tests.js diff --git a/packages/ddp-server-async/stream_server.js b/packages/ddp-server-legacy/stream_server.js similarity index 100% rename from packages/ddp-server-async/stream_server.js rename to packages/ddp-server-legacy/stream_server.js diff --git a/packages/ddp-server-legacy/writefence.js b/packages/ddp-server-legacy/writefence.js new file mode 100644 index 0000000000..e9310c9f7f --- /dev/null +++ b/packages/ddp-server-legacy/writefence.js @@ -0,0 +1,131 @@ +var Future = Npm.require('fibers/future'); + +// A write fence collects a group of writes, and provides a callback +// when all of the writes are fully committed and propagated (all +// observers have been notified of the write and acknowledged it.) +// +DDPServer._WriteFence = function () { + var self = this; + + self.armed = false; + self.fired = false; + self.retired = false; + self.outstanding_writes = 0; + self.before_fire_callbacks = []; + self.completion_callbacks = []; +}; + +// The current write fence. When there is a current write fence, code +// that writes to databases should register their writes with it using +// beginWrite(). +// +DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; + +_.extend(DDPServer._WriteFence.prototype, { + // Start tracking a write, and return an object to represent it. The + // object has a single method, committed(). This method should be + // called when the write is fully committed and propagated. You can + // continue to add writes to the WriteFence up until it is triggered + // (calls its callbacks because all writes have committed.) + beginWrite: function () { + var self = this; + + if (self.retired) + return { committed: function () {} }; + + if (self.fired) + throw new Error("fence has already activated -- too late to add writes"); + + self.outstanding_writes++; + var committed = false; + return { + committed: function () { + if (committed) + throw new Error("committed called twice on the same write"); + committed = true; + self.outstanding_writes--; + self._maybeFire(); + } + }; + }, + + // Arm the fence. Once the fence is armed, and there are no more + // uncommitted writes, it will activate. + arm: function () { + var self = this; + if (self === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); + self.armed = true; + self._maybeFire(); + }, + + // Register a function to be called once before firing the fence. + // Callback function can add new writes to the fence, in which case + // it won't fire until those writes are done as well. + onBeforeFire: function (func) { + var self = this; + if (self.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + self.before_fire_callbacks.push(func); + }, + + // Register a function to be called when the fence fires. + onAllCommitted: function (func) { + var self = this; + if (self.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + self.completion_callbacks.push(func); + }, + + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait: function () { + var self = this; + var future = new Future; + self.onAllCommitted(function () { + future['return'](); + }); + self.arm(); + future.wait(); + }, + + _maybeFire: function () { + var self = this; + if (self.fired) + throw new Error("write fence already activated?"); + if (self.armed && !self.outstanding_writes) { + function invokeCallback (func) { + try { + func(self); + } catch (err) { + Meteor._debug("exception in write fence callback", err); + } + } + + self.outstanding_writes++; + while (self.before_fire_callbacks.length > 0) { + var callbacks = self.before_fire_callbacks; + self.before_fire_callbacks = []; + _.each(callbacks, invokeCallback); + } + self.outstanding_writes--; + + if (!self.outstanding_writes) { + self.fired = true; + var callbacks = self.completion_callbacks; + self.completion_callbacks = []; + _.each(callbacks, invokeCallback); + } + } + }, + + // Deactivate this fence so that adding more writes has no effect. + // The fence must have already fired. + retire: function () { + var self = this; + if (! self.fired) + throw new Error("Can't retire a fence that hasn't fired."); + self.retired = true; + } +}); diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 3b4853c39e..9cadd4d4ee 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1,7 +1,5 @@ DDPServer = {}; -var Fiber = Npm.require('fibers'); - // Publication strategies define how we handle data from published cursors at the collection level // This allows someone to: // - Choose a trade-off between client-server bandwidth and server memory usage @@ -330,9 +328,9 @@ var Session = function (server, version, socket, options) { self.send({ msg: 'connected', session: self.id }); // On initial connect, spin up all the universal publishers. - Fiber(function () { + Meteor._runAsync(function() { self.startUniversalSubs(); - }).run(); + }); if (version !== 'pre1' && options.heartbeatInterval !== 0) { // We no longer need the low level timeout because we have heartbeats. @@ -557,10 +555,10 @@ Object.assign(Session.prototype, { // Any message counts as receiving a pong, as it demonstrates that // the client is still alive. if (self.heartbeat) { - Fiber(function () { + Meteor._runAsync(function() { self.heartbeat.messageReceived(); - }).run(); - } + }); + }; if (self.version !== 'pre1' && msg_in.msg === 'ping') { if (self._respondToPings) @@ -584,7 +582,7 @@ Object.assign(Session.prototype, { return; } - Fiber(function () { + function runHandlers() { var blocked = true; var unblock = function () { @@ -604,7 +602,9 @@ Object.assign(Session.prototype, { else self.sendError('Bad request', msg); unblock(); // in case the handler didn't already do it - }).run(); + } + + Meteor._runAsync(runHandlers); }; processNext(); @@ -762,33 +762,32 @@ Object.assign(Session.prototype, { } } - const getCurrentMethodInvocationResult = () => { - const currentContext = DDP._CurrentMethodInvocation._setNewContextAndGetCurrent( - invocation + const getCurrentMethodInvocationResult = () => + DDP._CurrentPublicationInvocation.withValue( + invocation, + () => + maybeAuditArgumentChecks( + handler, + invocation, + msg.params, + "call to '" + msg.method + "'" + ), + { + name: 'getCurrentMethodInvocationResult', + keyName: 'getCurrentMethodInvocationResult', + } ); - try { - let result; - const resultOrThenable = maybeAuditArgumentChecks( - handler, - invocation, - msg.params, - "call to '" + msg.method + "'" - ); - const isThenable = - resultOrThenable && typeof resultOrThenable.then === 'function'; - if (isThenable) { - result = Promise.await(resultOrThenable); - } else { - result = resultOrThenable; + resolve( + DDPServer._CurrentWriteFence.withValue( + fence, + getCurrentMethodInvocationResult, + { + name: 'DDPServer._CurrentWriteFence', + keyName: '_CurrentWriteFence', } - return result; - } finally { - DDP._CurrentMethodInvocation._set(currentContext); - } - }; - - resolve(DDPServer._CurrentWriteFence.withValue(fence, getCurrentMethodInvocationResult)); + ) + ); }); function finish() { @@ -899,7 +898,7 @@ Object.assign(Session.prototype, { // subs. self._dontStartNewUniversalSubs = false; self.startUniversalSubs(); - }); + }, { name: '_setUserId' }); // Start sending messages again, beginning with the diff from the previous // state of the world to the current state. No yields are allowed during @@ -1120,16 +1119,19 @@ Object.assign(Subscription.prototype, { const self = this; let resultOrThenable = null; try { - resultOrThenable = DDP._CurrentPublicationInvocation.withValue(self, () => - maybeAuditArgumentChecks( - self._handler, - self, - EJSON.clone(self._params), - // It's OK that this would look weird for universal subscriptions, - // because they have no arguments so there can never be an - // audit-argument-checks failure. - "publisher '" + self._name + "'" - ) + resultOrThenable = DDP._CurrentPublicationInvocation.withValue( + self, + () => + maybeAuditArgumentChecks( + self._handler, + self, + EJSON.clone(self._params), + // It's OK that this would look weird for universal subscriptions, + // because they have no arguments so there can never be an + // audit-argument-checks failure. + "publisher '" + self._name + "'" + ), + { name: self._name } ); } catch (e) { self.error(e); @@ -1152,6 +1154,7 @@ Object.assign(Subscription.prototype, { } else { self._publishHandlerResult(resultOrThenable); } + }, _publishHandlerResult: function (res) { @@ -1177,15 +1180,21 @@ Object.assign(Subscription.prototype, { return c && c._publishCursor; }; if (isCursor(res)) { - try { - res._publishCursor(self); - } catch (e) { - self.error(e); - return; + if (Meteor._isFibersEnabled) { + try { + res._publishCursor(self); + } catch (e) { + self.error(e); + return; + } + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else { + res._publishCursor(self).then(() => { + self.ready(); + }).catch((e) => self.error(e)); } - // _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)) { @@ -1207,15 +1216,21 @@ Object.assign(Subscription.prototype, { collectionNames[collectionName] = true; }; - try { - _.each(res, function (cur) { - cur._publishCursor(self); - }); - } catch (e) { - self.error(e); - return; + if (Meteor._isFibersEnabled) { + try { + _.each(res, function (cur) { + cur._publishCursor(self); + }); + } catch (e) { + self.error(e); + return; + } + self.ready(); + } else { + Promise.all(res.map((c) => c._publishCursor(self))).then(() => { + self.ready(); + }).catch((e) => self.error(e)); } - self.ready(); } else if (res) { // Truthy values other than cursors or arrays are probably a // user mistake (possible returning a Mongo document via, say, @@ -1492,9 +1507,11 @@ Server = function (options = {}) { sendError("Already connected", msg); return; } - Fiber(function () { + + Meteor._runAsync(function() { self._handleConnect(socket, msg); - }).run(); + }) + return; } @@ -1511,9 +1528,9 @@ Server = function (options = {}) { socket.on('close', function () { if (socket._meteorSession) { - Fiber(function () { + Meteor._runAsync(function() { socket._meteorSession.close(); - }).run(); + }); } }); }); @@ -1691,9 +1708,9 @@ Object.assign(Server.prototype, { // self.sessions to change while we're running this loop. self.sessions.forEach(function (session) { if (!session._dontStartNewUniversalSubs) { - Fiber(function() { + Meteor._runAsync(function() { session._startSubscription(handler); - }).run(); + }); } }); } diff --git a/packages/ddp-server/livedata_server_async_tests.js b/packages/ddp-server/livedata_server_async_tests.js index d145aeee92..d66a28f091 100644 --- a/packages/ddp-server/livedata_server_async_tests.js +++ b/packages/ddp-server/livedata_server_async_tests.js @@ -23,6 +23,8 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); +// console.log('methodInvocation', methodInvocation); +// console.log('publicationInvocation', !!publicationInvocation); // Check the publish function's environment variables and context. if (callback) { callback.call(this, methodInvocation, publicationInvocation); @@ -33,6 +35,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.onStop(function() { var onStopMethodInvocation = DDP._CurrentMethodInvocation.get(); var onStopPublicationInvocation = DDP._CurrentPublicationInvocation.get(); + callback.call( this, onStopMethodInvocation, @@ -45,7 +48,7 @@ Meteor.publish('livedata_server_test_sub_context_async', async function( this.stop(); } else { this.ready(); - Meteor.call('livedata_server_test_setuserid', userId); + await Meteor.callAsync('livedata_server_test_setuserid', userId); } }); diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js index cde56b6196..101b1c2a93 100644 --- a/packages/ddp-server/livedata_server_tests.js +++ b/packages/ddp-server/livedata_server_tests.js @@ -87,8 +87,8 @@ Meteor.methods({ return this.connection && this.connection.id; }, - livedata_server_test_outer: function () { - return Meteor.call('livedata_server_test_inner'); + livedata_server_test_outer: async function () { + return await Meteor.callAsync('livedata_server_test_inner'); }, livedata_server_test_setuserid: function (userId) { @@ -108,12 +108,17 @@ Tinytest.addAsync( }); makeTestConnection( - test, - function (clientConn, serverConn) { - clientConn.call('livedata_server_test_inner'); - clientConn.disconnect(); - }, - onComplete + test, + function(clientConn, serverConn) { + clientConn + .callAsync('livedata_server_test_inner') + .then(() => clientConn.disconnect()) + .catch(e => { + onComplete(); + throw new Meteor.Error(e); + }); + }, + onComplete ); } ); @@ -125,10 +130,11 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - var res = clientConn.call('livedata_server_test_inner'); - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); + clientConn.callAsync('livedata_server_test_inner').then(res => { + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }); }, onComplete ); @@ -142,10 +148,11 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - var res = clientConn.call('livedata_server_test_outer'); - test.equal(res, serverConn.id); - clientConn.disconnect(); - onComplete(); + clientConn.callAsync('livedata_server_test_outer').then(res => { + test.equal(res, serverConn.id); + clientConn.disconnect(); + onComplete(); + }); }, onComplete ); @@ -163,16 +170,16 @@ Meteor.publish("livedata_server_test_sub", function (connectionId) { this.stop(); }); -Meteor.publish("livedata_server_test_sub_method", function (connectionId) { +Meteor.publish("livedata_server_test_sub_method", async function (connectionId) { var callback = onSubscription[connectionId]; if (callback) { - var id = Meteor.call('livedata_server_test_inner'); + var id = await Meteor.callAsync('livedata_server_test_inner'); callback(id); } this.stop(); }); -Meteor.publish("livedata_server_test_sub_context", function (connectionId, userId) { +Meteor.publish("livedata_server_test_sub_context", async function (connectionId, userId) { var callback = onSubscription[connectionId]; var methodInvocation = DDP._CurrentMethodInvocation.get(); var publicationInvocation = DDP._CurrentPublicationInvocation.get(); @@ -194,7 +201,7 @@ Meteor.publish("livedata_server_test_sub_context", function (connectionId, userI this.stop(); } else { this.ready(); - Meteor.call('livedata_server_test_setuserid', userId); + await Meteor.callAsync('livedata_server_test_setuserid', userId); } }); @@ -311,13 +318,13 @@ Tinytest.addAsync( ); Meteor.methods({ - testResolvedPromise(arg) { - const invocation1 = DDP._CurrentMethodInvocation.get(); + async testResolvedPromise(arg) { + const invocationRunningFromCallAsync1 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); return Promise.resolve(arg).then(result => { - const invocation2 = DDP._CurrentMethodInvocation.get(); - // This equality holds because Promise callbacks are bound to the - // dynamic environment where .then was called. - if (invocation1 !== invocation2) { + const invocationRunningFromCallAsync2 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); + // What matters here is that both invocations are coming from the same call, + // so both of them can be considered a simulation. + if (invocationRunningFromCallAsync1 !== invocationRunningFromCallAsync2) { throw new Meteor.Error("invocation mismatch"); } return result + " after waiting"; @@ -344,9 +351,10 @@ Meteor.methods({ Tinytest.addAsync( "livedata server - waiting for Promise", - (test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => { + (test, onComplete) => makeTestConnection(test, async (clientConn, serverConn) => { + const testResolvedPromiseResult = await clientConn.callAsync("testResolvedPromise", "clientConn.call"); test.equal( - clientConn.call("testResolvedPromise", "clientConn.call"), + testResolvedPromiseResult, "clientConn.call after waiting" ); diff --git a/packages/ddp-server/package.js b/packages/ddp-server/package.js index 4077518df0..da413690b0 100644 --- a/packages/ddp-server/package.js +++ b/packages/ddp-server/package.js @@ -10,11 +10,6 @@ Npm.depends({ }); Package.onUse(function (api) { - if (process.env.DISABLE_FIBERS) { - api.use('ddp-server-async', 'server'); - api.export('DDPServer', 'server'); - return; - } api.use(['check', 'random', 'ejson', 'underscore', 'retry', 'mongo-id', 'diff-sequence', 'ecmascript'], 'server'); diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index e9310c9f7f..d85f028ff8 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -1,18 +1,121 @@ -var Future = Npm.require('fibers/future'); - // A write fence collects a group of writes, and provides a callback // when all of the writes are fully committed and propagated (all // observers have been notified of the write and acknowledged it.) // -DDPServer._WriteFence = function () { - var self = this; +DDPServer._WriteFence = class { + constructor() { + this.armed = false; + this.fired = false; + this.retired = false; + this.outstanding_writes = 0; + this.before_fire_callbacks = []; + this.completion_callbacks = []; + } - self.armed = false; - self.fired = false; - self.retired = false; - self.outstanding_writes = 0; - self.before_fire_callbacks = []; - self.completion_callbacks = []; + // Start tracking a write, and return an object to represent it. The + // object has a single method, committed(). This method should be + // called when the write is fully committed and propagated. You can + // continue to add writes to the WriteFence up until it is triggered + // (calls its callbacks because all writes have committed.) + beginWrite() { + if (this.retired) + return { committed: function () {} }; + + if (this.fired) + throw new Error("fence has already activated -- too late to add writes"); + + this.outstanding_writes++; + let committed = false; + const _committedFn = async () => { + if (committed) + throw new Error("committed called twice on the same write"); + committed = true; + this.outstanding_writes--; + await this._maybeFire(); + }; + + const self = this; + return { + committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, + }; + } + + // Arm the fence. Once the fence is armed, and there are no more + // uncommitted writes, it will activate. + arm() { + if (this === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); + this.armed = true; + return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); + } + + // Register a function to be called once before firing the fence. + // Callback function can add new writes to the fence, in which case + // it won't fire until those writes are done as well. + onBeforeFire(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.before_fire_callbacks.push(func); + } + + // Register a function to be called when the fence fires. + onAllCommitted(func) { + if (this.fired) + throw new Error("fence has already activated -- too late to " + + "add a callback"); + this.completion_callbacks.push(func); + } + + _armAndWait() { + let resolver; + const returnValue = new Promise(r => resolver = r); + this.onAllCommitted(resolver); + this.arm(); + + return returnValue; + } + // Convenience function. Arms the fence, then blocks until it fires. + armAndWait() { + return Meteor._isFibersEnabled ? Promise.await(this._armAndWait()) : this._armAndWait(); + } + + async _maybeFire() { + if (this.fired) + throw new Error("write fence already activated?"); + if (this.armed && !this.outstanding_writes) { + const invokeCallback = async (func) => { + try { + await func(this); + } catch (err) { + Meteor._debug("exception in write fence callback:", err); + } + }; + + this.outstanding_writes++; + while (this.before_fire_callbacks.length > 0) { + const cb = this.before_fire_callbacks.shift(); + await invokeCallback(cb); + } + this.outstanding_writes--; + + if (!this.outstanding_writes) { + this.fired = true; + while (this.completion_callbacks.length > 0) { + const cb = this.completion_callbacks.shift(); + await invokeCallback(cb); + } + } + } + } + + // Deactivate this fence so that adding more writes has no effect. + // The fence must have already fired. + retire() { + if (!this.fired) + throw new Error("Can't retire a fence that hasn't fired."); + this.retired = true; + } }; // The current write fence. When there is a current write fence, code @@ -20,112 +123,3 @@ DDPServer._WriteFence = function () { // beginWrite(). // DDPServer._CurrentWriteFence = new Meteor.EnvironmentVariable; - -_.extend(DDPServer._WriteFence.prototype, { - // Start tracking a write, and return an object to represent it. The - // object has a single method, committed(). This method should be - // called when the write is fully committed and propagated. You can - // continue to add writes to the WriteFence up until it is triggered - // (calls its callbacks because all writes have committed.) - beginWrite: function () { - var self = this; - - if (self.retired) - return { committed: function () {} }; - - if (self.fired) - throw new Error("fence has already activated -- too late to add writes"); - - self.outstanding_writes++; - var committed = false; - return { - committed: function () { - if (committed) - throw new Error("committed called twice on the same write"); - committed = true; - self.outstanding_writes--; - self._maybeFire(); - } - }; - }, - - // Arm the fence. Once the fence is armed, and there are no more - // uncommitted writes, it will activate. - arm: function () { - var self = this; - if (self === DDPServer._CurrentWriteFence.get()) - throw Error("Can't arm the current fence"); - self.armed = true; - self._maybeFire(); - }, - - // Register a function to be called once before firing the fence. - // Callback function can add new writes to the fence, in which case - // it won't fire until those writes are done as well. - onBeforeFire: function (func) { - var self = this; - if (self.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - self.before_fire_callbacks.push(func); - }, - - // Register a function to be called when the fence fires. - onAllCommitted: function (func) { - var self = this; - if (self.fired) - throw new Error("fence has already activated -- too late to " + - "add a callback"); - self.completion_callbacks.push(func); - }, - - // Convenience function. Arms the fence, then blocks until it fires. - armAndWait: function () { - var self = this; - var future = new Future; - self.onAllCommitted(function () { - future['return'](); - }); - self.arm(); - future.wait(); - }, - - _maybeFire: function () { - var self = this; - if (self.fired) - throw new Error("write fence already activated?"); - if (self.armed && !self.outstanding_writes) { - function invokeCallback (func) { - try { - func(self); - } catch (err) { - Meteor._debug("exception in write fence callback", err); - } - } - - self.outstanding_writes++; - while (self.before_fire_callbacks.length > 0) { - var callbacks = self.before_fire_callbacks; - self.before_fire_callbacks = []; - _.each(callbacks, invokeCallback); - } - self.outstanding_writes--; - - if (!self.outstanding_writes) { - self.fired = true; - var callbacks = self.completion_callbacks; - self.completion_callbacks = []; - _.each(callbacks, invokeCallback); - } - } - }, - - // Deactivate this fence so that adding more writes has no effect. - // The fence must have already fired. - retire: function () { - var self = this; - if (! self.fired) - throw new Error("Can't retire a fence that hasn't fired."); - self.retired = true; - } -}); From 7c4107c1f0ab0b336f755f42865d9dba0778bfed Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 5 Dec 2022 10:15:05 -0400 Subject: [PATCH 0093/1965] Removing Fibers import --- packages/ddp-server/livedata_server_async_tests.js | 1 - packages/ddp-server/livedata_server_tests.js | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/ddp-server/livedata_server_async_tests.js b/packages/ddp-server/livedata_server_async_tests.js index d66a28f091..c2ca9fcdee 100644 --- a/packages/ddp-server/livedata_server_async_tests.js +++ b/packages/ddp-server/livedata_server_async_tests.js @@ -1,4 +1,3 @@ -var Fiber = Npm.require('fibers'); function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js index 101b1c2a93..aa4cc6f845 100644 --- a/packages/ddp-server/livedata_server_tests.js +++ b/packages/ddp-server/livedata_server_tests.js @@ -1,6 +1,3 @@ -var Fiber = Npm.require('fibers'); - - Tinytest.addAsync( "livedata server - connectionHandle.onClose()", function (test, onComplete) { From 4c68d5342c2b1cd8c4f9e0918a51ad6c55fcb653 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Mon, 5 Dec 2022 17:23:54 -0300 Subject: [PATCH 0094/1965] Remove Fibers for Meteor Tools: - Adapting the import-scanner to correctly check the files and create the dependency graph. - Still having errors. --- tools/isobuild/bundler.js | 36 ++++---- tools/isobuild/compiler-plugin.js | 139 ++++++++++++++---------------- tools/isobuild/import-scanner.ts | 125 ++++++++++++++------------- tools/isobuild/isopack-cache.js | 4 +- tools/isobuild/meteor-npm.js | 8 +- tools/tool-env/isopackets.js | 4 +- 6 files changed, 157 insertions(+), 159 deletions(-) diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 1cc115ea3f..05157fea70 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -1134,8 +1134,8 @@ class Target { const file = new File({ info: 'resource ' + resource.servePath, arch: target.arch, - data: resource.data, - hash: resource.hash, + data: await resource.data, + hash: await resource.hash, sourcePath }); @@ -1164,7 +1164,7 @@ class Target { const isWeb = archinfo.matches(this.arch, 'web'); const isOs = archinfo.matches(this.arch, 'os'); - const jsOutputFilesMap = compilerPluginModule.PackageSourceBatch + const jsOutputFilesMap = await compilerPluginModule.PackageSourceBatch .computeJsOutputFilesMap(sourceBatches); sourceBatches.forEach(batch => { @@ -1219,18 +1219,18 @@ class Target { // First, find all the assets, so that we can associate them with each js // resource (for os unibuilds). const unibuildAssets = {}; - resources.forEach((resource) => { + for (const resource of resources) { if (resource.type !== 'asset') { - return; + continue; } const fileOptions = { info: 'unbuild ' + resource, arch: this.arch, - data: resource.data, + data: await resource.data, cacheable: false, - hash: resource.hash, - skipSri: !!resource.hash + hash: await resource.hash, + skipSri: !!await resource.hash }; const file = new File(fileOptions); @@ -1259,20 +1259,20 @@ class Target { this.asset.push(f); }); - }); + } // Now look for the other kinds of resources. - resources.forEach((resource) => { + for (const resource of resources) { if (resource.type === 'asset') { // already handled - return; + continue; } if (resource.type !== "js" && resource.lazy) { // Only files that compile to JS can be imported, so any other // files should be ignored here, if lazy. - return; + continue; } if (['js', 'css'].includes(resource.type)) { @@ -1283,7 +1283,7 @@ class Target { // XXX XXX can't we easily do that in the css handler in // meteor.js? - return; + continue; } let sourcePath; @@ -1293,8 +1293,8 @@ class Target { const f = new File({ info: 'resource ' + resource.servePath, arch: this.arch, - data: resource.data, - hash: resource.hash, + data: await resource.data, + hash: await resource.hash, cacheable: false, replaceable: resource.type === 'js' && sourceBatch.hmrAvailable, sourcePath @@ -1330,7 +1330,7 @@ class Target { } this[resource.type].push(f); - return; + continue; } if (['head', 'body'].includes(resource.type)) { @@ -1338,11 +1338,11 @@ class Target { throw new Error('HTML segments can only go to the client'); } this[resource.type].push(resource.data); - return; + continue; } throw new Error('Unknown type ' + resource.type); - }); + } this.js.forEach(file => { if (file.targetPath === "packages/dynamic-import.js") { diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index f97c4d4e8d..154ab51de1 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -8,23 +8,16 @@ var linker = require('./linker.js'); var _ = require('underscore'); var Profile = require('../tool-env/profile').Profile; import assert from "assert"; -import { - WatchSet, - sha1, - readAndWatchFileWithHash, -} from '../fs/watch'; +import {readAndWatchFileWithHash, sha1, WatchSet,} from '../fs/watch'; import LRU from 'lru-cache'; import {sourceMapLength} from '../utils/utils.js'; import {Console} from '../console/console.js'; import ImportScanner from './import-scanner'; import {cssToCommonJS} from "./css-modules"; import Resolver from "./resolver"; -import { - optimisticStatOrNull, - optimisticHashOrNull, -} from "../fs/optimistic"; +import {optimisticHashOrNull, optimisticStatOrNull,} from "../fs/optimistic"; -import { isTestFilePath } from './test-files.js'; +import {isTestFilePath} from './test-files.js'; const hasOwn = Object.prototype.hasOwnProperty; @@ -579,16 +572,16 @@ class ResourceSlot { self.packageSourceBatch = packageSourceBatch; if (self.inputResource.type === "source") { - if (sourceProcessor) { + if (self.sourceProcessor) { // If we have a sourceProcessor, it will handle the adding of the // final processed JavaScript. } else if (self.inputResource.extension === "js") { self._addDirectlyToJsOutputResources(); } } else { - if (sourceProcessor) { + if (self.sourceProcessor) { throw Error("sourceProcessor for non-source? " + - JSON.stringify(unibuildResourceInfo)); + JSON.stringify(self.inputResource)); } // Any resource that isn't handled by compiler plugins just gets passed // through. @@ -902,45 +895,45 @@ class OutputResource { }); } - finalize() { + async finalize() { if (this._finalizerPromise) { - this._finalizerPromise.await(); + await this._finalizerPromise; } else if (this._lazyFinalizer) { const finalize = this._lazyFinalizer; this._lazyFinalizer = null; - (this._finalizerPromise = - // It's important to initialize this._finalizerPromise to the new - // Promise before calling finalize(), so there's no possibility of - // finalize() triggering code that reenters this function before we - // have the final version of this._finalizerPromise. If this code - // used `new Promise(resolve => resolve(finalize()))` instead of - // `Promise.resolve().then(finalize)`, the finalize() call would - // begin before this._finalizerPromise was fully initialized. - Promise.resolve().then(finalize).then(result => { - if (result) { - Object.assign(this._initialOptions, result); - } else if (this._errors.length === 0) { - // In case the finalize() call failed without reporting any - // errors, create at least one generic error that can be - // reported when reportPendingErrors is called. - const error = new Error("lazyFinalizer failed"); - error.info = { resource: this, finalize } - this._errors.push(error); - } - // The this._finalizerPromise object only survives for the - // duration of the initial finalization. - this._finalizerPromise = null; - })).await(); + + // It's important to initialize this._finalizerPromise to the new + // Promise before calling finalize(), so there's no possibility of + // finalize() triggering code that reenters this function before we + // have the final version of this._finalizerPromise. If this code + // used `new Promise(resolve => resolve(finalize()))` instead of + // `Promise.resolve().then(finalize)`, the finalize() call would + // begin before this._finalizerPromise was fully initialized. + this._finalizerPromise = await (Promise.resolve().then(finalize).then(result => { + if (result) { + Object.assign(this._initialOptions, result); + } else if (this._errors.length === 0) { + // In case the finalize() call failed without reporting any + // errors, create at least one generic error that can be + // reported when reportPendingErrors is called. + const error = new Error("lazyFinalizer failed"); + error.info = { resource: this, finalize } + this._errors.push(error); + } + // The this._finalizerPromise object only survives for the + // duration of the initial finalization. + this._finalizerPromise = null; + })); } } - hasPendingErrors() { - this.finalize(); + async hasPendingErrors() { + await this.finalize(); return this._errors.length > 0; } - reportPendingErrors() { - if (this.hasPendingErrors()) { + async reportPendingErrors() { + if (await this.hasPendingErrors()) { const firstError = this._errors[0]; buildmessage.error( firstError.message, @@ -961,12 +954,12 @@ class OutputResource { // Method for getting properties that may be computed lazily, or that // require some one-time post-processing. - _get(name) { + async _get(name) { if (hasOwn.call(this, name)) { return this[name]; } - if (this.hasPendingErrors()) { + if (await this.hasPendingErrors()) { // If you're considering using this resource, you should call // hasPendingErrors or reportPendingErrors to find out if it's safe // to access computed properties like .data, .hash, or .sourceMap. @@ -990,7 +983,7 @@ class OutputResource { hashes.push(this._inputHash); } - hashes.push(sha1(this._get("data"))); + hashes.push(sha1(await this._get("data"))); return this._set("hash", sha1(...hashes)); } @@ -1210,7 +1203,7 @@ export class PackageSourceBatch { } } - return new ResourceSlot(resource, sourceProcessor, this); + return new ResourceSlot(resource, sourceProcessor, this); } addImportExtension(extension) { @@ -1277,7 +1270,7 @@ export class PackageSourceBatch { } // Returns a map from package names to arrays of JS output files. - static computeJsOutputFilesMap(sourceBatches) { + static async computeJsOutputFilesMap(sourceBatches) { const map = new Map; sourceBatches.forEach(batch => { @@ -1349,14 +1342,14 @@ export class PackageSourceBatch { const allRelocatedModules = Object.create(null); const scannerMap = new Map; - sourceBatches.forEach(batch => { + for (const batch of sourceBatches) { const name = batch.unibuild.pkg.name || null; const isApp = ! name; if (! batch.useMeteorInstall && ! isApp) { // If this batch represents a package that does not use the module // system, then we don't need to scan its dependencies. - return; + continue; } const nodeModulesPaths = []; @@ -1381,23 +1374,23 @@ export class PackageSourceBatch { cacheDir: batch.scannerCacheDir, }); - scanner.addInputFiles(entry.files); + await scanner.addInputFiles(entry.files); if (batch.useMeteorInstall) { - scanner.scanImports(); - ImportScanner.mergeMissing( - allMissingModules, - scanner.allMissingModules + await scanner.scanImports(); + await ImportScanner.mergeMissing( + allMissingModules, + scanner.allMissingModules ); } scannerMap.set(name, scanner); - }); + } - function handleMissing(missingModules) { + async function handleMissing(missingModules) { const missingMap = new Map; - _.each(missingModules, (importInfoList, id) => { + for (let [id, importInfoList] of Object.entries(missingModules)) { const parts = id.split("/"); let name = null; @@ -1432,27 +1425,27 @@ export class PackageSourceBatch { missingMap.set(name, Object.create(null)); } - ImportScanner.mergeMissing( - missingMap.get(name), - { [id]: importInfoList } + await ImportScanner.mergeMissing( + missingMap.get(name), + { [id]: importInfoList } ); - }); + } const nextMissingModules = Object.create(null); - missingMap.forEach((missing, name) => { + for (const [name, missing] of missingMap) { const { newlyAdded, newlyMissing } = - scannerMap.get(name).scanMissingModules(missing); + await scannerMap.get(name).scanMissingModules(missing); ImportScanner.mergeMissing(allRelocatedModules, newlyAdded); ImportScanner.mergeMissing(nextMissingModules, newlyMissing); - }); + } if (! _.isEmpty(nextMissingModules)) { - handleMissing(nextMissingModules); + await handleMissing(nextMissingModules); } } - handleMissing(allMissingModules); + await handleMissing(allMissingModules); Object.keys(allRelocatedModules).forEach(id => { delete allMissingModules[id]; @@ -1702,19 +1695,21 @@ export class PackageSourceBatch { const fileHashes = []; const cacheKeyPrefix = sha1(JSON.stringify({ linkerOptions, - files: jsResources.map((inputFile) => { - fileHashes.push(inputFile.hash); - return { + files: await jsResources.reduce(async (acc, inputFile) => { + const resolvedAcc = await acc; + + fileHashes.push(await inputFile.hash); + return [...resolvedAcc, { meteorInstallOptions: inputFile.meteorInstallOptions, absModuleId: inputFile.absModuleId, - sourceMap: !! inputFile.sourceMap, + sourceMap: !! await inputFile.sourceMap, mainModule: inputFile.mainModule, imported: inputFile.imported, alias: inputFile.alias, lazy: inputFile.lazy, bare: inputFile.bare, - }; - }) + }]; + }, Promise.resolve([])) })); const cacheKeySuffix = sha1(JSON.stringify({ LINKER_CACHE_SALT, diff --git a/tools/isobuild/import-scanner.ts b/tools/isobuild/import-scanner.ts index b45767b7b9..80c306f7e6 100644 --- a/tools/isobuild/import-scanner.ts +++ b/tools/isobuild/import-scanner.ts @@ -132,11 +132,11 @@ class DefaultHandlers { call( method: "js" | "mjs" | "json" | "css", file: RawFile, - ): string { + ): string | Promise { return this[method](file); } - js(file: RawFile) { + async js(file: RawFile) { const parts = file.absPath.split("/"); const nmi = parts.lastIndexOf("node_modules"); if (nmi >= 0) { @@ -156,7 +156,7 @@ class DefaultHandlers { return reifyCompileWithCache( file.dataString, - file.hash, + await file.hash, this.bundleArch, cacheFileName ) @@ -474,9 +474,9 @@ export default class ImportScanner { return null; } - addInputFiles(files: File[]) { - files.forEach(file => { - this.checkSourceAndTargetPaths(file); + async addInputFiles(files: File[]) { + for (const file of files) { + await this.checkSourceAndTargetPaths(file); // Note: this absolute path may not necessarily exist on the file // system, but any import statements or require calls in file.data @@ -489,16 +489,16 @@ export default class ImportScanner { file.imported = false; file.absModuleId = file.absModuleId || - this.getAbsModuleId(file.absPath); + this.getAbsModuleId(file.absPath); if (! this.addFile(file.absPath, file)) { // Collisions can happen if a compiler plugin calls addJavaScript // multiple times with the same sourcePath. #6422 - this.combineFiles(this.getFile(file.absPath)!, file); + await this.combineFiles(this.getFile(file.absPath)!, file); } this.addFileByRealPath(file, this.realPath(file.absPath)); - }); + } return this; } @@ -517,16 +517,16 @@ export default class ImportScanner { return file; } - private getInfoByRealPath(realPath: string): RawFile | null { + private async getInfoByRealPath(realPath: string): Promise { const files = this.realPathToFiles[realPath]; if (files && files.length > 0) { const firstFile = files[0]; - const dataString = this.getDataString(firstFile); + const dataString = await this.getDataString(firstFile); return { absPath: realPath, - data: firstFile.data, + data: await firstFile.data, dataString: dataString, - hash: firstFile.hash, + hash: await firstFile.hash, }; } return null; @@ -578,7 +578,7 @@ export default class ImportScanner { // Make sure file.sourcePath is defined, and handle the possibility that // file.targetPath differs from file.sourcePath. - private checkSourceAndTargetPaths(file: File) { + private async checkSourceAndTargetPaths(file: File) { file.sourcePath = this.getSourcePath(file); if (! isString(file.targetPath)) { @@ -647,13 +647,13 @@ export default class ImportScanner { // plugin calling inputFile.addJavaScript multiple times for the // same source file (see discussion in #9176), with different target // paths, code, laziness, etc. - sourceFile.dataString = this.getDataString(sourceFile) + + sourceFile.dataString = await this.getDataString(sourceFile) + // The + in "*+" indicates that the "default" property should be // included as well as any other re-exported properties. "module.link(" + JSON.stringify(relativeId) + ', { "*": "*+" });\n'; sourceFile.data = Buffer.from(sourceFile.dataString, "utf8"); - sourceFile.hash = sha1(sourceFile.data); + sourceFile.hash = sha1(await sourceFile.data); sourceFile.deps = sourceFile.deps || Object.create(null); sourceFile.deps![relativeId] = { dynamic: false, @@ -666,7 +666,7 @@ export default class ImportScanner { // Concatenate the contents of oldFile and newFile, combining source // maps and updating all other properties appropriately. Once this // combination is done, oldFile should be kept and newFile discarded. - private combineFiles(oldFile: File, newFile: File) { + private async combineFiles(oldFile: File, newFile: File) { const scanner = this; function checkProperty(name: "lazy" | "bare") { @@ -697,17 +697,17 @@ export default class ImportScanner { checkProperty("lazy"); checkProperty("bare"); - function getChunk(file: File) { + async function getChunk(file: File) { if (file.sourceMap) { - const consumer = Promise.await(new SourceMapConsumer(file.sourceMap)); + const consumer = await new SourceMapConsumer(file.sourceMap); const node = SourceNode.fromStringWithSourceMap( - scanner.getDataString(file), + await scanner.getDataString(file), consumer ); consumer.destroy(); return node; } else { - return scanner.getDataString(file); + return await scanner.getDataString(file); } } @@ -715,16 +715,16 @@ export default class ImportScanner { code: combinedDataString, map: combinedSourceMap, } = new SourceNode(null, null, null, [ - getChunk(oldFile), + await getChunk(oldFile), "\n\n", - getChunk(newFile) + await getChunk(newFile) ]).toStringWithSourceMap({ file: oldFile.servePath || newFile.servePath }); oldFile.dataString = combinedDataString; oldFile.data = Buffer.from(oldFile.dataString, "utf8"); - oldFile.hash = sha1(oldFile.data); + oldFile.hash = sha1(await oldFile.data); alignImportedStatuses(oldFile, newFile); @@ -734,17 +734,17 @@ export default class ImportScanner { } } - scanImports() { - this.outputFiles.forEach(file => { - if (! file.lazy) { - this.scanFile(file); + async scanImports() { + for (const file of this.outputFiles) { + if (!file.lazy) { + await this.scanFile(file); } - }); + } return this; } - scanMissingModules(missingModules: MissingMap) { + async scanMissingModules(missingModules: MissingMap) { assert.ok(missingModules); assert.ok(typeof missingModules === "object"); assert.ok(! Array.isArray(missingModules)); @@ -756,7 +756,7 @@ export default class ImportScanner { const previousAllMissingModules = this.allMissingModules; this.allMissingModules = newlyMissing; - Object.keys(missingModules).forEach(id => { + for (const id of Object.keys(missingModules)) { let staticImportInfo: ImportInfo | null = null; let dynamicImportInfo: ImportInfo | null = null; @@ -791,7 +791,7 @@ export default class ImportScanner { } if (staticImportInfo) { - this.scanFile({ + await this.scanFile({ ...fakeStub, // By specifying the .deps property of this fake file ahead of // time, we can avoid calling findImportedModuleIdentifiers in @@ -802,12 +802,12 @@ export default class ImportScanner { } if (dynamicImportInfo) { - this.scanFile({ + await this.scanFile({ ...fakeStub, deps: { [id]: dynamicImportInfo }, }, true); // forDynamicImport } - }); + } this.allMissingModules = previousAllMissingModules; @@ -973,16 +973,17 @@ export default class ImportScanner { return pathNormalize(pathJoin(".", sourcePath)); } - private findImportedModuleIdentifiers( + private async findImportedModuleIdentifiers( file: File, - ): Record { - if (IMPORT_SCANNER_CACHE.has(file.hash)) { - return IMPORT_SCANNER_CACHE.get(file.hash); + ): Promise> { + const fileHash = await file.hash; + if (IMPORT_SCANNER_CACHE.has(fileHash)) { + return IMPORT_SCANNER_CACHE.get(fileHash); } const result = findImportedModuleIdentifiers( - this.getDataString(file), - file.hash, + await this.getDataString(file), + fileHash, ); // there should always be file.hash, but better safe than sorry @@ -1062,7 +1063,7 @@ export default class ImportScanner { return relativeId; } - private scanFile(file: File, forDynamicImport = false) { + private async scanFile(file: File, forDynamicImport = false) { if (file.imported === "static") { // If we've already scanned this file non-dynamically, then we don't // need to scan it again. @@ -1089,7 +1090,7 @@ export default class ImportScanner { } try { - file.deps = file.deps || this.findImportedModuleIdentifiers(file); + file.deps = file.deps || await this.findImportedModuleIdentifiers(file); } catch (e: any) { if (e.$ParseError) { (buildmessage as any).error(e.message, { @@ -1102,19 +1103,19 @@ export default class ImportScanner { throw e; } - each(file.deps, (info: ImportInfo, id: string) => { + for (const [id, info] of Object.entries(file.deps)) { // Asynchronous module fetching only really makes sense in the // browser (even though it works equally well on the server), so // it's better if forDynamicImport never becomes true on the server. const dynamic = this.isWebBrowser() && - (forDynamicImport || - info.parentWasDynamic || - info.dynamic); + (forDynamicImport || + info.parentWasDynamic || + info.dynamic); const resolved = this.resolve(file, id, dynamic); const absImportedPath = resolved && resolved !== "missing" && resolved.path; if (! absImportedPath) { - return; + continue; } let depFile = this.getFile(absImportedPath); @@ -1139,22 +1140,22 @@ export default class ImportScanner { // If depFile has already been scanned, this._scanFile will return // immediately thanks to the depFile.imported-checking logic at // the top of the method. - this.scanFile(depFile, dynamic); + await this.scanFile(depFile, dynamic); - return; + continue; } - depFile = this.readDepFile(absImportedPath); + depFile = await this.readDepFile(absImportedPath); if (! depFile) { - return; + continue; } // Append this file to the output array and record its index. this.addFile(absImportedPath, depFile); // Recursively scan the module's imported dependencies. - this.scanFile(depFile, dynamic); - }); + await this.scanFile(depFile, dynamic); + } } isWeb() { @@ -1166,19 +1167,20 @@ export default class ImportScanner { return archMatches(this.bundleArch, "web.browser"); } - private getDataString(file: File) { - if (typeof file.dataString === "string") { - return file.dataString; + private async getDataString(file: File) { + const fileData = await file.data; + if (typeof fileData === "string") { + return fileData; } - const rawDataString = file.data.toString("utf8"); + const rawDataString = fileData.toString("utf8"); if (file.type === "js") { // Avoid compiling .js file with Reify when all we want is a string // version of file.data. file.dataString = stripHashBang(rawDataString); } else { file.dataString = rawDataString; - file.dataString = this.defaultHandlers.call(file.type as any, file); + file.dataString = await this.defaultHandlers.call(file.type as any, file); } if (! (file.data instanceof Buffer) || @@ -1231,6 +1233,9 @@ export default class ImportScanner { } }); + console.log(info.data) + console.log(info.hash) + console.log(info.dataString) info.dataString = jsonDataToCommonJS(jsonData); info.data = Buffer.from(info.dataString, "utf8"); info.hash = sha1(info.data); @@ -1281,7 +1286,7 @@ export default class ImportScanner { return info; } - private readDepFile(absPath: string): File | null { + private async readDepFile(absPath: string): Promise { const absModuleId = this.getAbsModuleId(absPath); if (! absModuleId) { // The given path cannot be installed on this architecture. @@ -1290,7 +1295,7 @@ export default class ImportScanner { const realPath = this.realPath(absPath); - let rawFile = this.getInfoByRealPath(realPath); + let rawFile = await this.getInfoByRealPath(realPath); if (rawFile) { // If we already have a file with the same real path, use its data // rather than reading the file again, or generating a stub. This diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index e7c1d75016..1c4bf8d45f 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -311,7 +311,7 @@ export class IsopackCache { buildmessage.assertInCapture(); await buildmessage.enterJob("building package " + name, async function () { var isopack; - if (previousIsopack && self._checkUpToDatePreloaded(previousIsopack)) { + if (previousIsopack && await self._checkUpToDatePreloaded(previousIsopack)) { isopack = previousIsopack; // We don't need to call self._lintLocalPackage here, because // lintingMessages is saved on the isopack. @@ -324,7 +324,7 @@ export class IsopackCache { // Do we have an up-to-date package on disk? var isopackBuildInfoJson = self.cacheDir && files.readJSONOrNull( self._isopackBuildInfoPath(name)); - var upToDate = self._checkUpToDate(isopackBuildInfoJson); + var upToDate = await self._checkUpToDate(isopackBuildInfoJson); if (upToDate) { // Reuse existing plugin cache dir diff --git a/tools/isobuild/meteor-npm.js b/tools/isobuild/meteor-npm.js index e6f292e0ac..c1e3b3d702 100644 --- a/tools/isobuild/meteor-npm.js +++ b/tools/isobuild/meteor-npm.js @@ -76,7 +76,7 @@ meteorNpm.updateDependencies = async function (packageName, // instances are trying to make this update in parallel, so we rename the // directory to something before doing the rm -rf. try { - files.rename(packageNpmDir, newPackageNpmDir); + await files.rename(packageNpmDir, newPackageNpmDir); } catch (e) { if (e.code !== 'ENOENT') { throw e; @@ -627,7 +627,7 @@ var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir, } if (oldNodeVersion !== currentNodeCompatibilityVersion()) { - files.rm_recursive(nodeModulesDir); + await files.rm_recursive(nodeModulesDir); } } @@ -879,7 +879,7 @@ Profile("meteorNpm.runNpmCommand", async function (args, cwd) { args.join(' ') + ' ...\n'); } - const env = getEnv({devBundle: devBundleDir}); + const env = await getEnv({devBundle: devBundleDir}); const opts = { env: env, @@ -893,7 +893,7 @@ Profile("meteorNpm.runNpmCommand", async function (args, cwd) { // Make sure we don't honor any user-provided configuration files. env.npm_config_userconfig = npmUserConfigFile; - return await new Promise(function (resolve) { + return new Promise(function (resolve) { require('child_process').execFile( commandToRun, args, opts, function (err, stdout, stderr) { if (meteorNpm._printNpmCalls) { diff --git a/tools/tool-env/isopackets.js b/tools/tool-env/isopackets.js index 1b4d22ffec..613634b209 100644 --- a/tools/tool-env/isopackets.js +++ b/tools/tool-env/isopackets.js @@ -125,9 +125,7 @@ export async function loadIsopackage(packageName, isopacketName = "combined") { } const isopacket = await load(); - console.log("Isopacket: ", isopacket); - console.log("PackageName: ", packageName); - console.log("***********"); + // TODO -> Check here. if (!_.has(isopacket, packageName)) { throw new Error("Unknown isopacket dependency: " + packageName); } From 0e10783a1a1d3aecadbf882fcc591a503547b905 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 6 Dec 2022 18:57:33 -0300 Subject: [PATCH 0095/1965] Remove Fibers for Meteor Tools: - As the getters are asynchronous for some file operations (i.e file.data, file.hash and file.sourceMap), we need to set those values in a different way. --- tools/isobuild/compiler-plugin.js | 47 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 154ab51de1..eb2abc8a63 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -909,7 +909,7 @@ class OutputResource { // used `new Promise(resolve => resolve(finalize()))` instead of // `Promise.resolve().then(finalize)`, the finalize() call would // begin before this._finalizerPromise was fully initialized. - this._finalizerPromise = await (Promise.resolve().then(finalize).then(result => { + (this._finalizerPromise = Promise.resolve().then(finalize).then(result => { if (result) { Object.assign(this._initialOptions, result); } else if (this._errors.length === 0) { @@ -917,13 +917,15 @@ class OutputResource { // errors, create at least one generic error that can be // reported when reportPendingErrors is called. const error = new Error("lazyFinalizer failed"); - error.info = { resource: this, finalize } + error.info = { resource: this, finalize }; this._errors.push(error); } // The this._finalizerPromise object only survives for the // duration of the initial finalization. this._finalizerPromise = null; })); + + await this._finalizerPromise; } } @@ -1298,40 +1300,41 @@ export class PackageSourceBatch { // Append install() calls to the install-packages.js file in the // modules package for every Meteor package name used. - map.get("modules").files.some(file => { + for (const file of map.get("modules").files) { if (file.sourcePath !== "install-packages.js") { - return false; + continue; } const meteorPackageInstalls = []; - - map.forEach((info, name) => { - if (! name) return; - + for (const [name, info] of map) { + if (!name) continue; const mainModule = info.mainModule && - `meteor/${name}/${info.mainModule.targetPath}`; + `meteor/${name}/${info.mainModule.targetPath}`; meteorPackageInstalls.push( - "install(" + JSON.stringify(name) + + "install(" + JSON.stringify(name) + (mainModule ? ", " + JSON.stringify(mainModule) : '') + - ");\n" + ");\n" ); - }); - - if (meteorPackageInstalls.length === 0) { - return false; } - file.data = Buffer.from( - file.data.toString("utf8") + "\n" + + if (meteorPackageInstalls.length === 0) { + continue; + } + + const fileData = await file.data; + const bufferData = Buffer.from( + fileData.toString("utf8") + "\n" + meteorPackageInstalls.join(""), - "utf8" + "utf8" ); + const fileHash = sha1(fileData); - file.hash = sha1(file.data); - - return true; - }); + // The getter's from file (file.data and file.hash) are async, unfortunately. + // That's why we need the Object.assign here. + Object.assign(file, { data: bufferData, hash: fileHash }); + break; + } // Map from module identifiers that previously could not be imported // to lists of info objects describing the failed imports. From a30809863322cd71cb89d8f37b0fd17ef236da3a Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 6 Dec 2022 18:16:35 -0400 Subject: [PATCH 0096/1965] fixing _scheduleRun: It was adding an infity amount of tasks. fixing tests --- .../ddp-client/common/livedata_connection.js | 31 -- .../test/livedata_connection_tests.js | 518 +++++++++--------- packages/ddp-client/test/stub_stream.js | 16 +- packages/meteor/async_helpers.js | 26 +- packages/meteor/dynamics_nodejs.js | 6 +- 5 files changed, 276 insertions(+), 321 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index 1855a0e0f5..03b42c2a5a 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -15,13 +15,6 @@ import { last, } from "meteor/ddp-common/utils.js"; -let Fiber; -let Future; -if (Meteor.isServer) { - Fiber = Npm.require('fibers'); - Future = Npm.require('fibers/future'); -} - class MongoIDMap extends IdMap { constructor() { super(MongoID.idStringify, MongoID.idParse); @@ -494,30 +487,6 @@ export class Connection { return handle; } - // options: - // - onLateError {Function(error)} called if an error was received after the ready event. - // (errors received before ready cause an error to be thrown) - _subscribeAndWait(name, args, options) { - const self = this; - const f = new Future(); - let ready = false; - args = args || []; - args.push({ - onReady() { - ready = true; - f['return'](); - }, - onError(e) { - if (!ready) f['throw'](e); - else options && options.onLateError && options.onLateError(e); - } - }); - - const handle = self.subscribe.apply(self, [name].concat(args)); - f.wait(); - return handle; - } - methods(methods) { Object.entries(methods).forEach(([name, func]) => { if (typeof func !== 'function') { diff --git a/packages/ddp-client/test/livedata_connection_tests.js b/packages/ddp-client/test/livedata_connection_tests.js index 32e014ccbf..3b9d4ce904 100644 --- a/packages/ddp-client/test/livedata_connection_tests.js +++ b/packages/ddp-client/test/livedata_connection_tests.js @@ -63,27 +63,27 @@ const testGotMessage = function(test, stream, expected) { return got; }; -const startAndConnect = function(test, stream) { - stream.reset(); // initial connection start. +const startAndConnect = async function(test, stream) { + await stream.reset(); // initial connection start. testGotMessage(test, stream, makeConnectMessage()); test.length(stream.sent, 0); - stream.receive({ msg: 'connected', session: SESSION_ID }); + await stream.receive({ msg: 'connected', session: SESSION_ID }); test.length(stream.sent, 0); }; const SESSION_ID = '17'; -Tinytest.add('livedata stub - receive data', function(test) { +Tinytest.addAsync('livedata stub - receive data', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // data comes in for unknown collection. const coll_name = Random.id(); - stream.receive({ + await stream.receive({ msg: 'added', collection: coll_name, id: '1234', @@ -101,7 +101,7 @@ Tinytest.add('livedata stub - receive data', function(test) { test.equal(coll.find({}).fetch(), [{ _id: '1234', a: 1 }]); // second message. applied directly to the db. - stream.receive({ + await stream.receive({ msg: 'changed', collection: coll_name, id: '1234', @@ -111,7 +111,7 @@ Tinytest.add('livedata stub - receive data', function(test) { test.isUndefined(conn._updatesForUnknownStores[coll_name]); }); -Tinytest.add('livedata stub - buffering data', function(test) { +Tinytest.addAsync('livedata stub - buffering data', async function(test) { // Install special setTimeout that allows tick-by-tick control in tests using sinonjs 'lolex' // This needs to be before the connection is instantiated. const clock = FakeTimers.install(); @@ -123,15 +123,15 @@ Tinytest.add('livedata stub - buffering data', function(test) { bufferedWritesMaxAge: 40 }); - startAndConnect(test, stream); + await startAndConnect(test, stream); const coll_name = Random.id(); const coll = new Mongo.Collection(coll_name, conn); const testDocCount = count => test.equal(coll.find({}).count(), count); - const addDoc = () => { - stream.receive({ + const addDoc = async () => { + await stream.receive({ msg: 'added', collection: coll_name, id: Random.id(), @@ -141,31 +141,31 @@ Tinytest.add('livedata stub - buffering data', function(test) { // Starting at 0 ticks. At this point we haven't advanced the fake clock at all. - addDoc(); // 1st Doc + await addDoc(); // 1st Doc testDocCount(0); // No doc been recognized yet because it's buffered, waiting for more. tick(6); // 6 total ticks testDocCount(0); // Ensure that the doc still hasn't shown up, despite the clock moving forward. tick(4); // 10 total ticks, 1st buffer interval testDocCount(1); // No other docs have arrived, so we 'see' the 1st doc. - addDoc(); // 2nd doc + await addDoc(); // 2nd doc tick(1); // 11 total ticks (1 since last flush) testDocCount(1); // Again, second doc hasn't arrived because we're waiting for more... tick(9); // 20 total ticks (10 ticks since last flush & the 2nd 10-tick interval) testDocCount(2); // Now we're here and got the second document. // Add several docs, frequently enough that we buffer multiple times before the next flush. - addDoc(); // 3 docs + await addDoc(); // 3 docs tick(6); // 26 ticks (6 since last flush) - addDoc(); // 4 docs + await addDoc(); // 4 docs tick(6); // 32 ticks (12 since last flush) - addDoc(); // 5 docs + await addDoc(); // 5 docs tick(6); // 38 ticks (18 since last flush) - addDoc(); // 6 docs + await addDoc(); // 6 docs tick(6); // 44 ticks (24 since last flush) - addDoc(); // 7 docs + await addDoc(); // 7 docs tick(9); // 53 ticks (33 since last flush) - addDoc(); // 8 docs + await addDoc(); // 8 docs tick(9); // 62 ticks! (42 ticks since last flush, over max-age - next interval triggers flush) testDocCount(2); // Still at 2 from before! (Just making sure) tick(1); // Ok, 63 ticks (10 since last doc, so this should cause the flush of all the docs) @@ -175,11 +175,11 @@ Tinytest.add('livedata stub - buffering data', function(test) { clock.uninstall(); }); -Tinytest.add('livedata stub - subscribe', function(test) { +Tinytest.addAsync('livedata stub - subscribe', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // subscribe let callback_fired = false; @@ -201,7 +201,7 @@ Tinytest.add('livedata stub - subscribe', function(test) { test.isFalse(reactivelyReady); // get the sub satisfied. callback fires. - stream.receive({ msg: 'ready', subs: [id] }); + await stream.receive({ msg: 'ready', subs: [id] }); test.isTrue(callback_fired); Tracker.flush(); test.isTrue(reactivelyReady); @@ -224,11 +224,11 @@ Tinytest.add('livedata stub - subscribe', function(test) { test.equal(message, { msg: 'sub', name: 'my_data', params: [] }); }); -Tinytest.add('livedata stub - reactive subscribe', function(test) { +Tinytest.addAsync('livedata stub - reactive subscribe', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const rFoo = new ReactiveVar('foo1'); const rBar = new ReactiveVar('bar1'); @@ -283,7 +283,7 @@ Tinytest.add('livedata stub - reactive subscribe', function(test) { test.isFalse(completerReady); // "completer" gets ready now. its callback should fire. - stream.receive({ msg: 'ready', subs: [idCompleter] }); + await stream.receive({ msg: 'ready', subs: [idCompleter] }); test.equal(onReadyCount, { completer: 1 }); test.length(stream.sent, 0); Tracker.flush(); @@ -331,7 +331,7 @@ Tinytest.add('livedata stub - reactive subscribe', function(test) { // the client; completing bar should call the onReady from the new // subscription because we always call onReady for a given reactively-saved // subscription. - stream.receive({ msg: 'ready', subs: [idStopperAgain, idBar1] }); + await stream.receive({ msg: 'ready', subs: [idStopperAgain, idBar1] }); test.equal(onReadyCount, { completer: 2, bar1: 1, stopper: 1 }); // Shut down the autorun. This should unsub us from all current subs at flush @@ -353,13 +353,13 @@ Tinytest.add('livedata stub - reactive subscribe', function(test) { test.equal(actualIds, expectedIds); }); -Tinytest.add('livedata stub - reactive subscribe handle correct', function( +Tinytest.addAsync('livedata stub - reactive subscribe handle correct', async function( test ) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const rFoo = new ReactiveVar('foo1'); @@ -402,7 +402,7 @@ Tinytest.add('livedata stub - reactive subscribe handle correct', function( test.isFalse(fooReady); // "foo" gets ready now. The handle should be ready and the autorun rerun - stream.receive({ msg: 'ready', subs: [idFoo2] }); + await stream.receive({ msg: 'ready', subs: [idFoo2] }); test.length(stream.sent, 0); Tracker.flush(); test.isTrue(fooHandle.ready()); @@ -427,7 +427,7 @@ Tinytest.add('livedata stub - reactive subscribe handle correct', function( test.isFalse(fooReady); // "foo" gets ready again - stream.receive({ msg: 'ready', subs: [idFoo3] }); + await stream.receive({ msg: 'ready', subs: [idFoo3] }); test.length(stream.sent, 0); Tracker.flush(); test.isTrue(fooHandle.ready()); @@ -436,11 +436,11 @@ Tinytest.add('livedata stub - reactive subscribe handle correct', function( autorunHandle.stop(); }); -Tinytest.add('livedata stub - this', function(test) { +Tinytest.addAsync('livedata stub - this', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); conn.methods({ test_this: function() { test.isTrue(this.isSimulation); @@ -461,30 +461,30 @@ Tinytest.add('livedata stub - this', function(test) { }); test.length(stream.sent, 0); - stream.receive({ msg: 'result', id: message.id, result: null }); - stream.receive({ msg: 'updated', methods: [message.id] }); + await stream.receive({ msg: 'result', id: message.id, result: null }); + await stream.receive({ msg: 'updated', methods: [message.id] }); }); if (Meteor.isClient) { - Tinytest.add('livedata stub - methods', function(test) { + Tinytest.addAsync('livedata stub - methods', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); // setup method conn.methods({ - do_something: function(x) { - coll.insert({ value: x }); + do_something: async function(x) { + await coll.insertAsync({ value: x }); } }); // setup observers const counts = { added: 0, removed: 0, changed: 0, moved: 0 }; - const handle = coll.find({}).observe({ + const handle = await coll.find({}).observe({ addedAt: function() { counts.added += 1; }, @@ -520,29 +520,29 @@ if (Meteor.isClient) { randomSeed: '*' }); - test.equal(coll.find({}).count(), 1); - test.equal(coll.find({ value: 'friday!' }).count(), 1); - const docId = coll.findOne({ value: 'friday!' })._id; + test.equal(await coll.find({}).count(), 1); + test.equal(await coll.find({ value: 'friday!' }).count(), 1); + const docId = await coll.findOneAsync({ value: 'friday!' })._id; // results does not yet result in callback, because data is not // ready. - stream.receive({ msg: 'result', id: message.id, result: '1234' }); + await stream.receive({ msg: 'result', id: message.id, result: '1234' }); test.isFalse(callback1Fired); // result message doesn't affect data - test.equal(coll.find({}).count(), 1); - test.equal(coll.find({ value: 'friday!' }).count(), 1); + test.equal(await coll.find({}).count(), 1); + test.equal(await coll.find({ value: 'friday!' }).count(), 1); test.equal(counts, { added: 1, removed: 0, changed: 0, moved: 0 }); // data methods do not show up (not quiescent yet) - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(docId), fields: { value: 'tuesday' } }); - test.equal(coll.find({}).count(), 1); - test.equal(coll.find({ value: 'friday!' }).count(), 1); + test.equal(await coll.find({}).count(), 1); + test.equal(await coll.find({ value: 'friday!' }).count(), 1); test.equal(counts, { added: 1, removed: 0, changed: 0, moved: 0 }); // send another methods (unknown on client) @@ -566,35 +566,35 @@ if (Meteor.isClient) { // get the first data satisfied message. changes are applied to database even // though another method is outstanding, because the other method didn't have // a stub. and its callback is called. - stream.receive({ msg: 'updated', methods: [message.id] }); + await stream.receive({ msg: 'updated', methods: [message.id] }); test.isTrue(callback1Fired); test.isFalse(callback2Fired); - test.equal(coll.find({}).count(), 1); - test.equal(coll.find({ value: 'tuesday' }).count(), 1); + test.equal(await coll.find({}).count(), 1); + test.equal(await coll.find({ value: 'tuesday' }).count(), 1); test.equal(counts, { added: 1, removed: 0, changed: 1, moved: 0 }); // second result - stream.receive({ msg: 'result', id: message2.id, result: 'bupkis' }); + await stream.receive({ msg: 'result', id: message2.id, result: 'bupkis' }); test.isFalse(callback2Fired); // get second satisfied; no new changes are applied. - stream.receive({ msg: 'updated', methods: [message2.id] }); + await stream.receive({ msg: 'updated', methods: [message2.id] }); test.isTrue(callback2Fired); - test.equal(coll.find({}).count(), 1); - test.equal(coll.find({ value: 'tuesday', _id: docId }).count(), 1); + test.equal(await coll.find({}).count(), 1); + test.equal(await coll.find({ value: 'tuesday', _id: docId }).count(), 1); test.equal(counts, { added: 1, removed: 0, changed: 1, moved: 0 }); handle.stop(); }); } -Tinytest.add('livedata stub - mutating method args', function(test) { +Tinytest.addAsync('livedata stub - mutating method args', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); conn.methods({ mutateArgs: function(arg) { @@ -616,10 +616,10 @@ Tinytest.add('livedata stub - mutating method args', function(test) { test.length(stream.sent, 0); }); -const observeCursor = function(test, cursor) { +const observeCursor = async function(test, cursor) { const counts = { added: 0, removed: 0, changed: 0, moved: 0 }; const expectedCounts = _.clone(counts); - const handle = cursor.observe({ + const handle = await cursor.observe({ addedAt: function() { counts.added += 1; }, @@ -646,11 +646,11 @@ const observeCursor = function(test, cursor) { // method calls another method in simulation. see not sent. if (Meteor.isClient) { - Tinytest.add('livedata stub - methods calling methods', function(test) { + Tinytest.addAsync('livedata stub - methods calling methods', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const coll_name = Random.id(); const coll = new Mongo.Collection(coll_name, { connection: conn }); @@ -665,7 +665,7 @@ if (Meteor.isClient) { } }); - const o = observeCursor(test, coll.find()); + const o = await observeCursor(test, coll.find()); // call method. conn.call('do_something', _.identity); @@ -683,40 +683,40 @@ if (Meteor.isClient) { // but inner method runs locally. o.expectCallbacks({ added: 1 }); test.equal(coll.find().count(), 1); - const docId = coll.findOne()._id; - test.equal(coll.findOne(), { _id: docId, a: 1 }); + const docId = await coll.findOneAsync()._id; + test.equal(await coll.findOneAsync(), { _id: docId, a: 1 }); // we get the results - stream.receive({ msg: 'result', id: message.id, result: '1234' }); + await stream.receive({ msg: 'result', id: message.id, result: '1234' }); // get data from the method. data from this doc does not show up yet, but data // from another doc does. - stream.receive({ + await stream.receive({ msg: 'added', collection: coll_name, id: MongoID.idStringify(docId), fields: { value: 'tuesday' } }); o.expectCallbacks(); - test.equal(coll.findOne(docId), { _id: docId, a: 1 }); - stream.receive({ + test.equal(await coll.findOneAsync(docId), { _id: docId, a: 1 }); + await stream.receive({ msg: 'added', collection: coll_name, id: 'monkey', fields: { value: 'bla' } }); o.expectCallbacks({ added: 1 }); - test.equal(coll.findOne(docId), { _id: docId, a: 1 }); - const newDoc = coll.findOne({ value: 'bla' }); + test.equal(await coll.findOneAsync(docId), { _id: docId, a: 1 }); + const newDoc = await coll.findOneAsync({ value: 'bla' }); test.isTrue(newDoc); test.equal(newDoc, { _id: newDoc._id, value: 'bla' }); // get method satisfied. all data shows up. the 'a' field is reverted and // 'value' field is set. - stream.receive({ msg: 'updated', methods: [message.id] }); + await stream.receive({ msg: 'updated', methods: [message.id] }); o.expectCallbacks({ changed: 1 }); - test.equal(coll.findOne(docId), { _id: docId, value: 'tuesday' }); - test.equal(coll.findOne(newDoc._id), { _id: newDoc._id, value: 'bla' }); + test.equal(await coll.findOneAsync(docId), { _id: docId, value: 'tuesday' }); + test.equal(await coll.findOneAsync(newDoc._id), { _id: newDoc._id, value: 'bla' }); o.stop(); }); @@ -746,7 +746,7 @@ Tinytest.add('livedata stub - method call before connect', function(test) { }); }); -Tinytest.add('livedata stub - reconnect', function(test) { +Tinytest.addAsync('livedata stub - reconnect', async function(test, onComplete) { const stream = new StubStream(); const conn = newConnection(stream); @@ -755,7 +755,7 @@ Tinytest.add('livedata stub - reconnect', function(test) { const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); - const o = observeCursor(test, coll.find()); + const o = await observeCursor(test, coll.find()); // subscribe let subCallbackFired = false; @@ -773,31 +773,31 @@ Tinytest.add('livedata stub - reconnect', function(test) { }); // get some data. it shows up. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: '1234', fields: { a: 1 } }); - test.equal(coll.find({}).count(), 1); + test.equal(await coll.find({}).count(), 1); o.expectCallbacks({ added: 1 }); test.isFalse(subCallbackFired); - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: '1234', fields: { b: 2 } }); - stream.receive({ + await stream.receive({ msg: 'ready', subs: [subMessage.id] // satisfy sub }); test.isTrue(subCallbackFired); subCallbackFired = false; // re-arm for test that it doesn't fire again. - test.equal(coll.find({ a: 1, b: 2 }).count(), 1); + test.equal(await coll.find({ a: 1, b: 2 }).count(), 1); o.expectCallbacks({ changed: 1 }); // call method. @@ -823,69 +823,69 @@ Tinytest.add('livedata stub - reconnect', function(test) { test.equal(stream.sent.length, 0); // more data. shows up immediately because there was no relevant method stub. - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: '1234', fields: { c: 3 } }); - test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + test.equal(await coll.findOneAsync('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); o.expectCallbacks({ changed: 1 }); // stream reset. reconnect! we send a connect, our pending method, and our // sub. The wait method still is blocked. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); testGotMessage(test, stream, methodMessage); testGotMessage(test, stream, subMessage); // reconnect with different session id - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); // resend data. doesn't show up: we're in reconnect quiescence. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: '1234', fields: { a: 1, b: 2, c: 3, d: 4 } }); - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: '2345', fields: { e: 5 } }); - test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); - test.isFalse(coll.findOne('2345')); + test.equal(await await coll.findOneAsync('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + test.isFalse(await coll.findOneAsync('2345')); o.expectCallbacks(); // satisfy and return the method - stream.receive({ + await stream.receive({ msg: 'updated', methods: [methodMessage.id] }); test.isFalse(methodCallbackFired); - stream.receive({ msg: 'result', id: methodMessage.id, result: 'bupkis' }); + await stream.receive({ msg: 'result', id: methodMessage.id, result: 'bupkis' }); // The callback still doesn't fire (and we don't send the wait method): we're // still in global quiescence test.isFalse(methodCallbackFired); test.equal(stream.sent.length, 0); // still no update. - test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); - test.isFalse(coll.findOne('2345')); + test.equal(await coll.findOneAsync('1234'), { _id: '1234', a: 1, b: 2, c: 3 }); + test.isFalse(await coll.findOneAsync('2345')); o.expectCallbacks(); // re-satisfy sub - stream.receive({ msg: 'ready', subs: [subMessage.id] }); + await stream.receive({ msg: 'ready', subs: [subMessage.id] }); // now the doc changes and method callback is called, and the wait method is // sent. the sub callback isn't re-called. test.isTrue(methodCallbackFired); test.isFalse(subCallbackFired); - test.equal(coll.findOne('1234'), { _id: '1234', a: 1, b: 2, c: 3, d: 4 }); - test.equal(coll.findOne('2345'), { _id: '2345', e: 5 }); + test.equal(await coll.findOneAsync('1234'), { _id: '1234', a: 1, b: 2, c: 3, d: 4 }); + test.equal(await coll.findOneAsync('2345'), { _id: '2345', e: 5 }); o.expectCallbacks({ added: 1, changed: 1 }); let waitMethodMessage = JSON.parse(stream.sent.shift()); @@ -897,9 +897,9 @@ Tinytest.add('livedata stub - reconnect', function(test) { id: waitMethodMessage.id }); test.equal(stream.sent.length, 0); - stream.receive({ msg: 'result', id: waitMethodMessage.id, result: 'bupkis' }); + await stream.receive({ msg: 'result', id: waitMethodMessage.id, result: 'bupkis' }); test.equal(stream.sent.length, 0); - stream.receive({ msg: 'updated', methods: [waitMethodMessage.id] }); + await stream.receive({ msg: 'updated', methods: [waitMethodMessage.id] }); // wait method done means we can send the third method test.equal(stream.sent.length, 1); @@ -916,14 +916,14 @@ Tinytest.add('livedata stub - reconnect', function(test) { }); if (Meteor.isClient) { - Tinytest.add('livedata stub - reconnect non-idempotent method', function( + Tinytest.addAsync('livedata stub - reconnect non-idempotent method', async function( test ) { // This test is for https://github.com/meteor/meteor/issues/6108 const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); let firstMethodCallbackFired = false; let firstMethodCallbackErrored = false; @@ -954,12 +954,12 @@ if (Meteor.isClient) { stream.sent.shift(); stream.sent.shift(); // reconnect - stream.reset(); + await stream.reset(); // verify that a reconnect message was sent. testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); // Make sure that the stream triggers connection. - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); //The method callback should fire even though the stream has not sent a response. //the callback should have been fired with an error. @@ -974,14 +974,14 @@ if (Meteor.isClient) { } function addReconnectTests(name, testFunc) { - Tinytest.add(name + ' (deprecated)', function(test) { + Tinytest.addAsync(name + ' (deprecated)', async function(test) { function deprecatedSetOnReconnect(conn, handler) { conn.onReconnect = handler; } - testFunc.call(this, test, deprecatedSetOnReconnect); + await testFunc.call(this, test, deprecatedSetOnReconnect); }); - Tinytest.add(name, function(test) { + Tinytest.addAsync(name, async function(test) { let stopper; function setOnReconnect(conn, handler) { stopper && stopper.stop(); @@ -991,7 +991,7 @@ function addReconnectTests(name, testFunc) { } }); } - testFunc.call(this, test, setOnReconnect); + await testFunc.call(this, test, setOnReconnect); stopper && stopper.stop(); }); } @@ -999,10 +999,10 @@ function addReconnectTests(name, testFunc) { if (Meteor.isClient) { addReconnectTests( 'livedata stub - reconnect method which only got result', - function(test, setOnReconnect) { + async function(test, setOnReconnect) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); @@ -1034,7 +1034,7 @@ if (Meteor.isClient) { ); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + const stubWrittenId = await coll.findOneAsync({ foo: 'bar' })._id; o.expectCallbacks({ added: 1 }); // Callback not called. test.equal(callbackOutput, []); @@ -1050,7 +1050,7 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // Get some data. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(stubWrittenId), @@ -1058,17 +1058,17 @@ if (Meteor.isClient) { }); // It doesn't show up yet. test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar' }); o.expectCallbacks(); // Get the result. - stream.receive({ msg: 'result', id: methodId, result: 'bla' }); + await stream.receive({ msg: 'result', id: methodId, result: 'bla' }); // Data unaffected. test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar' }); @@ -1080,11 +1080,11 @@ if (Meteor.isClient) { // Reset stream. Method does NOT get resent, because its result is already // in. Reconnect quiescence happens as soon as 'connected' is received because // there are no pending methods or subs in need of revival. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); // Still holding out hope for session resumption, so nothing updated yet. test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar' }); @@ -1093,18 +1093,18 @@ if (Meteor.isClient) { // Receive 'connected': time for reconnect quiescence! Data gets updated // locally (ie, data is reset) and callback gets called. - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); test.equal(coll.find().count(), 0); o.expectCallbacks({ removed: 1 }); test.equal(callbackOutput, ['bla']); test.equal(onResultReceivedOutput, ['bla']); - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(stubWrittenId), fields: { baz: 42 } }); - test.equal(coll.findOne(stubWrittenId), { _id: stubWrittenId, baz: 42 }); + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, baz: 42 }); o.expectCallbacks({ added: 1 }); // Run method again. We're going to do the same thing this time, except we're @@ -1124,7 +1124,7 @@ if (Meteor.isClient) { ); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId2 = coll.findOne({ foo: 'bar' })._id; + const stubWrittenId2 = await coll.findOneAsync({ foo: 'bar' })._id; o.expectCallbacks({ added: 1 }); // Callback not called. test.equal(callbackOutput, ['bla']); @@ -1140,7 +1140,7 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // Get some data. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(stubWrittenId2), @@ -1148,17 +1148,17 @@ if (Meteor.isClient) { }); // It doesn't show up yet. test.equal(coll.find().count(), 2); - test.equal(coll.findOne(stubWrittenId2), { + test.equal(await coll.findOneAsync(stubWrittenId2), { _id: stubWrittenId2, foo: 'bar' }); o.expectCallbacks(); // Get the result. - stream.receive({ msg: 'result', id: methodId2, result: 'blab' }); + await stream.receive({ msg: 'result', id: methodId2, result: 'blab' }); // Data unaffected. test.equal(coll.find().count(), 2); - test.equal(coll.findOne(stubWrittenId2), { + test.equal(await coll.findOneAsync(stubWrittenId2), { _id: stubWrittenId2, foo: 'bar' }); @@ -1175,7 +1175,7 @@ if (Meteor.isClient) { // Reset stream. Method does NOT get resent, because its result is already in, // but slowMethod gets called via onReconnect. Reconnect quiescence is now // blocking on slowMethod. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID + 1)); const slowMethodId = testGotMessage(test, stream, { msg: 'method', @@ -1185,7 +1185,7 @@ if (Meteor.isClient) { }).id; // Still holding out hope for session resumption, so nothing updated yet. test.equal(coll.find().count(), 2); - test.equal(coll.findOne(stubWrittenId2), { + test.equal(await coll.findOneAsync(stubWrittenId2), { _id: stubWrittenId2, foo: 'bar' }); @@ -1193,9 +1193,9 @@ if (Meteor.isClient) { test.equal(callbackOutput, ['bla']); // Receive 'connected'... but no reconnect quiescence yet due to slowMethod. - stream.receive({ msg: 'connected', session: SESSION_ID + 2 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 2 }); test.equal(coll.find().count(), 2); - test.equal(coll.findOne(stubWrittenId2), { + test.equal(await coll.findOneAsync(stubWrittenId2), { _id: stubWrittenId2, foo: 'bar' }); @@ -1203,7 +1203,7 @@ if (Meteor.isClient) { test.equal(callbackOutput, ['bla']); // Receive data matching our stub. It doesn't take effect yet. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(stubWrittenId2), @@ -1215,9 +1215,9 @@ if (Meteor.isClient) { // slowMethod callback)... ie, a reset followed by applying the data we just // got, as well as calling the callback from the method that half-finished // before reset. The net effect is deleting doc 'stubWrittenId'. - stream.receive({ msg: 'updated', methods: [slowMethodId] }); + await stream.receive({ msg: 'updated', methods: [slowMethodId] }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId2), { + test.equal(await coll.findOneAsync(stubWrittenId2), { _id: stubWrittenId2, foo: 'bar' }); @@ -1225,7 +1225,7 @@ if (Meteor.isClient) { test.equal(callbackOutput, ['bla', 'blab']); // slowMethod returns a value now. - stream.receive({ msg: 'result', id: slowMethodId, result: 'slow' }); + await stream.receive({ msg: 'result', id: slowMethodId, result: 'slow' }); o.expectCallbacks(); test.equal(callbackOutput, ['bla', 'blab', 'slow']); @@ -1233,16 +1233,16 @@ if (Meteor.isClient) { } ); } -Tinytest.add('livedata stub - reconnect method which only got data', function( +Tinytest.addAsync('livedata stub - reconnect method which only got data', async function( test ) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); - const o = observeCursor(test, coll.find()); + const o = await observeCursor(test, coll.find()); // Call a method. We'll get the data-done message but not the result before // reconnect. @@ -1273,7 +1273,7 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( test.equal(stream.sent.length, 0); // Get some data. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: 'photo', @@ -1281,14 +1281,14 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( }); // It shows up instantly because the stub didn't write anything. test.equal(coll.find().count(), 1); - test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + test.equal(await coll.findOneAsync('photo'), { _id: 'photo', baz: 42 }); o.expectCallbacks({ added: 1 }); // Get the data-done message. - stream.receive({ msg: 'updated', methods: [methodId] }); + await stream.receive({ msg: 'updated', methods: [methodId] }); // Data still here. test.equal(coll.find().count(), 1); - test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + test.equal(await coll.findOneAsync('photo'), { _id: 'photo', baz: 42 }); o.expectCallbacks(); // Method callback not called yet (no result yet). test.equal(callbackOutput, []); @@ -1296,7 +1296,7 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( // Reset stream. Method gets resent (with same ID), and blocks reconnect // quiescence. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); testGotMessage(test, stream, { msg: 'method', @@ -1306,15 +1306,15 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( }); // Still holding out hope for session resumption, so nothing updated yet. test.equal(coll.find().count(), 1); - test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + test.equal(await coll.findOneAsync('photo'), { _id: 'photo', baz: 42 }); o.expectCallbacks(); test.equal(callbackOutput, []); test.equal(onResultReceivedOutput, []); // Receive 'connected'. Still blocking on reconnect quiescence. - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne('photo'), { _id: 'photo', baz: 42 }); + test.equal(await coll.findOneAsync('photo'), { _id: 'photo', baz: 42 }); o.expectCallbacks(); test.equal(callbackOutput, []); test.equal(onResultReceivedOutput, []); @@ -1322,12 +1322,12 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( // Receive method result. onResultReceived is called but the main callback // isn't (ie, we don't get confused by the fact that we got data-done the // *FIRST* time through). - stream.receive({ msg: 'result', id: methodId, result: 'res' }); + await stream.receive({ msg: 'result', id: methodId, result: 'res' }); test.equal(callbackOutput, []); test.equal(onResultReceivedOutput, ['res']); // Now we get data-done. Collection is reset and callback is called. - stream.receive({ msg: 'updated', methods: [methodId] }); + await stream.receive({ msg: 'updated', methods: [methodId] }); test.equal(coll.find().count(), 0); o.expectCallbacks({ removed: 1 }); test.equal(callbackOutput, ['res']); @@ -1336,10 +1336,10 @@ Tinytest.add('livedata stub - reconnect method which only got data', function( o.stop(); }); if (Meteor.isClient) { - Tinytest.add('livedata stub - multiple stubs same doc', function(test) { + Tinytest.addAsync('livedata stub - multiple stubs same doc', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); @@ -1361,7 +1361,7 @@ if (Meteor.isClient) { conn.call('insertSomething', _.identity); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + const stubWrittenId = await coll.findOneAsync({ foo: 'bar' })._id; o.expectCallbacks({ added: 1 }); // Method sent. const insertMethodId = testGotMessage(test, stream, { @@ -1377,7 +1377,7 @@ if (Meteor.isClient) { conn.call('updateIt', stubWrittenId, _.identity); // This stub write is visible too. test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar', baz: 42 @@ -1393,7 +1393,7 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // Get some data... slightly different than what we wrote. - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: MongoID.idStringify(stubWrittenId), @@ -1405,7 +1405,7 @@ if (Meteor.isClient) { }); // It doesn't show up yet. test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar', baz: 42 @@ -1414,9 +1414,9 @@ if (Meteor.isClient) { // And get the first method-done. Still no updates to minimongo: we can't // quiesce the doc until the second method is done. - stream.receive({ msg: 'updated', methods: [insertMethodId] }); + await stream.receive({ msg: 'updated', methods: [insertMethodId] }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar', baz: 42 @@ -1424,7 +1424,7 @@ if (Meteor.isClient) { o.expectCallbacks(); // More data. Not quite what we wrote. Also ignored for now. - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: MongoID.idStringify(stubWrittenId), @@ -1432,7 +1432,7 @@ if (Meteor.isClient) { cleared: ['other'] }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'bar', baz: 42 @@ -1440,9 +1440,9 @@ if (Meteor.isClient) { o.expectCallbacks(); // Second data-ready. Now everything takes effect! - stream.receive({ msg: 'updated', methods: [updateMethodId] }); + await stream.receive({ msg: 'updated', methods: [updateMethodId] }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne(stubWrittenId), { + test.equal(await coll.findOneAsync(stubWrittenId), { _id: stubWrittenId, foo: 'barb', other2: 'bla', @@ -1455,14 +1455,14 @@ if (Meteor.isClient) { } if (Meteor.isClient) { - Tinytest.add( + Tinytest.addAsync( "livedata stub - unsent methods don't block quiescence", - function(test) { + async function(test) { // This test is for https://github.com/meteor/meteor/issues/555 const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); @@ -1485,7 +1485,7 @@ if (Meteor.isClient) { // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId = coll.findOne({ foo: 'bar' })._id; + const stubWrittenId = await coll.findOneAsync({ foo: 'bar' })._id; // first method sent const firstMethodId = testGotMessage(test, stream, { @@ -1497,8 +1497,8 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // ack the first method - stream.receive({ msg: 'updated', methods: [firstMethodId] }); - stream.receive({ msg: 'result', id: firstMethodId }); + await stream.receive({ msg: 'updated', methods: [firstMethodId] }); + await stream.receive({ msg: 'result', id: firstMethodId }); // Wait method sent. const waitMethodId = testGotMessage(test, stream, { @@ -1510,8 +1510,8 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // ack the wait method - stream.receive({ msg: 'updated', methods: [waitMethodId] }); - stream.receive({ msg: 'result', id: waitMethodId }); + await stream.receive({ msg: 'updated', methods: [waitMethodId] }); + await stream.receive({ msg: 'result', id: waitMethodId }); // insert method sent. const insertMethodId = testGotMessage(test, stream, { @@ -1524,19 +1524,19 @@ if (Meteor.isClient) { test.equal(stream.sent.length, 0); // ack the insert method - stream.receive({ msg: 'updated', methods: [insertMethodId] }); - stream.receive({ msg: 'result', id: insertMethodId }); + await stream.receive({ msg: 'updated', methods: [insertMethodId] }); + await stream.receive({ msg: 'result', id: insertMethodId }); // simulation reverted. test.equal(coll.find({ foo: 'bar' }).count(), 0); } ); } -Tinytest.add('livedata stub - reactive resub', function(test) { +Tinytest.addAsync('livedata stub - reactive resub', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const readiedSubs = {}; const markAllReady = function() { @@ -1627,10 +1627,10 @@ Tinytest.add('livedata connection - reactive userId', function(test) { test.equal(conn.userId(), 1337); }); -Tinytest.add('livedata connection - two wait methods', function(test) { +Tinytest.addAsync('livedata connection - two wait methods', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); @@ -1673,19 +1673,19 @@ Tinytest.add('livedata connection - two wait methods', function(test) { // Receive some data. "one" is not a wait method and there are no stubs, so it // gets applied immediately. test.equal(coll.find().count(), 0); - stream.receive({ + await stream.receive({ msg: 'added', collection: collName, id: 'foo', fields: { x: 1 } }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne('foo'), { _id: 'foo', x: 1 }); + test.equal(await coll.findOneAsync('foo'), { _id: 'foo', x: 1 }); // Let "one!" finish. Both messages are required to fire the callback. - stream.receive({ msg: 'result', id: one_message.id }); + await stream.receive({ msg: 'result', id: one_message.id }); test.equal(responses, []); - stream.receive({ msg: 'updated', methods: [one_message.id] }); + await stream.receive({ msg: 'updated', methods: [one_message.id] }); test.equal(responses, ['one']); // Now we've send out "two!". @@ -1697,23 +1697,23 @@ Tinytest.add('livedata connection - two wait methods', function(test) { // Receive more data. "two" is a wait method, so the data doesn't get applied // yet. - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: 'foo', fields: { y: 3 } }); test.equal(coll.find().count(), 1); - test.equal(coll.findOne('foo'), { _id: 'foo', x: 1 }); + test.equal(await coll.findOneAsync('foo'), { _id: 'foo', x: 1 }); // Let "two!" finish, with its end messages in the opposite order to "one!". - stream.receive({ msg: 'updated', methods: [two_message.id] }); + await stream.receive({ msg: 'updated', methods: [two_message.id] }); test.equal(responses, ['one']); test.equal(stream.sent.length, 0); // data-done message is enough to allow data to be written. test.equal(coll.find().count(), 1); - test.equal(coll.findOne('foo'), { _id: 'foo', x: 1, y: 3 }); - stream.receive({ msg: 'result', id: two_message.id }); + test.equal(await coll.findOneAsync('foo'), { _id: 'foo', x: 1, y: 3 }); + await stream.receive({ msg: 'result', id: two_message.id }); test.equal(responses, ['one', 'two']); // Verify that we just sent "three!" and "four!" now that we got @@ -1725,14 +1725,14 @@ Tinytest.add('livedata connection - two wait methods', function(test) { test.equal(four_message.params, ['four!']); // Out of order response is OK for non-wait methods. - stream.receive({ msg: 'result', id: three_message.id }); - stream.receive({ msg: 'result', id: four_message.id }); - stream.receive({ msg: 'updated', methods: [four_message.id] }); + await stream.receive({ msg: 'result', id: three_message.id }); + await stream.receive({ msg: 'result', id: four_message.id }); + await stream.receive({ msg: 'updated', methods: [four_message.id] }); test.equal(responses, ['one', 'two', 'four']); test.equal(stream.sent.length, 0); // Let three finish too. - stream.receive({ msg: 'updated', methods: [three_message.id] }); + await stream.receive({ msg: 'updated', methods: [three_message.id] }); test.equal(responses, ['one', 'two', 'four', 'three']); // Verify that we just sent "five!" (the next wait method). @@ -1742,8 +1742,8 @@ Tinytest.add('livedata connection - two wait methods', function(test) { test.equal(responses, ['one', 'two', 'four', 'three']); // Let five finish. - stream.receive({ msg: 'result', id: five_message.id }); - stream.receive({ msg: 'updated', methods: [five_message.id] }); + await stream.receive({ msg: 'result', id: five_message.id }); + await stream.receive({ msg: 'updated', methods: [five_message.id] }); test.equal(responses, ['one', 'two', 'four', 'three', 'five']); let six_message = JSON.parse(stream.sent.shift()); @@ -1752,10 +1752,10 @@ Tinytest.add('livedata connection - two wait methods', function(test) { addReconnectTests( 'livedata connection - onReconnect prepends messages correctly with a wait method', - function(test, setOnReconnect) { + async function(test, setOnReconnect) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // setup method conn.methods({ do_something: function(x) {} }); @@ -1773,7 +1773,7 @@ addReconnectTests( // reconnect stream.sent = []; - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); // Test that we sent what we expect to send, and we're blocked on @@ -1807,22 +1807,22 @@ addReconnectTests( } ); -Tinytest.add('livedata connection - ping without id', function(test) { +Tinytest.addAsync('livedata connection - ping without id', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); - stream.receive({ msg: 'ping' }); + await stream.receive({ msg: 'ping' }); testGotMessage(test, stream, { msg: 'pong' }); }); -Tinytest.add('livedata connection - ping with id', function(test) { +Tinytest.addAsync('livedata connection - ping with id', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const id = Random.id(); - stream.receive({ msg: 'ping', id: id }); + await stream.receive({ msg: 'ping', id: id }); testGotMessage(test, stream, { msg: 'pong', id: id }); }); @@ -1978,10 +1978,10 @@ Tinytest.addAsync('livedata connection - version negotiation error', function( addReconnectTests( 'livedata connection - onReconnect prepends messages correctly without a wait method', - function(test, setOnReconnect) { + async function(test, setOnReconnect) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // setup method conn.methods({ do_something: function(x) {} }); @@ -1999,7 +1999,7 @@ addReconnectTests( // reconnect stream.sent = []; - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); // Test that we sent what we expect to send, and we're blocked on @@ -2034,10 +2034,10 @@ addReconnectTests( addReconnectTests( 'livedata connection - onReconnect with sent messages', - function(test, setOnReconnect) { + async function(test, setOnReconnect) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // setup method conn.methods({ do_something: function(x) {} }); @@ -2050,7 +2050,7 @@ addReconnectTests( // initial connect stream.sent = []; - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(conn._lastSessionId)); // Test that we sent just the login message. @@ -2062,15 +2062,15 @@ addReconnectTests( }).id; // we connect. - stream.receive({ msg: 'connected', session: Random.id() }); + await stream.receive({ msg: 'connected', session: Random.id() }); test.length(stream.sent, 0); // login got result (but not yet data) - stream.receive({ msg: 'result', id: loginId, result: 'foo' }); + await stream.receive({ msg: 'result', id: loginId, result: 'foo' }); test.length(stream.sent, 0); // login got data. now we send next method. - stream.receive({ msg: 'updated', methods: [loginId] }); + await stream.receive({ msg: 'updated', methods: [loginId] }); testGotMessage(test, stream, { msg: 'method', @@ -2081,13 +2081,13 @@ addReconnectTests( } ); -addReconnectTests('livedata stub - reconnect double wait method', function( +addReconnectTests('livedata stub - reconnect double wait method', async function( test, setOnReconnect ) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const output = []; setOnReconnect(conn, function() { @@ -2111,13 +2111,13 @@ addReconnectTests('livedata stub - reconnect double wait method', function( test.equal(stream.sent.length, 0); // Get the result. This means it will not be resent. - stream.receive({ msg: 'result', id: halfwayId, result: 'bla' }); + await stream.receive({ msg: 'result', id: halfwayId, result: 'bla' }); // Callback not called. test.equal(output, []); // Reset stream. halfwayMethod does NOT get resent, but reconnectMethod does! // Reconnect quiescence happens when reconnectMethod is done. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); const reconnectId = testGotMessage(test, stream, { msg: 'method', @@ -2131,18 +2131,18 @@ addReconnectTests('livedata stub - reconnect double wait method', function( // Receive 'connected', but reconnect quiescence is blocking on // reconnectMethod. - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); test.equal(output, []); // Data-done for reconnectMethod. This gets us to reconnect quiescence, so // halfwayMethod's callback fires. reconnectMethod's is still waiting on its // result. - stream.receive({ msg: 'updated', methods: [reconnectId] }); + await stream.receive({ msg: 'updated', methods: [reconnectId] }); test.equal(output.shift(), 'halfway'); test.equal(output, []); // Get result of reconnectMethod. Its callback fires. - stream.receive({ msg: 'result', id: reconnectId, result: 'foo' }); + await stream.receive({ msg: 'result', id: reconnectId, result: 'foo' }); test.equal(output.shift(), 'reconnect'); test.equal(output, []); @@ -2158,11 +2158,11 @@ addReconnectTests('livedata stub - reconnect double wait method', function( }); }); -Tinytest.add('livedata stub - subscribe errors', function(test) { +Tinytest.addAsync('livedata stub - subscribe errors', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // subscribe let onReadyFired = false; @@ -2202,7 +2202,7 @@ Tinytest.add('livedata stub - subscribe errors', function(test) { }); // Reject the sub. - stream.receive({ + await stream.receive({ msg: 'nosub', id: subMessage.id, error: new Meteor.Error(404, 'Subscription not found') @@ -2221,7 +2221,7 @@ Tinytest.add('livedata stub - subscribe errors', function(test) { test.equal(subErrorInError.reason, 'Subscription not found'); // stream reset: reconnect! - stream.reset(); + await stream.reset(); // We send a connect. testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); // We should NOT re-sub to the sub, because we processed the error. @@ -2229,11 +2229,11 @@ Tinytest.add('livedata stub - subscribe errors', function(test) { test.isFalse(onReadyFired); }); -Tinytest.add('livedata stub - subscribe stop', function(test) { +Tinytest.addAsync('livedata stub - subscribe stop', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); // subscribe let onReadyFired = false; @@ -2256,7 +2256,7 @@ Tinytest.add('livedata stub - subscribe stop', function(test) { }); if (Meteor.isClient) { - Tinytest.add('livedata stub - stubs before connected', function(test) { + Tinytest.addAsync('livedata stub - stubs before connected', async function(test) { const stream = new StubStream(); const conn = newConnection(stream); @@ -2264,7 +2264,7 @@ if (Meteor.isClient) { const coll = new Mongo.Collection(collName, { connection: conn }); // Start and send "connect", but DON'T get 'connected' quite yet. - stream.reset(); // initial connection start. + await stream.reset(); // initial connection start. testGotMessage(test, stream, makeConnectMessage()); test.length(stream.sent, 0); @@ -2272,7 +2272,7 @@ if (Meteor.isClient) { // Insert a document. The stub updates "conn" directly. coll.insert({ _id: 'foo', bar: 42 }, _.identity); test.equal(coll.find().count(), 1); - test.equal(coll.findOne(), { _id: 'foo', bar: 42 }); + test.equal(await coll.findOneAsync(), { _id: 'foo', bar: 42 }); // It also sends the method message. let methodMessage = JSON.parse(stream.sent.shift()); test.isUndefined(methodMessage.randomSeed); @@ -2286,33 +2286,33 @@ if (Meteor.isClient) { // Now receive a connected message. This should not clear the // _documentsWrittenByStub state! - stream.receive({ msg: 'connected', session: SESSION_ID }); + await stream.receive({ msg: 'connected', session: SESSION_ID }); test.length(stream.sent, 0); test.equal(coll.find().count(), 1); // Now receive the "updated" message for the method. This should revert the // insert. - stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + await stream.receive({ msg: 'updated', methods: [methodMessage.id] }); test.length(stream.sent, 0); test.equal(coll.find().count(), 0); }); } if (Meteor.isClient) { - Tinytest.add( + Tinytest.addAsync ( 'livedata stub - method call between reset and quiescence', - function(test) { + async function(test) { const stream = new StubStream(); const conn = newConnection(stream); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); conn.methods({ - update_value: function() { - coll.update('aaa', { value: 222 }); + update_value: async function() { + await coll.updateAsync('aaa', { value: 222 }); } }); @@ -2336,23 +2336,23 @@ if (Meteor.isClient) { const subReadyMessage = { msg: 'ready', subs: [subMessage.id] }; - stream.receive(subDocMessage); - stream.receive(subReadyMessage); - test.isTrue(coll.findOne('aaa').value == 111); + await stream.receive(subDocMessage); + await stream.receive(subReadyMessage); + test.isTrue(await coll.findOneAsync('aaa').value == 111); // Initiate reconnect. - stream.reset(); + await stream.reset(); testGotMessage(test, stream, makeConnectMessage(SESSION_ID)); testGotMessage(test, stream, subMessage); - stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); + await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); // Now in reconnect, can still see the document. - test.isTrue(coll.findOne('aaa').value == 111); + test.isTrue(await coll.findOneAsync('aaa').value == 111); - conn.call('update_value'); + await conn.callAsync('update_value'); // Observe the stub-written value. - test.isTrue(coll.findOne('aaa').value == 222); + test.isTrue(await coll.findOneAsync('aaa').value == 222); let methodMessage = JSON.parse(stream.sent.shift()); test.equal(methodMessage, { @@ -2363,29 +2363,29 @@ if (Meteor.isClient) { }); test.length(stream.sent, 0); - stream.receive(subDocMessage); - stream.receive(subReadyMessage); + await stream.receive(subDocMessage); + await stream.receive(subReadyMessage); // By this point quiescence is reached and stores have been reset. // The stub-written value is still there. - test.isTrue(coll.findOne('aaa').value == 222); + test.isTrue(await coll.findOneAsync('aaa').value == 222); - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: 'aaa', fields: { value: 333 } }); - stream.receive({ msg: 'updated', methods: [methodMessage.id] }); - stream.receive({ msg: 'result', id: methodMessage.id, result: null }); + await stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + await stream.receive({ msg: 'result', id: methodMessage.id, result: null }); // Server wrote a different value, make sure it's visible now. - test.isTrue(coll.findOne('aaa').value == 333); + test.isTrue(await coll.findOneAsync('aaa').value == 333); } ); - Tinytest.add('livedata stub - buffering and methods interaction', function( + Tinytest.addAsync('livedata stub - buffering and methods interaction', async function( test ) { const stream = new StubStream(); @@ -2395,14 +2395,14 @@ if (Meteor.isClient) { bufferedWritesMaxAge: 10000 }); - startAndConnect(test, stream); + await startAndConnect(test, stream); const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); conn.methods({ - update_value: function() { - const value = coll.findOne('aaa').subscription; + update_value: async function() { + const value = await coll.findOneAsync('aaa').subscription; // Method should have access to the latest value of the collection. coll.update('aaa', { $set: { method: value + 110 } }); } @@ -2428,9 +2428,9 @@ if (Meteor.isClient) { const subReadyMessage = { msg: 'ready', subs: [subMessage.id] }; - stream.receive(subDocMessage); - stream.receive(subReadyMessage); - test.equal(coll.findOne('aaa').subscription, 111); + await stream.receive(subDocMessage); + await stream.receive(subReadyMessage); + test.equal(await coll.findOneAsync('aaa').subscription, 111); const subDocChangeMessage = { msg: 'changed', @@ -2439,18 +2439,18 @@ if (Meteor.isClient) { fields: { subscription: 112 } }; - stream.receive(subDocChangeMessage); + await stream.receive(subDocChangeMessage); // Still 111 because buffer has not been flushed. - test.equal(coll.findOne('aaa').subscription, 111); + test.equal(await coll.findOneAsync('aaa').subscription, 111); // Call updates the stub. - conn.call('update_value'); + await conn.callAsync('update_value'); // Observe the stub-written value. - test.equal(coll.findOne('aaa').method, 222); + test.equal(await coll.findOneAsync('aaa').method, 222); // subscription field is updated to the latest value // because of the method call. - test.equal(coll.findOne('aaa').subscription, 112); + test.equal(await coll.findOneAsync('aaa').subscription, 112); let methodMessage = JSON.parse(stream.sent.shift()); test.equal(methodMessage, { @@ -2464,23 +2464,23 @@ if (Meteor.isClient) { // "Server-side" change from the method arrives and method returns. // With potentially fixed value for method field, if stub didn't // use 112 as the subscription field value. - stream.receive({ + await stream.receive({ msg: 'changed', collection: collName, id: 'aaa', fields: { method: 222 } }); - stream.receive({ msg: 'updated', methods: [methodMessage.id] }); - stream.receive({ msg: 'result', id: methodMessage.id, result: null }); + await stream.receive({ msg: 'updated', methods: [methodMessage.id] }); + await stream.receive({ msg: 'result', id: methodMessage.id, result: null }); - test.equal(coll.findOne('aaa').method, 222); - test.equal(coll.findOne('aaa').subscription, 112); + test.equal(await coll.findOneAsync('aaa').method, 222); + test.equal(await coll.findOneAsync('aaa').subscription, 112); // Buffer should already be flushed because of a non-update message. // And after a flush we really want subscription field to be 112. conn._flushBufferedWrites(); - test.equal(coll.findOne('aaa').method, 222); - test.equal(coll.findOne('aaa').subscription, 112); + test.equal(await coll.findOneAsync('aaa').method, 222); + test.equal(await coll.findOneAsync('aaa').subscription, 112); }); } diff --git a/packages/ddp-client/test/stub_stream.js b/packages/ddp-client/test/stub_stream.js index 1b0186a348..d65990055d 100644 --- a/packages/ddp-client/test/stub_stream.js +++ b/packages/ddp-client/test/stub_stream.js @@ -32,23 +32,23 @@ _.extend(StubStream.prototype, { }, // Methods for tests - receive: function(data) { + receive: async function(data) { const self = this; if (typeof data === 'object') { data = EJSON.stringify(data); } - _.each(self.callbacks['message'], function(cb) { - cb(data); - }); + for (const cb of self.callbacks['message']) { + await cb(data); + } }, - reset: function() { + reset: async function() { const self = this; - _.each(self.callbacks['reset'], function(cb) { - cb(); - }); + for (const cb of self.callbacks['reset']) { + await cb(); + } }, // Provide a tag to detect stub streams. diff --git a/packages/meteor/async_helpers.js b/packages/meteor/async_helpers.js index e326265c03..7be1653c76 100644 --- a/packages/meteor/async_helpers.js +++ b/packages/meteor/async_helpers.js @@ -35,36 +35,22 @@ class AsynchronousQueue { this._draining = false; } - queueTask(task) { + async queueTask(task) { this._taskHandles.push({ task: task, name: task.name }); - return this._scheduleRun(); + await this._scheduleRun(); } - _scheduleRun() { + async _scheduleRun() { // Already running or scheduled? Do nothing. if (this._runningOrRunScheduled) return; this._runningOrRunScheduled = true; - let resolver; - const returnValue = new Promise(r => resolver = r); - setImmediate(() => { - Meteor._runAsync(async () => { - await this._run(); - - if (!resolver) { - throw new Error("Resolver not found for task"); - } - - resolver(); - }); - }); - - return returnValue; + await this._run(); } async _run() { @@ -91,7 +77,7 @@ class AsynchronousQueue { await this._scheduleRun(); } - runTask(task) { + async runTask(task) { const handle = { task: Meteor.bindEnvironment(task, function(e) { Meteor._debug('Exception from task', e); @@ -100,7 +86,7 @@ class AsynchronousQueue { name: task.name }; this._taskHandles.push(handle); - return this._scheduleRun(); + await this._scheduleRun(); } flush() { diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 62ea44205c..8d2f3f9406 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -245,12 +245,12 @@ const bindEnvironmentAsync = (func, onException, _this) => { async () => { let ret; try { - Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, dynamics); + if (currentSlot) { + Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, dynamics); + } ret = await func.apply(_this, args); } catch (e) { onException(e); - } finally { - Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, undefined); } return ret; }, From 3933c2c9377cb603bccbeb07e44804a59075674f Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 6 Dec 2022 19:43:39 -0300 Subject: [PATCH 0097/1965] Remove Fibers for Meteor Tools: - No need to change this code. It doesn't make difference, but if we don't need to change, do not change it! --- tools/isobuild/compiler-plugin.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index eb2abc8a63..d7b25a1b66 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -1306,8 +1306,9 @@ export class PackageSourceBatch { } const meteorPackageInstalls = []; - for (const [name, info] of map) { - if (!name) continue; + map.forEach((info, name) => { + if (! name) return; + const mainModule = info.mainModule && `meteor/${name}/${info.mainModule.targetPath}`; @@ -1316,7 +1317,7 @@ export class PackageSourceBatch { (mainModule ? ", " + JSON.stringify(mainModule) : '') + ");\n" ); - } + }); if (meteorPackageInstalls.length === 0) { continue; From 92233e132cb98ae969a38f9ab1b7d4d94f0486b9 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Tue, 6 Dec 2022 20:35:57 -0300 Subject: [PATCH 0098/1965] Require review alternative to Fibers uses --- .../common/livedata_connection.js | 3 +- .../test/livedata_test_service.js | 6 ++-- .../ddp-client/common/livedata_connection.js | 3 +- packages/ddp-client/package.js | 5 --- packages/ddp-server/writefence.js | 3 +- .../stylus/plugin/compile-stylus.js | 3 +- packages/meteor/async_helpers.js | 2 -- packages/meteor/helpers.js | 4 ++- tools/isobuild/bundler.js | 1 - tools/static-assets/server/boot.js | 31 ++++++++++--------- 10 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/ddp-client-async/common/livedata_connection.js b/packages/ddp-client-async/common/livedata_connection.js index c30ff6f48d..1ea402ec98 100644 --- a/packages/ddp-client-async/common/livedata_connection.js +++ b/packages/ddp-client-async/common/livedata_connection.js @@ -19,7 +19,8 @@ let Fiber; let Future; if (Meteor.isServer) { Fiber = Npm.require('fibers'); - Future = Npm.require('fibers/future'); + // TODO Review fiber use + Future = Meteor._isFibersEnabled ? Npm.require('fibers/future') : null; } class MongoIDMap extends IdMap { diff --git a/packages/ddp-client-async/test/livedata_test_service.js b/packages/ddp-client-async/test/livedata_test_service.js index db32cf3cc4..11326e99c3 100644 --- a/packages/ddp-client-async/test/livedata_test_service.js +++ b/packages/ddp-client-async/test/livedata_test_service.js @@ -34,7 +34,8 @@ Meteor.methods({ // We used to improperly serialize errors that were thrown through a // future first. - if (Meteor.isServer && options.throwThroughFuture) { + // TODO Review fiber use + if (Meteor.isServer && options.throwThroughFuture && Meteor._isFibersEnabled) { const Future = Npm.require('fibers/future'); const f = new Future(); f['throw'](e); @@ -59,7 +60,8 @@ if (Meteor.isServer) { // other. const waiters = Object.create(null); - const Future = Npm.require('fibers/future'); + // TODO Review fiber use + const Future = Meteor._isFibersEnabled ? Npm.require('fibers/future') : null; const returnThroughFuture = function(token, returnValue) { // Make sure that when we call return, the fields are already cleared. diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index c30ff6f48d..da7bd848c4 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -17,7 +17,8 @@ import { let Fiber; let Future; -if (Meteor.isServer) { +// TODO Review fiber use +if (Meteor.isServer && Meteor._isFibersEnabled) { Fiber = Npm.require('fibers'); Future = Npm.require('fibers/future'); } diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 64cbf8d09e..0cdc77a953 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -9,11 +9,6 @@ Npm.depends({ }); Package.onUse((api) => { - if (process.env.DISABLE_FIBERS) { - api.use('ddp-client-async'); - api.export('DDP', 'server'); - return; - } api.use([ 'check', 'random', diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index e9310c9f7f..d480f89500 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -1,4 +1,5 @@ -var Future = Npm.require('fibers/future'); +// TODO Review fiber use +var Future = Meteor._isFibersEnabled ? Npm.require('fibers/future') : null; // A write fence collects a group of writes, and provides a callback // when all of the writes are fully committed and propagated (all diff --git a/packages/deprecated/stylus/plugin/compile-stylus.js b/packages/deprecated/stylus/plugin/compile-stylus.js index cc0003d3e3..5d6e5b492d 100644 --- a/packages/deprecated/stylus/plugin/compile-stylus.js +++ b/packages/deprecated/stylus/plugin/compile-stylus.js @@ -1,7 +1,8 @@ const stylus = Npm.require('stylus'); const nib = Npm.require('nib'); const autoprefixer = Npm.require('autoprefixer-stylus'); -const Future = Npm.require('fibers/future'); +// TODO Review fiber use +const Future = Meteor._isFibersEnabled ? Npm.require('fibers/future') : null; const fs = Plugin.fs; const path = Plugin.path; diff --git a/packages/meteor/async_helpers.js b/packages/meteor/async_helpers.js index e326265c03..e8862322f3 100644 --- a/packages/meteor/async_helpers.js +++ b/packages/meteor/async_helpers.js @@ -1,5 +1,3 @@ -var Fiber = Npm.require('fibers'); -var Future = Npm.require('fibers/future'); Meteor._noYieldsAllowed = function (f) { return f(); diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index 242921945c..2cdac99646 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -1,4 +1,6 @@ -if (Meteor.isServer) + +// TODO Review fiber use +if (Meteor.isServer && Meteor._isFibersEnabled) var Future = Npm.require('fibers/future'); if (typeof __meteor_runtime_config__ === 'object' && diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 05157fea70..c354eac1d4 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -149,7 +149,6 @@ // wait until later. var assert = require('assert'); -var Fiber = require('fibers'); var _ = require('underscore'); var compiler = require('./compiler.js'); diff --git a/tools/static-assets/server/boot.js b/tools/static-assets/server/boot.js index 68ae408045..c70dcaaa0b 100644 --- a/tools/static-assets/server/boot.js +++ b/tools/static-assets/server/boot.js @@ -1,7 +1,7 @@ -var Fiber = require("fibers"); +// var Fiber = require("fibers"); var fs = require("fs"); var path = require("path"); -var Future = require("fibers/future"); +//var Future = require("fibers/future"); var sourcemap_support = require('source-map-support'); var bootUtils = require('./boot-utils.js'); @@ -52,8 +52,9 @@ if (!process.env.APP_ID) { // Map from load path to its source map. var parsedSourceMaps = {}; +// TODO Review fiber use const meteorDebugFuture = - process.env.METEOR_INSPECT_BRK ? new Future : null; + process.env.METEOR_INSPECT_BRK ? null : null; function maybeWaitForDebuggerToAttach() { if (meteorDebugFuture) { @@ -75,7 +76,7 @@ function maybeWaitForDebuggerToAttach() { `Debugger did not attach after ${waitLimitMinutes} minutes; continuing.` ); - meteorDebugFuture.return(); + //meteorDebugFuture.return(); } else { // This pause function contains a debugger keyword that will only @@ -95,7 +96,7 @@ function maybeWaitForDebuggerToAttach() { // time, we can conclude the debugger keyword must be active, // which means a debugging client must be connected, which means // we should stop polling and let the main Fiber continue. - meteorDebugFuture.return(); + //meteorDebugFuture.return(); } else { // If the pause() function call didn't take a meaningful amount @@ -108,7 +109,7 @@ function maybeWaitForDebuggerToAttach() { }, pollIntervalMs); // The polling will continue while we wait here. - meteorDebugFuture.wait(); + //meteorDebugFuture.wait(); } } @@ -311,8 +312,8 @@ var loadServerBundles = Profile("Load server bundles", function () { var getAsset = function (assetPath, encoding, callback) { var fut; if (! callback) { - fut = new Future(); - callback = fut.resolver(); + //fut = new Future(); + //callback = fut.resolver(); } // This assumes that we've already loaded the meteor package, so meteor // itself can't call Assets.get*. (We could change this function so that @@ -499,12 +500,12 @@ function startServerProcess() { }); } -if (IS_FIBERS_ENABLED) { - Fiber(function() { - startServerProcess(); - }).run(); - return; -} else { +// if (IS_FIBERS_ENABLED) { +// Fiber(function() { +// startServerProcess(); +// }).run(); +// return; +// } else { startServerProcess(); -} +//} From b87eb586098d39aacb20f9de8dd15500695c9531 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Tue, 6 Dec 2022 21:12:34 -0300 Subject: [PATCH 0099/1965] Remove Fibers for Meteor Tools: - Correctly create hash from new file.data. --- tools/isobuild/compiler-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index d7b25a1b66..729e4e881b 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -1329,7 +1329,7 @@ export class PackageSourceBatch { meteorPackageInstalls.join(""), "utf8" ); - const fileHash = sha1(fileData); + const fileHash = sha1(bufferData); // The getter's from file (file.data and file.hash) are async, unfortunately. // That's why we need the Object.assign here. From 283b5acc2e2e151c3987805d9e56a0b3e8677001 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 7 Dec 2022 09:49:29 -0300 Subject: [PATCH 0100/1965] Login commands test --- tools/meteor-services/auth.js | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tools/meteor-services/auth.js b/tools/meteor-services/auth.js index 67fbf7333a..c90ccdc5a4 100644 --- a/tools/meteor-services/auth.js +++ b/tools/meteor-services/auth.js @@ -10,16 +10,16 @@ var Console = require('../console/console.js').Console; var auth = exports; -function loadDDP() { - return require("../tool-env/isopackets.js") - .loadIsopackage("ddp-client") - .DDP; +async function loadDDP() { + const isopackage = require("../tool-env/isopackets.js"); + const { DDP } = await isopackage.loadIsopackage("ddp-client"); + return DDP; } // Opens and returns a DDP connection to the accounts server. Remember // to close it when you're done with it! -var openAccountsConnection = function () { - return loadDDP().connect(config.getAuthDDPUrl(), { +var openAccountsConnection = async function () { + return (await loadDDP()).connect(config.getAuthDDPUrl(), { headers: { 'User-Agent': httpHelpers.getUserAgent() } }); }; @@ -28,9 +28,9 @@ var openAccountsConnection = function () { // that is a connection to the accounts server, which gets closed when // `f` returns or throws. var withAccountsConnection = function (f) { - return function (...args) { + return async function (...args) { var self = this; - var conn = openAccountsConnection(); + var conn = await openAccountsConnection(); args.push(conn); try { var result = f.apply(self, args); @@ -47,7 +47,7 @@ var withAccountsConnection = function (f) { // XXX if we reconnect we won't reauthenticate. Fix that before using // this for long-lived connections. var loggedInAccountsConnection = async function (token) { - var connection = loadDDP().connect( + var connection = (await loadDDP()).connect( config.getAuthDDPUrl() ); @@ -91,13 +91,13 @@ var loggedInAccountsConnection = async function (token) { // provided, one will be opened and then closed before returning. var sessionMethodCaller = function (methodName, options) { options = options || {}; - return function (...args) { + return async function (...args) { args.push({ session: auth.getSessionId(config.getAccountsDomain()) || null }); var timer; - var conn = options.connection || openAccountsConnection(); + var conn = options.connection || await openAccountsConnection(); function cleanUp() { timer && clearTimeout(timer); @@ -289,7 +289,7 @@ var removePendingRevoke = function (domain, tokenIds) { // session. just changes the error message. // - connection: an open connection to the accounts server. If not // provided, this function will open one itself. -var tryRevokeOldTokens = function (options) { +var tryRevokeOldTokens = async function (options) { options = Object.assign({ timeout: 5000 }, options || {}); @@ -313,8 +313,7 @@ var tryRevokeOldTokens = function (options) { warned = true; } }; - - _.each(domainsWithRevokedTokens, function (domain) { + for (const domain in domainsWithRevokedTokens) { var data = readSessionData(); var session = data.sessions[domain] || {}; var tokenIds = session.pendingRevoke || []; @@ -327,7 +326,7 @@ var tryRevokeOldTokens = function (options) { if (session.type === "meteor-account") { try { - sessionMethodCaller('revoke', { + await sessionMethodCaller('revoke', { timeout: options.timeout, connection: options.connection })(tokenIds); @@ -346,7 +345,7 @@ var tryRevokeOldTokens = function (options) { logoutFailWarning(domain); return; } - }); + } }; var sendAuthorizeRequest = function (clientId, redirectUri, state) { @@ -457,7 +456,7 @@ var oauthFlow = function (conn, options) { // error message to stderr if the login fails // - connection: an open connection to the accounts server. If not // provided, this function will open its own connection. -var doInteractivePasswordLogin = function (options) { +var doInteractivePasswordLogin = async function (options) { var loginData = {}; if (_.has(options, 'username')) { @@ -478,7 +477,7 @@ var doInteractivePasswordLogin = function (options) { } }; - var conn = options.connection || openAccountsConnection(); + var conn = options.connection || await openAccountsConnection(); var maybeCloseConnection = function () { if (! options.connection) { @@ -576,7 +575,7 @@ exports.loginCommand = withAccountsConnection(async function (options, loginOptions.connection = connection; - if (! doInteractivePasswordLogin(loginOptions)) { + if (! await doInteractivePasswordLogin(loginOptions)) { return 1; } } From 1df9607b6107909097b2b77830d14198b4739424 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 7 Dec 2022 09:49:52 -0300 Subject: [PATCH 0101/1965] Login commands test --- tools/cli/commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cli/commands.js b/tools/cli/commands.js index 71a49e5f8e..a853d175ef 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -2170,8 +2170,8 @@ main.registerCommand({ email: { type: Boolean } }, catalogRefresh: new catalog.Refresh.Never() -}, function (options) { - return auth.loginCommand(Object.assign({ +}, async function (options) { + return await auth.loginCommand(Object.assign({ overwriteExistingToken: true }, options)); }); From 1fc3d0cd6492b7f60d50a21655b86fc77ac7d630 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 7 Dec 2022 14:19:25 -0400 Subject: [PATCH 0102/1965] fixing livedata stub and livedata connection tests --- .../test/livedata_connection_tests.js | 46 +++++++++---------- .../ddp-client/test/livedata_test_service.js | 15 +++--- packages/ddp-client/test/livedata_tests.js | 34 +++++++------- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/ddp-client/test/livedata_connection_tests.js b/packages/ddp-client/test/livedata_connection_tests.js index 3b9d4ce904..58bc55bd59 100644 --- a/packages/ddp-client/test/livedata_connection_tests.js +++ b/packages/ddp-client/test/livedata_connection_tests.js @@ -477,8 +477,8 @@ if (Meteor.isClient) { // setup method conn.methods({ - do_something: async function(x) { - await coll.insertAsync({ value: x }); + do_something: function(x) { + coll.insert({ value: x }); } }); @@ -522,7 +522,7 @@ if (Meteor.isClient) { test.equal(await coll.find({}).count(), 1); test.equal(await coll.find({ value: 'friday!' }).count(), 1); - const docId = await coll.findOneAsync({ value: 'friday!' })._id; + const docId = (await coll.findOneAsync({ value: 'friday!' }))._id; // results does not yet result in callback, because data is not // ready. @@ -683,7 +683,7 @@ if (Meteor.isClient) { // but inner method runs locally. o.expectCallbacks({ added: 1 }); test.equal(coll.find().count(), 1); - const docId = await coll.findOneAsync()._id; + const docId = (await coll.findOneAsync())._id; test.equal(await coll.findOneAsync(), { _id: docId, a: 1 }); // we get the results @@ -1006,7 +1006,7 @@ if (Meteor.isClient) { const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); - const o = observeCursor(test, coll.find()); + const o = await observeCursor(test, coll.find()); conn.methods({ writeSomething: function() { @@ -1034,7 +1034,7 @@ if (Meteor.isClient) { ); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId = await coll.findOneAsync({ foo: 'bar' })._id; + const stubWrittenId = (await coll.findOneAsync({ foo: 'bar' }))._id; o.expectCallbacks({ added: 1 }); // Callback not called. test.equal(callbackOutput, []); @@ -1124,7 +1124,7 @@ if (Meteor.isClient) { ); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId2 = await coll.findOneAsync({ foo: 'bar' })._id; + const stubWrittenId2 = (await coll.findOneAsync({ foo: 'bar' }))._id; o.expectCallbacks({ added: 1 }); // Callback not called. test.equal(callbackOutput, ['bla']); @@ -1343,7 +1343,7 @@ if (Meteor.isClient) { const collName = Random.id(); const coll = new Mongo.Collection(collName, { connection: conn }); - const o = observeCursor(test, coll.find()); + const o = await observeCursor(test, coll.find()); conn.methods({ insertSomething: function() { @@ -1361,7 +1361,7 @@ if (Meteor.isClient) { conn.call('insertSomething', _.identity); // Stub write is visible. test.equal(coll.find({ foo: 'bar' }).count(), 1); - const stubWrittenId = await coll.findOneAsync({ foo: 'bar' })._id; + const stubWrittenId = (await coll.findOneAsync({ foo: 'bar' }))._id; o.expectCallbacks({ added: 1 }); // Method sent. const insertMethodId = testGotMessage(test, stream, { @@ -2338,7 +2338,7 @@ if (Meteor.isClient) { await stream.receive(subDocMessage); await stream.receive(subReadyMessage); - test.isTrue(await coll.findOneAsync('aaa').value == 111); + test.isTrue((await coll.findOneAsync('aaa')).value == 111); // Initiate reconnect. await stream.reset(); @@ -2347,12 +2347,12 @@ if (Meteor.isClient) { await stream.receive({ msg: 'connected', session: SESSION_ID + 1 }); // Now in reconnect, can still see the document. - test.isTrue(await coll.findOneAsync('aaa').value == 111); + test.isTrue((await coll.findOneAsync('aaa')).value == 111); await conn.callAsync('update_value'); // Observe the stub-written value. - test.isTrue(await coll.findOneAsync('aaa').value == 222); + test.isTrue((await coll.findOneAsync('aaa')).value == 222); let methodMessage = JSON.parse(stream.sent.shift()); test.equal(methodMessage, { @@ -2369,7 +2369,7 @@ if (Meteor.isClient) { // By this point quiescence is reached and stores have been reset. // The stub-written value is still there. - test.isTrue(await coll.findOneAsync('aaa').value == 222); + test.isTrue((await coll.findOneAsync('aaa')).value == 222); await stream.receive({ msg: 'changed', @@ -2381,7 +2381,7 @@ if (Meteor.isClient) { await stream.receive({ msg: 'result', id: methodMessage.id, result: null }); // Server wrote a different value, make sure it's visible now. - test.isTrue(await coll.findOneAsync('aaa').value == 333); + test.isTrue((await coll.findOneAsync('aaa')).value == 333); } ); @@ -2402,7 +2402,7 @@ if (Meteor.isClient) { conn.methods({ update_value: async function() { - const value = await coll.findOneAsync('aaa').subscription; + const value = (await coll.findOneAsync('aaa')).subscription; // Method should have access to the latest value of the collection. coll.update('aaa', { $set: { method: value + 110 } }); } @@ -2430,7 +2430,7 @@ if (Meteor.isClient) { await stream.receive(subDocMessage); await stream.receive(subReadyMessage); - test.equal(await coll.findOneAsync('aaa').subscription, 111); + test.equal((await coll.findOneAsync('aaa')).subscription, 111); const subDocChangeMessage = { msg: 'changed', @@ -2441,16 +2441,16 @@ if (Meteor.isClient) { await stream.receive(subDocChangeMessage); // Still 111 because buffer has not been flushed. - test.equal(await coll.findOneAsync('aaa').subscription, 111); + test.equal((await coll.findOneAsync('aaa')).subscription, 111); // Call updates the stub. await conn.callAsync('update_value'); // Observe the stub-written value. - test.equal(await coll.findOneAsync('aaa').method, 222); + test.equal((await coll.findOneAsync('aaa')).method, 222); // subscription field is updated to the latest value // because of the method call. - test.equal(await coll.findOneAsync('aaa').subscription, 112); + test.equal((await coll.findOneAsync('aaa')).subscription, 112); let methodMessage = JSON.parse(stream.sent.shift()); test.equal(methodMessage, { @@ -2473,14 +2473,14 @@ if (Meteor.isClient) { await stream.receive({ msg: 'updated', methods: [methodMessage.id] }); await stream.receive({ msg: 'result', id: methodMessage.id, result: null }); - test.equal(await coll.findOneAsync('aaa').method, 222); - test.equal(await coll.findOneAsync('aaa').subscription, 112); + test.equal((await coll.findOneAsync('aaa')).method, 222); + test.equal((await coll.findOneAsync('aaa')).subscription, 112); // Buffer should already be flushed because of a non-update message. // And after a flush we really want subscription field to be 112. conn._flushBufferedWrites(); - test.equal(await coll.findOneAsync('aaa').method, 222); - test.equal(await coll.findOneAsync('aaa').subscription, 112); + test.equal((await coll.findOneAsync('aaa')).method, 222); + test.equal((await coll.findOneAsync('aaa')).subscription, 112); }); } diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index 9f0c7d1fe4..9a3aa7cc6f 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -59,28 +59,29 @@ if (Meteor.isServer) { // other. const waiters = Object.create(null); - const Future = Npm.require('fibers/future'); - const returnThroughFuture = function(token, returnValue) { // Make sure that when we call return, the fields are already cleared. const record = waiters[token]; if (!record) return; delete waiters[token]; - record.future['return'](returnValue); + record.future(returnValue); }; Meteor.methods({ delayedTrue: function(token) { check(token, String); - const record = (waiters[token] = { - future: new Future(), + + let resolver; + const promise = new Promise(res => resolver = res); + waiters[token] = { + future: resolver, timer: Meteor.setTimeout(function() { returnThroughFuture(token, true); }, 1000) - }); + }; this.unblock(); - return record.future.wait(); + return promise; }, makeDelayedTrueImmediatelyReturnFalse: function(token) { check(token, String); diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index a5fae51937..951ee11aa0 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -79,10 +79,10 @@ Tinytest.add('livedata - non-function method', function(test) { }); const echoTest = function(item) { - return function(test, expect) { + return async function(test, expect) { if (Meteor.isServer) { - test.equal(Meteor.call('echo', item), [item]); - test.equal(Meteor.call('echoOne', item), item); + test.equal(await Meteor.callAsync('echo', item), [item]); + test.equal(await Meteor.callAsync('echoOne', item), item); } if (Meteor.isClient) test.equal(Meteor.call('echo', item), undefined); @@ -96,13 +96,13 @@ const echoTest = function(item) { testAsyncMulti('livedata - basic method invocation', [ // Unknown methods - function(test, expect) { + async function(test, expect) { if (Meteor.isServer) { // On server, with no callback, throws exception let ret; let threw; try { - ret = Meteor.call('unknown method'); + ret = await Meteor.callAsync('unknown method'); } catch (e) { test.equal(e.error, 404); threw = true; @@ -125,18 +125,18 @@ testAsyncMulti('livedata - basic method invocation', [ test.equal(ret, undefined); }, - function(test, expect) { + async function(test, expect) { // make sure 'undefined' is preserved as such, instead of turning // into null (JSON does not have 'undefined' so there is special // code for this) - if (Meteor.isServer) test.equal(Meteor.call('nothing'), undefined); + if (Meteor.isServer) test.equal(await Meteor.callAsync('nothing'), undefined); if (Meteor.isClient) test.equal(Meteor.call('nothing'), undefined); test.equal(Meteor.call('nothing', expect(undefined, undefined)), undefined); }, - function(test, expect) { - if (Meteor.isServer) test.equal(Meteor.call('echo'), []); + async function(test, expect) { + if (Meteor.isServer) test.equal(await Meteor.callAsync('echo'), []); if (Meteor.isClient) test.equal(Meteor.call('echo'), undefined); test.equal(Meteor.call('echo', expect(undefined, [])), undefined); @@ -153,9 +153,9 @@ testAsyncMulti('livedata - basic method invocation', [ echoTest(Infinity), echoTest(-Infinity), - function(test, expect) { + async function(test, expect) { if (Meteor.isServer) - test.equal(Meteor.call('echo', 12, { x: 13 }), [12, { x: 13 }]); + test.equal(await Meteor.callAsync('echo', 12, { x: 13 }), [12, { x: 13 }]); if (Meteor.isClient) test.equal(Meteor.call('echo', 12, { x: 13 }), undefined); @@ -198,7 +198,7 @@ testAsyncMulti('livedata - basic method invocation', [ } }, - function(test, expect) { + async function(test, expect) { // No callback if (Meteor.isServer) { @@ -209,7 +209,7 @@ testAsyncMulti('livedata - basic method invocation', [ Meteor.call('exception', 'server'); }); // No exception, because no code will run on the client - test.equal(Meteor.call('exception', 'client'), undefined); + test.equal(await Meteor.callAsync('exception', 'client'), undefined); } if (Meteor.isClient) { @@ -273,15 +273,15 @@ testAsyncMulti('livedata - basic method invocation', [ ), undefined ); - test.equal(Meteor.call('exception', 'client'), undefined); + test.equal(await Meteor.callAsync('exception', 'client'), undefined); } }, - function(test, expect) { + async function(test, expect) { if (Meteor.isServer) { let threw = false; try { - Meteor.call('exception', 'both', { intended: true }); + await Meteor.callAsync('exception', 'both', { intended: true }); } catch (e) { threw = true; test.equal(e.error, 999); @@ -290,7 +290,7 @@ testAsyncMulti('livedata - basic method invocation', [ test.isTrue(threw); threw = false; try { - Meteor.call('exception', 'both', { + await Meteor.callAsync('exception', 'both', { intended: true, throwThroughFuture: true }); From 068e655514244cba0940b549409e45e479f5dc57 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 7 Dec 2022 14:23:18 -0400 Subject: [PATCH 0103/1965] fixing "livedata - method call on server blocks in a fiber way" test --- packages/ddp-client/test/livedata_tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index 951ee11aa0..66a835ae8f 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -992,10 +992,10 @@ if (Meteor.isServer) { ); }, - function(test, expect) { + async function(test, expect) { const self = this; if (self.conn.status().connected) { - test.equal(self.conn.call('s2s', 'foo'), 's2s foo'); + test.equal(await self.conn.callAsync('s2s', 'foo'), 's2s foo'); } } ]); From dc21bbeb2a47493c3bf442d86efb70c935935dc5 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 7 Dec 2022 14:27:34 -0400 Subject: [PATCH 0104/1965] fixing "livedata - DDP.randomStream" test --- packages/ddp-client/test/random_stream_tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ddp-client/test/random_stream_tests.js b/packages/ddp-client/test/random_stream_tests.js index 03b1ce05ec..b2a44281fe 100644 --- a/packages/ddp-client/test/random_stream_tests.js +++ b/packages/ddp-client/test/random_stream_tests.js @@ -1,8 +1,8 @@ -Tinytest.add('livedata - DDP.randomStream', function(test) { +Tinytest.addAsync('livedata - DDP.randomStream', async function(test) { const randomSeed = Random.id(); const context = { randomSeed: randomSeed }; - let sequence = DDP._CurrentMethodInvocation.withValue(context, function() { + let sequence = await DDP._CurrentMethodInvocation.withValue(context, function() { return DDP.randomStream('1'); }); @@ -21,7 +21,7 @@ Tinytest.add('livedata - DDP.randomStream', function(test) { test.equal(id1, id1Cloned); // We should get the same sequence when we use the same key - sequence = DDP._CurrentMethodInvocation.withValue(context, function() { + sequence = await DDP._CurrentMethodInvocation.withValue(context, function() { return DDP.randomStream('1'); }); seeds = sequence.alea.args; From e89ecdda323a7532fb0e2d3b46ad2682ed9814d6 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:45:43 -0300 Subject: [PATCH 0105/1965] tests: removed other tests --- packages/accounts-base/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index b707f589a2..9c89bb5fd5 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -63,5 +63,5 @@ Package.onTest(api => { api.addFiles('accounts_tests_setup.js', 'server'); api.mainModule('server_tests.js', 'server'); - api.mainModule('client_tests.js', 'client'); + // api.mainModule('client_tests.js', 'client'); }); From 2333bc35a3acd208715a59b9fb2390b921db8a4b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:45:58 -0300 Subject: [PATCH 0106/1965] tests: made setup async --- packages/accounts-base/accounts_tests_setup.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index bd79562fe0..31ed7e2196 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -1,25 +1,25 @@ -const getTokenFromSecret = ({ selector, secret: secretParam }) => { +const getTokenFromSecret = async ({ selector, secret: secretParam }) => { let secret = secretParam; if (!secret) { const { services: { twoFactorAuthentication } = {} } = - Meteor.users.findOne(selector) || {}; + await Meteor.users.findOne(selector) || {}; if (!twoFactorAuthentication) { throw new Meteor.Error(500, 'twoFactorAuthentication not set.'); } secret = twoFactorAuthentication.secret; } - const { token } = Accounts._generate2faToken(secret); + const { token } = Accounts._generate2faToken(secret); return token; }; Meteor.methods({ - removeAccountsTestUser(username) { - Meteor.users.remove({ username }); + async removeAccountsTestUser(username) { + await Meteor.users.remove({ username }); }, - forceEnableUser2fa(selector, secret) { - Meteor.users.update( + async forceEnableUser2fa(selector, secret) { + await Meteor.users.update( selector, { $set: { From ba98f5fe31107652fce5e7eb9bcde5094e41830b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:46:10 -0300 Subject: [PATCH 0107/1965] test: made all tests async --- packages/accounts-base/accounts_tests.js | 58 ++++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 797bd758f0..266c6a0bd0 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -17,14 +17,14 @@ Tinytest.add( test => test.throws(() => Accounts.config({foo: "bar"})) ); -Tinytest.add('accounts - config - token lifetime', test => { +Tinytest.addAsync('accounts - config - token lifetime', async test => { const { loginExpirationInDays } = Accounts._options; Accounts._options.loginExpirationInDays = 2; test.equal(Accounts._getTokenLifetimeMs(), 2 * 24 * 60 * 60 * 1000); Accounts._options.loginExpirationInDays = loginExpirationInDays; }); -Tinytest.add('accounts - config - unexpiring tokens', test => { +Tinytest.addAsync('accounts - config - unexpiring tokens', async test => { const { loginExpirationInDays } = Accounts._options; // When setting loginExpirationInDays to null in the global Accounts @@ -52,7 +52,7 @@ Tinytest.add('accounts - config - unexpiring tokens', test => { Accounts._options.loginExpirationInDays = loginExpirationInDays; }); -Tinytest.add('accounts - config - default token lifetime', test => { +Tinytest.addAsync('accounts - config - default token lifetime', async test => { const options = Accounts._options; Accounts._options = {}; test.equal( @@ -62,7 +62,7 @@ Tinytest.add('accounts - config - default token lifetime', test => { Accounts._options = options; }); -Tinytest.add('accounts - config - defaultFieldSelector', test => { +Tinytest.addAsync('accounts - config - defaultFieldSelector', async test => { const options = Accounts._options; Accounts._options = {}; const setValue = {bigArray: 0}; @@ -77,12 +77,12 @@ Accounts.validateNewUser(user => { return true; }); -Tinytest.add('accounts - validateNewUser gets passed user with _id', test => { - const newUserId = Accounts.updateOrCreateUserFromExternalService('foobook', {id: Random.id()}).userId; - test.isTrue(newUserId in idsInValidateNewUser); +Tinytest.addAsync('accounts - validateNewUser gets passed user with _id', async test => { + const { userId } = await Accounts.updateOrCreateUserFromExternalService('foobook', { id: Random.id() }); + test.isTrue(userId in idsInValidateNewUser); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { const facebookId = Random.id(); // create an account with facebook @@ -112,7 +112,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', test Meteor.users.remove(uid1); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Meteor Developer', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { const developerId = Random.id(); const uid1 = Accounts.updateOrCreateUserFromExternalService( 'meteor-developer', @@ -138,7 +138,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Meteor Develope Meteor.users.remove(uid1); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Weibo', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { const weiboId1 = Random.id(); const weiboId2 = Random.id(); @@ -158,7 +158,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Weibo', test => Meteor.users.remove(uid2); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Twitter', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { const twitterIdOld = parseInt(Random.hexString(4), 16); const twitterIdNew = ''+twitterIdOld; @@ -184,7 +184,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Twitter', test }); -Tinytest.add('accounts - insertUserDoc username', test => { +Tinytest.addAsync('accounts - insertUserDoc username', async test => { const userIn = { username: Random.id() }; @@ -210,7 +210,7 @@ Tinytest.add('accounts - insertUserDoc username', test => { Meteor.users.remove(userId); }); -Tinytest.add('accounts - insertUserDoc email', test => { +Tinytest.addAsync('accounts - insertUserDoc email', async test => { const email1 = Random.id(); const email2 = Random.id(); const email3 = Random.id(); @@ -380,7 +380,7 @@ Tinytest.addAsync( } ); -Tinytest.add('accounts - get new token', test => { +Tinytest.addAsync('accounts - get new token', async test => { // Test that the `getNewToken` method returns us a valid token, with // the same expiration as our original token. const userId = Accounts.insertUserDoc({}, { username: Random.id() }); @@ -441,9 +441,9 @@ Tinytest.addAsync('accounts - remove other tokens', (test, onComplete) => { } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - hook callbacks can access Meteor.userId()', - test => { + async test => { const userId = Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); Accounts._insertLoginToken(userId, stampedToken); @@ -491,9 +491,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - hook callbacks obey options.defaultFieldSelector', - test => { + async test => { const ignoreFieldName = "bigArray"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); const stampedToken = Accounts._generateStampedLoginToken(); @@ -548,9 +548,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - Meteor.user() obeys options.defaultFieldSelector', - test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -607,7 +607,7 @@ Tinytest.add( Tinytest.addAsync( 'accounts async - Meteor.userAsync() obeys options.defaultFieldSelector', - async test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -660,9 +660,9 @@ Tinytest.addAsync( Accounts.userId = origAccountsUserId; } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify onExternalLogin hook can update oauth user profiles', - test => { + async test => { // Verify user profile data is saved properly when not using the // onExternalLogin hook. let facebookId = Random.id(); @@ -721,9 +721,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify beforeExternalLogin hook can stop user login', - test => { + async test => { // Verify user data is saved properly when not using the // beforeExternalLogin hook. let facebookId = Random.id(); @@ -762,9 +762,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify setAdditionalFindUserOnExternalLogin hook can provide user', - test => { + async test => { // create test user, without a google service const testEmail = "test@testdomain.com" const uid0 = Accounts.createUser({email: testEmail}) @@ -795,9 +795,9 @@ Tinytest.add( ); if(Meteor.isServer) { - Tinytest.add( + Tinytest.addAsync( 'accounts - make sure that extra params to accounts urls are added', - test => { + async test => { // No extra params const verifyEmailURL = new URL(Accounts.urls.verifyEmail('test')); test.equal(verifyEmailURL.searchParams.toString(), ""); From ac1dfe897422d0a2da8426e27353d5f03d8e6970 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:46:23 -0300 Subject: [PATCH 0108/1965] chore: updated test methods --- packages/accounts-base/accounts_server.js | 148 ++++++++++++---------- 1 file changed, 84 insertions(+), 64 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 2ad5a2cb83..f9c4e82bc5 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -918,6 +918,12 @@ export class AccountsServer extends AccountsCommon { ); }; + /** + * + * @param userId + * @private + * @returns {Promise} + */ _clearAllLoginTokens(userId) { this.users.update(userId, { $set: { @@ -1099,7 +1105,14 @@ export class AccountsServer extends AccountsCommon { // tests. oldestValidDate is simulate expiring tokens without waiting // for them to actually expire. userId is used by tests to only expire // tokens for the test user. - _expireTokens(oldestValidDate, userId) { + /** + * + * @param oldestValidDate + * @param userId + * @private + * @return {Promise} + */ + async _expireTokens(oldestValidDate, userId) { const tokenLifetimeMs = this._getTokenLifetimeMs(); // when calling from a test with extra arguments, you must specify both! @@ -1114,7 +1127,7 @@ export class AccountsServer extends AccountsCommon { // Backwards compatible with older versions of meteor that stored login token // timestamps as numbers. - this.users.update({ ...userFilter, + await this.users.update({ ...userFilter, $or: [ { "services.resume.loginTokens.when": { $lt: oldestValidDate } }, { "services.resume.loginTokens.when": { $lt: +oldestValidDate } } @@ -1151,7 +1164,7 @@ export class AccountsServer extends AccountsCommon { }; // Called by accounts-password - insertUserDoc(options, user) { + async insertUserDoc(options, user) { // - clone user document, to protect from modification // - add createdAt timestamp // - prepare an _id, so that you can modify other collections (eg @@ -1196,7 +1209,7 @@ export class AccountsServer extends AccountsCommon { let userId; try { - userId = this.users.insert(fullUser); + userId = await this.users.insert(fullUser); } catch (e) { // XXX string parsing sucks, maybe // https://jira.mongodb.org/browse/SERVER-3069 will get fixed one day @@ -1226,9 +1239,9 @@ export class AccountsServer extends AccountsCommon { /// CLEAN UP FOR `logoutOtherClients` /// - _deleteSavedTokensForUser(userId, tokensToDelete) { + async _deleteSavedTokensForUser(userId, tokensToDelete) { if (tokensToDelete) { - this.users.update(userId, { + await this.users.update(userId, { $unset: { "services.resume.haveLoginTokensToDelete": 1, "services.resume.loginTokensToDelete": 1 @@ -1247,16 +1260,23 @@ export class AccountsServer extends AccountsCommon { // shouldn't happen very often. We shouldn't put a delay here because // that would give a lot of power to an attacker with a stolen login // token and the ability to crash the server. - Meteor.startup(() => { - this.users.find({ + Meteor.startup(async () => { + await this.users.find({ "services.resume.haveLoginTokensToDelete": true - }, {fields: { + }, { + fields: { "services.resume.loginTokensToDelete": 1 - }}).forEach(user => { + } + }).forEach(user => { this._deleteSavedTokensForUser( user._id, user.services.resume.loginTokensToDelete - ); + ) + // We don't need to wait for this to complete. + .then(_ => _) + .catch(err => { + console.log(err); + }); }); }); }; @@ -1276,7 +1296,7 @@ export class AccountsServer extends AccountsCommon { // @returns {Object} Object with token and id keys, like the result // of the "login" method. // - updateOrCreateUserFromExternalService( + async updateOrCreateUserFromExternalService( serviceName, serviceData, options @@ -1311,9 +1331,7 @@ export class AccountsServer extends AccountsCommon { } else { selector[serviceIdKey] = serviceData.id; } - - let user = this.users.findOne(selector, {fields: this._options.defaultFieldSelector}); - + let user = await this.users.findOne(selector, {fields: this._options.defaultFieldSelector}); // Check to see if the developer has a custom way to find the user outside // of the general selectors above. if (!user && this._additionalFindUserOnExternalLogin) { @@ -1337,7 +1355,7 @@ export class AccountsServer extends AccountsCommon { } if (user) { - pinEncryptedFieldsToUser(serviceData, user._id); + await pinEncryptedFieldsToUser(serviceData, user._id); let setAttrs = {}; Object.keys(serviceData).forEach(key => @@ -1347,7 +1365,7 @@ export class AccountsServer extends AccountsCommon { // XXX Maybe we should re-use the selector above and notice if the update // touches nothing? setAttrs = { ...setAttrs, ...opts }; - this.users.update(user._id, { + await this.users.update(user._id, { $set: setAttrs }); @@ -1359,9 +1377,10 @@ export class AccountsServer extends AccountsCommon { // Create a new user with the service data. user = {services: {}}; user.services[serviceName] = serviceData; + const userId = await this.insertUserDoc(opts, user); return { type: serviceName, - userId: this.insertUserDoc(opts, user) + userId }; } }; @@ -1543,7 +1562,7 @@ const setupDefaultLoginHandlers = accounts => { }; // Login handler for resume tokens. -const defaultResumeLoginHandler = (accounts, options) => { +const defaultResumeLoginHandler = async (accounts, options) => { if (!options.resume) return undefined; @@ -1554,7 +1573,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // First look for just the new-style hashed login token, to avoid // sending the unhashed token to the database in a query if we don't // need to. - let user = accounts.users.findOne( + let user = await accounts.users.findOne( {"services.resume.loginTokens.hashedToken": hashedToken}, {fields: {"services.resume.loginTokens.$": 1}}); @@ -1564,7 +1583,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // the old-style token OR the new-style token, because another // client connection logging in simultaneously might have already // converted the token. - user = accounts.users.findOne({ + user = await accounts.users.findOne({ $or: [ {"services.resume.loginTokens.hashedToken": hashedToken}, {"services.resume.loginTokens.token": options.resume} @@ -1583,13 +1602,13 @@ const defaultResumeLoginHandler = (accounts, options) => { // {hashedToken, when} for a hashed token or {token, when} for an // unhashed token. let oldUnhashedStyleToken; - let token = user.services.resume.loginTokens.find(token => + let token = await user.services.resume.loginTokens.find(token => token.hashedToken === hashedToken ); if (token) { oldUnhashedStyleToken = false; } else { - token = user.services.resume.loginTokens.find(token => + token = await user.services.resume.loginTokens.find(token => token.token === options.resume ); oldUnhashedStyleToken = true; @@ -1609,7 +1628,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // after we read it). Using $addToSet avoids getting an index // error if another client logging in simultaneously has already // inserted the new hashed token. - accounts.users.update( + await accounts.users.update( { _id: user._id, "services.resume.loginTokens.token": options.resume @@ -1625,7 +1644,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // Remove the old token *after* adding the new, since otherwise // another client trying to login between our removing the old and // adding the new wouldn't find a token to login with. - accounts.users.update(user._id, { + await accounts.users.update(user._id, { $pull: { "services.resume.loginTokens": { "token": options.resume } } @@ -1641,49 +1660,50 @@ const defaultResumeLoginHandler = (accounts, options) => { }; }; -const expirePasswordToken = ( - accounts, - oldestValidDate, - tokenFilter, - userId -) => { - // boolean value used to determine if this method was called from enroll account workflow - let isEnroll = false; - const userFilter = userId ? {_id: userId} : {}; - // check if this method was called from enroll account workflow - if(tokenFilter['services.password.enroll.reason']) { - isEnroll = true; - } - let resetRangeOr = { - $or: [ - { "services.password.reset.when": { $lt: oldestValidDate } }, - { "services.password.reset.when": { $lt: +oldestValidDate } } - ] - }; - if(isEnroll) { - resetRangeOr = { +const expirePasswordToken = + async ( + accounts, + oldestValidDate, + tokenFilter, + userId + ) => { + // boolean value used to determine if this method was called from enroll account workflow + let isEnroll = false; + const userFilter = userId ? { _id: userId } : {}; + // check if this method was called from enroll account workflow + if (tokenFilter['services.password.enroll.reason']) { + isEnroll = true; + } + let resetRangeOr = { $or: [ - { "services.password.enroll.when": { $lt: oldestValidDate } }, - { "services.password.enroll.when": { $lt: +oldestValidDate } } + { "services.password.reset.when": { $lt: oldestValidDate } }, + { "services.password.reset.when": { $lt: +oldestValidDate } } ] }; - } - const expireFilter = { $and: [tokenFilter, resetRangeOr] }; - if(isEnroll) { - accounts.users.update({...userFilter, ...expireFilter}, { - $unset: { - "services.password.enroll": "" - } - }, { multi: true }); - } else { - accounts.users.update({...userFilter, ...expireFilter}, { - $unset: { - "services.password.reset": "" - } - }, { multi: true }); - } + if (isEnroll) { + resetRangeOr = { + $or: [ + { "services.password.enroll.when": { $lt: oldestValidDate } }, + { "services.password.enroll.when": { $lt: +oldestValidDate } } + ] + }; + } + const expireFilter = { $and: [tokenFilter, resetRangeOr] }; + if (isEnroll) { + await accounts.users.update({ ...userFilter, ...expireFilter }, { + $unset: { + "services.password.enroll": "" + } + }, { multi: true }); + } else { + await accounts.users.update({ ...userFilter, ...expireFilter }, { + $unset: { + "services.password.reset": "" + } + }, { multi: true }); + } -}; + }; const setExpireTokensInterval = accounts => { accounts.expireTokenInterval = Meteor.setInterval(() => { From aae8843a25db7bc6700e1a9dacf6a4efa605482d Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 8 Dec 2022 10:42:27 -0400 Subject: [PATCH 0109/1965] fixing "livedata - changing userid reruns subscriptions without flapping data on the wire" test --- .../ddp-client/test/livedata_test_service.js | 73 ++++++------- packages/ddp-client/test/livedata_tests.js | 100 ++++++++++-------- 2 files changed, 95 insertions(+), 78 deletions(-) diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index 9a3aa7cc6f..d278a8932b 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -157,47 +157,48 @@ Meteor.methods({ objectsWithUsers = new Mongo.Collection('objectsWithUsers'); -if (Meteor.isServer) { - objectsWithUsers.remove({}); - objectsWithUsers.insert({ name: 'owned by none', ownerUserIds: [null] }); - objectsWithUsers.insert({ name: 'owned by one - a', ownerUserIds: ['1'] }); - objectsWithUsers.insert({ - name: 'owned by one/two - a', - ownerUserIds: ['1', '2'] - }); - objectsWithUsers.insert({ - name: 'owned by one/two - b', - ownerUserIds: ['1', '2'] - }); - objectsWithUsers.insert({ name: 'owned by two - a', ownerUserIds: ['2'] }); - objectsWithUsers.insert({ name: 'owned by two - b', ownerUserIds: ['2'] }); +Meteor.startup(async function() { + if (Meteor.isServer) { + await objectsWithUsers.removeAsync({}); + await objectsWithUsers.insertAsync({name: 'owned by none', ownerUserIds: [null]}); + await objectsWithUsers.insertAsync({name: 'owned by one - a', ownerUserIds: ['1']}); + await objectsWithUsers.insertAsync({ + name: 'owned by one/two - a', + ownerUserIds: ['1', '2'] + }); + await objectsWithUsers.insertAsync({ + name: 'owned by one/two - b', + ownerUserIds: ['1', '2'] + }); + await objectsWithUsers.insertAsync({name: 'owned by two - a', ownerUserIds: ['2']}); + await objectsWithUsers.insertAsync({name: 'owned by two - b', ownerUserIds: ['2']}); - Meteor.publish('objectsWithUsers', function() { - return objectsWithUsers.find( - { ownerUserIds: this.userId }, - { fields: { ownerUserIds: 0 } } - ); - }); - - (function() { - const userIdWhenStopped = Object.create(null); - Meteor.publish('recordUserIdOnStop', function(key) { - check(key, String); - const self = this; - self.onStop(function() { - userIdWhenStopped[key] = self.userId; - }); + Meteor.publish('objectsWithUsers', function () { + return objectsWithUsers.find( + {ownerUserIds: this.userId}, + {fields: {ownerUserIds: 0}} + ); }); - Meteor.methods({ - userIdWhenStopped: function(key) { + (function () { + const userIdWhenStopped = Object.create(null); + Meteor.publish('recordUserIdOnStop', function (key) { check(key, String); - return userIdWhenStopped[key]; - } - }); - })(); -} + const self = this; + self.onStop(function () { + userIdWhenStopped[key] = self.userId; + }); + }); + Meteor.methods({ + userIdWhenStopped: function (key) { + check(key, String); + return userIdWhenStopped[key]; + } + }); + })(); + } +}); /*****/ /// Helper for "livedata - setUserId fails when called on server" diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index 66a835ae8f..2005952141 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -1,6 +1,8 @@ import { DDP } from '../common/namespace.js'; import { Connection } from '../common/livedata_connection.js'; +const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + // XXX should check error codes const failure = function(test, code, reason) { return function(error, result) { @@ -435,7 +437,7 @@ if (Meteor.isClient) { testAsyncMulti( 'livedata - changing userid reruns subscriptions without flapping data on the wire', [ - function(test, expect) { + async function(test, expect) { const messages = []; const undoEavesdrop = eavesdropOnCollection( Meteor.connection, @@ -484,54 +486,68 @@ if (Meteor.isClient) { let afterSecondSetUserId; let afterThirdSetUserId; - Meteor.subscribe( - 'objectsWithUsers', - expect(function() { - expectMessages(1, 0, ['owned by none']); - Meteor.apply( - 'setUserId', - ['1'], - { wait: true }, - afterFirstSetUserId - ); - }) - ); + const handle = Meteor.subscribe('objectsWithUsers'); - afterFirstSetUserId = expect(function() { - expectMessages(3, 1, [ - 'owned by one - a', - 'owned by one/two - a', - 'owned by one/two - b' - ]); + let control = 0; + // Just make sure the subscription is ready before running the tests + // As everything now runs async, the tests were running before the data fully came in + while (!handle.ready()) { + if (!handle.ready()) { + // Just in case something happens with the subscription, we have this control + if (control++ === 1000) { + throw new Error("Subscribe to objectsWithUsers is taking too long!"); + } + await _sleep(0); + return; + } + expectMessages(1, 0, ['owned by none']); Meteor.apply( 'setUserId', - ['2'], + ['1'], { wait: true }, - afterSecondSetUserId + afterFirstSetUserId ); - }); + afterFirstSetUserId = expect(function() { + expectMessages(3, 1, [ + 'owned by one - a', + 'owned by one/two - a', + 'owned by one/two - b', + ]); + Meteor.apply( + 'setUserId', + ['2'], + { wait: true }, + afterSecondSetUserId + ); + }); - afterSecondSetUserId = expect(function() { - expectMessages(2, 1, [ - 'owned by one/two - a', - 'owned by one/two - b', - 'owned by two - a', - 'owned by two - b' - ]); - Meteor.apply('setUserId', ['2'], { wait: true }, afterThirdSetUserId); - }); + afterSecondSetUserId = expect(function() { + expectMessages(2, 1, [ + 'owned by one/two - a', + 'owned by one/two - b', + 'owned by two - a', + 'owned by two - b', + ]); + Meteor.apply( + 'setUserId', + ['2'], + { wait: true }, + afterThirdSetUserId + ); + }); - afterThirdSetUserId = expect(function() { - // Nothing should have been sent since the results of the - // query are the same ("don't flap data on the wire") - expectMessages(0, 0, [ - 'owned by one/two - a', - 'owned by one/two - b', - 'owned by two - a', - 'owned by two - b' - ]); - undoEavesdrop(); - }); + afterThirdSetUserId = expect(function() { + // Nothing should have been sent since the results of the + // query are the same ("don't flap data on the wire") + expectMessages(0, 0, [ + 'owned by one/two - a', + 'owned by one/two - b', + 'owned by two - a', + 'owned by two - b', + ]); + undoEavesdrop(); + }); + } }, function(test, expect) { const key = Random.id(); From aee880e2bc6c1df871b2cd915b462df24c1466b3 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 8 Dec 2022 12:47:55 -0400 Subject: [PATCH 0110/1965] fixing "livedata - compound methods" test --- .../ddp-client/test/livedata_test_service.js | 14 +- packages/ddp-client/test/livedata_tests.js | 249 ++++++++++-------- 2 files changed, 150 insertions(+), 113 deletions(-) diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index d278a8932b..8938a1a8ca 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -109,8 +109,8 @@ Ledger.allow({ fetch: [] }); -Meteor.startup(function() { - if (Meteor.isServer) Ledger.remove({}); // XXX can this please be Ledger.remove()? +Meteor.startup(async function() { + if (Meteor.isServer) await Ledger.removeAsync({}); }); if (Meteor.isServer) @@ -120,14 +120,14 @@ if (Meteor.isServer) }); Meteor.methods({ - 'ledger/transfer': function(world, from_name, to_name, amount, cheat) { + 'ledger/transfer': async function(world, from_name, to_name, amount, cheat) { check(world, String); check(from_name, String); check(to_name, String); check(amount, Number); check(cheat, Match.Optional(Boolean)); - const from = Ledger.findOne({ name: from_name, world: world }); - const to = Ledger.findOne({ name: to_name, world: world }); + const from = await Ledger.findOneAsync({ name: from_name, world: world }); + const to = await Ledger.findOneAsync({ name: to_name, world: world }); if (Meteor.isServer) cheat = false; @@ -146,8 +146,8 @@ Meteor.methods({ if (from.balance < amount && !cheat) throw new Meteor.Error(409, 'Insufficient funds'); - Ledger.update(from._id, { $inc: { balance: -amount } }); - Ledger.update(to._id, { $inc: { balance: amount } }); + await Ledger.updateAsync({_id: from._id}, { $inc: { balance: -amount } }); + await Ledger.updateAsync({_id: to._id, }, { $inc: { balance: amount } }); } }); diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index 2005952141..3c181410eb 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -1,7 +1,23 @@ import { DDP } from '../common/namespace.js'; import { Connection } from '../common/livedata_connection.js'; -const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +const _sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + +const callWhenSubReady = async (subName, handle, cb = () => {}) => { + let control = 0; + + while (!handle.ready()) { + if (!handle.ready()) { + // Just in case something happens with the subscription, we have this control + if (control++ === 1000) { + throw new Error(`Subscribe to ${subName} is taking too long!`); + } + await _sleep(0); + return; + } + await cb(); + } +}; // XXX should check error codes const failure = function(test, code, reason) { @@ -131,7 +147,8 @@ testAsyncMulti('livedata - basic method invocation', [ // make sure 'undefined' is preserved as such, instead of turning // into null (JSON does not have 'undefined' so there is special // code for this) - if (Meteor.isServer) test.equal(await Meteor.callAsync('nothing'), undefined); + if (Meteor.isServer) + test.equal(await Meteor.callAsync('nothing'), undefined); if (Meteor.isClient) test.equal(Meteor.call('nothing'), undefined); test.equal(Meteor.call('nothing', expect(undefined, undefined)), undefined); @@ -157,7 +174,10 @@ testAsyncMulti('livedata - basic method invocation', [ async function(test, expect) { if (Meteor.isServer) - test.equal(await Meteor.callAsync('echo', 12, { x: 13 }), [12, { x: 13 }]); + test.equal(await Meteor.callAsync('echo', 12, { x: 13 }), [ + 12, + { x: 13 }, + ]); if (Meteor.isClient) test.equal(Meteor.call('echo', 12, { x: 13 }), undefined); @@ -294,7 +314,7 @@ testAsyncMulti('livedata - basic method invocation', [ try { await Meteor.callAsync('exception', 'both', { intended: true, - throwThroughFuture: true + throwThroughFuture: true, }); } catch (e) { threw = true; @@ -329,74 +349,81 @@ testAsyncMulti('livedata - basic method invocation', [ 'server', { intended: true, - throwThroughFuture: true + throwThroughFuture: true, }, expect(failure(test, 999, 'Client-visible test exception')) ), undefined ); } - } + }, ]); -const checkBalances = function(test, a, b) { - const alice = Ledger.findOne({ name: 'alice', world: test.runId() }); - const bob = Ledger.findOne({ name: 'bob', world: test.runId() }); +const checkBalances = async function(test, a, b) { + const alice = await Ledger.findOneAsync({ + name: 'alice', + world: test.runId(), + }); + const bob = await Ledger.findOneAsync({ name: 'bob', world: test.runId() }); + test.equal(alice.balance, a); test.equal(bob.balance, b); }; +const subscribeBeforeRun = async (subName, testId, cb) => { + if (Meteor.isClient) { + const handle = Meteor.subscribe(subName, testId); + await callWhenSubReady(subName, handle); + handle.stop(); + } + await cb(); +}; + // would be nice to have a database-aware test harness of some kind -- // this is a big hack (and XXX pollutes the global test namespace) testAsyncMulti('livedata - compound methods', [ - function(test, expect) { - if (Meteor.isClient) Meteor.subscribe('ledger', test.runId(), expect()); - - Ledger.insert( - { name: 'alice', balance: 100, world: test.runId() }, - expect(function() {}) - ); - Ledger.insert( - { name: 'bob', balance: 50, world: test.runId() }, - expect(function() {}) - ); + async function(test) { + await Ledger.insertAsync({ + name: 'alice', + balance: 100, + world: test.runId(), + }); + await Ledger.insertAsync({ name: 'bob', balance: 50, world: test.runId() }); }, - function(test, expect) { - Meteor.call( - 'ledger/transfer', - test.runId(), - 'alice', - 'bob', - 10, - expect(function(err, result) { - test.equal(err, undefined); - test.equal(result, undefined); - checkBalances(test, 90, 60); - }) - ); - checkBalances(test, 90, 60); + async function(test) { + await subscribeBeforeRun('ledger', test.runId(), async () => { + await Meteor.callAsync( + 'ledger/transfer', + test.runId(), + 'alice', + 'bob', + 10 + ); + await checkBalances(test, 90, 60); + }); }, - function(test, expect) { - Meteor.call( - 'ledger/transfer', - test.runId(), - 'alice', - 'bob', - 100, - true, - expect(function(err, result) { - failure(test, 409)(err, result); - // Balances are reverted back to pre-stub values. - checkBalances(test, 90, 60); - }) - ); + async function(test) { + await subscribeBeforeRun('ledger', test.runId(), async () => { + try { + await Meteor.callAsync( + 'ledger/transfer', + test.runId(), + 'alice', + 'bob', + 100, + true + ); + } catch (e) {} - if (Meteor.isClient) - // client can fool itself by cheating, but only until the sync - // finishes - checkBalances(test, -10, 160); - else checkBalances(test, 90, 60); - } + if (Meteor.isClient) { + // client can fool itself by cheating, but only until the sync + // finishes + await checkBalances(test, -10, 160); + } else { + await checkBalances(test, 90, 60); + } + }); + }, ]); // Replaces the Connection's `_livedata_data` method to push incoming @@ -488,25 +515,11 @@ if (Meteor.isClient) { const handle = Meteor.subscribe('objectsWithUsers'); - let control = 0; // Just make sure the subscription is ready before running the tests // As everything now runs async, the tests were running before the data fully came in - while (!handle.ready()) { - if (!handle.ready()) { - // Just in case something happens with the subscription, we have this control - if (control++ === 1000) { - throw new Error("Subscribe to objectsWithUsers is taking too long!"); - } - await _sleep(0); - return; - } + await callWhenSubReady('objectsWithUsers', handle, () => { expectMessages(1, 0, ['owned by none']); - Meteor.apply( - 'setUserId', - ['1'], - { wait: true }, - afterFirstSetUserId - ); + Meteor.apply('setUserId', ['1'], { wait: true }, afterFirstSetUserId); afterFirstSetUserId = expect(function() { expectMessages(3, 1, [ 'owned by one - a', @@ -547,7 +560,7 @@ if (Meteor.isClient) { ]); undoEavesdrop(); }); - } + }); }, function(test, expect) { const key = Random.id(); @@ -579,7 +592,7 @@ if (Meteor.isClient) { { wait: true }, expect(function() {}) ); - } + }, ] ); } @@ -630,7 +643,7 @@ Meteor.methods({ return 2; } return 0; - } + }, }); if (Meteor.isClient) { @@ -639,12 +652,22 @@ if (Meteor.isClient) { const id = Random.id(); testAsyncMulti('livedata - added from two different subs', [ function(test, expect) { - Meteor.call('livedata/setup', id, expect(function() {})); + Meteor.call( + 'livedata/setup', + id, + expect(function() {}) + ); }, function(test, expect) { MultiPub = new Mongo.Collection('MultiPubCollection' + id); - const sub1 = Meteor.subscribe('pub1' + id, expect(function() {})); - const sub2 = Meteor.subscribe('pub2' + id, expect(function() {})); + const sub1 = Meteor.subscribe( + 'pub1' + id, + expect(function() {}) + ); + const sub2 = Meteor.subscribe( + 'pub2' + id, + expect(function() {}) + ); }, function(test, expect) { Meteor.call( @@ -669,7 +692,7 @@ if (Meteor.isClient) { }, function(test, expect) { test.equal(MultiPub.findOne('foo'), { _id: 'foo', a: 'aa', b: 'bb' }); - } + }, ]); })(); } @@ -688,7 +711,7 @@ if (Meteor.isClient) { test.isTrue(coll.findOne(token)); }) ); - } + }, ]); testAsyncMulti('livedata - runtime universal sub creation', [ @@ -704,7 +727,7 @@ if (Meteor.isClient) { test.isTrue(coll.findOne(token)); }) ); - } + }, ]); testAsyncMulti('livedata - no setUserId after unblock', [ @@ -716,7 +739,7 @@ if (Meteor.isClient) { test.isTrue(result); }) ); - } + }, ]); testAsyncMulti( @@ -730,7 +753,7 @@ if (Meteor.isClient) { // Use a separate connection so that we can safely check to see if // conn._subscriptions is empty. conn = new Connection('/', { - reloadWithOutstanding: true + reloadWithOutstanding: true, }); collName = Random.id(); coll = new Mongo.Collection(collName, { connection: conn }); @@ -746,7 +769,7 @@ if (Meteor.isClient) { ? 'Internal server error' : 'Explicit error' ) - ) + ), }); }; testSubError({ throwInHandler: true }); @@ -768,7 +791,7 @@ if (Meteor.isClient) { onReady: expect(), onError: function(error) { errorFromRerun = error; - } + }, } ); }, @@ -777,7 +800,11 @@ if (Meteor.isClient) { test.equal(coll.find().count(), 1); test.isFalse(errorFromRerun); test.equal(_.size(conn._subscriptions), 1); // white-box test - conn.call('setUserId', 'bla', expect(function() {})); + conn.call( + 'setUserId', + 'bla', + expect(function() {}) + ); }, function(test, expect) { // Now that we've re-run, we should have stopped the subscription, @@ -796,13 +823,16 @@ if (Meteor.isClient) { { onError: function() { gotErrorFromStopper = true; - } + }, } ); // Call a method. This method won't be processed until the publisher's // function returns, so blocking on it being done ensures that we've // gotten the removed/nosub/etc. - conn.call('nothing', expect(function() {})); + conn.call( + 'nothing', + expect(function() {}) + ); }, function(test, expect) { test.equal(coll.find().count(), 0); @@ -810,7 +840,7 @@ if (Meteor.isClient) { test.isFalse(gotErrorFromStopper); test.equal(_.size(conn._subscriptions), 0); // white-box test conn._stream.disconnect({ _permanent: true }); - } + }, ]; })() ); @@ -826,7 +856,7 @@ if (Meteor.isClient) { // Use a separate connection so that we can safely check to see if // conn._subscriptions is empty. conn = new Connection('/', { - reloadWithOutstanding: true + reloadWithOutstanding: true, }); collName = Random.id(); coll = new Mongo.Collection(collName, { connection: conn }); @@ -842,7 +872,7 @@ if (Meteor.isClient) { ? 'Internal server error' : 'Explicit error' ) - ) + ), }); }; testSubError({ throwInHandler: true }); @@ -864,7 +894,7 @@ if (Meteor.isClient) { onReady: expect(), onStop: function(error) { errorFromRerun = error; - } + }, } ); }, @@ -873,7 +903,11 @@ if (Meteor.isClient) { test.equal(coll.find().count(), 1); test.isFalse(errorFromRerun); test.equal(_.size(conn._subscriptions), 1); // white-box test - conn.call('setUserId', 'bla', expect(function() {})); + conn.call( + 'setUserId', + 'bla', + expect(function() {}) + ); }, function(test, expect) { // Now that we've re-run, we should have stopped the subscription, @@ -894,13 +928,16 @@ if (Meteor.isClient) { if (error) { gotErrorFromStopper = true; } - } + }, } ); // Call a method. This method won't be processed until the publisher's // function returns, so blocking on it being done ensures that we've // gotten the removed/nosub/etc. - conn.call('nothing', expect(function() {})); + conn.call( + 'nothing', + expect(function() {}) + ); }, function(test, expect) { test.equal(coll.find().count(), 0); @@ -908,7 +945,7 @@ if (Meteor.isClient) { test.isFalse(gotErrorFromStopper); test.equal(_.size(conn._subscriptions), 0); // white-box test conn._stream.disconnect({ _permanent: true }); - } + }, ]; })() ); @@ -924,7 +961,7 @@ if (Meteor.isClient) { test.equal(One.find().count(), 2); test.equal(Two.find().count(), 3); }), - onError: failure() + onError: failure(), } ); }, @@ -934,7 +971,7 @@ if (Meteor.isClient) { { dup: 1 }, { onReady: failure(), - onError: expect(failure(test, 500, 'Internal server error')) + onError: expect(failure(test, 500, 'Internal server error')), } ); }, @@ -944,10 +981,10 @@ if (Meteor.isClient) { { notCursor: 1 }, { onReady: failure(), - onError: expect(failure(test, 500, 'Internal server error')) + onError: expect(failure(test, 500, 'Internal server error')), } ); - } + }, ]); } @@ -960,7 +997,7 @@ if (Meteor.isServer) { s2s: function(arg) { check(arg, String); return 's2s ' + arg; - } + }, }); } (function() { @@ -989,7 +1026,7 @@ if (Meteor.isServer) { }) ); } - } + }, ]); })(); @@ -1013,7 +1050,7 @@ if (Meteor.isServer) { if (self.conn.status().connected) { test.equal(await self.conn.callAsync('s2s', 'foo'), 's2s foo'); } - } + }, ]); })(); } @@ -1030,7 +1067,7 @@ if (Meteor.isServer) { }), 500 ); - } + }, ]); })(); @@ -1054,13 +1091,13 @@ if (Meteor.isServer) { onReady: expect(function() { test.equal(PublisherCloningCollection.findOne(), { _id: 'a', - x: { y: 43 } + x: { y: 43 }, }); }), - onError: failure() + onError: failure(), } ); - } + }, ]); } @@ -1099,7 +1136,7 @@ testAsyncMulti('livedata - result by value', [ test.equal(self.firstResult.length + 1, secondResult.length); }) ); - } + }, ]); // XXX some things to test in greater detail: From fa878234ad306b93a79af10b27962bcda5d2faed Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 8 Dec 2022 13:02:53 -0400 Subject: [PATCH 0111/1965] fixing "publish multiple cursors" test --- .../ddp-client/test/livedata_test_service.js | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index 8938a1a8ca..57c47b1e8d 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -333,36 +333,38 @@ if (Meteor.isServer) { One = new Mongo.Collection('collectionOne'); Two = new Mongo.Collection('collectionTwo'); -if (Meteor.isServer) { - One.remove({}); - One.insert({ name: 'value1' }); - One.insert({ name: 'value2' }); +Meteor.startup(async () => { + if (Meteor.isServer) { + await One.removeAsync({}); + await One.insertAsync({ name: 'value1' }); + await One.insertAsync({ name: 'value2' }); - Two.remove({}); - Two.insert({ name: 'value3' }); - Two.insert({ name: 'value4' }); - Two.insert({ name: 'value5' }); + await Two.removeAsync({}); + await Two.insertAsync({ name: 'value3' }); + await Two.insertAsync({ name: 'value4' }); + await Two.insertAsync({ name: 'value5' }); - Meteor.publish('multiPublish', function(options) { - // See below to see what options are accepted. - check(options, Object); - 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'; - }); -} + Meteor.publish('multiPublish', function(options) { + // See below to see what options are accepted. + check(options, Object); + 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'; + }); + } +}); /// Helper for "livedata - result by value" const resultByValueArrays = Object.create(null); From 919de73cd61c3c1d926c9ab8398746ed1f07f945 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:34:15 -0300 Subject: [PATCH 0112/1965] tests: solved insertUserDoc username --- packages/accounts-base/accounts_tests.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 266c6a0bd0..94e283e6ef 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -190,19 +190,18 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { }; // user does not already exist. create a user object with fields set. - const userId = Accounts.insertUserDoc( + const userId = await Accounts.insertUserDoc( {profile: {name: 'Foo Bar'}}, userIn ); - const userOut = Meteor.users.findOne(userId); - + const userOut = await Meteor.users.findOne(userId); test.equal(typeof userOut.createdAt, 'object'); test.equal(userOut.profile.name, 'Foo Bar'); test.equal(userOut.username, userIn.username); // run the hook again. now the user exists, so it throws an error. - test.throws( - () => Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + await test.throwsAsync( + async () => await Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), 'Username already exists.' ); From 3492786085d3d20180c8444ccc4c7083221af402 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:37:07 -0300 Subject: [PATCH 0113/1965] tests: solved insertUserDoc email --- packages/accounts-base/accounts_tests.js | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 94e283e6ef..1bc9fb5c3c 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -180,7 +180,7 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', test.length(users2, 1); // cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(uid1); }); @@ -206,7 +206,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { ); // cleanup - Meteor.users.remove(userId); + await Meteor.users.remove(userId); }); Tinytest.addAsync('accounts - insertUserDoc email', async test => { @@ -219,11 +219,11 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { }; // user does not already exist. create a user object with fields set. - const userId = Accounts.insertUserDoc( + const userId = await Accounts.insertUserDoc( {profile: {name: 'Foo Bar'}}, userIn ); - const userOut = Meteor.users.findOne(userId); + const userOut = await Meteor.users.findOne(userId); test.equal(typeof userOut.createdAt, 'object'); test.equal(userOut.profile.name, 'Foo Bar'); @@ -231,33 +231,35 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // run the hook again with the exact same emails. // run the hook again. now the user exists, so it throws an error. - test.throws( - () => Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + await test.throwsAsync( + async () => await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn), 'Email already exists.' ); // now with only one of them. - test.throws(() => - Accounts.insertUserDoc({}, {emails: [{address: email1}]}), + await test.throwsAsync( + async () => + await Accounts.insertUserDoc({}, { emails: [{ address: email1 }] }), 'Email already exists.' ); - test.throws(() => - Accounts.insertUserDoc({}, {emails: [{address: email2}]}), + await test.throwsAsync( + async () => + await Accounts.insertUserDoc({}, { emails: [{ address: email2 }] }), 'Email already exists.' ); // a third email works. - const userId3 = Accounts.insertUserDoc( + const userId3 = await Accounts.insertUserDoc( {}, {emails: [{address: email3}]} ); - const user3 = Meteor.users.findOne(userId3); + const user3 = await Meteor.users.findOne(userId3); test.equal(typeof user3.createdAt, 'object'); // cleanup - Meteor.users.remove(userId); - Meteor.users.remove(userId3); + await Meteor.users.remove(userId); + await Meteor.users.remove(userId3); }); // More token expiration tests are in accounts-password From 02fc0f0531004f8ff0253530c251a280d1717bb2 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:44:00 -0300 Subject: [PATCH 0114/1965] tests: solved expire numeric token --- packages/accounts-base/accounts_tests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 1bc9fb5c3c..e35a79210d 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -263,13 +263,13 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { }); // More token expiration tests are in accounts-password -Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { +Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => { const userIn = { username: Random.id() }; - const userId = Accounts.insertUserDoc({ profile: { + const userId = await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn); const date = new Date(new Date() - 5000); - Meteor.users.update(userId, { + await Meteor.users.update(userId, { $set: { "services.resume.loginTokens": [{ hashedToken: Random.id(), @@ -280,7 +280,7 @@ Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { }] } }); - const observe = Meteor.users.find(userId).observe({ + const observe = await Meteor.users.find(userId).observe({ changed: newUser => { if (newUser.services && newUser.services.resume && (!newUser.services.resume.loginTokens || @@ -290,7 +290,7 @@ Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { } } }); - Accounts._expireTokens(new Date(), userId); + await Accounts._expireTokens(new Date(), userId); }); From 61aab831da26d9ced3e831b819253d1c0969b46c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:54:15 -0300 Subject: [PATCH 0115/1965] chore: asyncfied _insertHashedLoginToken --- packages/accounts-base/accounts_server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index f9c4e82bc5..006cfe8ce8 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -899,10 +899,10 @@ export class AccountsServer extends AccountsCommon { // Using $addToSet avoids getting an index error if another client // logging in simultaneously has already inserted the new hashed // token. - _insertHashedLoginToken(userId, hashedToken, query) { + async _insertHashedLoginToken(userId, hashedToken, query) { query = query ? { ...query } : {}; query._id = userId; - this.users.update(query, { + await this.users.update(query, { $addToSet: { "services.resume.loginTokens": hashedToken } @@ -910,8 +910,8 @@ export class AccountsServer extends AccountsCommon { }; // Exported for tests. - _insertLoginToken(userId, stampedToken, query) { - this._insertHashedLoginToken( + async _insertLoginToken(userId, stampedToken, query) { + await this._insertHashedLoginToken( userId, this._hashStampedToken(stampedToken), query From b1b7b6b6f951375fd6f05e4aea4a743fb26ad53d Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:54:26 -0300 Subject: [PATCH 0116/1965] tests: resolved login token --- packages/accounts-base/accounts_tests.js | 48 +++++++++++++----------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index e35a79210d..f0a8c6bf5d 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -296,43 +296,48 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => // Login tokens used to be stored unhashed in the database. We want // to make sure users can still login after upgrading. -const insertUnhashedLoginToken = (userId, stampedToken) => { - Meteor.users.update( +const insertUnhashedLoginToken = async (userId, stampedToken) => { + await Meteor.users.update( userId, {$push: {'services.resume.loginTokens': stampedToken}} ); }; -Tinytest.addAsync('accounts - login token', (test, onComplete) => { +Tinytest.addAsync('accounts - login token', async (test) => { // Test that we can login when the database contains a leftover // old style unhashed login token. - const userId1 = Accounts.insertUserDoc({}, {username: Random.id()}); + const userId1 = + await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken1 = Accounts._generateStampedLoginToken(); - insertUnhashedLoginToken(userId1, stampedToken1); + await insertUnhashedLoginToken(userId1, stampedToken1); let connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken1.token}); + await connection.callAsync('login', { resume: stampedToken1.token }); connection.disconnect(); // Steal the unhashed token from the database and use it to login. // This is a sanity check so that when we *can't* login with a // stolen *hashed* token, we know it's not a problem with the test. - const userId2 = Accounts.insertUserDoc({}, {username: Random.id()}); - insertUnhashedLoginToken(userId2, Accounts._generateStampedLoginToken()); - const stolenToken1 = Meteor.users.findOne(userId2).services.resume.loginTokens[0].token; + const userId2 = + await Accounts.insertUserDoc({}, { username: Random.id() }); + await insertUnhashedLoginToken(userId2, Accounts._generateStampedLoginToken()); + const user2 = await Meteor.users.findOne(userId2); + const stolenToken1 = user2.services.resume.loginTokens[0].token; test.isTrue(stolenToken1); connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stolenToken1}); + await connection.callAsync('login', { resume: stolenToken1 }); connection.disconnect(); // Now do the same thing, this time with a stolen hashed token. - const userId3 = Accounts.insertUserDoc({}, {username: Random.id()}); - Accounts._insertLoginToken(userId3, Accounts._generateStampedLoginToken()); - const stolenToken2 = Meteor.users.findOne(userId3).services.resume.loginTokens[0].hashedToken; + const userId3 = + await Accounts.insertUserDoc({}, { username: Random.id() }); + await Accounts._insertLoginToken(userId3, Accounts._generateStampedLoginToken()); + const user3 = await Meteor.users.findOne(userId3); + const stolenToken2 = user3.services.resume.loginTokens[0].hashedToken; test.isTrue(stolenToken2); connection = DDP.connect(Meteor.absoluteUrl()); // evil plan foiled - test.throws( - () => connection.call('login', {resume: stolenToken2}), + await test.throwsAsync( + async () => await connection.callAsync('login', { resume: stolenToken2 }), /You\'ve been logged out by the server/ ); connection.disconnect(); @@ -340,24 +345,25 @@ Tinytest.addAsync('accounts - login token', (test, onComplete) => { // Old style unhashed tokens are replaced by hashed tokens when // encountered. This means that after someone logins once, the // old unhashed token is no longer available to be stolen. - const userId4 = Accounts.insertUserDoc({}, {username: Random.id()}); + const userId4 = + await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken2 = Accounts._generateStampedLoginToken(); - insertUnhashedLoginToken(userId4, stampedToken2); + await insertUnhashedLoginToken(userId4, stampedToken2); connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken2.token}); + await connection.callAsync('login', { resume: stampedToken2.token }); connection.disconnect(); // The token is no longer available to be stolen. - const stolenToken3 = Meteor.users.findOne(userId4).services.resume.loginTokens[0].token; + const user4 = await Meteor.users.findOne(userId4); + const stolenToken3 = user4.services.resume.loginTokens[0].token; test.isFalse(stolenToken3); // After the upgrade, the client can still login with their original // unhashed login token. connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken2.token}); + await connection.callAsync('login', { resume: stampedToken2.token }); connection.disconnect(); - onComplete(); }); Tinytest.addAsync( From 4a15dc2ab973cc6d741a9cb94364a9d396c4d5b1 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:14:56 -0300 Subject: [PATCH 0117/1965] tests: solved remove other tokens --- packages/accounts-base/accounts_tests.js | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index f0a8c6bf5d..2665935f21 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -4,8 +4,8 @@ import { Accounts } from 'meteor/accounts-base'; import { Random } from 'meteor/random'; Meteor.methods({ - getCurrentLoginToken: function () { - return Accounts._getLoginToken(this.connection.id); + getCurrentLoginToken: async function () { + return await Accounts._getLoginToken(this.connection.id); } }); @@ -390,56 +390,60 @@ Tinytest.addAsync( Tinytest.addAsync('accounts - get new token', async test => { // Test that the `getNewToken` method returns us a valid token, with // the same expiration as our original token. - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const conn = DDP.connect(Meteor.absoluteUrl()); - conn.call('login', { resume: stampedToken.token }); - test.equal(conn.call('getCurrentLoginToken'), - Accounts._hashLoginToken(stampedToken.token)); + await conn.callAsync('login', { resume: stampedToken.token }); + test.equal(await conn.callAsync('getCurrentLoginToken'), + Accounts._hashLoginToken(stampedToken.token)); - const newTokenResult = conn.call('getNewToken'); + const newTokenResult = await conn.callAsync('getNewToken'); test.equal(newTokenResult.tokenExpires, - Accounts._tokenExpiration(stampedToken.when)); - test.equal(conn.call('getCurrentLoginToken'), - Accounts._hashLoginToken(newTokenResult.token)); + Accounts._tokenExpiration(stampedToken.when)); + const token = await conn.callAsync('getCurrentLoginToken'); + console.log(token); + test.equal(await conn.callAsync('getCurrentLoginToken'), + Accounts._hashLoginToken(newTokenResult.token)); conn.disconnect(); // A second connection should be able to log in with the new token // we got. const secondConn = DDP.connect(Meteor.absoluteUrl()); - secondConn.call('login', { resume: newTokenResult.token }); + await secondConn.callAsync('login', { resume: newTokenResult.token }); secondConn.disconnect(); } ); -Tinytest.addAsync('accounts - remove other tokens', (test, onComplete) => { +Tinytest.addAsync('accounts - remove other tokens', async (test) => { // Test that the `removeOtherTokens` method removes all tokens other // than the caller's token, thereby logging out and closing other // connections. - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedTokens = []; const conns = []; for(let i = 0; i < 2; i++) { stampedTokens.push(Accounts._generateStampedLoginToken()); - Accounts._insertLoginToken(userId, stampedTokens[i]); + await Accounts._insertLoginToken(userId, stampedTokens[i]); const conn = DDP.connect(Meteor.absoluteUrl()); - conn.call('login', { resume: stampedTokens[i].token }); - test.equal(conn.call('getCurrentLoginToken'), + await conn.callAsync('login', { resume: stampedTokens[i].token }); + test.equal(await conn.callAsync('getCurrentLoginToken'), Accounts._hashLoginToken(stampedTokens[i].token)); conns.push(conn); }; - conns[0].call('removeOtherTokens'); - simplePoll(() => { - const tokens = conns.map(conn => conn.call('getCurrentLoginToken')); + await conns[0].callAsync('removeOtherTokens'); + simplePoll(async () => { + let tokens = []; + for (const conn of conns) { + tokens.push(await conn.callAsync('getCurrentLoginToken')); + } return ! tokens[1] && tokens[0] === Accounts._hashLoginToken(stampedTokens[0].token); }, () => { // success conns.forEach(conn => conn.disconnect()); - onComplete(); }, () => { // timed out throw new Error("accounts - remove other tokens timed out"); From 1c60eca86238bae685e8f18e42373712c4966794 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:17:32 -0300 Subject: [PATCH 0118/1965] tests: solved get new token and removed log --- packages/accounts-base/accounts_tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 2665935f21..27b8f8d160 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -402,7 +402,6 @@ Tinytest.addAsync('accounts - get new token', async test => { test.equal(newTokenResult.tokenExpires, Accounts._tokenExpiration(stampedToken.when)); const token = await conn.callAsync('getCurrentLoginToken'); - console.log(token); test.equal(await conn.callAsync('getCurrentLoginToken'), Accounts._hashLoginToken(newTokenResult.token)); conn.disconnect(); From 704dcb6d8841e84aeb680c8524d3f612cc42cdfa Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:17:42 -0300 Subject: [PATCH 0119/1965] chore: added await in get new token --- packages/accounts-base/accounts_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 006cfe8ce8..d0767854ad 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -694,7 +694,7 @@ export class AccountsServer extends AccountsCommon { } const newStampedToken = accounts._generateStampedLoginToken(); newStampedToken.when = currentStampedToken.when; - accounts._insertLoginToken(this.userId, newStampedToken); + await accounts._insertLoginToken(this.userId, newStampedToken); return accounts._loginUser(this, this.userId, newStampedToken); }; From f1845a7d6be722a91af50410988cae2def9b1811 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:24:03 -0300 Subject: [PATCH 0120/1965] tests: solved hook callbacks can access Meteor.userId() --- packages/accounts-base/accounts_tests.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 27b8f8d160..78f07476b3 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -454,9 +454,9 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { Tinytest.addAsync( 'accounts - hook callbacks can access Meteor.userId()', async test => { - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const validateStopper = Accounts.validateLoginAttempt(attempt => { test.equal(Meteor.userId(), validateAttemptExpectedUserId, "validateLoginAttempt"); @@ -478,20 +478,22 @@ Tinytest.addAsync( // On a new connection, Meteor.userId() should be null until logged in. let validateAttemptExpectedUserId = null; const onLoginExpectedUserId = userId; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Now that the user is logged in on the connection, Meteor.userId() should // return that user. validateAttemptExpectedUserId = userId; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Trigger onLoginFailure callbacks const onLoginFailureExpectedUserId = userId; - test.throws(() => conn.call('login', { resume: "bogus" }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: "bogus" }), '403'); // Trigger onLogout callbacks const onLogoutExpectedUserId = userId; - conn.call('logout'); + await conn.callAsync('logout'); conn.disconnect(); validateStopper.stop(); From bd6d9dab287620e90bbe5b60d17229165688caf6 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:32:50 -0300 Subject: [PATCH 0121/1965] tests: solved hook callbacks obey options.defaultFieldSelector --- packages/accounts-base/accounts_tests.js | 234 ++++++++++++----------- 1 file changed, 121 insertions(+), 113 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 78f07476b3..efb69dc3b4 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -14,7 +14,7 @@ Meteor.methods({ // (impossible?) Tinytest.add( 'accounts - config validates keys', - test => test.throws(() => Accounts.config({foo: "bar"})) + test => test.throws(() => Accounts.config({ foo: "bar" })) ); Tinytest.addAsync('accounts - config - token lifetime', async test => { @@ -65,8 +65,8 @@ Tinytest.addAsync('accounts - config - default token lifetime', async test => { Tinytest.addAsync('accounts - config - defaultFieldSelector', async test => { const options = Accounts._options; Accounts._options = {}; - const setValue = {bigArray: 0}; - Accounts.config({defaultFieldSelector: setValue}); + const setValue = { bigArray: 0 }; + Accounts.config({ defaultFieldSelector: setValue }); test.equal(Accounts._options.defaultFieldSelector, setValue); Accounts._options = options; }); @@ -87,8 +87,8 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', // create an account with facebook const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId, monkey: 42}, {profile: {foo: 1}}).id; - const users1 = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.facebook.monkey, 42); @@ -96,10 +96,10 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', // create again with the same id, see that we get the same user. // it should update services.facebook but not profile. const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId, llama: 50}, - {profile: {foo: 1000, bar: 2}}).id; + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }).id; test.equal(uid1, uid2); - const users2 = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + const users2 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users2, 1); test.equal(users2[0].profile.foo, 1); test.equal(users2[0].profile.bar, undefined); @@ -144,14 +144,14 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', as // users that have different service ids get different users const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', {id: weiboId1}, {profile: {foo: 1}}).id; + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', {id: weiboId2}, {profile: {bar: 2}}).id; - test.equal(Meteor.users.find({"services.weibo.id": {$in: [weiboId1, weiboId2]}}).count(), 2); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId1}).profile.foo, 1); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId1}).emails, undefined); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId2}).profile.bar, 2); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId2}).emails, undefined); + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; + test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); // cleanup Meteor.users.remove(uid1); @@ -160,12 +160,12 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', as Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { const twitterIdOld = parseInt(Random.hexString(4), 16); - const twitterIdNew = ''+twitterIdOld; + const twitterIdNew = '' + twitterIdOld; // create an account with twitter using the old ID format of integer const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', {id: twitterIdOld, monkey: 42}, {profile: {foo: 1}}).id; - const users1 = Meteor.users.find({"services.twitter.id": twitterIdOld}).fetch(); + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.twitter.monkey, 42); @@ -174,9 +174,9 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', // test that the existing user is found, and that the ID // gets updated to a string value const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', {id: twitterIdNew, monkey: 42}, {profile: {foo: 1}}).id; + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; test.equal(uid1, uid2); - const users2 = Meteor.users.find({"services.twitter.id": twitterIdNew}).fetch(); + const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); test.length(users2, 1); // cleanup @@ -191,7 +191,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { // user does not already exist. create a user object with fields set. const userId = await Accounts.insertUserDoc( - {profile: {name: 'Foo Bar'}}, + { profile: { name: 'Foo Bar' } }, userIn ); const userOut = await Meteor.users.findOne(userId); @@ -201,7 +201,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { // run the hook again. now the user exists, so it throws an error. await test.throwsAsync( - async () => await Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + async () => await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn), 'Username already exists.' ); @@ -214,13 +214,13 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { const email2 = Random.id(); const email3 = Random.id(); const userIn = { - emails: [{address: email1, verified: false}, - {address: email2, verified: true}] + emails: [{ address: email1, verified: false }, + { address: email2, verified: true }] }; // user does not already exist. create a user object with fields set. const userId = await Accounts.insertUserDoc( - {profile: {name: 'Foo Bar'}}, + { profile: { name: 'Foo Bar' } }, userIn ); const userOut = await Meteor.users.findOne(userId); @@ -252,7 +252,7 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // a third email works. const userId3 = await Accounts.insertUserDoc( - {}, {emails: [{address: email3}]} + {}, { emails: [{ address: email3 }] } ); const user3 = await Meteor.users.findOne(userId3); test.equal(typeof user3.createdAt, 'object'); @@ -265,9 +265,11 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // More token expiration tests are in accounts-password Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => { const userIn = { username: Random.id() }; - const userId = await Accounts.insertUserDoc({ profile: { - name: 'Foo Bar' - } }, userIn); + const userId = await Accounts.insertUserDoc({ + profile: { + name: 'Foo Bar' + } + }, userIn); const date = new Date(new Date() - 5000); await Meteor.users.update(userId, { $set: { @@ -283,7 +285,7 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => const observe = await Meteor.users.find(userId).observe({ changed: newUser => { if (newUser.services && newUser.services.resume && - (!newUser.services.resume.loginTokens || + (!newUser.services.resume.loginTokens || newUser.services.resume.loginTokens.length === 0)) { observe.stop(); onComplete(); @@ -299,7 +301,7 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => const insertUnhashedLoginToken = async (userId, stampedToken) => { await Meteor.users.update( userId, - {$push: {'services.resume.loginTokens': stampedToken}} + { $push: { 'services.resume.loginTokens': stampedToken } } ); }; @@ -422,15 +424,16 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { const stampedTokens = []; const conns = []; - for(let i = 0; i < 2; i++) { + for (let i = 0; i < 2; i++) { stampedTokens.push(Accounts._generateStampedLoginToken()); await Accounts._insertLoginToken(userId, stampedTokens[i]); const conn = DDP.connect(Meteor.absoluteUrl()); await conn.callAsync('login', { resume: stampedTokens[i].token }); test.equal(await conn.callAsync('getCurrentLoginToken'), - Accounts._hashLoginToken(stampedTokens[i].token)); + Accounts._hashLoginToken(stampedTokens[i].token)); conns.push(conn); - }; + } + ; await conns[0].callAsync('removeOtherTokens'); simplePoll(async () => { @@ -438,7 +441,7 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { for (const conn of conns) { tokens.push(await conn.callAsync('getCurrentLoginToken')); } - return ! tokens[1] && + return !tokens[1] && tokens[0] === Accounts._hashLoginToken(stampedTokens[0].token); }, () => { // success @@ -507,13 +510,14 @@ Tinytest.addAsync( 'accounts - hook callbacks obey options.defaultFieldSelector', async test => { const ignoreFieldName = "bigArray"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; Accounts._options = {}; - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); - test.equal(Accounts._options.defaultFieldSelector, {[ignoreFieldName]: 0}, 'defaultFieldSelector'); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); + test.equal(Accounts._options.defaultFieldSelector, { [ignoreFieldName]: 0 }, 'defaultFieldSelector'); const validateStopper = Accounts.validateLoginAttempt(attempt => { test.isUndefined(allowLogin != 'bogus' ? attempt.user[ignoreFieldName] : attempt.user, "validateLoginAttempt") @@ -533,23 +537,27 @@ Tinytest.addAsync( // test a new connection let allowLogin = true; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Now that the user is logged in on the connection, Meteor.userId() should // return that user. - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Trigger onLoginFailure callbacks, this will not include the user object allowLogin = 'bogus'; - test.throws(() => conn.call('login', { resume: "bogus" }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: "bogus" }), '403'); // test a forced login fail which WILL include the user object allowLogin = false; - test.throws(() => conn.call('login', { resume: stampedToken.token }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: stampedToken.token }), '403'); // Trigger onLogout callbacks const onLogoutExpectedUserId = userId; - conn.call('logout'); + await conn.callAsync('logout'); Accounts._options = options; conn.disconnect(); @@ -581,31 +589,31 @@ Tinytest.addAsync( test.isNotUndefined(user[ignoreFieldName], 'included by default'); // test the field is excluded - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); user = Meteor.user(); test.isUndefined(user[ignoreFieldName], 'excluded'); user = Meteor.user({}); test.isUndefined(user[ignoreFieldName], 'excluded {}'); // test the field can still be retrieved if required - user = Meteor.user({fields: {[ignoreFieldName]: 1}}); + user = Meteor.user({ fields: { [ignoreFieldName]: 1 } }); test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved'); test.isUndefined(user.username, 'field can be retrieved username'); // test a combined negative field specifier - user = Meteor.user({fields: {username: 0}}); + user = Meteor.user({ fields: { username: 0 } }); test.isUndefined(user[ignoreFieldName], 'combined field selector'); test.isUndefined(user.username, 'combined field selector username'); // test an explicit request for the full user object - user = Meteor.user({fields: {}}); + user = Meteor.user({ fields: {} }); test.isNotUndefined(user[ignoreFieldName], 'full selector'); test.isNotUndefined(user.username, 'full selector username'); Accounts._options = {}; // Test that a custom field gets retrieved properly - Accounts.config({defaultFieldSelector: {[customField]: 1}}); + Accounts.config({ defaultFieldSelector: { [customField]: 1 } }); user = Meteor.user() test.isNotUndefined(user[customField]); test.isUndefined(user.username); @@ -619,7 +627,7 @@ Tinytest.addAsync( Tinytest.addAsync( 'accounts async - Meteor.userAsync() obeys options.defaultFieldSelector', - async test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -684,7 +692,7 @@ Tinytest.addAsync( { profile: { foo: 1 } }, ).userId; const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}}); + const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); let users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); @@ -696,7 +704,7 @@ Tinytest.addAsync( // Also verify that the user object is filtered by _options.defaultFieldSelector const accountsOptions = Accounts._options; Accounts._options = {}; - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); Accounts.onExternalLogin((options, user) => { options.profile.foo = 2; test.isUndefined(users[ignoreFieldName], 'ignoreField - after limit fields'); @@ -734,79 +742,79 @@ Tinytest.addAsync( ); Tinytest.addAsync( - 'accounts - verify beforeExternalLogin hook can stop user login', - async test => { - // Verify user data is saved properly when not using the - // beforeExternalLogin hook. - let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - ).userId; - const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}}); - let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); - test.length(users, 1); - test.equal(users[0].profile.foo, 1); - test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); + 'accounts - verify beforeExternalLogin hook can stop user login', + async test => { + // Verify user data is saved properly when not using the + // beforeExternalLogin hook. + let facebookId = Random.id(); + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + ).userId; + const ignoreFieldName = "bigArray"; + const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + let users = + Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); + test.equal(users[0].profile.foo, 1); + test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); - // Verify that when beforeExternalLogin returns false - // that an error throws and user is not saved - Accounts.beforeExternalLogin((serviceName, serviceData, user) => { - // Check that we get the correct data - test.equal(serviceName, 'facebook'); - test.equal(serviceData, { id: facebookId }); - test.equal(user._id, uid1); - return false - }); + // Verify that when beforeExternalLogin returns false + // that an error throws and user is not saved + Accounts.beforeExternalLogin((serviceName, serviceData, user) => { + // Check that we get the correct data + test.equal(serviceName, 'facebook'); + test.equal(serviceData, { id: facebookId }); + test.equal(user._id, uid1); + return false + }); - test.throws(() => Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - )); + test.throws(() => Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + )); - // Cleanup - Meteor.users.remove(uid1); - Accounts._beforeExternalLoginHook = null; - } + // Cleanup + Meteor.users.remove(uid1); + Accounts._beforeExternalLoginHook = null; + } ); Tinytest.addAsync( 'accounts - verify setAdditionalFindUserOnExternalLogin hook can provide user', async test => { - // create test user, without a google service - const testEmail = "test@testdomain.com" - const uid0 = Accounts.createUser({email: testEmail}) + // create test user, without a google service + const testEmail = "test@testdomain.com" + const uid0 = Accounts.createUser({ email: testEmail }) - // Verify that user is found from email and service merged - Accounts.setAdditionalFindUserOnExternalLogin(({serviceName, serviceData}) => { - if (serviceName === "google") { - return Accounts.findUserByEmail(serviceData.email) - } - }) - - let googleId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'google', - { id: googleId, email: testEmail }, - { profile: { foo: 1 } }, - ).userId; - - test.equal(uid0, uid1) - - // Cleanup - if (uid1 !== uid0) { - Meteor.users.remove(uid0) + // Verify that user is found from email and service merged + Accounts.setAdditionalFindUserOnExternalLogin(({ serviceName, serviceData }) => { + if (serviceName === "google") { + return Accounts.findUserByEmail(serviceData.email) } - Meteor.users.remove(uid1); - Accounts.selectCustomUserOnExternalLogin = null; + }) + + let googleId = Random.id(); + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'google', + { id: googleId, email: testEmail }, + { profile: { foo: 1 } }, + ).userId; + + test.equal(uid0, uid1) + + // Cleanup + if (uid1 !== uid0) { + Meteor.users.remove(uid0) + } + Meteor.users.remove(uid1); + Accounts.selectCustomUserOnExternalLogin = null; } ); -if(Meteor.isServer) { +if (Meteor.isServer) { Tinytest.addAsync( 'accounts - make sure that extra params to accounts urls are added', async test => { @@ -815,7 +823,7 @@ if(Meteor.isServer) { test.equal(verifyEmailURL.searchParams.toString(), ""); // Extra params - const extraParams = { test: 'success'}; + const extraParams = { test: 'success' }; const resetPasswordURL = new URL(Accounts.urls.resetPassword('test', extraParams)); test.equal(resetPasswordURL.searchParams.get('test'), extraParams.test); const enrollAccountURL = new URL(Accounts.urls.enrollAccount('test', extraParams)); From 76ba34a8b0e8e79adb413073c5fbf2017a5351c7 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:38:51 -0300 Subject: [PATCH 0122/1965] tests: solved Meteor.user() obeys options.defaultFieldSelector --- packages/accounts-base/accounts_tests.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index efb69dc3b4..12b0d36109 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -573,40 +573,42 @@ Tinytest.addAsync( async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; // stub Meteor.userId() so it works outside methods and returns the correct user: const origAccountsUserId = Accounts.userId; - Accounts.userId = () => userId; + Accounts.userId = + () => userId; Accounts._options = {}; // test the field is included by default - let user = Meteor.user(); + let user = await Meteor.user(); test.isNotUndefined(user[ignoreFieldName], 'included by default'); // test the field is excluded Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); - user = Meteor.user(); + user = await Meteor.user(); test.isUndefined(user[ignoreFieldName], 'excluded'); - user = Meteor.user({}); + user = await Meteor.user({}); test.isUndefined(user[ignoreFieldName], 'excluded {}'); // test the field can still be retrieved if required - user = Meteor.user({ fields: { [ignoreFieldName]: 1 } }); + user = await Meteor.user({ fields: { [ignoreFieldName]: 1 } }); test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved'); test.isUndefined(user.username, 'field can be retrieved username'); // test a combined negative field specifier - user = Meteor.user({ fields: { username: 0 } }); + user = await Meteor.user({ fields: { username: 0 } }); test.isUndefined(user[ignoreFieldName], 'combined field selector'); test.isUndefined(user.username, 'combined field selector username'); // test an explicit request for the full user object - user = Meteor.user({ fields: {} }); + user = await Meteor.user({ fields: {} }); test.isNotUndefined(user[ignoreFieldName], 'full selector'); test.isNotUndefined(user.username, 'full selector username'); @@ -614,7 +616,7 @@ Tinytest.addAsync( // Test that a custom field gets retrieved properly Accounts.config({ defaultFieldSelector: { [customField]: 1 } }); - user = Meteor.user() + user = await Meteor.user() test.isNotUndefined(user[customField]); test.isUndefined(user.username); test.isUndefined(user[ignoreFieldName]); From 8e06687c2c3585ad2024fc6bdea2d4f7ea3e0b71 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:41:51 -0300 Subject: [PATCH 0123/1965] tests: solved verify onExternalLogin hook can update oauth user profiles --- packages/accounts-base/accounts_tests.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 12b0d36109..77b51de146 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -688,15 +688,15 @@ Tinytest.addAsync( // Verify user profile data is saved properly when not using the // onExternalLogin hook. let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( + const u1 = await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 1 } }, - ).userId; + ); const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + const c = await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -712,13 +712,13 @@ Tinytest.addAsync( test.isUndefined(users[ignoreFieldName], 'ignoreField - after limit fields'); return options; }); - Accounts.updateOrCreateUserFromExternalService( + await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 1 } }, ); // test.isUndefined(users[0][ignoreFieldName], 'ignoreField - fields limited'); - users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 2); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - still there'); @@ -726,18 +726,18 @@ Tinytest.addAsync( // Verify user profile data can be modified using the onExternalLogin // hook, for new users. facebookId = Random.id(); - const uid2 = Accounts.updateOrCreateUserFromExternalService( + const u2 = await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 3 } }, - ).userId; - users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + ); + users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 2); // Cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); + await Meteor.users.remove(u1); + await Meteor.users.remove(u2.userId); Accounts._onExternalLoginHook = null; Accounts._options = accountsOptions; } From bb4c5f2ca58f640bcbf99a40431beb3500bbdf0d Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:46:13 -0300 Subject: [PATCH 0124/1965] tests: solved verify beforeExternalLogin hook can stop user login --- packages/accounts-base/accounts_tests.js | 43 +++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 77b51de146..6130cf30d1 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -694,9 +694,13 @@ Tinytest.addAsync( { profile: { foo: 1 } }, ); const ignoreFieldName = "bigArray"; - const c = await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); + + const c = + await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); + let users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -749,15 +753,22 @@ Tinytest.addAsync( // Verify user data is saved properly when not using the // beforeExternalLogin hook. let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - ).userId; + + const u = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + ); + const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + + const c = + await Meteor.users.update(u.userId, { $set: { [ignoreFieldName]: [1] } }); + let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -768,18 +779,20 @@ Tinytest.addAsync( // Check that we get the correct data test.equal(serviceName, 'facebook'); test.equal(serviceData, { id: facebookId }); - test.equal(user._id, uid1); + test.equal(user._id, u.userId); return false }); - test.throws(() => Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - )); + await test.throwsAsync( + async () => + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + )); // Cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(u.userId); Accounts._beforeExternalLoginHook = null; } ); From c5432bd12472221614524236f397c4c4bc39a48a Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:54:00 -0300 Subject: [PATCH 0125/1965] tests: solved verify setAdditionalFindUserOnExternalLogin hook can provide user --- packages/accounts-base/accounts_tests.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 6130cf30d1..6e1269930f 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -802,29 +802,28 @@ Tinytest.addAsync( async test => { // create test user, without a google service const testEmail = "test@testdomain.com" - const uid0 = Accounts.createUser({ email: testEmail }) + const uid0 = await Accounts.createUser({ email: testEmail }) // Verify that user is found from email and service merged - Accounts.setAdditionalFindUserOnExternalLogin(({ serviceName, serviceData }) => { + Accounts.setAdditionalFindUserOnExternalLogin(async ({ serviceName, serviceData }) => { if (serviceName === "google") { - return Accounts.findUserByEmail(serviceData.email) + return await Accounts.findUserByEmail(serviceData.email) } }) let googleId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( + const u1 = await Accounts.updateOrCreateUserFromExternalService( 'google', { id: googleId, email: testEmail }, { profile: { foo: 1 } }, - ).userId; - - test.equal(uid0, uid1) + ); + test.equal(uid0, u1.userId) // Cleanup - if (uid1 !== uid0) { - Meteor.users.remove(uid0) + if (u1.userId !== uid0) { + await Meteor.users.remove(uid0) } - Meteor.users.remove(uid1); + await Meteor.users.remove(u1.userId); Accounts.selectCustomUserOnExternalLogin = null; } ); From 3d047f8b4d19d66fc27fc9ec33ac2763e89af354 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:54:09 -0300 Subject: [PATCH 0126/1965] chore: added await to _additionalFindUserOnExternalLogin --- packages/accounts-base/accounts_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index d0767854ad..c6d46fc3cf 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -1335,7 +1335,7 @@ export class AccountsServer extends AccountsCommon { // Check to see if the developer has a custom way to find the user outside // of the general selectors above. if (!user && this._additionalFindUserOnExternalLogin) { - user = this._additionalFindUserOnExternalLogin({serviceName, serviceData, options}) + user = await this._additionalFindUserOnExternalLogin({serviceName, serviceData, options}) } // Before continuing, run user hook to see if we should continue From ece19bad1d7f11d7083750f58fb0daeebc1b6d30 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:03:26 -0300 Subject: [PATCH 0127/1965] tests: solved facebook --- packages/accounts-base/accounts_tests.js | 32 ++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 6e1269930f..9b3029607a 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -13,7 +13,7 @@ Meteor.methods({ // *are* validated, but Accounts._options is global state which makes this hard // (impossible?) Tinytest.add( - 'accounts - config validates keys', + 'accounts - config - validates keys', test => test.throws(() => Accounts.config({ foo: "bar" })) ); @@ -86,20 +86,24 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', const facebookId = Random.id(); // create an account with facebook - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.facebook.monkey, 42); // create again with the same id, see that we get the same user. // it should update services.facebook but not profile. - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, llama: 50 }, - { profile: { foo: 1000, bar: 2 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users2, 1); test.equal(users2[0].profile.foo, 1); test.equal(users2[0].profile.bar, undefined); @@ -109,7 +113,7 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', test.equal(users2[0].services.facebook.monkey, 42); // cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(u1.id); }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { @@ -632,14 +636,16 @@ Tinytest.addAsync( async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; // stub Meteor.userId() so it works outside methods and returns the correct user: const origAccountsUserId = Accounts.userId; - Accounts.userId = () => userId; + Accounts.userId = + () => userId; Accounts._options = {}; From d7c2d0868d2283f8b85b20348d770f14d983a22c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:05:07 -0300 Subject: [PATCH 0128/1965] tests: solved Meteor Developer --- packages/accounts-base/accounts_tests.js | 217 ++++++++++++----------- 1 file changed, 111 insertions(+), 106 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 9b3029607a..739b6b28f3 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -82,112 +82,6 @@ Tinytest.addAsync('accounts - validateNewUser gets passed user with _id', async test.isTrue(userId in idsInValidateNewUser); }); -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { - const facebookId = Random.id(); - - // create an account with facebook - const u1 = - await Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); - const users1 = - await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.foo, 1); - test.equal(users1[0].services.facebook.monkey, 42); - - // create again with the same id, see that we get the same user. - // it should update services.facebook but not profile. - const u2 = - await Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, llama: 50 }, - { profile: { foo: 1000, bar: 2 } }); - test.equal(u1.id, u2.id); - const users2 = - await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); - test.length(users2, 1); - test.equal(users2[0].profile.foo, 1); - test.equal(users2[0].profile.bar, undefined); - test.equal(users2[0].services.facebook.llama, 50); - // make sure we *don't* lose values not passed this call to - // updateOrCreateUserFromExternalService - test.equal(users2[0].services.facebook.monkey, 42); - - // cleanup - await Meteor.users.remove(u1.id); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { - const developerId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'meteor-developer', - { id: developerId, username: 'meteor-developer' }, - { profile: { name: 'meteor-developer' } } - ).id; - const users1 = Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.name, 'meteor-developer'); - - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'meteor-developer', - { id: developerId, username: 'meteor-developer' }, - { profile: { name: 'meteor-developer', username: 'developer' } } - ).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); - test.length(users2, 1); - test.equal(users1[0].profile.name, 'meteor-developer'); - test.equal(users1[0].profile.username, undefined); - - // cleanup - Meteor.users.remove(uid1); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { - const weiboId1 = Random.id(); - const weiboId2 = Random.id(); - - // users that have different service ids get different users - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; - test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); - - // cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { - const twitterIdOld = parseInt(Random.hexString(4), 16); - const twitterIdNew = '' + twitterIdOld; - - // create an account with twitter using the old ID format of integer - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.foo, 1); - test.equal(users1[0].services.twitter.monkey, 42); - - // Update the account with the new ID format of string - // test that the existing user is found, and that the ID - // gets updated to a string value - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); - test.length(users2, 1); - - // cleanup - await Meteor.users.remove(uid1); -}); - - Tinytest.addAsync('accounts - insertUserDoc username', async test => { const userIn = { username: Random.id() @@ -851,3 +745,114 @@ if (Meteor.isServer) { } ); } + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { + const facebookId = Random.id(); + + // create an account with facebook + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.foo, 1); + test.equal(users1[0].services.facebook.monkey, 42); + + // create again with the same id, see that we get the same user. + // it should update services.facebook but not profile. + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + test.length(users2, 1); + test.equal(users2[0].profile.foo, 1); + test.equal(users2[0].profile.bar, undefined); + test.equal(users2[0].services.facebook.llama, 50); + // make sure we *don't* lose values not passed this call to + // updateOrCreateUserFromExternalService + test.equal(users2[0].services.facebook.monkey, 42); + + // cleanup + await Meteor.users.remove(u1.id); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { + const developerId = + Random.id(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'meteor-developer', + { id: developerId, username: 'meteor-developer' }, + { profile: { name: 'meteor-developer' } } + ); + const users1 = + await Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.name, 'meteor-developer'); + + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'meteor-developer', + { id: developerId, username: 'meteor-developer' }, + { profile: { name: 'meteor-developer', username: 'developer' } } + ); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); + test.length(users2, 1); + test.equal(users1[0].profile.name, 'meteor-developer'); + test.equal(users1[0].profile.username, undefined); + + // cleanup + await Meteor.users.remove(u1); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { + const weiboId1 = Random.id(); + const weiboId2 = Random.id(); + + // users that have different service ids get different users + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; + const uid2 = Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; + test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); + + // cleanup + Meteor.users.remove(uid1); + Meteor.users.remove(uid2); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { + const twitterIdOld = parseInt(Random.hexString(4), 16); + const twitterIdNew = '' + twitterIdOld; + + // create an account with twitter using the old ID format of integer + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.foo, 1); + test.equal(users1[0].services.twitter.monkey, 42); + + // Update the account with the new ID format of string + // test that the existing user is found, and that the ID + // gets updated to a string value + const uid2 = Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; + test.equal(uid1, uid2); + const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); + test.length(users2, 1); + + // cleanup + await Meteor.users.remove(uid1); +}); + From a5639c11201d629378cf0738e1f752f881e32f1e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:09:51 -0300 Subject: [PATCH 0129/1965] tests: solved twtitter --- packages/accounts-base/accounts_tests.js | 55 +++++++++++++++--------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 739b6b28f3..4431c7f6a9 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -812,23 +812,32 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Dev }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { - const weiboId1 = Random.id(); - const weiboId2 = Random.id(); + const weiboId1 = + Random.id(); + const weiboId2 = + Random.id(); // users that have different service ids get different users - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; - test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }); + test.equal(await Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + + const user1 = + await Meteor.users.findOne({ "services.weibo.id": weiboId1 }); + const user2 = + await Meteor.users.findOne({ "services.weibo.id": weiboId2 }); + test.equal(user1.profile.foo, 1); + test.equal(user1.emails, undefined); + test.equal(user2.profile.bar, 2); + test.equal(user2.emails, undefined); // cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); + Meteor.users.remove(u1.id); + Meteor.users.remove(u2.id); }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { @@ -836,9 +845,11 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', const twitterIdNew = '' + twitterIdOld; // create an account with twitter using the old ID format of integer - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.twitter.monkey, 42); @@ -846,13 +857,15 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', // Update the account with the new ID format of string // test that the existing user is found, and that the ID // gets updated to a string value - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); test.length(users2, 1); // cleanup - await Meteor.users.remove(uid1); + await Meteor.users.remove(u1.id); }); From 6f79cd8d13ee3c080060a20327ee8c4c08866747 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:39:09 -0300 Subject: [PATCH 0130/1965] tests: finished --- packages/accounts-base/accounts_client_tests.js | 10 +++++----- packages/accounts-base/accounts_server.js | 17 +++++++++-------- packages/accounts-base/accounts_tests.js | 2 ++ packages/accounts-base/accounts_tests_setup.js | 4 ++-- packages/accounts-base/package.js | 2 +- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/accounts-base/accounts_client_tests.js b/packages/accounts-base/accounts_client_tests.js index 880a71e4fe..5558884273 100644 --- a/packages/accounts-base/accounts_client_tests.js +++ b/packages/accounts-base/accounts_client_tests.js @@ -36,9 +36,9 @@ const createUserAndLogout = (test, done, nextTests) => { }, }, () => { - Meteor.logout(() => { + Meteor.logout(async () => { // Make sure we're logged out - test.isFalse(Meteor.user()); + test.isFalse(await Meteor.userAsync()); // Handle next tests nextTests(test, done); }); @@ -245,13 +245,13 @@ Tinytest.addAsync( ); -Tinytest.addAsync( + Tinytest.addAsync( 'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails with invalid code', (test, done) => { createUserAndLogout(test, done, () => { forceEnableUser2fa(() => { - Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', e => { - test.isFalse(Meteor.user()); + Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', async e => { + test.isFalse(await Meteor.user()); test.equal(e.reason, 'Invalid 2FA code'); removeTestUser(done); }); diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index c6d46fc3cf..7b28f3bd22 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -402,10 +402,10 @@ export class AccountsServer extends AccountsCommon { // indicates that the login token has already been inserted into the // database and doesn't need to be inserted again. (It's used by the // "resume" login handler). - _loginUser(methodInvocation, userId, stampedLoginToken) { + async _loginUser(methodInvocation, userId, stampedLoginToken) { if (! stampedLoginToken) { stampedLoginToken = this._generateStampedLoginToken(); - this._insertLoginToken(userId, stampedLoginToken); + await this._insertLoginToken(userId, stampedLoginToken); } // This order (and the avoidance of yields) is important to make @@ -476,12 +476,13 @@ export class AccountsServer extends AccountsCommon { this._validateLogin(methodInvocation.connection, attempt); if (attempt.allowed) { + const o = await this._loginUser( + methodInvocation, + result.userId, + result.stampedLoginToken + ) const ret = { - ...this._loginUser( - methodInvocation, - result.userId, - result.stampedLoginToken - ), + ...o, ...result.options }; ret.type = attempt.type; @@ -695,7 +696,7 @@ export class AccountsServer extends AccountsCommon { const newStampedToken = accounts._generateStampedLoginToken(); newStampedToken.when = currentStampedToken.when; await accounts._insertLoginToken(this.userId, newStampedToken); - return accounts._loginUser(this, this.userId, newStampedToken); + return await accounts._loginUser(this, this.userId, newStampedToken); }; // Removes all tokens except the token associated with the current diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 4431c7f6a9..458c6ce589 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -702,6 +702,8 @@ Tinytest.addAsync( async test => { // create test user, without a google service const testEmail = "test@testdomain.com" + // being sure that the user is not already in the database + await Meteor.users.remove({ "emails.address": testEmail }); const uid0 = await Accounts.createUser({ email: testEmail }) // Verify that user is found from email and service merged diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index 31ed7e2196..c83ce5677b 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -9,7 +9,7 @@ const getTokenFromSecret = async ({ selector, secret: secretParam }) => { } secret = twoFactorAuthentication.secret; } - const { token } = Accounts._generate2faToken(secret); + const { token } = await Accounts._generate2faToken(secret); return token; }; @@ -30,7 +30,7 @@ Meteor.methods({ }, } ); - return getTokenFromSecret({ selector, secret }); + return await getTokenFromSecret({ selector, secret }); }, getTokenFromSecret, }); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 9c89bb5fd5..b707f589a2 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -63,5 +63,5 @@ Package.onTest(api => { api.addFiles('accounts_tests_setup.js', 'server'); api.mainModule('server_tests.js', 'server'); - // api.mainModule('client_tests.js', 'client'); + api.mainModule('client_tests.js', 'client'); }); From f3b3a9ab2040dde34c4c6c56383b37ed4d4c12b2 Mon Sep 17 00:00:00 2001 From: denihs Date: Thu, 8 Dec 2022 15:52:38 -0400 Subject: [PATCH 0131/1965] making the test-in-browser component dark mode --- packages/test-in-browser/driver.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/test-in-browser/driver.css b/packages/test-in-browser/driver.css index 1a870bc9e9..f58f42fea1 100644 --- a/packages/test-in-browser/driver.css +++ b/packages/test-in-browser/driver.css @@ -1,6 +1,8 @@ body { height: 100vh; width: 100vw; + background-color: black !important; + color: white !important; } .test-in-browser { @@ -71,11 +73,12 @@ body { .test_table .groupname { font-weight: bold; - background: #ddd; + background: #212121; padding-left: 5px; line-height: 24px; vertical-align: middle; font-size: 16px; + color: white; } .test_table .event { @@ -114,11 +117,11 @@ body { line-height: 24px; vertical-align: middle; font-size: 14px; - color: #666; + color: #757575; } -.test_table .succeeded .teststatus { color: #090; /* green */ background: #cfc; } -.test_table .failed .teststatus { color: #900; /* red */ background: #fcc; } +.test_table .succeeded .teststatus { color: #090; /* green */ background: black; } +.test_table .failed .teststatus { color: #900; /* red */ background: black; } .test_table .running .teststatus { color: #ccc; } .test_table .event .expected_fail { color: #600; } From 17a8a59b2ef194d714ddea961fe8d8b6222818fa Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 10 Dec 2022 00:00:22 -0300 Subject: [PATCH 0132/1965] Remove Fibers for Meteor Tools: - Fixing issues with create component - fixing remote catalog. --- packages/constraint-solver/catalog-loader.js | 12 ++-- .../constraint-solver-input.js | 4 +- .../constraint-solver/constraint-solver.js | 26 +++---- packages/constraint-solver/solver.js | 68 +++++++++--------- packages/logic-solver/logic.js | 4 +- tools/cli/main.js | 29 ++++---- tools/console/progress.ts | 9 +-- tools/isobuild/bundler.js | 4 +- tools/isobuild/isopack-cache.js | 10 +-- tools/isobuild/isopack.js | 14 ++-- tools/isobuild/unibuild.js | 6 +- tools/packaging/catalog/catalog-remote.js | 4 +- tools/packaging/catalog/catalog.js | 12 ++-- tools/packaging/package-map.js | 32 +++++---- tools/packaging/tropohouse.js | 71 ++++++++++--------- tools/project-context.js | 22 +++--- tools/utils/buildmessage.js | 10 +-- tools/utils/http-helpers.js | 14 ++-- 18 files changed, 176 insertions(+), 175 deletions(-) diff --git a/packages/constraint-solver/catalog-loader.js b/packages/constraint-solver/catalog-loader.js index 22f666b699..941218d1cf 100644 --- a/packages/constraint-solver/catalog-loader.js +++ b/packages/constraint-solver/catalog-loader.js @@ -51,10 +51,10 @@ var convertDeps = function (catalogDeps) { // Since we don't fetch different versions of a package independently // at the moment, this helper is where we get our data. -CS.CatalogLoader.prototype._getSortedVersionRecords = function (pkg) { +CS.CatalogLoader.prototype._getSortedVersionRecords = async function (pkg) { if (! _.has(this._sortedVersionRecordsCache, pkg)) { this._sortedVersionRecordsCache[pkg] = - this.catalog.getSortedVersionRecords(pkg); + await this.catalog.getSortedVersionRecords(pkg); } return this._sortedVersionRecordsCache[pkg]; @@ -80,10 +80,10 @@ CS.CatalogLoader.prototype.loadSingleVersion = function (pkg, version) { } }; -CS.CatalogLoader.prototype.loadAllVersions = function (pkg) { +CS.CatalogLoader.prototype.loadAllVersions = async function (pkg) { var self = this; var cache = self.catalogCache; - var versionRecs = self._getSortedVersionRecords(pkg); + var versionRecs = await self._getSortedVersionRecords(pkg); _.each(versionRecs, function (rec) { var version = rec.version; if (! cache.hasPackageVersion(pkg, version)) { @@ -95,7 +95,7 @@ CS.CatalogLoader.prototype.loadAllVersions = function (pkg) { // Takes an array of package names. Loads all versions of them and their // (strong) dependencies. -CS.CatalogLoader.prototype.loadAllVersionsRecursive = function (packageList) { +CS.CatalogLoader.prototype.loadAllVersionsRecursive = async function (packageList) { var self = this; // Within a call to loadAllVersionsRecursive, we only visit each package @@ -116,7 +116,7 @@ CS.CatalogLoader.prototype.loadAllVersionsRecursive = function (packageList) { while (loadQueue.length) { var pkg = loadQueue.pop(); - self.loadAllVersions(pkg); + await self.loadAllVersions(pkg); _.each(self.catalogCache.getPackageVersions(pkg), function (v) { var depMap = self.catalogCache.getDependencyMap(pkg, v); _.each(depMap, function (dep, package2) { diff --git a/packages/constraint-solver/constraint-solver-input.js b/packages/constraint-solver/constraint-solver-input.js index 9835e44030..f9b807934e 100644 --- a/packages/constraint-solver/constraint-solver-input.js +++ b/packages/constraint-solver/constraint-solver-input.js @@ -136,9 +136,9 @@ function getMentionedPackages(input) { return _.keys(packages); } -CS.Input.prototype.loadFromCatalog = function (catalogLoader) { +CS.Input.prototype.loadFromCatalog = async function (catalogLoader) { // Load packages into the cache (if they aren't loaded already). - catalogLoader.loadAllVersionsRecursive(getMentionedPackages(this)); + await catalogLoader.loadAllVersionsRecursive(getMentionedPackages(this)); }; CS.Input.prototype.loadOnlyPreviousSolution = function (catalogLoader) { diff --git a/packages/constraint-solver/constraint-solver.js b/packages/constraint-solver/constraint-solver.js index f185894d09..88bc8c81c1 100644 --- a/packages/constraint-solver/constraint-solver.js +++ b/packages/constraint-solver/constraint-solver.js @@ -42,14 +42,14 @@ CS.PackagesResolver = function (catalog, options) { // - supportedIsobuildFeaturePackages - map from package name to list of // version strings of isobuild feature packages that are available in the // catalog -CS.PackagesResolver.prototype.resolve = function (dependencies, constraints, +CS.PackagesResolver.prototype.resolve = async function (dependencies, constraints, options) { var self = this; options = options || {}; var Profile = (self._options.Profile || CS.DummyProfile); var input; - Profile.time("new CS.Input", function () { + await Profile.time("new CS.Input", function () { input = new CS.Input(dependencies, constraints, self.catalogCache, _.pick(options, 'upgrade', @@ -91,15 +91,15 @@ CS.PackagesResolver.prototype.resolve = function (dependencies, constraints, }); } - Profile.time( + await Profile.time( "Input#loadOnlyPreviousSolution", function () { - input.loadOnlyPreviousSolution(self.catalogLoader); + return input.loadOnlyPreviousSolution(self.catalogLoader); }); if (options.previousSolution && options.missingPreviousVersionIsError) { // see comment where missingPreviousVersionIsError is passed in - Profile.time("check for previous versions in catalog", function () { + await Profile.time("check for previous versions in catalog", function () { _.each(options.previousSolution, function (version, pkg) { if (! input.catalogCache.hasPackageVersion(pkg, version)) { CS.throwConstraintSolverError( @@ -122,7 +122,7 @@ CS.PackagesResolver.prototype.resolve = function (dependencies, constraints, // packages, because that would always result in just using the current // version, hence disabling upgrades. try { - output = CS.PackagesResolver._resolveWithInput(input, resolveOptions); + output = await CS.PackagesResolver._resolveWithInput(input, resolveOptions); } catch (e) { if (e.constraintSolverError) { output = null; @@ -134,14 +134,14 @@ CS.PackagesResolver.prototype.resolve = function (dependencies, constraints, if (! output) { // do a solve with all package versions available in the catalog. - Profile.time( + await Profile.time( "Input#loadFromCatalog", function () { - input.loadFromCatalog(self.catalogLoader); + return input.loadFromCatalog(self.catalogLoader); }); // if we fail to find a solution this time, this will throw. - output = CS.PackagesResolver._resolveWithInput(input, resolveOptions); + output = await CS.PackagesResolver._resolveWithInput(input, resolveOptions); } if (resultCache) { @@ -159,7 +159,7 @@ CS.PackagesResolver.prototype.resolve = function (dependencies, constraints, // - allAnswers (for testing, calculate all possible answers and put an extra // property named "allAnswers" on the result) // - Profile (the profiler interface in `tools/profile.js`) -CS.PackagesResolver._resolveWithInput = function (input, options) { +CS.PackagesResolver._resolveWithInput = async function (input, options) { options = options || {}; if (Meteor.isServer && @@ -169,7 +169,7 @@ CS.PackagesResolver._resolveWithInput = function (input, options) { } var solver; - (options.Profile || CS.DummyProfile).time("new CS.Solver", function () { + await (options.Profile || CS.DummyProfile).time("new CS.Solver", function () { solver = new CS.Solver(input, { nudge: options.nudge, Profile: options.Profile @@ -177,8 +177,8 @@ CS.PackagesResolver._resolveWithInput = function (input, options) { }); // Disable runtime type checks (they slow things down a bunch) - return Logic.disablingAssertions(function () { - var result = solver.getAnswer({ + return Logic.disablingAssertions(async function () { + var result = await solver.getAnswer({ allAnswers: options.allAnswers }); // if we're here, no conflicts were found (or an error would have diff --git a/packages/constraint-solver/solver.js b/packages/constraint-solver/solver.js index 4876109848..d53e1f7026 100644 --- a/packages/constraint-solver/solver.js +++ b/packages/constraint-solver/solver.js @@ -371,14 +371,14 @@ var DEBUG = false; // * minimize(step, options) // * minimize([step1, step2, ...], options) // * minimize(stepName, costTerms, costWeights, options) -CS.Solver.prototype.minimize = function (step, options) { +CS.Solver.prototype.minimize = async function (step, options) { var self = this; if (_.isArray(step)) { // minimize([steps...], options) - _.each(step, function (st) { - self.minimize(st, options); - }); + for (const st of step) { + await self.minimize(st, options) + } return; } @@ -395,13 +395,13 @@ CS.Solver.prototype.minimize = function (step, options) { } var theStep = new CS.Solver.Step( stepName_, costTerms_, (costWeights_ == null ? 1 : costWeights_)); - self.minimize(theStep, options_); + await self.minimize(theStep, options_); return; } // minimize(step, options); - self.Profile.time("minimize " + step.name, function () { + await self.Profile.time("minimize " + step.name, async function () { var logic = self.logic; @@ -417,7 +417,7 @@ CS.Solver.prototype.minimize = function (step, options) { var optimized = groupMutuallyExclusiveTerms(costTerms, costWeights); - self.setSolution(logic.minimizeWeightedSum( + self.setSolution(await logic.minimizeWeightedSum( self.solution, optimized.costTerms, optimized.costWeights, { progress: async function (status, cost) { if (self.options.nudge) { @@ -646,7 +646,7 @@ CS.Solver.prototype.setSolution = function (solution) { // about an unknown package, we may ask about packages that were // forbidden in an early analysis of the problem and never entered // into the Solver. - self.solution.ignoreUnknownVariables(); + return self.solution.ignoreUnknownVariables(); }; CS.Solver.prototype.getAnswer = function (options) { @@ -656,7 +656,7 @@ CS.Solver.prototype.getAnswer = function (options) { }); }; -CS.Solver.prototype._getAnswer = function (options) { +CS.Solver.prototype._getAnswer = async function (options) { var self = this; var input = self.input; var analysis = self.analysis; @@ -665,19 +665,19 @@ CS.Solver.prototype._getAnswer = function (options) { var Profile = self.Profile; var logic; - Profile.time("new Logic.Solver (MiniSat start-up)", function () { + await Profile.time("new Logic.Solver (MiniSat start-up)", function () { logic = self.logic = new Logic.Solver(); }); // require root dependencies - Profile.time("require root dependencies", function () { + await Profile.time("require root dependencies", function () { _.each(input.dependencies, function (p) { logic.require(p); }); }); // generate package version variables for known, reachable packages - Profile.time("generate package variables", function () { + await Profile.time("generate package variables", function () { _.each(_.keys(analysis.reachablePackages), function (p) { if (! _.has(analysis.packagesWithNoAllowedVersions, p)) { var versionVars = _.map(self.getVersions(p), @@ -694,7 +694,7 @@ CS.Solver.prototype._getAnswer = function (options) { }); // generate strong dependency requirements - Profile.time("generate dependency requirements", function () { + await Profile.time("generate dependency requirements", function () { _.each(_.keys(analysis.reachablePackages), function (p) { _.each(self.getVersions(p), function (v) { _.each(cache.getDependencyMap(p, v), function (dep) { @@ -710,7 +710,7 @@ CS.Solver.prototype._getAnswer = function (options) { // generate constraints -- but technically don't enforce them, because // we haven't forced the conflictVars to be false - Profile.time("generate constraints", function () { + await Profile.time("generate constraints", function () { _.each(analysis.constraints, function (c) { // We logically require that EITHER a constraint is marked as a // conflict OR it comes from a package version that is not selected @@ -740,7 +740,7 @@ CS.Solver.prototype._getAnswer = function (options) { // using the solution if it exists // * Calling `minimize()`, which always maintains satisfiability - Profile.time("pre-solve", function () { + await Profile.time("pre-solve", function () { self.setSolution(logic.solve()); }); // There is always a solution at this point, namely, @@ -753,9 +753,9 @@ CS.Solver.prototype._getAnswer = function (options) { // which we didn't do earlier because we needed to establish an // initial solution before asking the solver if it's possible to // not use these packages. - Profile.time("forbid packages with no matching versions", function () { - _.each(analysis.packagesWithNoAllowedVersions, function (constrs, p) { - var newSolution = logic.solveAssuming(Logic.not(p)); + await Profile.time("forbid packages with no matching versions", function () { + _.each(analysis.packagesWithNoAllowedVersions, async function (constrs, p) { + var newSolution = await logic.solveAssuming(Logic.not(p)); if (newSolution) { self.setSolution(newSolution); logic.forbid(p); @@ -776,7 +776,7 @@ CS.Solver.prototype._getAnswer = function (options) { // than 0, we'll throw an error later, after we apply the constraints // and the cost function, so that we can explain the problem to the // user in a convincing way. - self.minimize('unknown_packages', _.keys(analysis.unknownPackages)); + await self.minimize('unknown_packages', _.keys(analysis.unknownPackages)); // try not to set the conflictVar on any constraint. If the minimum // is greater than 0, we'll throw an error later, after we've run the @@ -784,11 +784,11 @@ CS.Solver.prototype._getAnswer = function (options) { // If there are conflicts, this minimization can be time-consuming // (several seconds or more). The strategy 'bottom-up' helps by // looking for solutions with few conflicts first. - self.minimize('conflicts', _.pluck(analysis.constraints, 'conflictVar'), + await self.minimize('conflicts', _.pluck(analysis.constraints, 'conflictVar'), { strategy: 'bottom-up' }); // Try not to use "unanticipated" prerelease versions - self.minimize('unanticipated_prereleases', + await self.minimize('unanticipated_prereleases', analysis.unanticipatedPrereleases); var previousRootSteps = self.getVersionDistanceSteps( @@ -821,20 +821,20 @@ CS.Solver.prototype._getAnswer = function (options) { // unless you pass --allow-incompatible-update. It will actually be enforced // farther down, but for now, we want to apply this constraint before handling // updates. - self.minimize(previousRootIncompat); + await self.minimize(previousRootIncompat); } - self.minimize(self.getVersionCostSteps( + await self.minimize(self.getVersionCostSteps( 'update', toUpdate, CS.VersionPricer.MODE_UPDATE)); if (input.allowIncompatibleUpdate) { // If you pass `--allow-incompatible-update`, we will still try to minimize // version changes to root deps that break compatibility, but with a lower // priority than taking as-new-as-possible versions for `meteor update`. - self.minimize(previousRootIncompat); + await self.minimize(previousRootIncompat); } - self.minimize(previousRootVersionParts); + await self.minimize(previousRootVersionParts); var otherPrevious = _.filter(_.map(input.previousSolution, function (v, p) { return new CS.PackageAndVersion(p, v); @@ -844,7 +844,7 @@ CS.Solver.prototype._getAnswer = function (options) { ! input.isRootDependency(p); }); - self.minimize(self.getVersionDistanceSteps( + await self.minimize(self.getVersionDistanceSteps( 'previous_indirect', otherPrevious, input.upgradeIndirectDepPatchVersions)); @@ -852,7 +852,7 @@ CS.Solver.prototype._getAnswer = function (options) { return ! input.isInPreviousSolution(p); }); - self.minimize(self.getVersionCostSteps( + await self.minimize(self.getVersionCostSteps( 'new_root', newRootDeps, CS.VersionPricer.MODE_UPDATE)); // Lock down versions of all root, previous, and updating packages that @@ -875,7 +875,7 @@ CS.Solver.prototype._getAnswer = function (options) { // about the versions of the unimportant packages that it's a very weak // signal. In other words, the user might be better off with some tie-breaker // that looks only at the important packages anyway. - Profile.time("lock down important versions", function () { + await Profile.time("lock down important versions", function () { _.each(self.currentVersionMap(), function (v, pkg) { if (input.isRootDependency(pkg) || input.isInPreviousSolution(pkg) || @@ -895,15 +895,15 @@ CS.Solver.prototype._getAnswer = function (options) { } }); - self.minimize(self.getVersionCostSteps( + await self.minimize(self.getVersionCostSteps( 'new_indirect', otherPackages, CS.VersionPricer.MODE_GRAVITY_WITH_PATCHES)); - self.minimize('total_packages', _.keys(analysis.reachablePackages)); + await self.minimize('total_packages', _.keys(analysis.reachablePackages)); // throw errors about unknown packages if (self.stepsByName['unknown_packages'].optimum > 0) { - Profile.time("generate error for unknown packages", function () { + await Profile.time("generate error for unknown packages", function () { var unknownPackages = _.keys(analysis.unknownPackages); var unknownPackagesNeeded = _.filter(unknownPackages, function (p) { return self.solution.evaluate(p); @@ -943,7 +943,7 @@ CS.Solver.prototype._getAnswer = function (options) { var incompatRootChanges = _.keys(self.getStepContributions( self.stepsByName['previous_root_incompat'])); - Profile.time("generate errors for incompatible root change", function () { + await Profile.time("generate errors for incompatible root change", function () { var numActualErrors = 0; _.each(incompatRootChanges, function (pvStr) { var pv = CS.PackageAndVersion.fromString(pvStr); @@ -972,13 +972,13 @@ CS.Solver.prototype._getAnswer = function (options) { var result = { neededToUseUnanticipatedPrereleases: ( self.stepsByName['unanticipated_prereleases'].optimum > 0), - answer: Profile.time("generate version map", function () { + answer: await Profile.time("generate version map", function () { return self.currentVersionMap(); }) }; if (allAnswers) { - Profile.time("generate all answers", function () { + await Profile.time("generate all answers", function () { var allAnswersList = [result.answer]; var nextAnswer = function () { var formula = self.solution.getFormula(); diff --git a/packages/logic-solver/logic.js b/packages/logic-solver/logic.js index 6571bf89e1..804d4e3c80 100644 --- a/packages/logic-solver/logic.js +++ b/packages/logic-solver/logic.js @@ -38,11 +38,11 @@ Logic._assertIfEnabled = function (value, tester, description) { // Disabling runtime assertions speeds up clause generation. Assertions // are disabled when the local variable `assert` is null instead of // `Logic._assert`. -Logic.disablingAssertions = function (f) { +Logic.disablingAssertions = async function (f) { var oldAssert = assert; try { assert = null; - return f(); + return await f(); } finally { assert = oldAssert; } diff --git a/tools/cli/main.js b/tools/cli/main.js index 764b21def9..f2d0755688 100644 --- a/tools/cli/main.js +++ b/tools/cli/main.js @@ -458,9 +458,9 @@ var springboard = async function (rel, options) { await Console.withProgressDisplayVisible(async function () { var messages = await buildmessage.capture({ title: "downloading the command-line tool" - }, function () { - return catalog.runAndRetryWithRefreshIfHelpful(function () { - tropohouse.default.downloadPackagesMissingFromMap(packageMap, { + }, async function () { + await catalog.runAndRetryWithRefreshIfHelpful(async function () { + await tropohouse.default.downloadPackagesMissingFromMap(packageMap, { serverArchitectures, }); }); @@ -563,7 +563,7 @@ var springboard = async function (rel, options) { }; // Springboard to a pre-0.9.0 release. -var oldSpringboard = function (toolsVersion) { +var oldSpringboard = async function (toolsVersion) { // Strip off the "node" and "meteor.js" from argv and replace it with the // appropriate tools's meteor shell script. var newArgv = process.argv.slice(2); @@ -572,7 +572,7 @@ var oldSpringboard = function (toolsVersion) { // Release our connection to the sqlite catalog database for the current // process, so that the springboarded process can reestablish it. - catalog.official.closePermanently(); + await catalog.official.closePermanently(); // Now exec; we're not coming back. require('kexec')(cmd, newArgv); @@ -866,7 +866,6 @@ asyncLocalStorage.run({}, async function () { appDir = files.pathResolve(appDir); } - // TODO -> Maybe await here? await require('../tool-env/isopackets.js').ensureIsopacketsLoadable(); // Initialize the server catalog. Among other things, this is where we get @@ -982,8 +981,8 @@ asyncLocalStorage.run({}, async function () { // Somehow we have a catalog that doesn't have any releases on the // default track. Try syncing, at least. (This is a pretty unlikely // error case, since you should always start with a non-empty catalog.) - await Console.withProgressDisplayVisible(function () { - return catalog.refreshOrWarn(); + await Console.withProgressDisplayVisible(async function () { + await catalog.refreshOrWarn(); }); releaseName = release.latestKnown(); } @@ -1039,12 +1038,12 @@ asyncLocalStorage.run({}, async function () { if (warehouse.realReleaseExistsInWarehouse(releaseName)) { var manifest = warehouse.ensureReleaseExistsAndReturnManifest( releaseName); - oldSpringboard(manifest.tools); // doesn't return + await oldSpringboard(manifest.tools); // doesn't return } // ATTEMPT 3: modern release, troposphere sync needed. - await Console.withProgressDisplayVisible(function () { - return catalog.refreshOrWarn(); + await Console.withProgressDisplayVisible(async function () { + await catalog.refreshOrWarn(); }); // Try to load the release even if the refresh failed, since it might @@ -1087,7 +1086,7 @@ asyncLocalStorage.run({}, async function () { } if (manifest) { // OK, it was an legacy release. We should old-springboard to it. - oldSpringboard(manifest.tools); // doesn't return + await oldSpringboard(manifest.tools); // doesn't return } } } @@ -1107,7 +1106,7 @@ asyncLocalStorage.run({}, async function () { if (process.platform === "win32") { // Give a good warning if this release exists, but only in the super old // warehouse. - var result = httpHelpers.request( + var result = await httpHelpers.request( "http://warehouse.meteor.com/releases/" + releaseName + ".release.json"); if(result.response.statusCode === 200) { Console.error("Meteor on Windows does not support running any releases", @@ -1519,8 +1518,8 @@ asyncLocalStorage.run({}, async function () { var catalogRefreshStrategy = command.catalogRefresh; if (! catalog.triedToRefreshRecently && catalogRefreshStrategy.beforeCommand) { - await buildmessage.enterJob({title: 'updating package catalog'}, function () { - return catalogRefreshStrategy.beforeCommand(); + await buildmessage.enterJob({title: 'updating package catalog'}, async function () { + await catalogRefreshStrategy.beforeCommand(); }); } diff --git a/tools/console/progress.ts b/tools/console/progress.ts index 94ededdd31..1a514554ed 100644 --- a/tools/console/progress.ts +++ b/tools/console/progress.ts @@ -45,7 +45,7 @@ export class Progress { return "Progress [state=" + JSON.stringify(this.state) + "]"; } - reportProgressDone() { + async reportProgressDone() { const state = { ...this.selfState, done: true, @@ -58,7 +58,7 @@ export class Progress { state.current = state.end; } - this.reportProgress(state); + await this.reportProgress(state); } // Tries to determine which is the 'current' job in the tree @@ -138,13 +138,14 @@ export class Progress { } // Receives a state report indicating progress of self - reportProgress(state: ProgressState) { + async reportProgress(state: ProgressState) { this.selfState = state; this.updateTotalState(); // Nudge the spinner/progress bar, but don't yield (might not be safe to yield) - require('./console.js').Console.nudge(false); + const { Console } = require("./console.js"); + await Console.nudge(false); this.notifyState(); } diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 05157fea70..4b670bfd47 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -2827,7 +2827,7 @@ var writeFile = Profile("bundler writeFile", async function (file, builder, opti // directories) let data = file.contents(); - const hash = file.hash(); + const hash = await file.hash(); if (builder.usePreviousWrite(file.targetPath, hash)) { return; @@ -3493,7 +3493,7 @@ async function lintBundle (projectContext, isopack, packageSource) { } const localPackagesMessages = - projectContext.getLintingMessagesForLocalPackages(); + await projectContext.getLintingMessagesForLocalPackages(); if (localPackagesMessages) { lintedAnything = true; lintingMessages.merge(localPackagesMessages); diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index 1c4bf8d45f..61c1ddaa62 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -242,8 +242,8 @@ export class IsopackCache { if (buildmessage.jobHasMessages()) { return; } - await Profile.time('IsopackCache Build local isopack', () => { - return self._loadLocalPackage(name, packageInfo, previousIsopack); + await Profile.time('IsopackCache Build local isopack', async () => { + await self._loadLocalPackage(name, packageInfo, previousIsopack); }); }); } else if (packageInfo.kind === 'versioned') { @@ -262,7 +262,7 @@ export class IsopackCache { // we assume that it never changes. (Admittedly, this means we won't // notice if we download an additional build for the package.) isopack = previousIsopack; - packagesToLoad = isopack.getStrongOrderedUsedAndImpliedPackages(); + packagesToLoad = await isopack.getStrongOrderedUsedAndImpliedPackages(); } if (! isopack) { // Load the isopack from disk. @@ -513,11 +513,11 @@ export class IsopackCache { return this._lintPackageWithSourceRoot === packageSource.sourceRoot; } - getLintingMessagesForLocalPackages() { + async getLintingMessagesForLocalPackages() { const messages = new buildmessage._MessageSet(); let anyLinters = false; - this._packageMap.eachPackage((name, packageInfo) => { + await this._packageMap.eachPackage((name, packageInfo) => { const isopack = this._isopacks[name]; if (packageInfo.kind === 'local') { if (!this._shouldLintPackage(packageInfo.packageSource)) { diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 40c2a91264..76f3fa2c2e 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -1156,22 +1156,22 @@ Object.assign(Isopack.prototype, { } // Plugins - _.each(self.plugins, function (pluginsByArch, name) { - _.each(pluginsByArch, function (plugin) { + for (const [name, pluginsByArch] of Object.entries(self.plugins)) { + for (const plugin of Object.values(pluginsByArch)) { // XXX the name of the plugin doesn't typically contain a colon, but // escape it just in case. var pluginDir = builder.generateFilename( - 'plugin.' + colonConverter.convert(name) + '.' + plugin.arch, - { directory: true }); - var pluginBuild = plugin.write(builder.enter(pluginDir)); + 'plugin.' + colonConverter.convert(name) + '.' + plugin.arch, + { directory: true }); + var pluginBuild = await plugin.write(builder.enter(pluginDir)); var pluginEntry = { name: name, arch: plugin.arch, path: files.pathJoin(pluginDir, pluginBuild.controlFile) }; mainJson.plugins.push(pluginEntry); - }); - }); + } + } // Tools // First, are we supposed to include our own source as a tool? diff --git a/tools/isobuild/unibuild.js b/tools/isobuild/unibuild.js index 5ecf5c40a9..acd5772413 100644 --- a/tools/isobuild/unibuild.js +++ b/tools/isobuild/unibuild.js @@ -213,7 +213,7 @@ export class Unibuild { packageName: isopack.name, sourceRoot: unibuildBasePath, // Rebuild binary npm packages if unibuild arch matches host arch. - rebuildBinaries: archinfo.matches(archinfo.host(), arch) + rebuildBinaries: archinfo.matches(await archinfo.host(), arch) }); return new this(isopack, { @@ -252,7 +252,7 @@ export class Unibuild { // Figure out where the npm dependencies go. let node_modules = {}; - for (const nmd of unibuild.nodeModulesDirectories) { + for (const nmd of Object.values(unibuild.nodeModulesDirectories)) { const bundlePath = _.has(npmDirsToCopy, nmd.sourcePath) // We already have this npm directory from another unibuild. ? npmDirsToCopy[nmd.sourcePath] @@ -307,7 +307,7 @@ export class Unibuild { } }); - for (const [type, parts] of Object.entries(offset)) { + for (const [type, parts] of Object.entries(concat)) { if (parts.length) { await builder.write(files.pathJoin(unibuildDir, type), { data: Buffer.concat(concat[type], offset[type]) diff --git a/tools/packaging/catalog/catalog-remote.js b/tools/packaging/catalog/catalog-remote.js index de32fb6118..8f35d85a40 100644 --- a/tools/packaging/catalog/catalog-remote.js +++ b/tools/packaging/catalog/catalog-remote.js @@ -566,9 +566,9 @@ Object.assign(RemoteCatalog.prototype, { // Just getVersion mapped over getSortedVersions, but only makes one round // trip to sqlite. - getSortedVersionRecords: function (name) { + getSortedVersionRecords: async function (name) { var self = this; - var versionRecords = this._contentQuery( + var versionRecords = await this._contentQuery( "SELECT content FROM versions WHERE packageName=?", [name]); if (! versionRecords) return []; diff --git a/tools/packaging/catalog/catalog.js b/tools/packaging/catalog/catalog.js index 8e2fe31b1c..348fee5606 100644 --- a/tools/packaging/catalog/catalog.js +++ b/tools/packaging/catalog/catalog.js @@ -16,9 +16,9 @@ catalog.Refresh.OnceAtStart = function (options) { self.options = Object.assign({}, options); }; -catalog.Refresh.OnceAtStart.prototype.beforeCommand = function () { +catalog.Refresh.OnceAtStart.prototype.beforeCommand = async function () { var self = this; - if (!catalog.refreshOrWarn(self.options)) { + if (!await catalog.refreshOrWarn(self.options)) { if (self.options.ignoreErrors) { Console.debug("Failed to update package catalog, but will continue."); } else { @@ -42,10 +42,10 @@ catalog.Refresh.Never = function (options) { // // THIS IS A HIGH-LEVEL UI COMMAND. DO NOT CALL IT FROM LOW-LEVEL CODE (ie, call // it only from main.js or command implementations). -catalog.refreshOrWarn = function (options) { +catalog.refreshOrWarn = async function (options) { catalog.triedToRefreshRecently = true; try { - catalog.official.refresh(options); + await catalog.official.refresh(options); catalog.refreshFailed = false; return true; } catch (err) { @@ -96,8 +96,8 @@ catalog.runAndRetryWithRefreshIfHelpful = async function (attempt) { catalog.official.offline); // Run `attempt` in a nested buildmessage context. - var messages = await buildmessage.capture(function () { - return attempt(canRetry); + var messages = await buildmessage.capture(async function () { + await attempt(canRetry); }); // Did it work? Great. diff --git a/tools/packaging/package-map.js b/tools/packaging/package-map.js index 83955e752f..a00034ef66 100644 --- a/tools/packaging/package-map.js +++ b/tools/packaging/package-map.js @@ -160,23 +160,27 @@ exports.PackageMap.fromReleaseVersion = function (releaseVersion) { exports.PackageMapDelta = function (options) { var self = this; self._changedPackages = {}; - - options.packageMap.eachPackage(function (packageName, info) { - var oldVersion = _.has(options.cachedVersions, packageName) - ? options.cachedVersions[packageName] : null; - self._storeAddOrChange( - packageName, info, oldVersion, options.anticipatedPrereleases, - options.neededToUseUnanticipatedPrereleases); - }); - - _.each(options.cachedVersions, function (oldVersion, packageName) { - if (! options.packageMap.getInfo(packageName)) { - self._storeRemove(packageName, oldVersion); - } - }); + self.options = options; }; Object.assign(exports.PackageMapDelta.prototype, { + init: async function() { + const self = this; + + await self.options.packageMap.eachPackage(function (packageName, info) { + var oldVersion = _.has(self.options.cachedVersions, packageName) + ? self.options.cachedVersions[packageName] : null; + self._storeAddOrChange( + packageName, info, oldVersion, self.options.anticipatedPrereleases, + self.options.neededToUseUnanticipatedPrereleases); + }); + + _.each(self.options.cachedVersions, function (oldVersion, packageName) { + if (! self.options.packageMap.getInfo(packageName)) { + self._storeRemove(packageName, oldVersion); + } + }); + }, _storeAddOrChange: function (packageName, newInfo, oldVersion, anticipatedPrereleases, neededToUseUnanticipatedPrereleases) { diff --git a/tools/packaging/tropohouse.js b/tools/packaging/tropohouse.js index f63e3e30aa..320a0990fc 100644 --- a/tools/packaging/tropohouse.js +++ b/tools/packaging/tropohouse.js @@ -42,9 +42,9 @@ exports.default = new exports.Tropohouse(defaultWarehouseDir()); * @param {Boolean} forceConvert Convert paths even on unix, for testing * @return {String} Temporary directory with contents of package */ -exports._extractAndConvert = function (packageTarball, forceConvert) { +exports._extractAndConvert = async function (packageTarball, forceConvert) { var targetDirectory = files.mkdtemp(); - files.extractTarGz(packageTarball, targetDirectory, { + await files.extractTarGz(packageTarball, targetDirectory, { forceConvert: forceConvert }); @@ -324,14 +324,14 @@ Object.assign(exports.Tropohouse.prototype, { return downloadedArches; }, - _saveIsopack: function (isopack, packageName) { + _saveIsopack: async function (isopack, packageName) { // XXX does this actually need the name as an argument or can we just get // it from isopack? var self = this; if (self.platform === "win32") { - isopack.saveToPath(self.packagePath(packageName, isopack.version), { + await isopack.saveToPath(self.packagePath(packageName, isopack.version), { includePreCompilerPluginIsopackVersions: true }); } else { @@ -347,11 +347,11 @@ Object.assign(exports.Tropohouse.prototype, { var combinedDirectory = self.packagePath( packageName, newPackageLinkTarget); - isopack.saveToPath(combinedDirectory, { + await isopack.saveToPath(combinedDirectory, { includePreCompilerPluginIsopackVersions: true }); - files.symlinkOverSync(newPackageLinkTarget, + await files.symlinkOverSync(newPackageLinkTarget, self.packagePath(packageName, isopack.version)); } }, @@ -415,15 +415,15 @@ Object.assign(exports.Tropohouse.prototype, { } var packagePath = self.packagePath(packageName, version); - var download = function download () { + var download = async function download () { buildmessage.assertInCapture(); Console.debug("Downloading missing local versions of package", packageName + "@" + version, ":", archesToDownload); - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "downloading " + packageName + "@" + version + "..." - }, function() { + }, async function() { var buildInputDirs = []; var buildTempDirs = []; var packageLinkTarget = null; @@ -463,21 +463,21 @@ Object.assign(exports.Tropohouse.prototype, { // XXX how does concurrency work here? we could just get errors if we // try to rename over the other thing? but that's the same as in // warehouse? - _.each(buildsToDownload, ({ build: { url }}) => { - const packageTarball = buildmessage.enterJob({ + for (const { build: { url }} of buildsToDownload) { + const packageTarball = await buildmessage.enterJob({ title: "downloading " + packageName + "@" + version + "..." - }, () => { + }, async () => { try { // Override the download domain name and protocol if METEOR_WAREHOUSE_URLBASE // provided. if (process.env.METEOR_WAREHOUSE_URLBASE) { url = url.replace( - /^[a-zA-Z]+:\/\/[^\/]+/, - process.env.METEOR_WAREHOUSE_URLBASE + /^[a-zA-Z]+:\/\/[^\/]+/, + process.env.METEOR_WAREHOUSE_URLBASE ); } - return httpHelpers.getUrlWithResuming({ + return await httpHelpers.getUrlWithResuming({ url: url, encoding: null, progress: buildmessage.getCurrentProgressTracker(), @@ -496,31 +496,32 @@ Object.assign(exports.Tropohouse.prototype, { return; } - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "extracting " + packageName + "@" + version + "..." - }, () => { + }, async () => { const buildTempDir = exports._extractAndConvert(packageTarball); buildInputDirs.push(buildTempDir); buildTempDirs.push(buildTempDir); }); - }); + } if (buildmessage.jobHasMessages()) { return; } - buildmessage.enterJob({ + await buildmessage.enterJob({ title: "loading " + packageName + "@" + version + "..." - }, () => { + }, async () => { // We need to turn our builds into a single isopack. var isopack = new Isopack(); - _.each(buildInputDirs, (buildTempDir, i) => { - isopack._loadUnibuildsFromPath(packageName, buildTempDir, { + for (let i = 0; i <= buildInputDirs.length; i++) { + const buildTempDir = buildInputDirs[i]; + await isopack._loadUnibuildsFromPath(packageName, buildTempDir, { firstIsopack: i === 0, }); - }); + } - self._saveIsopack(isopack, packageName, version); + await self._saveIsopack(isopack, packageName, version); }); // Delete temp directories now (asynchronously). @@ -530,7 +531,7 @@ Object.assign(exports.Tropohouse.prototype, { // Clean up old version. if (packageLinkTarget) { - files.rm_recursive(self.packagePath(packageName, packageLinkTarget)); + await files.rm_recursive(self.packagePath(packageName, packageLinkTarget)); } }); }; @@ -547,19 +548,19 @@ Object.assign(exports.Tropohouse.prototype, { // already have. // // Reports errors via buildmessage. - downloadPackagesMissingFromMap: function (packageMap, options) { + downloadPackagesMissingFromMap: async function (packageMap, options) { var self = this; buildmessage.assertInCapture(); options = options || {}; - var serverArchs = options.serverArchitectures || [archinfo.host()]; + var serverArchs = options.serverArchitectures || [await archinfo.host()]; var downloader; var downloaders = []; - packageMap.eachPackage(function (packageName, info) { + await packageMap.eachPackage(async function (packageName, info) { if (info.kind !== 'versioned') { return; } - buildmessage.enterJob( + await buildmessage.enterJob( "checking for " + packageName + "@" + info.version, function () { downloader = self._makeDownloader({ @@ -591,10 +592,10 @@ Object.assign(exports.Tropohouse.prototype, { // Just one package to download? Use a good message. if (downloaders.length === 1) { downloader = downloaders[0]; - buildmessage.enterJob( + await buildmessage.enterJob( "downloading " + downloader.packageName + "@" + downloader.version, - function () { - downloader.download(); + async function () { + await downloader.download(); } ); return; @@ -603,11 +604,11 @@ Object.assign(exports.Tropohouse.prototype, { // Download multiple packages in parallel. // XXX use a better progress bar that shows how many you've // finished downloading. - buildmessage.forkJoin({ + await buildmessage.forkJoin({ title: 'downloading ' + downloaders.length + ' packages', parallel: true - }, downloaders, function (downloader) { - downloader.download(); + }, downloaders, async function (downloader) { + await downloader.download(); }); }, diff --git a/tools/project-context.js b/tools/project-context.js index ca1439421a..0f12c620fa 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -333,21 +333,21 @@ Object.assign(ProjectContext.prototype, { return this._completeStagesThrough(STAGE.DOWNLOAD_MISSING_PACKAGES); }); }, - buildLocalPackages: function () { + buildLocalPackages: async function () { return Profile.run('ProjectContext buildLocalPackages', () => { return this._completeStagesThrough(STAGE.BUILD_LOCAL_PACKAGES); }); }, - saveChangedMetadata: function () { - return Profile.run('ProjectContext saveChangedMetadata', () => { - return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); + saveChangedMetadata: async function () { + return await Profile.run('ProjectContext saveChangedMetadata', async () => { + return await this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, prepareProjectForBuild: async function () { // This is the same as saveChangedMetadata, but if we insert stages after // that one it will continue to mean "fully finished". - return await Profile.run('ProjectContext prepareProjectForBuild', () => { - return this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); + return await Profile.run('ProjectContext prepareProjectForBuild', async () => { + return await this._completeStagesThrough(STAGE.SAVE_CHANGED_METADATA); }); }, @@ -624,8 +624,8 @@ Object.assign(ProjectContext.prototype, { await Profile.time( "Select Package Versions" + (resolverRunCount > 1 ? (" (Try " + resolverRunCount + ")") : ""), - function () { - solution = resolver.resolve( + async function () { + solution = await resolver.resolve( depsAndConstraints.deps, depsAndConstraints.constraints, resolveOptions); }); @@ -658,6 +658,8 @@ Object.assign(ProjectContext.prototype, { anticipatedPrereleases: anticipatedPrereleases }); + await self.packageMapDelta.init(); + self._saveResolverResultCache(); self._completedStage = STAGE.RESOLVE_CONSTRAINTS; @@ -960,8 +962,8 @@ Object.assign(ProjectContext.prototype, { ? null : self._forceRebuildPackages); } - await buildmessage.enterJob('building local packages', function () { - return self.isopackCache.buildLocalPackages(); + await buildmessage.enterJob('building local packages', async function () { + await self.isopackCache.buildLocalPackages(); }); self._completedStage = STAGE.BUILD_LOCAL_PACKAGES; }), diff --git a/tools/utils/buildmessage.js b/tools/utils/buildmessage.js index f75efc2851..80bd5e2be6 100644 --- a/tools/utils/buildmessage.js +++ b/tools/utils/buildmessage.js @@ -210,10 +210,10 @@ var reportProgress = function (state) { } }; -var reportProgressDone = function () { +var reportProgressDone = async function () { var progress = currentProgress.get(); if (progress) { - progress.reportProgressDone(); + await progress.reportProgressDone(); } }; @@ -283,7 +283,7 @@ async function capture(options, f) { } catch (e) { console.error(e); } finally { - progress.reportProgressDone(); + await progress.reportProgressDone(); resetFns.forEach(fn => fn()); @@ -356,7 +356,7 @@ async function enterJob(options, f) { try { return await f(); } finally { - progress.reportProgressDone(); + await progress.reportProgressDone(); while (resetFns.length) { await resetFns.pop()(); @@ -388,7 +388,7 @@ async function enterJob(options, f) { try { return await f(); } finally { - progress.reportProgressDone(); + await progress.reportProgressDone(); while (resetFns.length) { await resetFns.pop()(); diff --git a/tools/utils/http-helpers.js b/tools/utils/http-helpers.js index c8abaa86c9..59a588cb47 100644 --- a/tools/utils/http-helpers.js +++ b/tools/utils/http-helpers.js @@ -363,13 +363,7 @@ Object.assign(exports, { } if (promise) { - try { - return promise.await(); - } finally { - if (progress) { - progress.reportProgressDone(); - } - } + return promise.finally(async () => await progress.reportProgressDone()); } else { return req; } @@ -436,7 +430,7 @@ Object.assign(exports, { // We only use this for package downloads. In theory we could use it for // all requests but that seems like overkill and it isn't well tested in // other scenarioes. - getUrlWithResuming(urlOrOptions) { + async getUrlWithResuming(urlOrOptions) { const options = _.isObject(urlOrOptions) ? _.clone(urlOrOptions) : { url: urlOrOptions, }; @@ -495,8 +489,8 @@ Object.assign(exports, { } } - const result = attempt().await(); - const response = result.response + const result = await attempt(); + const response = result.response; if (response.statusCode >= 400 && response.statusCode < 600) { const href = response.request.href; throw Error(`Could not get ${href}; server returned [${response.statusCode}]`); From 41da434fde46f4c9292374f76340523b1839463a Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Sat, 10 Dec 2022 00:52:42 -0300 Subject: [PATCH 0133/1965] Remove Fibers for Meteor Tools: - Add missing await when reading file on import-scanner and also wait when ensuring the packages dependencies are loading. - The "create" command is running now! --- tools/isobuild/compiler-plugin.js | 2 +- tools/isobuild/import-scanner.ts | 6 +++--- tools/isobuild/isopack-cache.js | 6 +++--- tools/isobuild/isopack.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 729e4e881b..d317d41bca 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -1344,7 +1344,7 @@ export class PackageSourceBatch { // Records the subset of allMissingModules that were successfully // relocated to a source batch that could handle them. const allRelocatedModules = Object.create(null); - const scannerMap = new Map; + const scannerMap = new Map(); for (const batch of sourceBatches) { const name = batch.unibuild.pkg.name || null; diff --git a/tools/isobuild/import-scanner.ts b/tools/isobuild/import-scanner.ts index 80c306f7e6..a91fceb0fe 100644 --- a/tools/isobuild/import-scanner.ts +++ b/tools/isobuild/import-scanner.ts @@ -1244,7 +1244,7 @@ export default class ImportScanner { return info; } - private readModule(absPath: string): RawFile | null { + private async readModule(absPath: string): RawFile | null { const dotExt = pathExtname(absPath).toLowerCase(); if (dotExt === ".node") { @@ -1278,7 +1278,7 @@ export default class ImportScanner { } } - info.dataString = this.defaultHandlers.call(ext as any, info); + info.dataString = await this.defaultHandlers.call(ext as any, info); if (info.dataString !== dataString) { info.data = Buffer.from(info.dataString, "utf8"); } @@ -1322,7 +1322,7 @@ export default class ImportScanner { } else { rawFile = absModuleId.endsWith("/package.json") ? this.readPackageJson(absPath) - : this.readModule(absPath); + : await this.readModule(absPath); // If the module is not readable, _readModule may return null. // Otherwise it will return { data, dataString, hash }. diff --git a/tools/isobuild/isopack-cache.js b/tools/isobuild/isopack-cache.js index 61c1ddaa62..982059084a 100644 --- a/tools/isobuild/isopack-cache.js +++ b/tools/isobuild/isopack-cache.js @@ -298,9 +298,9 @@ export class IsopackCache { // Also load its dependencies. This is so that if this package is being // built as part of a plugin, all the transitive dependencies of the // plugin are loaded. - _.each(packagesToLoad, function (packageToLoad) { - ensureLoaded(packageToLoad); - }); + for (const packageToLoad of packagesToLoad) { + await ensureLoaded(packageToLoad); + } } else { throw Error("unknown packageInfo kind?"); } diff --git a/tools/isobuild/isopack.js b/tools/isobuild/isopack.js index 76f3fa2c2e..ab19e7a80b 100644 --- a/tools/isobuild/isopack.js +++ b/tools/isobuild/isopack.js @@ -1122,7 +1122,7 @@ Object.assign(Isopack.prototype, { const usesModules = ! isopackCache || isopackCache.uses(self, "modules", unibuild.arch); - const unibuildJson = unibuild.toJSON({ + const unibuildJson = await unibuild.toJSON({ builder, unibuildDir, usesModules, From 5955819eeaaa1f0ac05abb89c2e90bd2df371cd3 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Mon, 12 Dec 2022 15:54:54 -0300 Subject: [PATCH 0134/1965] wip: updating styles in tinytest --- packages/test-in-browser/driver.css | 35 ++++++++++++++++++++-------- packages/test-in-browser/driver.html | 7 ++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/test-in-browser/driver.css b/packages/test-in-browser/driver.css index f58f42fea1..ad59df151d 100644 --- a/packages/test-in-browser/driver.css +++ b/packages/test-in-browser/driver.css @@ -1,8 +1,23 @@ + +* { + font-family: 'Space Grotesk', 'VT323', monospace; + /* Variables */ + --bg-black : #18181b; + --primary-white : #F9FAFB; + + --red-200 : #fecaca; + --red-500 : #dc2626; + --red-900 : #7f1d1d; + + --gray-300: #d6d3d1; +} + body { height: 100vh; width: 100vw; - background-color: black !important; - color: white !important; + background-color: var(--bg-black) !important; + color: var(--primary-white) !important; + } .test-in-browser { @@ -125,8 +140,8 @@ body { .test_table .running .teststatus { color: #ccc; } .test_table .event .expected_fail { color: #600; } -.test_table .event .fail { color: #d00; } -.test_table .event .exception { color: #d00; } +.test_table .event .fail { color: #dc2626; } +.test_table .event .exception { color: #dc2626; } .test_table .event .nodata { color: #ccc; font-style: italic; } .test_table .event .xtimes, .test_table .event .failkey { font-weight: bold; } @@ -137,7 +152,7 @@ body { .test_table .event:hover .debug { display: inline; - color: gray; + color: #737373; /* neutral 500 */ text-decoration: underline; cursor: pointer; } @@ -156,18 +171,18 @@ body { text-decoration: none; } .string_equal_expected ins { - background: #ed0; + background: #facc15; /* yellow 400 */ } .string_equal_actual ins { - color: #d00; - background: #fcc; + color: #dc2626; /* red 600 */ + background: #fecaca; /* red 200 */ } #current-client-test { - color: #ccc; + color: #d6d3d1; /* gray 300 */ margin-right: 15px; } .failedTests { - color: #900; /* red */ + color: #7f1d1d; /* red 900 */ } diff --git a/packages/test-in-browser/driver.html b/packages/test-in-browser/driver.html index 52bd3b8aad..0d86cdb6b4 100644 --- a/packages/test-in-browser/driver.html +++ b/packages/test-in-browser/driver.html @@ -1,5 +1,12 @@