From f650be7b89953c95d946b13284bb686afe061b47 Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 6 Feb 2023 14:56:37 -0400 Subject: [PATCH 01/16] - using _AsynchronousQueue in the server - continue for looping instead of breaking it --- packages/autoupdate/autoupdate_server.js | 2 +- .../ddp-client/common/livedata_connection.js | 7 +- packages/meteor/async_helpers.js | 35 ++++- packages/meteor/fiber_helpers.js | 129 ------------------ packages/meteor/package.js | 6 +- packages/minimongo/local_collection.js | 14 +- packages/mongo/observe_multiplex.js | 6 +- packages/mongo/oplog_observe_driver.js | 8 +- packages/tinytest/tinytest.js | 2 +- packages/webapp/webapp_server.js | 2 +- 10 files changed, 50 insertions(+), 161 deletions(-) delete mode 100644 packages/meteor/fiber_helpers.js diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js index 86a5812845..7d20443c27 100644 --- a/packages/autoupdate/autoupdate_server.js +++ b/packages/autoupdate/autoupdate_server.js @@ -52,7 +52,7 @@ Autoupdate.autoupdateVersionRefreshable = null; Autoupdate.autoupdateVersionCordova = null; Autoupdate.appId = __meteor_runtime_config__.appId = process.env.APP_ID; -var syncQueue = new Meteor._SynchronousQueue(); +var syncQueue = new Meteor._AsynchronousQueue(); async function updateVersions(shouldReloadClientProgram) { // Step 1: load the current client program on the server diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index f014585503..f0a36a6d3f 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -256,6 +256,8 @@ export class Connection { } }; + self.queue = new Meteor._AsynchronousQueue(); + if (Meteor.isServer) { self._stream.on( 'message', @@ -1833,7 +1835,10 @@ export class Connection { } } - async onMessage(raw_msg) { + onMessage(raw_msg) { + this.queue.queueTask(() => this._onMessage(raw_msg), raw_msg); + } + async _onMessage(raw_msg) { let msg; try { msg = DDPCommon.parseDDP(raw_msg); diff --git a/packages/meteor/async_helpers.js b/packages/meteor/async_helpers.js index 7755f895fa..ee13f8bc55 100644 --- a/packages/meteor/async_helpers.js +++ b/packages/meteor/async_helpers.js @@ -1,8 +1,27 @@ Meteor._noYieldsAllowed = function (f) { - return f(); + const result = f(); + if (Meteor._isPromise(result)) { + throw new Error("function is a promise when calling Meteor._noYieldsAllowed"); + } + return result }; -Meteor._DoubleEndedQueue = Npm.require('denque'); +class FakeDoubleEndedQueue { + constructor() { + this.queue = []; + } + push(task) { + this.queue.push(task); + } + shift() { + return this.queue.shift(); + } + isEmpty() { + return this.queue.length === 0; + } +} + +Meteor._DoubleEndedQueue = Meteor.isServer ? Npm.require('denque') : FakeDoubleEndedQueue; // Meteor._SynchronousQueue is a queue which runs task functions serially. // Tasks are assumed to be synchronous: ie, it's assumed that they are @@ -50,8 +69,15 @@ class AsynchronousQueue { let resolve; const promise = new Promise(r => resolve = r); - setImmediate(() => { - this._run().finally(() => resolve()); + const runImmediateHandle = (fn) => { + if (Meteor.isServer) { + setImmediate(fn); + return; + } + setTimeout(fn, 0); + }; + runImmediateHandle(() => { + this._run().finally(resolve); }); return promise; } @@ -136,7 +162,6 @@ class AsynchronousQueue { } Meteor._AsynchronousQueue = AsynchronousQueue; -Meteor._SynchronousQueue = AsynchronousQueue; // Sleep. Mostly used for debugging (eg, inserting latency into server diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js deleted file mode 100644 index a6bf89b30c..0000000000 --- a/packages/meteor/fiber_helpers.js +++ /dev/null @@ -1,129 +0,0 @@ -Meteor._noYieldsAllowed = function (f) { - return f(); -}; - -Meteor._DoubleEndedQueue = Npm.require('denque'); - -// 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(); - } - - async 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); - - // XXX: We should be doing this a different way. - await Meteor._sleepForMs(10); - 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). -// -Meteor._sleepForMs = (ms) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/packages/meteor/package.js b/packages/meteor/package.js index e7e880670d..50e9cd63c1 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -34,11 +34,7 @@ Package.onUse(function (api) { api.addFiles('timers.js', ['client', 'server']); api.addFiles('errors.js', ['client', 'server']); api.addFiles('asl-helpers.js', 'server'); - if (process.env.DISABLE_FIBERS) { - api.addFiles('async_helpers.js', 'server'); - } else { - api.addFiles('fiber_helpers.js', 'server'); - } + api.addFiles('async_helpers.js', ['client', 'server']); api.addFiles('fiber_stubs_client.js', 'client'); api.addFiles('asl-helpers-client.js', 'client'); api.addFiles('startup_client.js', ['client']); diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index f7ff82aeb5..da68c0d4c8 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -131,7 +131,7 @@ export default class LocalCollection { const query = this.queries[qid]; if (query.dirty) { - break; + continue; } const matchResult = query.matcher.documentMatches(doc); @@ -174,7 +174,7 @@ export default class LocalCollection { const query = this.queries[qid]; if (query.dirty) { - break; + continue; } const matchResult = query.matcher.documentMatches(doc); @@ -623,11 +623,6 @@ export default class LocalCollection { let updateCount = 0; - - // for (const qid of Object.keys(this.queries)) { - // this._recomputeResults(this.queries[qid], qidToOriginalResults[qid]); - // } - this._eachPossiblyMatchingDocSync(selector, (doc, id) => { const queryResult = matcher.documentMatches(doc); @@ -637,7 +632,6 @@ export default class LocalCollection { recomputeQids = this._modifyAndNotifySync( doc, mod, - recomputeQids, queryResult.arrayIndices ); @@ -770,7 +764,7 @@ export default class LocalCollection { const query = this.queries[qid]; if (query.dirty) { - break; + continue; } const afterMatch = query.matcher.documentMatches(doc); @@ -815,7 +809,7 @@ export default class LocalCollection { const query = this.queries[qid]; if (query.dirty) { - break; + continue; } const afterMatch = query.matcher.documentMatches(doc); diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index d248e06943..43da4b141b 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -189,8 +189,6 @@ ObserveMultiplexer = class { return; // note: docs may be an _IdMap or an OrderedDict await this._cache.docs.forEachAsync(async (doc, id) => { - //TODO FIXME - if (!this._handles) console.log({this:this}); if (!_.has(this._handles, handle._id)) throw Error("handle got removed before sending initial adds!"); const { _id, ...fields } = handle.nonMutatingCallbacks ? doc @@ -215,8 +213,8 @@ ObserveHandle = class { // 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._addedBefore = async function (id, fields, before) { + await callbacks.added(id, fields); }; } }); diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index f8fccc731f..3a988e4ba3 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -115,8 +115,8 @@ OplogObserveDriver = function (options) { forEachTrigger(self._cursorDescription, function (trigger) { self._stopHandles.push(self._mongoHandle._oplogHandle.onOplogEntry( - trigger, function (notification) { - Meteor._noYieldsAllowed(finishIfNeedToPollQuery(function () { + trigger, async function (notification) { + await finishIfNeedToPollQuery(function () { var op = notification.op; if (notification.dropCollection || notification.dropDatabase) { // Note: this call is not allowed to block on anything (especially @@ -131,7 +131,7 @@ OplogObserveDriver = function (options) { return self._handleOplogEntrySteadyOrFetching(op); } } - })); + }); } )); }); @@ -162,7 +162,7 @@ OplogObserveDriver = function (options) { for (const driver of Object.values(drivers)) { if (driver._stopped) - return; + continue; var write = await fence.beginWrite(); if (driver._phase === PHASE.STEADY) { diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 38bba2e38a..05a98f1b38 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -455,7 +455,7 @@ export const TestManager = new (class TestManager { constructor() { this.tests = {}; this.ordered_tests = []; - this.testQueue = Meteor.isServer && new Meteor._SynchronousQueue(); + this.testQueue = Meteor.isServer && new Meteor._AsynchronousQueue(); this.onlyTestsNames = []; } diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index f520484cc3..01071eb24d 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -796,7 +796,7 @@ onMessage('webapp-reload-client', async ({ arch }) => { async function runWebAppServer() { var shuttingDown = false; - var syncQueue = new Meteor._SynchronousQueue(); + var syncQueue = new Meteor._AsynchronousQueue(); var getItemPathname = function(itemUrl) { return decodeURIComponent(parseUrl(itemUrl).pathname); From cabfe8f52213f38d3ff80399836a6e02cea2d2da Mon Sep 17 00:00:00 2001 From: denihs Date: Mon, 6 Feb 2023 14:57:08 -0400 Subject: [PATCH 02/16] adding todo fibers --- packages/mongo/oplog_tailing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/oplog_tailing.js b/packages/mongo/oplog_tailing.js index d28c4ddbb6..02039501b2 100644 --- a/packages/mongo/oplog_tailing.js +++ b/packages/mongo/oplog_tailing.js @@ -78,7 +78,7 @@ OplogHandle = function (oplogUrl, dbName) { self._workerActive = false; const shouldAwait = self._startTailing(); - //TODO Why wait? + //TODO[fibers] Why wait? }; Object.assign(OplogHandle.prototype, { From cd957ac8e9bca0fe0596072258f8aa4909c9e630 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:09:37 -0400 Subject: [PATCH 03/16] fix 'minimongo - basics' --- packages/minimongo/local_collection.js | 6 +- packages/minimongo/minimongo_tests_client.js | 62 ++++++++++---------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index da68c0d4c8..9be8468628 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -96,7 +96,11 @@ export default class LocalCollection { return this.find(selector, options).fetch()[0]; } async findOneAsync(selector, options = {}) { - return Promise.resolve(this.findOne(selector, options)); + if (arguments.length === 0) { + selector = {}; + } + options.limit = 1; + return (await this.find(selector, options).fetchAsync())[0]; } prepareInsert(doc) { assertHasValidFieldNames(doc); diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 62b07fdecb..2a6e70f136 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -66,16 +66,16 @@ const log_callbacks = operations => ({ }); // XXX test shared structure in all MM entrypoints -Tinytest.add('minimongo - basics', test => { +Tinytest.addAsync('minimongo - basics', async test => { const c = new LocalCollection(); let fluffyKitten_id; let count; - fluffyKitten_id = c.insertAsync({type: 'kitten', name: 'fluffy'}); - c.insertAsync({type: 'kitten', name: 'snookums'}); - c.insertAsync({type: 'cryptographer', name: 'alice'}); - c.insertAsync({type: 'cryptographer', name: 'bob'}); - c.insertAsync({type: 'cryptographer', name: 'cara'}); + fluffyKitten_id = await c.insertAsync({type: 'kitten', name: 'fluffy'}); + await c.insertAsync({type: 'kitten', name: 'snookums'}); + await c.insertAsync({type: 'cryptographer', name: 'alice'}); + await c.insertAsync({type: 'cryptographer', name: 'bob'}); + await c.insertAsync({type: 'cryptographer', name: 'cara'}); test.equal(c.find().count(), 5); test.equal(c.find({type: 'kitten'}).count(), 2); test.equal(c.find({type: 'cryptographer'}).count(), 3); @@ -83,14 +83,14 @@ Tinytest.add('minimongo - basics', test => { test.length(c.find({type: 'cryptographer'}).fetch(), 3); test.equal(fluffyKitten_id, c.findOne({type: 'kitten', name: 'fluffy'})._id); - c.remove({name: 'cara'}); + await c.removeAsync({name: 'cara'}); test.equal(c.find().count(), 4); test.equal(c.find({type: 'kitten'}).count(), 2); test.equal(c.find({type: 'cryptographer'}).count(), 2); test.length(c.find({type: 'kitten'}).fetch(), 2); test.length(c.find({type: 'cryptographer'}).fetch(), 2); - count = c.updateAsync({name: 'snookums'}, {$set: {type: 'cryptographer'}}); + count = await c.updateAsync({name: 'snookums'}, {$set: {type: 'cryptographer'}}); test.equal(count, 1); test.equal(c.find().count(), 4); test.equal(c.find({type: 'kitten'}).count(), 1); @@ -98,25 +98,25 @@ Tinytest.add('minimongo - basics', test => { test.length(c.find({type: 'kitten'}).fetch(), 1); test.length(c.find({type: 'cryptographer'}).fetch(), 3); - c.remove(null); - c.remove(false); - c.remove(undefined); + await c.removeAsync(null); + await c.removeAsync(false); + await c.removeAsync(undefined); test.equal(c.find().count(), 4); - c.remove({_id: null}); - c.remove({_id: false}); - c.remove({_id: undefined}); - count = c.remove(); + await c.removeAsync({_id: null}); + await c.removeAsync({_id: false}); + await c.removeAsync({_id: undefined}); + count = await c.removeAsync(); test.equal(count, 0); test.equal(c.find().count(), 4); - count = c.remove({}); + count = await c.removeAsync({}); test.equal(count, 4); test.equal(c.find().count(), 0); - c.insertAsync({_id: 1, name: 'strawberry', tags: ['fruit', 'red', 'squishy']}); - c.insertAsync({_id: 2, name: 'apple', tags: ['fruit', 'red', 'hard']}); - c.insertAsync({_id: 3, name: 'rose', tags: ['flower', 'red', 'squishy']}); + await c.insertAsync({_id: 1, name: 'strawberry', tags: ['fruit', 'red', 'squishy']}); + await c.insertAsync({_id: 2, name: 'apple', tags: ['fruit', 'red', 'hard']}); + await c.insertAsync({_id: 3, name: 'rose', tags: ['flower', 'red', 'squishy']}); test.equal(c.find({tags: 'flower'}).count(), 1); test.equal(c.find({tags: 'fruit'}).count(), 2); @@ -125,12 +125,12 @@ Tinytest.add('minimongo - basics', test => { test.length(c.find({tags: 'fruit'}).fetch(), 2); test.length(c.find({tags: 'red'}).fetch(), 3); - test.equal(c.findOne(1).name, 'strawberry'); - test.equal(c.findOne(2).name, 'apple'); - test.equal(c.findOne(3).name, 'rose'); - test.equal(c.findOne(4), undefined); - test.equal(c.findOne('abc'), undefined); - test.equal(c.findOne(undefined), undefined); + test.equal((await c.findOneAsync(1)).name, 'strawberry'); + test.equal((await c.findOneAsync(2)).name, 'apple'); + test.equal((await c.findOneAsync(3)).name, 'rose'); + test.equal(await c.findOneAsync(4), undefined); + test.equal(await c.findOneAsync('abc'), undefined); + test.equal(await c.findOneAsync(undefined), undefined); test.equal(c.find(1).count(), 1); test.equal(c.find(4).count(), 0); @@ -180,13 +180,13 @@ Tinytest.add('minimongo - basics', test => { test.equal(c.find({tags: 'fruit'}, {sort: ['_id', 'desc'], skip: 1, limit: 1}).count(), 1); // Regression test for #455. - c.insertAsync({foo: {bar: 'baz'}}); + await c.insertAsync({foo: {bar: 'baz'}}); test.equal(c.find({foo: {bam: 'baz'}}).count(), 0); test.equal(c.find({foo: {bar: 'baz'}}).count(), 1); // Regression test for #5301 - c.remove({}); - c.insertAsync({a: 'a', b: 'b'}); + await c.removeAsync({}); + await c.insertAsync({a: 'a', b: 'b'}); const noop = () => null; test.equal(c.find({a: noop}).count(), 1); test.equal(c.find({a: 'a', b: noop}).count(), 1); @@ -195,7 +195,7 @@ Tinytest.add('minimongo - basics', test => { // Regression test for #4260 // Only insert enumerable, own properties from the object - c.remove({}); + await c.removeAsync({}); function Thing() { this.a = 1; this.b = 2; @@ -204,8 +204,8 @@ Tinytest.add('minimongo - basics', test => { Thing.prototype.c = 3; Thing.prototype.d = () => null; const before = new Thing(); - c.insertAsync(before); - const after = c.findOne(); + await c.insertAsync(before); + const after = await c.findOneAsync(); test.equal(after.a, 1); test.equal(after.b, undefined); test.equal(after.c, undefined); From 692d3eb5f5dd438c49bdced4345239257c8513e7 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:20:08 -0400 Subject: [PATCH 04/16] fix 'minimongo - observe ordered with projection --- packages/minimongo/minimongo_tests_client.js | 68 ++++++++++---------- tools/isobuild/bundler.js | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 2a6e70f136..2632cd37c4 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -1859,7 +1859,7 @@ Tinytest.add('minimongo - fetch with projection, deep copy', test => { test.equal(filteredDoc.a.x, 43, 'projection returning deep copy - excluding'); }); -Tinytest.add('minimongo - observe ordered with projection', test => { +Tinytest.addAsync('minimongo - observe ordered with projection', async test => { // These tests are copy-paste from "minimongo -observe ordered", // slightly modified to test projection const operations = []; @@ -1867,94 +1867,94 @@ Tinytest.add('minimongo - observe ordered with projection', test => { let handle; const c = new LocalCollection(); - handle = c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(cbs); + handle = await c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(cbs); test.isTrue(handle.collection === c); - c.insert({_id: 'foo', a: 1, b: 2}); + await c.insertAsync({_id: 'foo', a: 1, b: 2}); test.equal(operations.shift(), ['added', {a: 1}, 0, null]); - c.update({a: 1}, {$set: {a: 2, b: 1}}); + await c.updateAsync({a: 1}, {$set: {a: 2, b: 1}}); test.equal(operations.shift(), ['changed', {a: 2}, 0, {a: 1}]); - c.insert({_id: 'bar', a: 10, c: 33}); + await c.insertAsync({_id: 'bar', a: 10, c: 33}); test.equal(operations.shift(), ['added', {a: 10}, 1, null]); - c.update({}, {$inc: {a: 1}}, {multi: true}); - c.update({}, {$inc: {c: 1}}, {multi: true}); + await c.updateAsync({}, {$inc: {a: 1}}, {multi: true}); + await c.updateAsync({}, {$inc: {c: 1}}, {multi: true}); test.equal(operations.shift(), ['changed', {a: 3}, 0, {a: 2}]); test.equal(operations.shift(), ['changed', {a: 11}, 1, {a: 10}]); - c.update({a: 11}, {a: 1, b: 44}); + await c.updateAsync({a: 11}, {a: 1, b: 44}); test.equal(operations.shift(), ['changed', {a: 1}, 1, {a: 11}]); test.equal(operations.shift(), ['moved', {a: 1}, 1, 0, 'foo']); - c.remove({a: 2}); + await c.removeAsync({a: 2}); test.equal(operations.shift(), undefined); - c.remove({a: 3}); + await c.removeAsync({a: 3}); test.equal(operations.shift(), ['removed', 'foo', 1, {a: 3}]); // test stop handle.stop(); const idA2 = Random.id(); - c.insert({_id: idA2, a: 2}); + await c.insertAsync({_id: idA2, a: 2}); test.equal(operations.shift(), undefined); const cursor = c.find({}, {fields: {a: 1, _id: 0}}); test.throws(() => { cursor.observeChanges({added() {}}); }); - test.throws(() => { - cursor.observe({added() {}}); + await test.throwsAsync(async () => { + await cursor.observe({added() {}}); }); // test initial inserts (and backwards sort) - handle = c.find({}, {sort: {a: -1}, fields: { a: 1 } }).observe(cbs); + handle = await c.find({}, {sort: {a: -1}, fields: { a: 1 } }).observe(cbs); test.equal(operations.shift(), ['added', {a: 2}, 0, null]); test.equal(operations.shift(), ['added', {a: 1}, 1, null]); handle.stop(); // test _suppress_initial - handle = c.find({}, {sort: {a: -1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_suppress_initial: true})); + handle = await c.find({}, {sort: {a: -1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_suppress_initial: true})); test.equal(operations.shift(), undefined); - c.insert({a: 100, b: { foo: 'bar' }}); + await c.insertAsync({a: 100, b: { foo: 'bar' }}); test.equal(operations.shift(), ['added', {a: 100}, 0, idA2]); handle.stop(); // test skip and limit. - c.remove({}); - handle = c.find({}, {sort: {a: 1}, skip: 1, limit: 2, fields: { blacklisted: 0 }}).observe(cbs); + await c.removeAsync({}); + handle = await c.find({}, {sort: {a: 1}, skip: 1, limit: 2, fields: { blacklisted: 0 }}).observe(cbs); test.equal(operations.shift(), undefined); - c.insert({a: 1, blacklisted: 1324}); + await c.insertAsync({a: 1, blacklisted: 1324}); test.equal(operations.shift(), undefined); - c.insert({_id: 'foo', a: 2, blacklisted: ['something']}); + await c.insertAsync({_id: 'foo', a: 2, blacklisted: ['something']}); test.equal(operations.shift(), ['added', {a: 2}, 0, null]); - c.insert({a: 3, blacklisted: { 2: 3 }}); + await c.insertAsync({a: 3, blacklisted: { 2: 3 }}); test.equal(operations.shift(), ['added', {a: 3}, 1, null]); - c.insert({a: 4, blacklisted: 6}); + await c.insertAsync({a: 4, blacklisted: 6}); test.equal(operations.shift(), undefined); - c.update({a: 1}, {a: 0, blacklisted: 4444}); + await c.updateAsync({a: 1}, {a: 0, blacklisted: 4444}); test.equal(operations.shift(), undefined); - c.update({a: 0}, {a: 5, blacklisted: 11111}); + await c.updateAsync({a: 0}, {a: 5, blacklisted: 11111}); test.equal(operations.shift(), ['removed', 'foo', 0, {a: 2}]); test.equal(operations.shift(), ['added', {a: 4}, 1, null]); - c.update({a: 3}, {a: 3.5, blacklisted: 333.4444}); + await c.updateAsync({a: 3}, {a: 3.5, blacklisted: 333.4444}); test.equal(operations.shift(), ['changed', {a: 3.5}, 0, {a: 3}]); handle.stop(); // test _no_indices - c.remove({}); - handle = c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_no_indices: true})); - c.insert({_id: 'foo', a: 1, zoo: 'crazy'}); + await c.removeAsync({}); + handle = await c.find({}, {sort: {a: 1}, fields: { a: 1 }}).observe(Object.assign(cbs, {_no_indices: true})); + await c.insertAsync({_id: 'foo', a: 1, zoo: 'crazy'}); test.equal(operations.shift(), ['added', {a: 1}, -1, null]); - c.update({a: 1}, {$set: {a: 2, foobar: 'player'}}); + await c.updateAsync({a: 1}, {$set: {a: 2, foobar: 'player'}}); test.equal(operations.shift(), ['changed', {a: 2}, -1, {a: 1}]); - c.insert({a: 10, b: 123.45}); + await c.insertAsync({a: 10, b: 123.45}); test.equal(operations.shift(), ['added', {a: 10}, -1, null]); - c.update({}, {$inc: {a: 1, b: 2}}, {multi: true}); + await c.updateAsync({}, {$inc: {a: 1, b: 2}}, {multi: true}); test.equal(operations.shift(), ['changed', {a: 3}, -1, {a: 2}]); test.equal(operations.shift(), ['changed', {a: 11}, -1, {a: 10}]); - c.update({a: 11, b: 125.45}, {a: 1, b: 444}); + await c.updateAsync({a: 11, b: 125.45}, {a: 1, b: 444}); test.equal(operations.shift(), ['changed', {a: 1}, -1, {a: 11}]); test.equal(operations.shift(), ['moved', {a: 1}, -1, -1, 'foo']); - c.remove({a: 2}); + await c.removeAsync({a: 2}); test.equal(operations.shift(), undefined); - c.remove({a: 3}); + await c.removeAsync({a: 3}); test.equal(operations.shift(), ['removed', 'foo', -1, {a: 3}]); handle.stop(); }); diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 358e197157..b38da99c78 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -3441,7 +3441,7 @@ 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. - for (const target of targets) { + for (const target of Object.values(targets)) { await writeClientTarget(target); } } else { From 643ae2bd07d3c76e899e2701189669da46b1e7fa Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:28:04 -0400 Subject: [PATCH 05/16] fix 'minimongo - modify' --- packages/minimongo/minimongo_tests_client.js | 804 +++++++++---------- 1 file changed, 402 insertions(+), 402 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 2632cd37c4..7e95490d45 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -2406,13 +2406,13 @@ Tinytest.add('minimongo - binary search', test => { checkSearchBackward([2, 2, 2, 2, 2, 2, 2], 3, 0, 'Backward: Highly degenerate array, upper'); }); -Tinytest.add('minimongo - modify', test => { - const modifyWithQuery = (doc, query, mod, expected) => { +Tinytest.addAsync('minimongo - modify', async test => { + const modifyWithQuery = async (doc, query, mod, expected) => { const coll = new LocalCollection; - coll.insert(doc); + await coll.insertAsync(doc); // The query is relevant for 'a.$.b'. - coll.update(query, mod); - const actual = coll.findOne(); + await coll.updateAsync(query, mod); + const actual = await coll.findOneAsync(); if (!expected._id) { delete actual._id; // added by insert @@ -2424,26 +2424,26 @@ Tinytest.add('minimongo - modify', test => { test.equal(actual, expected, EJSON.stringify({input: doc, mod})); } }; - const modify = (doc, mod, expected) => { - modifyWithQuery(doc, {}, mod, expected); + const modify = async (doc, mod, expected) => { + await modifyWithQuery(doc, {}, mod, expected); }; - const exceptionWithQuery = (doc, query, mod) => { + const exceptionWithQuery = async (doc, query, mod) => { const coll = new LocalCollection; - coll.insert(doc); - test.throws(() => { - coll.update(query, mod); + await coll.insertAsync(doc); + await test.throwsAsync(async () => { + await coll.updateAsync(query, mod); }); }; - const exception = (doc, mod) => { - exceptionWithQuery(doc, {}, mod); + const exception = async (doc, mod) => { + await exceptionWithQuery(doc, {}, mod); }; - const upsert = (query, mod, expected) => { + const upsert = async (query, mod, expected) => { const coll = new LocalCollection; - const result = coll.upsert(query, mod); + const result = await coll.upsertAsync(query, mod); - const actual = coll.findOne(); + const actual = await coll.findOneAsync(); if (expected._id) { test.equal(result.insertedId, expected._id); @@ -2454,13 +2454,13 @@ Tinytest.add('minimongo - modify', test => { test.equal(actual, expected); }; - const upsertUpdate = (initialDoc, query, mod, expected) => { + const upsertUpdate = async (initialDoc, query, mod, expected) => { const collection = new LocalCollection; - collection.insert(initialDoc); + await collection.insertAsync(initialDoc); - const result = collection.upsert(query, mod); - const actual = collection.findOne(); + const result = await collection.upsertAsync(query, mod); + const actual = await collection.findOneAsync(); if (!expected._id) { delete actual._id; @@ -2469,153 +2469,153 @@ Tinytest.add('minimongo - modify', test => { test.equal(actual, expected); }; - const upsertException = (query, mod) => { + const upsertException = async (query, mod) => { const coll = new LocalCollection; - test.throws(() => { - coll.upsert(query, mod); + await test.throwsAsync(async () => { + await coll.upsertAsync(query, mod); }); }; // document replacement - modify({}, {}, {}); - modify({a: 12}, {}, {}); // tested against mongodb - modify({a: 12}, {a: 13}, {a: 13}); - modify({a: 12, b: 99}, {a: 13}, {a: 13}); - exception({a: 12}, {a: 13, $set: {b: 13}}); - exception({a: 12}, {$set: {b: 13}, a: 13}); + await modify({}, {}, {}); + await modify({a: 12}, {}, {}); // tested against mongodb + await modify({a: 12}, {a: 13}, {a: 13}); + await modify({a: 12, b: 99}, {a: 13}, {a: 13}); + await exception({a: 12}, {a: 13, $set: {b: 13}}); + await exception({a: 12}, {$set: {b: 13}, a: 13}); - exception({a: 12}, {$a: 13}); // invalid operator - exception({a: 12}, {b: {$a: 13}}); - exception({a: 12}, {b: {'a.b': 13}}); - exception({a: 12}, {b: {'\0a': 13}}); + await exception({a: 12}, {$a: 13}); // invalid operator + await exception({a: 12}, {b: {$a: 13}}); + await exception({a: 12}, {b: {'a.b': 13}}); + await exception({a: 12}, {b: {'\0a': 13}}); // keys - modify({}, {$set: {a: 12}}, {a: 12}); - modify({}, {$set: {'a.b': 12}}, {a: {b: 12}}); - modify({}, {$set: {'a.b.c': 12}}, {a: {b: {c: 12}}}); - modify({a: {d: 99}}, {$set: {'a.b.c': 12}}, {a: {d: 99, b: {c: 12}}}); - modify({}, {$set: {'a.b.3.c': 12}}, {a: {b: {3: {c: 12}}}}); - modify({a: {b: []}}, {$set: {'a.b.3.c': 12}}, { + await modify({}, {$set: {a: 12}}, {a: 12}); + await modify({}, {$set: {'a.b': 12}}, {a: {b: 12}}); + await modify({}, {$set: {'a.b.c': 12}}, {a: {b: {c: 12}}}); + await modify({a: {d: 99}}, {$set: {'a.b.c': 12}}, {a: {d: 99, b: {c: 12}}}); + await modify({}, {$set: {'a.b.3.c': 12}}, {a: {b: {3: {c: 12}}}}); + await modify({a: {b: []}}, {$set: {'a.b.3.c': 12}}, { a: {b: [null, null, null, {c: 12}]}}); - exception({a: [null, null, null]}, {$set: {'a.1.b': 12}}); - exception({a: [null, 1, null]}, {$set: {'a.1.b': 12}}); - exception({a: [null, 'x', null]}, {$set: {'a.1.b': 12}}); - exception({a: [null, [], null]}, {$set: {'a.1.b': 12}}); - modify({a: [null, null, null]}, {$set: {'a.3.b': 12}}, { + await exception({a: [null, null, null]}, {$set: {'a.1.b': 12}}); + await exception({a: [null, 1, null]}, {$set: {'a.1.b': 12}}); + await exception({a: [null, 'x', null]}, {$set: {'a.1.b': 12}}); + await exception({a: [null, [], null]}, {$set: {'a.1.b': 12}}); + await modify({a: [null, null, null]}, {$set: {'a.3.b': 12}}, { a: [null, null, null, {b: 12}]}); - exception({a: []}, {$set: {'a.b': 12}}); - exception({a: 12}, {$set: {'a.b': 99}}); // tested on mongo - exception({a: 'x'}, {$set: {'a.b': 99}}); - exception({a: true}, {$set: {'a.b': 99}}); - exception({a: null}, {$set: {'a.b': 99}}); - modify({a: {}}, {$set: {'a.3': 12}}, {a: {3: 12}}); - modify({a: []}, {$set: {'a.3': 12}}, {a: [null, null, null, 12]}); - exception({}, {$set: {'': 12}}); // tested on mongo - exception({}, {$set: {'.': 12}}); // tested on mongo - exception({}, {$set: {'a.': 12}}); // tested on mongo - exception({}, {$set: {'. ': 12}}); // tested on mongo - exception({}, {$inc: {'... ': 12}}); // tested on mongo - exception({}, {$set: {'a..b': 12}}); // tested on mongo - modify({a: [1, 2, 3]}, {$set: {'a.01': 99}}, {a: [1, 99, 3]}); - modify({a: [1, {a: 98}, 3]}, {$set: {'a.01.b': 99}}, {a: [1, {a: 98, b: 99}, 3]}); - modify({}, {$set: {'2.a.b': 12}}, {2: {a: {b: 12}}}); // tested - exception({x: []}, {$set: {'x.2..a': 99}}); - modify({x: [null, null]}, {$set: {'x.2.a': 1}}, {x: [null, null, {a: 1}]}); - exception({x: [null, null]}, {$set: {'x.1.a': 1}}); + await exception({a: []}, {$set: {'a.b': 12}}); + await exception({a: 12}, {$set: {'a.b': 99}}); // tested on mongo + await exception({a: 'x'}, {$set: {'a.b': 99}}); + await exception({a: true}, {$set: {'a.b': 99}}); + await exception({a: null}, {$set: {'a.b': 99}}); + await modify({a: {}}, {$set: {'a.3': 12}}, {a: {3: 12}}); + await modify({a: []}, {$set: {'a.3': 12}}, {a: [null, null, null, 12]}); + await exception({}, {$set: {'': 12}}); // tested on mongo + await exception({}, {$set: {'.': 12}}); // tested on mongo + await exception({}, {$set: {'a.': 12}}); // tested on mongo + await exception({}, {$set: {'. ': 12}}); // tested on mongo + await exception({}, {$inc: {'... ': 12}}); // tested on mongo + await exception({}, {$set: {'a..b': 12}}); // tested on mongo + await modify({a: [1, 2, 3]}, {$set: {'a.01': 99}}, {a: [1, 99, 3]}); + await modify({a: [1, {a: 98}, 3]}, {$set: {'a.01.b': 99}}, {a: [1, {a: 98, b: 99}, 3]}); + await modify({}, {$set: {'2.a.b': 12}}, {2: {a: {b: 12}}}); // tested + await exception({x: []}, {$set: {'x.2..a': 99}}); + await modify({x: [null, null]}, {$set: {'x.2.a': 1}}, {x: [null, null, {a: 1}]}); + await exception({x: [null, null]}, {$set: {'x.1.a': 1}}); // a.$.b - modifyWithQuery({a: [{x: 2}, {x: 4}]}, {'a.x': 4}, {$set: {'a.$.z': 9}}, + await modifyWithQuery({a: [{x: 2}, {x: 4}]}, {'a.x': 4}, {$set: {'a.$.z': 9}}, {a: [{x: 2}, {x: 4, z: 9}]}); - exception({a: [{x: 2}, {x: 4}]}, {$set: {'a.$.z': 9}}); - exceptionWithQuery({a: [{x: 2}, {x: 4}], b: 5}, {b: 5}, {$set: {'a.$.z': 9}}); + await exception({a: [{x: 2}, {x: 4}]}, {$set: {'a.$.z': 9}}); + await exceptionWithQuery({a: [{x: 2}, {x: 4}], b: 5}, {b: 5}, {$set: {'a.$.z': 9}}); // can't have two $ - exceptionWithQuery({a: [{x: [2]}]}, {'a.x': 2}, {$set: {'a.$.x.$': 9}}); - modifyWithQuery({a: [5, 6, 7]}, {a: 6}, {$set: {'a.$': 9}}, {a: [5, 9, 7]}); - modifyWithQuery({a: [{b: [{c: 9}, {c: 10}]}, {b: {c: 11}}]}, {'a.b.c': 10}, + await exceptionWithQuery({a: [{x: [2]}]}, {'a.x': 2}, {$set: {'a.$.x.$': 9}}); + await modifyWithQuery({a: [5, 6, 7]}, {a: 6}, {$set: {'a.$': 9}}, {a: [5, 9, 7]}); + await modifyWithQuery({a: [{b: [{c: 9}, {c: 10}]}, {b: {c: 11}}]}, {'a.b.c': 10}, {$unset: {'a.$.b': 1}}, {a: [{}, {b: {c: 11}}]}); - modifyWithQuery({a: [{b: [{c: 9}, {c: 10}]}, {b: {c: 11}}]}, {'a.b.c': 11}, + await modifyWithQuery({a: [{b: [{c: 9}, {c: 10}]}, {b: {c: 11}}]}, {'a.b.c': 11}, {$unset: {'a.$.b': 1}}, {a: [{b: [{c: 9}, {c: 10}]}, {}]}); - modifyWithQuery({a: [1]}, {'a.0': 1}, {$set: {'a.$': 5}}, {a: [5]}); - modifyWithQuery({a: [9]}, {a: {$mod: [2, 1]}}, {$set: {'a.$': 5}}, {a: [5]}); + await modifyWithQuery({a: [1]}, {'a.0': 1}, {$set: {'a.$': 5}}, {a: [5]}); + await modifyWithQuery({a: [9]}, {a: {$mod: [2, 1]}}, {$set: {'a.$': 5}}, {a: [5]}); // Negatives don't set '$'. - exceptionWithQuery({a: [1]}, {$not: {a: 2}}, {$set: {'a.$': 5}}); - exceptionWithQuery({a: [1]}, {'a.0': {$ne: 2}}, {$set: {'a.$': 5}}); + await exceptionWithQuery({a: [1]}, {$not: {a: 2}}, {$set: {'a.$': 5}}); + await exceptionWithQuery({a: [1]}, {'a.0': {$ne: 2}}, {$set: {'a.$': 5}}); // One $or clause works. - modifyWithQuery({a: [{x: 2}, {x: 4}]}, + await modifyWithQuery({a: [{x: 2}, {x: 4}]}, {$or: [{'a.x': 4}]}, {$set: {'a.$.z': 9}}, {a: [{x: 2}, {x: 4, z: 9}]}); // More $or clauses throw. - exceptionWithQuery({a: [{x: 2}, {x: 4}]}, + await exceptionWithQuery({a: [{x: 2}, {x: 4}]}, {$or: [{'a.x': 4}, {'a.x': 4}]}, {$set: {'a.$.z': 9}}); // $and uses the last one. - modifyWithQuery({a: [{x: 1}, {x: 3}]}, + await modifyWithQuery({a: [{x: 1}, {x: 3}]}, {$and: [{'a.x': 1}, {'a.x': 3}]}, {$set: {'a.$.x': 5}}, {a: [{x: 1}, {x: 5}]}); - modifyWithQuery({a: [{x: 1}, {x: 3}]}, + await modifyWithQuery({a: [{x: 1}, {x: 3}]}, {$and: [{'a.x': 3}, {'a.x': 1}]}, {$set: {'a.$.x': 5}}, {a: [{x: 5}, {x: 3}]}); // Same goes for the implicit AND of a document selector. - modifyWithQuery({a: [{x: 1}, {y: 3}]}, + await modifyWithQuery({a: [{x: 1}, {y: 3}]}, {'a.x': 1, 'a.y': 3}, {$set: {'a.$.z': 5}}, {a: [{x: 1}, {y: 3, z: 5}]}); - modifyWithQuery({a: [{x: 1}, {y: 1}, {x: 1, y: 1}]}, + await modifyWithQuery({a: [{x: 1}, {y: 1}, {x: 1, y: 1}]}, {a: {$elemMatch: {x: 1, y: 1}}}, {$set: {'a.$.x': 2}}, {a: [{x: 1}, {y: 1}, {x: 2, y: 1}]}); - modifyWithQuery({a: [{b: [{x: 1}, {y: 1}, {x: 1, y: 1}]}]}, + await modifyWithQuery({a: [{b: [{x: 1}, {y: 1}, {x: 1, y: 1}]}]}, {'a.b': {$elemMatch: {x: 1, y: 1}}}, {$set: {'a.$.b': 3}}, {a: [{b: 3}]}); // with $near, make sure it does not find the closest one (#3599) - modifyWithQuery({a: []}, + await modifyWithQuery({a: []}, {'a.b': {$near: [5, 5]}}, {$set: {'a.$.b': 'k'}}, {a: []}); - modifyWithQuery({a: [{b: [ [3, 3], [4, 4] ]}]}, + await modifyWithQuery({a: [{b: [ [3, 3], [4, 4] ]}]}, {'a.b': {$near: [5, 5]}}, {$set: {'a.$.b': 'k'}}, {a: [{b: 'k'}]}); - modifyWithQuery({a: [{b: [1, 1]}, + await modifyWithQuery({a: [{b: [1, 1]}, {b: [ [3, 3], [4, 4] ]}, {b: [9, 9]}]}, {'a.b': {$near: [5, 5]}}, {$set: {'a.$.b': 'k'}}, {a: [{b: 'k'}, {b: [[3, 3], [4, 4]]}, {b: [9, 9]}]}); - modifyWithQuery({a: [{b: [1, 1]}, + await modifyWithQuery({a: [{b: [1, 1]}, {b: [ [3, 3], [4, 4] ]}, {b: [9, 9]}]}, {'a.b': {$near: [9, 9], $maxDistance: 1}}, {$set: {'a.$.b': 'k'}}, {a: [{b: 'k'}, {b: [[3, 3], [4, 4]]}, {b: [9, 9]}]}); - modifyWithQuery({a: [{b: [1, 1]}, + await modifyWithQuery({a: [{b: [1, 1]}, {b: [ [3, 3], [4, 4] ]}, {b: [9, 9]}]}, {'a.b': {$near: [9, 9]}}, {$set: {'a.$.b': 'k'}}, {a: [{b: 'k'}, {b: [[3, 3], [4, 4]]}, {b: [9, 9]}]}); - modifyWithQuery({a: [{b: [9, 9]}, + await modifyWithQuery({a: [{b: [9, 9]}, {b: [ [3, 3], [4, 4] ]}, {b: [9, 9]}]}, {'a.b': {$near: [9, 9]}}, {$set: {'a.$.b': 'k'}}, {a: [{b: 'k'}, {b: [[3, 3], [4, 4]]}, {b: [9, 9]}]}); - modifyWithQuery({a: [{b: [4, 3]}, + await modifyWithQuery({a: [{b: [4, 3]}, {c: [1, 1]}]}, {'a.c': {$near: [1, 1]}}, {$set: {'a.$.c': 'k'}}, {a: [{c: 'k', b: [4, 3]}, {c: [1, 1]}]}); - modifyWithQuery({a: [{c: [9, 9]}, + await modifyWithQuery({a: [{c: [9, 9]}, {b: [ [3, 3], [4, 4] ]}, {b: [1, 1]}]}, {'a.b': {$near: [1, 1]}}, {$set: {'a.$.b': 'k'}}, {a: [{c: [9, 9], b: 'k'}, {b: [ [3, 3], [4, 4]]}, {b: [1, 1]}]}); - modifyWithQuery({a: [{c: [9, 9], b: [4, 3]}, + await modifyWithQuery({a: [{c: [9, 9], b: [4, 3]}, {b: [ [3, 3], [4, 4] ]}, {b: [1, 1]}]}, {'a.b': {$near: [1, 1]}}, @@ -2623,151 +2623,151 @@ Tinytest.add('minimongo - modify', test => { {a: [{c: [9, 9], b: 'k'}, {b: [ [3, 3], [4, 4]]}, {b: [1, 1]}]}); // $inc - modify({a: 1, b: 2}, {$inc: {a: 10}}, {a: 11, b: 2}); - modify({a: 1, b: 2}, {$inc: {c: 10}}, {a: 1, b: 2, c: 10}); - exception({a: 1}, {$inc: {a: '10'}}); - exception({a: 1}, {$inc: {a: true}}); - exception({a: 1}, {$inc: {a: [10]}}); - exception({a: '1'}, {$inc: {a: 10}}); - exception({a: [1]}, {$inc: {a: 10}}); - exception({a: {}}, {$inc: {a: 10}}); - exception({a: false}, {$inc: {a: 10}}); - exception({a: null}, {$inc: {a: 10}}); - modify({a: [1, 2]}, {$inc: {'a.1': 10}}, {a: [1, 12]}); - modify({a: [1, 2]}, {$inc: {'a.2': 10}}, {a: [1, 2, 10]}); - modify({a: [1, 2]}, {$inc: {'a.3': 10}}, {a: [1, 2, null, 10]}); - modify({a: {b: 2}}, {$inc: {'a.b': 10}}, {a: {b: 12}}); - modify({a: {b: 2}}, {$inc: {'a.c': 10}}, {a: {b: 2, c: 10}}); - exception({}, {$inc: {_id: 1}}); + await modify({a: 1, b: 2}, {$inc: {a: 10}}, {a: 11, b: 2}); + await modify({a: 1, b: 2}, {$inc: {c: 10}}, {a: 1, b: 2, c: 10}); + await exception({a: 1}, {$inc: {a: '10'}}); + await exception({a: 1}, {$inc: {a: true}}); + await exception({a: 1}, {$inc: {a: [10]}}); + await exception({a: '1'}, {$inc: {a: 10}}); + await exception({a: [1]}, {$inc: {a: 10}}); + await exception({a: {}}, {$inc: {a: 10}}); + await exception({a: false}, {$inc: {a: 10}}); + await exception({a: null}, {$inc: {a: 10}}); + await modify({a: [1, 2]}, {$inc: {'a.1': 10}}, {a: [1, 12]}); + await modify({a: [1, 2]}, {$inc: {'a.2': 10}}, {a: [1, 2, 10]}); + await modify({a: [1, 2]}, {$inc: {'a.3': 10}}, {a: [1, 2, null, 10]}); + await modify({a: {b: 2}}, {$inc: {'a.b': 10}}, {a: {b: 12}}); + await modify({a: {b: 2}}, {$inc: {'a.c': 10}}, {a: {b: 2, c: 10}}); + await exception({}, {$inc: {_id: 1}}); // $currentDate - modify({}, {$currentDate: {a: true}}, (result, msg) => { test.instanceOf(result.a, Date, msg); }); - modify({}, {$currentDate: {a: {$type: 'date'}}}, (result, msg) => { test.instanceOf(result.a, Date, msg); }); - exception({}, {$currentDate: {a: false}}); - exception({}, {$currentDate: {a: {}}}); - exception({}, {$currentDate: {a: {$type: 'timestamp'}}}); + await modify({}, {$currentDate: {a: true}}, (result, msg) => { test.instanceOf(result.a, Date, msg); }); + await modify({}, {$currentDate: {a: {$type: 'date'}}}, (result, msg) => { test.instanceOf(result.a, Date, msg); }); + await exception({}, {$currentDate: {a: false}}); + await exception({}, {$currentDate: {a: {}}}); + await exception({}, {$currentDate: {a: {$type: 'timestamp'}}}); // $min - modify({a: 1, b: 2}, {$min: {b: 1}}, {a: 1, b: 1}); - modify({a: 1, b: 2}, {$min: {b: 3}}, {a: 1, b: 2}); - modify({a: 1, b: 2}, {$min: {c: 10}}, {a: 1, b: 2, c: 10}); - exception({a: 1}, {$min: {a: '10'}}); - exception({a: 1}, {$min: {a: true}}); - exception({a: 1}, {$min: {a: [10]}}); - exception({a: '1'}, {$min: {a: 10}}); - exception({a: [1]}, {$min: {a: 10}}); - exception({a: {}}, {$min: {a: 10}}); - exception({a: false}, {$min: {a: 10}}); - exception({a: null}, {$min: {a: 10}}); - modify({a: [1, 2]}, {$min: {'a.1': 1}}, {a: [1, 1]}); - modify({a: [1, 2]}, {$min: {'a.1': 3}}, {a: [1, 2]}); - modify({a: [1, 2]}, {$min: {'a.2': 10}}, {a: [1, 2, 10]}); - modify({a: [1, 2]}, {$min: {'a.3': 10}}, {a: [1, 2, null, 10]}); - modify({a: {b: 2}}, {$min: {'a.b': 1}}, {a: {b: 1}}); - modify({a: {b: 2}}, {$min: {'a.c': 10}}, {a: {b: 2, c: 10}}); - exception({}, {$min: {_id: 1}}); + await modify({a: 1, b: 2}, {$min: {b: 1}}, {a: 1, b: 1}); + await modify({a: 1, b: 2}, {$min: {b: 3}}, {a: 1, b: 2}); + await modify({a: 1, b: 2}, {$min: {c: 10}}, {a: 1, b: 2, c: 10}); + await exception({a: 1}, {$min: {a: '10'}}); + await exception({a: 1}, {$min: {a: true}}); + await exception({a: 1}, {$min: {a: [10]}}); + await exception({a: '1'}, {$min: {a: 10}}); + await exception({a: [1]}, {$min: {a: 10}}); + await exception({a: {}}, {$min: {a: 10}}); + await exception({a: false}, {$min: {a: 10}}); + await exception({a: null}, {$min: {a: 10}}); + await modify({a: [1, 2]}, {$min: {'a.1': 1}}, {a: [1, 1]}); + await modify({a: [1, 2]}, {$min: {'a.1': 3}}, {a: [1, 2]}); + await modify({a: [1, 2]}, {$min: {'a.2': 10}}, {a: [1, 2, 10]}); + await modify({a: [1, 2]}, {$min: {'a.3': 10}}, {a: [1, 2, null, 10]}); + await modify({a: {b: 2}}, {$min: {'a.b': 1}}, {a: {b: 1}}); + await modify({a: {b: 2}}, {$min: {'a.c': 10}}, {a: {b: 2, c: 10}}); + await exception({}, {$min: {_id: 1}}); //$mul - modify({a: 1, b: 1}, {$mul: {b: 2}}, {a: 1, b: 2}); - modify({a: 1, b: 1}, {$mul: {c: 2}}, {a: 1, b: 1, c: 0}); - modify({a: 1, b: 2}, {$mul: {b: 2}}, {a: 1, b: 4}); - modify({a: 1, b: 2}, {$mul: {b: 10}}, {a: 1, b: 20}); - exception({a: 1}, {$mul: {a: '10'}}); - exception({a: 1}, {$mul: {a: true}}); - exception({a: 1}, {$mul: {a: [10]}}); - exception({a: '1'}, {$mul: {a: 10}}); - exception({a: [1]}, {$mul: {a: 10}}); - exception({a: {}}, {$mul: {a: 10}}); - exception({a: false}, {$mul: {a: 10}}); - exception({a: null}, {$mul: {a: 10}}); - exception({}, {$mul: {_id: 1}}); - modify({a: [1, 2]}, {$mul: {'a.0': 2}}, {a: [2, 2]}); - modify({a: [1, 2]}, {$mul: {'a.1': 3}}, {a: [1, 6]}); - modify({a: [1, 2]}, {$mul: {'a.1': 10}}, {a: [1, 20]}); - modify({a: [1, 2]}, {$mul: {'a.2': 10}}, {a: [1, 2, 0]}); - modify({a: {b: 2}}, {$mul: {'a.b': 1}}, {a: {b: 2}}); - modify({a: {b: 2}}, {$mul: {'a.c': 10}}, {a: {b: 2, c: 0}}); + await modify({a: 1, b: 1}, {$mul: {b: 2}}, {a: 1, b: 2}); + await modify({a: 1, b: 1}, {$mul: {c: 2}}, {a: 1, b: 1, c: 0}); + await modify({a: 1, b: 2}, {$mul: {b: 2}}, {a: 1, b: 4}); + await modify({a: 1, b: 2}, {$mul: {b: 10}}, {a: 1, b: 20}); + await exception({a: 1}, {$mul: {a: '10'}}); + await exception({a: 1}, {$mul: {a: true}}); + await exception({a: 1}, {$mul: {a: [10]}}); + await exception({a: '1'}, {$mul: {a: 10}}); + await exception({a: [1]}, {$mul: {a: 10}}); + await exception({a: {}}, {$mul: {a: 10}}); + await exception({a: false}, {$mul: {a: 10}}); + await exception({a: null}, {$mul: {a: 10}}); + await exception({}, {$mul: {_id: 1}}); + await modify({a: [1, 2]}, {$mul: {'a.0': 2}}, {a: [2, 2]}); + await modify({a: [1, 2]}, {$mul: {'a.1': 3}}, {a: [1, 6]}); + await modify({a: [1, 2]}, {$mul: {'a.1': 10}}, {a: [1, 20]}); + await modify({a: [1, 2]}, {$mul: {'a.2': 10}}, {a: [1, 2, 0]}); + await modify({a: {b: 2}}, {$mul: {'a.b': 1}}, {a: {b: 2}}); + await modify({a: {b: 2}}, {$mul: {'a.c': 10}}, {a: {b: 2, c: 0}}); // $max - modify({a: 1, b: 2}, {$max: {b: 1}}, {a: 1, b: 2}); - modify({a: 1, b: 2}, {$max: {b: 3}}, {a: 1, b: 3}); - modify({a: 1, b: 2}, {$max: {c: 10}}, {a: 1, b: 2, c: 10}); - exception({a: 1}, {$max: {a: '10'}}); - exception({a: 1}, {$max: {a: true}}); - exception({a: 1}, {$max: {a: [10]}}); - exception({a: '1'}, {$max: {a: 10}}); - exception({a: [1]}, {$max: {a: 10}}); - exception({a: {}}, {$max: {a: 10}}); - exception({a: false}, {$max: {a: 10}}); - exception({a: null}, {$max: {a: 10}}); - modify({a: [1, 2]}, {$max: {'a.1': 3}}, {a: [1, 3]}); - modify({a: [1, 2]}, {$max: {'a.1': 1}}, {a: [1, 2]}); - modify({a: [1, 2]}, {$max: {'a.2': 10}}, {a: [1, 2, 10]}); - modify({a: [1, 2]}, {$max: {'a.3': 10}}, {a: [1, 2, null, 10]}); - modify({a: {b: 2}}, {$max: {'a.b': 3}}, {a: {b: 3}}); - modify({a: {b: 2}}, {$max: {'a.c': 10}}, {a: {b: 2, c: 10}}); - exception({}, {$max: {_id: 1}}); + await modify({a: 1, b: 2}, {$max: {b: 1}}, {a: 1, b: 2}); + await modify({a: 1, b: 2}, {$max: {b: 3}}, {a: 1, b: 3}); + await modify({a: 1, b: 2}, {$max: {c: 10}}, {a: 1, b: 2, c: 10}); + await exception({a: 1}, {$max: {a: '10'}}); + await exception({a: 1}, {$max: {a: true}}); + await exception({a: 1}, {$max: {a: [10]}}); + await exception({a: '1'}, {$max: {a: 10}}); + await exception({a: [1]}, {$max: {a: 10}}); + await exception({a: {}}, {$max: {a: 10}}); + await exception({a: false}, {$max: {a: 10}}); + await exception({a: null}, {$max: {a: 10}}); + await modify({a: [1, 2]}, {$max: {'a.1': 3}}, {a: [1, 3]}); + await modify({a: [1, 2]}, {$max: {'a.1': 1}}, {a: [1, 2]}); + await modify({a: [1, 2]}, {$max: {'a.2': 10}}, {a: [1, 2, 10]}); + await modify({a: [1, 2]}, {$max: {'a.3': 10}}, {a: [1, 2, null, 10]}); + await modify({a: {b: 2}}, {$max: {'a.b': 3}}, {a: {b: 3}}); + await modify({a: {b: 2}}, {$max: {'a.c': 10}}, {a: {b: 2, c: 10}}); + await exception({}, {$max: {_id: 1}}); // $set - modify({a: 1, b: 2}, {$set: {a: 10}}, {a: 10, b: 2}); - modify({a: 1, b: 2}, {$set: {c: 10}}, {a: 1, b: 2, c: 10}); - modify({a: 1, b: 2}, {$set: {a: {c: 10}}}, {a: {c: 10}, b: 2}); - modify({a: [1, 2], b: 2}, {$set: {a: [3, 4]}}, {a: [3, 4], b: 2}); - modify({a: [1, 2, 3], b: 2}, {$set: {'a.1': [3, 4]}}, + await modify({a: 1, b: 2}, {$set: {a: 10}}, {a: 10, b: 2}); + await modify({a: 1, b: 2}, {$set: {c: 10}}, {a: 1, b: 2, c: 10}); + await modify({a: 1, b: 2}, {$set: {a: {c: 10}}}, {a: {c: 10}, b: 2}); + await modify({a: [1, 2], b: 2}, {$set: {a: [3, 4]}}, {a: [3, 4], b: 2}); + await modify({a: [1, 2, 3], b: 2}, {$set: {'a.1': [3, 4]}}, {a: [1, [3, 4], 3], b: 2}); - modify({a: [1], b: 2}, {$set: {'a.1': 9}}, {a: [1, 9], b: 2}); - modify({a: [1], b: 2}, {$set: {'a.2': 9}}, {a: [1, null, 9], b: 2}); - modify({a: {b: 1}}, {$set: {'a.c': 9}}, {a: {b: 1, c: 9}}); - modify({}, {$set: {'x._id': 4}}, {x: {_id: 4}}); + await modify({a: [1], b: 2}, {$set: {'a.1': 9}}, {a: [1, 9], b: 2}); + await modify({a: [1], b: 2}, {$set: {'a.2': 9}}, {a: [1, null, 9], b: 2}); + await modify({a: {b: 1}}, {$set: {'a.c': 9}}, {a: {b: 1, c: 9}}); + await modify({}, {$set: {'x._id': 4}}, {x: {_id: 4}}); // Changing _id is disallowed - exception({}, {$set: {_id: 4}}); - exception({_id: 1}, {$set: {_id: 4}}); - modify({_id: 4}, {$set: {_id: 4}}, {_id: 4}); // not-changing _id is not bad + await exception({}, {$set: {_id: 4}}); + await exception({_id: 1}, {$set: {_id: 4}}); + await modify({_id: 4}, {$set: {_id: 4}}, {_id: 4}); // not-changing _id is not bad // restricted field names - exception({a: {}}, {$set: {a: {$a: 1}}}); - exception({ a: {} }, { $set: { a: { c: + await exception({a: {}}, {$set: {a: {$a: 1}}}); + await exception({ a: {} }, { $set: { a: { c: [{ b: { $a: 1 } }] } } }); - exception({a: {}}, {$set: {a: {'\0a': 1}}}); - exception({a: {}}, {$set: {a: {'a.b': 1}}}); + await exception({a: {}}, {$set: {a: {'\0a': 1}}}); + await exception({a: {}}, {$set: {a: {'a.b': 1}}}); // $unset - modify({}, {$unset: {a: 1}}, {}); - modify({a: 1}, {$unset: {a: 1}}, {}); - modify({a: 1, b: 2}, {$unset: {a: 1}}, {b: 2}); - modify({a: 1, b: 2}, {$unset: {a: 0}}, {b: 2}); - modify({a: 1, b: 2}, {$unset: {a: false}}, {b: 2}); - modify({a: 1, b: 2}, {$unset: {a: null}}, {b: 2}); - modify({a: 1, b: 2}, {$unset: {a: [1]}}, {b: 2}); - modify({a: 1, b: 2}, {$unset: {a: {}}}, {b: 2}); - modify({a: {b: 2, c: 3}}, {$unset: {'a.b': 1}}, {a: {c: 3}}); - modify({a: [1, 2, 3]}, {$unset: {'a.1': 1}}, {a: [1, null, 3]}); // tested - modify({a: [1, 2, 3]}, {$unset: {'a.2': 1}}, {a: [1, 2, null]}); // tested - modify({a: [1, 2, 3]}, {$unset: {'a.x': 1}}, {a: [1, 2, 3]}); // tested - modify({a: {b: 1}}, {$unset: {'a.b.c.d': 1}}, {a: {b: 1}}); - modify({a: {b: 1}}, {$unset: {'a.x.c.d': 1}}, {a: {b: 1}}); - modify({a: {b: {c: 1}}}, {$unset: {'a.b.c': 1}}, {a: {b: {}}}); - exception({}, {$unset: {_id: 1}}); + await modify({}, {$unset: {a: 1}}, {}); + await modify({a: 1}, {$unset: {a: 1}}, {}); + await modify({a: 1, b: 2}, {$unset: {a: 1}}, {b: 2}); + await modify({a: 1, b: 2}, {$unset: {a: 0}}, {b: 2}); + await modify({a: 1, b: 2}, {$unset: {a: false}}, {b: 2}); + await modify({a: 1, b: 2}, {$unset: {a: null}}, {b: 2}); + await modify({a: 1, b: 2}, {$unset: {a: [1]}}, {b: 2}); + await modify({a: 1, b: 2}, {$unset: {a: {}}}, {b: 2}); + await modify({a: {b: 2, c: 3}}, {$unset: {'a.b': 1}}, {a: {c: 3}}); + await modify({a: [1, 2, 3]}, {$unset: {'a.1': 1}}, {a: [1, null, 3]}); // tested + await modify({a: [1, 2, 3]}, {$unset: {'a.2': 1}}, {a: [1, 2, null]}); // tested + await modify({a: [1, 2, 3]}, {$unset: {'a.x': 1}}, {a: [1, 2, 3]}); // tested + await modify({a: {b: 1}}, {$unset: {'a.b.c.d': 1}}, {a: {b: 1}}); + await modify({a: {b: 1}}, {$unset: {'a.x.c.d': 1}}, {a: {b: 1}}); + await modify({a: {b: {c: 1}}}, {$unset: {'a.b.c': 1}}, {a: {b: {}}}); + await exception({}, {$unset: {_id: 1}}); // $push - modify({}, {$push: {a: 1}}, {a: [1]}); - modify({a: []}, {$push: {a: 1}}, {a: [1]}); - modify({a: [1]}, {$push: {a: 2}}, {a: [1, 2]}); - exception({a: true}, {$push: {a: 1}}); - modify({a: [1]}, {$push: {a: [2]}}, {a: [1, [2]]}); - modify({a: []}, {$push: {'a.1': 99}}, {a: [null, [99]]}); // tested - modify({a: {}}, {$push: {'a.x': 99}}, {a: {x: [99]}}); - modify({}, {$push: {a: {$each: [1, 2, 3]}}}, + await modify({}, {$push: {a: 1}}, {a: [1]}); + await modify({a: []}, {$push: {a: 1}}, {a: [1]}); + await modify({a: [1]}, {$push: {a: 2}}, {a: [1, 2]}); + await exception({a: true}, {$push: {a: 1}}); + await modify({a: [1]}, {$push: {a: [2]}}, {a: [1, [2]]}); + await modify({a: []}, {$push: {'a.1': 99}}, {a: [null, [99]]}); // tested + await modify({a: {}}, {$push: {'a.x': 99}}, {a: {x: [99]}}); + await modify({}, {$push: {a: {$each: [1, 2, 3]}}}, {a: [1, 2, 3]}); - modify({a: []}, {$push: {a: {$each: [1, 2, 3]}}}, + await modify({a: []}, {$push: {a: {$each: [1, 2, 3]}}}, {a: [1, 2, 3]}); - modify({a: [true]}, {$push: {a: {$each: [1, 2, 3]}}}, + await modify({a: [true]}, {$push: {a: {$each: [1, 2, 3]}}}, {a: [true, 1, 2, 3]}); - modify({a: [true]}, {$push: {a: {$each: [1, 2, 3], $slice: -2}}}, + await modify({a: [true]}, {$push: {a: {$each: [1, 2, 3], $slice: -2}}}, {a: [2, 3]}); - modify({a: [false, true]}, {$push: {a: {$each: [1], $slice: -2}}}, + await modify({a: [false, true]}, {$push: {a: {$each: [1], $slice: -2}}}, {a: [true, 1]}); - modify( + await modify( {a: [{x: 3}, {x: 1}]}, {$push: {a: { $each: [{x: 4}, {x: 2}], @@ -2775,204 +2775,204 @@ Tinytest.add('minimongo - modify', test => { $sort: {x: 1}, }}}, {a: [{x: 3}, {x: 4}]}); - modify({}, {$push: {a: {$each: [1, 2, 3], $slice: 0}}}, {a: []}); - modify({a: [1, 2]}, {$push: {a: {$each: [1, 2, 3], $slice: 0}}}, {a: []}); + await modify({}, {$push: {a: {$each: [1, 2, 3], $slice: 0}}}, {a: []}); + await modify({a: [1, 2]}, {$push: {a: {$each: [1, 2, 3], $slice: 0}}}, {a: []}); // $push with $position modifier // No negative number for $position - exception({a: []}, {$push: {a: {$each: [0], $position: -1}}}); - modify({a: [1, 2]}, {$push: {a: {$each: [0], $position: 0}}}, + await exception({a: []}, {$push: {a: {$each: [0], $position: -1}}}); + await modify({a: [1, 2]}, {$push: {a: {$each: [0], $position: 0}}}, {a: [0, 1, 2]}); - modify({a: [1, 2]}, {$push: {a: {$each: [-1, 0], $position: 0}}}, + await modify({a: [1, 2]}, {$push: {a: {$each: [-1, 0], $position: 0}}}, {a: [-1, 0, 1, 2]}); - modify({a: [1, 3]}, {$push: {a: {$each: [2], $position: 1}}}, {a: [1, 2, 3]}); - modify({a: [1, 4]}, {$push: {a: {$each: [2, 3], $position: 1}}}, + await modify({a: [1, 3]}, {$push: {a: {$each: [2], $position: 1}}}, {a: [1, 2, 3]}); + await modify({a: [1, 4]}, {$push: {a: {$each: [2, 3], $position: 1}}}, {a: [1, 2, 3, 4]}); - modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 3}}}, {a: [1, 2, 3]}); - modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 99}}}, + await modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 3}}}, {a: [1, 2, 3]}); + await modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 99}}}, {a: [1, 2, 3]}); - modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 99, $slice: -2}}}, + await modify({a: [1, 2]}, {$push: {a: {$each: [3], $position: 99, $slice: -2}}}, {a: [2, 3]}); - modify( + await modify( {a: [{x: 1}, {x: 2}]}, {$push: {a: {$each: [{x: 3}], $position: 0, $sort: {x: 1}, $slice: -3}}}, {a: [{x: 1}, {x: 2}, {x: 3}]} ); - modify( + await modify( {a: [{x: 1}, {x: 2}]}, {$push: {a: {$each: [{x: 3}], $position: 0, $sort: {x: 1}, $slice: 0}}}, {a: []} ); // restricted field names - exception({}, {$push: {$a: 1}}); - exception({}, {$push: {'\0a': 1}}); - exception({}, {$push: {a: {$a: 1}}}); - exception({}, {$push: {a: {$each: [{$a: 1}]}}}); - exception({}, {$push: {a: {$each: [{'a.b': 1}]}}}); - exception({}, {$push: {a: {$each: [{'\0a': 1}]}}}); - modify({}, {$push: {a: {$each: [{'': 1}]}}}, {a: [ { '': 1 } ]}); - modify({}, {$push: {a: {$each: [{' ': 1}]}}}, {a: [ { ' ': 1 } ]}); - exception({}, {$push: {a: {$each: [{'.': 1}]}}}); + await exception({}, {$push: {$a: 1}}); + await exception({}, {$push: {'\0a': 1}}); + await exception({}, {$push: {a: {$a: 1}}}); + await exception({}, {$push: {a: {$each: [{$a: 1}]}}}); + await exception({}, {$push: {a: {$each: [{'a.b': 1}]}}}); + await exception({}, {$push: {a: {$each: [{'\0a': 1}]}}}); + await modify({}, {$push: {a: {$each: [{'': 1}]}}}, {a: [ { '': 1 } ]}); + await modify({}, {$push: {a: {$each: [{' ': 1}]}}}, {a: [ { ' ': 1 } ]}); + await exception({}, {$push: {a: {$each: [{'.': 1}]}}}); // #issue 5167 // $push $slice with positive numbers - modify({}, {$push: {a: {$each: [], $slice: 5}}}, {a: []}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [], $slice: 1}}}, {a: [1]}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 1}}}, {a: [1]}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 2}}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 4}}}, {a: [1, 2, 3, 4]}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 5}}}, {a: [1, 2, 3, 4, 5]}); - modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 10}}}, {a: [1, 2, 3, 4, 5]}); + await modify({}, {$push: {a: {$each: [], $slice: 5}}}, {a: []}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [], $slice: 1}}}, {a: [1]}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 1}}}, {a: [1]}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 2}}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 4}}}, {a: [1, 2, 3, 4]}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 5}}}, {a: [1, 2, 3, 4, 5]}); + await modify({a: [1, 2, 3]}, {$push: {a: {$each: [4, 5], $slice: 10}}}, {a: [1, 2, 3, 4, 5]}); // $pushAll - modify({}, {$pushAll: {a: [1]}}, {a: [1]}); - modify({a: []}, {$pushAll: {a: [1]}}, {a: [1]}); - modify({a: [1]}, {$pushAll: {a: [2]}}, {a: [1, 2]}); - modify({}, {$pushAll: {a: [1, 2]}}, {a: [1, 2]}); - modify({a: []}, {$pushAll: {a: [1, 2]}}, {a: [1, 2]}); - modify({a: [1]}, {$pushAll: {a: [2, 3]}}, {a: [1, 2, 3]}); - modify({}, {$pushAll: {a: []}}, {a: []}); - modify({a: []}, {$pushAll: {a: []}}, {a: []}); - modify({a: [1]}, {$pushAll: {a: []}}, {a: [1]}); - exception({a: true}, {$pushAll: {a: [1]}}); - exception({a: []}, {$pushAll: {a: 1}}); - modify({a: []}, {$pushAll: {'a.1': [99]}}, {a: [null, [99]]}); - modify({a: []}, {$pushAll: {'a.1': []}}, {a: [null, []]}); - modify({a: {}}, {$pushAll: {'a.x': [99]}}, {a: {x: [99]}}); - modify({a: {}}, {$pushAll: {'a.x': []}}, {a: {x: []}}); - exception({a: [1]}, {$pushAll: {a: [{$a: 1}]}}); - exception({a: [1]}, {$pushAll: {a: [{'\0a': 1}]}}); - exception({a: [1]}, {$pushAll: {a: [{'a.b': 1}]}}); + await modify({}, {$pushAll: {a: [1]}}, {a: [1]}); + await modify({a: []}, {$pushAll: {a: [1]}}, {a: [1]}); + await modify({a: [1]}, {$pushAll: {a: [2]}}, {a: [1, 2]}); + await modify({}, {$pushAll: {a: [1, 2]}}, {a: [1, 2]}); + await modify({a: []}, {$pushAll: {a: [1, 2]}}, {a: [1, 2]}); + await modify({a: [1]}, {$pushAll: {a: [2, 3]}}, {a: [1, 2, 3]}); + await modify({}, {$pushAll: {a: []}}, {a: []}); + await modify({a: []}, {$pushAll: {a: []}}, {a: []}); + await modify({a: [1]}, {$pushAll: {a: []}}, {a: [1]}); + await exception({a: true}, {$pushAll: {a: [1]}}); + await exception({a: []}, {$pushAll: {a: 1}}); + await modify({a: []}, {$pushAll: {'a.1': [99]}}, {a: [null, [99]]}); + await modify({a: []}, {$pushAll: {'a.1': []}}, {a: [null, []]}); + await modify({a: {}}, {$pushAll: {'a.x': [99]}}, {a: {x: [99]}}); + await modify({a: {}}, {$pushAll: {'a.x': []}}, {a: {x: []}}); + await exception({a: [1]}, {$pushAll: {a: [{$a: 1}]}}); + await exception({a: [1]}, {$pushAll: {a: [{'\0a': 1}]}}); + await exception({a: [1]}, {$pushAll: {a: [{'a.b': 1}]}}); // $addToSet - modify({}, {$addToSet: {a: 1}}, {a: [1]}); - modify({a: []}, {$addToSet: {a: 1}}, {a: [1]}); - modify({a: [1]}, {$addToSet: {a: 2}}, {a: [1, 2]}); - modify({a: [1, 2]}, {$addToSet: {a: 1}}, {a: [1, 2]}); - modify({a: [1, 2]}, {$addToSet: {a: 2}}, {a: [1, 2]}); - modify({a: [1, 2]}, {$addToSet: {a: 3}}, {a: [1, 2, 3]}); - exception({a: true}, {$addToSet: {a: 1}}); - modify({a: [1]}, {$addToSet: {a: [2]}}, {a: [1, [2]]}); - modify({}, {$addToSet: {a: {x: 1}}}, {a: [{x: 1}]}); - modify({a: [{x: 1}]}, {$addToSet: {a: {x: 1}}}, {a: [{x: 1}]}); - modify({a: [{x: 1}]}, {$addToSet: {a: {x: 2}}}, {a: [{x: 1}, {x: 2}]}); - modify({a: [{x: 1, y: 2}]}, {$addToSet: {a: {x: 1, y: 2}}}, + await modify({}, {$addToSet: {a: 1}}, {a: [1]}); + await modify({a: []}, {$addToSet: {a: 1}}, {a: [1]}); + await modify({a: [1]}, {$addToSet: {a: 2}}, {a: [1, 2]}); + await modify({a: [1, 2]}, {$addToSet: {a: 1}}, {a: [1, 2]}); + await modify({a: [1, 2]}, {$addToSet: {a: 2}}, {a: [1, 2]}); + await modify({a: [1, 2]}, {$addToSet: {a: 3}}, {a: [1, 2, 3]}); + await exception({a: true}, {$addToSet: {a: 1}}); + await modify({a: [1]}, {$addToSet: {a: [2]}}, {a: [1, [2]]}); + await modify({}, {$addToSet: {a: {x: 1}}}, {a: [{x: 1}]}); + await modify({a: [{x: 1}]}, {$addToSet: {a: {x: 1}}}, {a: [{x: 1}]}); + await modify({a: [{x: 1}]}, {$addToSet: {a: {x: 2}}}, {a: [{x: 1}, {x: 2}]}); + await modify({a: [{x: 1, y: 2}]}, {$addToSet: {a: {x: 1, y: 2}}}, {a: [{x: 1, y: 2}]}); - modify({a: [{x: 1, y: 2}]}, {$addToSet: {a: {y: 2, x: 1}}}, + await modify({a: [{x: 1, y: 2}]}, {$addToSet: {a: {y: 2, x: 1}}}, {a: [{x: 1, y: 2}, {y: 2, x: 1}]}); - modify({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, 4]}}}, {a: [1, 2, 3, 4]}); - modify({}, {$addToSet: {a: {$each: []}}}, {a: []}); - modify({}, {$addToSet: {a: {$each: [1]}}}, {a: [1]}); - modify({a: []}, {$addToSet: {'a.1': 99}}, {a: [null, [99]]}); - modify({a: {}}, {$addToSet: {'a.x': 99}}, {a: {x: [99]}}); + await modify({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, 4]}}}, {a: [1, 2, 3, 4]}); + await modify({}, {$addToSet: {a: {$each: []}}}, {a: []}); + await modify({}, {$addToSet: {a: {$each: [1]}}}, {a: [1]}); + await modify({a: []}, {$addToSet: {'a.1': 99}}, {a: [null, [99]]}); + await modify({a: {}}, {$addToSet: {'a.x': 99}}, {a: {x: [99]}}); // invalid field names - exception({}, {$addToSet: {a: {$b: 1}}}); - exception({}, {$addToSet: {a: {'a.b': 1}}}); - exception({}, {$addToSet: {a: {'a.': 1}}}); - exception({}, {$addToSet: {a: {'\u0000a': 1}}}); - exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, {$a: 1}]}}}); - exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, {'\0a': 1}]}}}); - exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, [{$a: 1}]]}}}); - exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, [{b: {c: [{a: 1}, {'d.s': 2}]}}]]}}}); - exception({a: [1, 2]}, {$addToSet: {a: {b: [3, 1, [{b: {c: [{a: 1}, {'d.s': 2}]}}]]}}}); + await exception({}, {$addToSet: {a: {$b: 1}}}); + await exception({}, {$addToSet: {a: {'a.b': 1}}}); + await exception({}, {$addToSet: {a: {'a.': 1}}}); + await exception({}, {$addToSet: {a: {'\u0000a': 1}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, {$a: 1}]}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, {'\0a': 1}]}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, [{$a: 1}]]}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, [{b: {c: [{a: 1}, {'d.s': 2}]}}]]}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {b: [3, 1, [{b: {c: [{a: 1}, {'d.s': 2}]}}]]}}}); // $each is first element and thus an operator - modify({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, 4], b: 12}}}, {a: [ 1, 2, 3, 4 ]}); + await modify({a: [1, 2]}, {$addToSet: {a: {$each: [3, 1, 4], b: 12}}}, {a: [ 1, 2, 3, 4 ]}); // this should fail because $each is now a field name (not first in object) and thus invalid field name with $ - exception({a: [1, 2]}, {$addToSet: {a: {b: 12, $each: [3, 1, 4]}}}); + await exception({a: [1, 2]}, {$addToSet: {a: {b: 12, $each: [3, 1, 4]}}}); // $pop - modify({}, {$pop: {a: 1}}, {}); // tested - modify({}, {$pop: {a: -1}}, {}); // tested - modify({a: []}, {$pop: {a: 1}}, {a: []}); - modify({a: []}, {$pop: {a: -1}}, {a: []}); - modify({a: [1, 2, 3]}, {$pop: {a: 1}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$pop: {a: 10}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$pop: {a: 0.001}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$pop: {a: 0}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$pop: {a: 'stuff'}}, {a: [1, 2]}); - modify({a: [1, 2, 3]}, {$pop: {a: -1}}, {a: [2, 3]}); - modify({a: [1, 2, 3]}, {$pop: {a: -10}}, {a: [2, 3]}); - modify({a: [1, 2, 3]}, {$pop: {a: -0.001}}, {a: [2, 3]}); - exception({a: true}, {$pop: {a: 1}}); - exception({a: true}, {$pop: {a: -1}}); - modify({a: []}, {$pop: {'a.1': 1}}, {a: []}); // tested - modify({a: [1, [2, 3], 4]}, {$pop: {'a.1': 1}}, {a: [1, [2], 4]}); - modify({a: {}}, {$pop: {'a.x': 1}}, {a: {}}); // tested - modify({a: {x: [2, 3]}}, {$pop: {'a.x': 1}}, {a: {x: [2]}}); + await modify({}, {$pop: {a: 1}}, {}); // tested + await modify({}, {$pop: {a: -1}}, {}); // tested + await modify({a: []}, {$pop: {a: 1}}, {a: []}); + await modify({a: []}, {$pop: {a: -1}}, {a: []}); + await modify({a: [1, 2, 3]}, {$pop: {a: 1}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$pop: {a: 10}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$pop: {a: 0.001}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$pop: {a: 0}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$pop: {a: 'stuff'}}, {a: [1, 2]}); + await modify({a: [1, 2, 3]}, {$pop: {a: -1}}, {a: [2, 3]}); + await modify({a: [1, 2, 3]}, {$pop: {a: -10}}, {a: [2, 3]}); + await modify({a: [1, 2, 3]}, {$pop: {a: -0.001}}, {a: [2, 3]}); + await exception({a: true}, {$pop: {a: 1}}); + await exception({a: true}, {$pop: {a: -1}}); + await modify({a: []}, {$pop: {'a.1': 1}}, {a: []}); // tested + await modify({a: [1, [2, 3], 4]}, {$pop: {'a.1': 1}}, {a: [1, [2], 4]}); + await modify({a: {}}, {$pop: {'a.x': 1}}, {a: {}}); // tested + await modify({a: {x: [2, 3]}}, {$pop: {'a.x': 1}}, {a: {x: [2]}}); // $pull - modify({}, {$pull: {a: 1}}, {}); - modify({}, {$pull: {'a.x': 1}}, {}); - modify({a: {}}, {$pull: {'a.x': 1}}, {a: {}}); - exception({a: true}, {$pull: {a: 1}}); - modify({a: [2, 1, 2]}, {$pull: {a: 1}}, {a: [2, 2]}); - modify({a: [2, 1, 2]}, {$pull: {a: 2}}, {a: [1]}); - modify({a: [2, 1, 2]}, {$pull: {a: 3}}, {a: [2, 1, 2]}); - modify({a: [1, null, 2, null]}, {$pull: {a: null}}, {a: [1, 2]}); - modify({a: []}, {$pull: {a: 3}}, {a: []}); - modify({a: [[2], [2, 1], [3]]}, {$pull: {a: [2, 1]}}, + await modify({}, {$pull: {a: 1}}, {}); + await modify({}, {$pull: {'a.x': 1}}, {}); + await modify({a: {}}, {$pull: {'a.x': 1}}, {a: {}}); + await exception({a: true}, {$pull: {a: 1}}); + await modify({a: [2, 1, 2]}, {$pull: {a: 1}}, {a: [2, 2]}); + await modify({a: [2, 1, 2]}, {$pull: {a: 2}}, {a: [1]}); + await modify({a: [2, 1, 2]}, {$pull: {a: 3}}, {a: [2, 1, 2]}); + await modify({a: [1, null, 2, null]}, {$pull: {a: null}}, {a: [1, 2]}); + await modify({a: []}, {$pull: {a: 3}}, {a: []}); + await modify({a: [[2], [2, 1], [3]]}, {$pull: {a: [2, 1]}}, {a: [[2], [3]]}); // tested - modify({a: [{b: 1, c: 2}, {b: 2, c: 2}]}, {$pull: {a: {b: 1}}}, + await modify({a: [{b: 1, c: 2}, {b: 2, c: 2}]}, {$pull: {a: {b: 1}}}, {a: [{b: 2, c: 2}]}); - modify({a: [{b: 1, c: 2}, {b: 2, c: 2}]}, {$pull: {a: {c: 2}}}, + await modify({a: [{b: 1, c: 2}, {b: 2, c: 2}]}, {$pull: {a: {c: 2}}}, {a: []}); // XXX implement this functionality! // probably same refactoring as $elemMatch? - // modify({a: [1, 2, 3, 4]}, {$pull: {$gt: 2}}, {a: [1,2]}); fails! + // await modify({a: [1, 2, 3, 4]}, {$pull: {$gt: 2}}, {a: [1,2]}); fails! // $pullAll - modify({}, {$pullAll: {a: [1]}}, {}); - modify({a: [1, 2, 3]}, {$pullAll: {a: []}}, {a: [1, 2, 3]}); - modify({a: [1, 2, 3]}, {$pullAll: {a: [2]}}, {a: [1, 3]}); - modify({a: [1, 2, 3]}, {$pullAll: {a: [2, 1]}}, {a: [3]}); - modify({a: [1, 2, 3]}, {$pullAll: {a: [1, 2]}}, {a: [3]}); - modify({}, {$pullAll: {'a.b.c': [2]}}, {}); - exception({a: true}, {$pullAll: {a: [1]}}); - exception({a: [1, 2, 3]}, {$pullAll: {a: 1}}); - modify({x: [{a: 1}, {a: 1, b: 2}]}, {$pullAll: {x: [{a: 1}]}}, + await modify({}, {$pullAll: {a: [1]}}, {}); + await modify({a: [1, 2, 3]}, {$pullAll: {a: []}}, {a: [1, 2, 3]}); + await modify({a: [1, 2, 3]}, {$pullAll: {a: [2]}}, {a: [1, 3]}); + await modify({a: [1, 2, 3]}, {$pullAll: {a: [2, 1]}}, {a: [3]}); + await modify({a: [1, 2, 3]}, {$pullAll: {a: [1, 2]}}, {a: [3]}); + await modify({}, {$pullAll: {'a.b.c': [2]}}, {}); + await exception({a: true}, {$pullAll: {a: [1]}}); + await exception({a: [1, 2, 3]}, {$pullAll: {a: 1}}); + await modify({x: [{a: 1}, {a: 1, b: 2}]}, {$pullAll: {x: [{a: 1}]}}, {x: [{a: 1, b: 2}]}); // $rename - modify({}, {$rename: {a: 'b'}}, {}); - modify({a: [12]}, {$rename: {a: 'b'}}, {b: [12]}); - modify({a: {b: 12}}, {$rename: {a: 'c'}}, {c: {b: 12}}); - modify({a: {b: 12}}, {$rename: {'a.b': 'a.c'}}, {a: {c: 12}}); - modify({a: {b: 12}}, {$rename: {'a.b': 'x'}}, {a: {}, x: 12}); // tested - modify({a: {b: 12}}, {$rename: {'a.b': 'q.r'}}, {a: {}, q: {r: 12}}); - modify({a: {b: 12}}, {$rename: {'a.b': 'q.2.r'}}, {a: {}, q: {2: {r: 12}}}); - modify({a: {b: 12}, q: {}}, {$rename: {'a.b': 'q.2.r'}}, + await modify({}, {$rename: {a: 'b'}}, {}); + await modify({a: [12]}, {$rename: {a: 'b'}}, {b: [12]}); + await modify({a: {b: 12}}, {$rename: {a: 'c'}}, {c: {b: 12}}); + await modify({a: {b: 12}}, {$rename: {'a.b': 'a.c'}}, {a: {c: 12}}); + await modify({a: {b: 12}}, {$rename: {'a.b': 'x'}}, {a: {}, x: 12}); // tested + await modify({a: {b: 12}}, {$rename: {'a.b': 'q.r'}}, {a: {}, q: {r: 12}}); + await modify({a: {b: 12}}, {$rename: {'a.b': 'q.2.r'}}, {a: {}, q: {2: {r: 12}}}); + await modify({a: {b: 12}, q: {}}, {$rename: {'a.b': 'q.2.r'}}, {a: {}, q: {2: {r: 12}}}); - exception({a: {b: 12}, q: []}, {$rename: {'a.b': 'q.2'}}); // tested - exception({a: {b: 12}, q: []}, {$rename: {'a.b': 'q.2.r'}}); // tested + await exception({a: {b: 12}, q: []}, {$rename: {'a.b': 'q.2'}}); // tested + await exception({a: {b: 12}, q: []}, {$rename: {'a.b': 'q.2.r'}}); // tested // These strange MongoDB behaviors throw. - // modify({a: {b: 12}, q: []}, {$rename: {'q.1': 'x'}}, + // await modify({a: {b: 12}, q: []}, {$rename: {'q.1': 'x'}}, // {a: {b: 12}, x: []}); // tested - // modify({a: {b: 12}, q: []}, {$rename: {'q.1.j': 'x'}}, + // await modify({a: {b: 12}, q: []}, {$rename: {'q.1.j': 'x'}}, // {a: {b: 12}, x: []}); // tested - exception({}, {$rename: {a: 'a'}}); - exception({}, {$rename: {'a.b': 'a.b'}}); - modify({a: 12, b: 13}, {$rename: {a: 'b'}}, {b: 12}); - exception({a: [12]}, {$rename: {a: '$b'}}); - exception({a: [12]}, {$rename: {a: '\0a'}}); + await exception({}, {$rename: {a: 'a'}}); + await exception({}, {$rename: {'a.b': 'a.b'}}); + await modify({a: 12, b: 13}, {$rename: {a: 'b'}}, {b: 12}); + await exception({a: [12]}, {$rename: {a: '$b'}}); + await exception({a: [12]}, {$rename: {a: '\0a'}}); // $setOnInsert - modify({a: 0}, {$setOnInsert: {a: 12}}, {a: 0}); - upsert({a: 12}, {$setOnInsert: {b: 12}}, {a: 12, b: 12}); - upsert({a: 12}, {$setOnInsert: {_id: 'test'}}, {_id: 'test', a: 12}); - upsert({'a.b': 10}, {$setOnInsert: {a: {b: 10, c: 12}}}, {a: {b: 10, c: 12}}); - upsert({'a.b': 10}, {$setOnInsert: {c: 12}}, {a: {b: 10}, c: 12}); - upsert({_id: 'test'}, {$setOnInsert: {c: 12}}, {_id: 'test', c: 12}); - upsert('test', {$setOnInsert: {c: 12}}, {_id: 'test', c: 12}); - upsertException({a: 0}, {$setOnInsert: {$a: 12}}); - upsertException({a: 0}, {$setOnInsert: {'\0a': 12}}); - upsert({a: 0}, {$setOnInsert: {b: {a: 1}}}, {a: 0, b: {a: 1}}); - upsertException({a: 0}, {$setOnInsert: {b: {$a: 1}}}); - upsertException({a: 0}, {$setOnInsert: {b: {'a.b': 1}}}); - upsertException({a: 0}, {$setOnInsert: {b: {'\0a': 1}}}); + await modify({a: 0}, {$setOnInsert: {a: 12}}, {a: 0}); + await upsert({a: 12}, {$setOnInsert: {b: 12}}, {a: 12, b: 12}); + await upsert({a: 12}, {$setOnInsert: {_id: 'test'}}, {_id: 'test', a: 12}); + await upsert({'a.b': 10}, {$setOnInsert: {a: {b: 10, c: 12}}}, {a: {b: 10, c: 12}}); + await upsert({'a.b': 10}, {$setOnInsert: {c: 12}}, {a: {b: 10}, c: 12}); + await upsert({_id: 'test'}, {$setOnInsert: {c: 12}}, {_id: 'test', c: 12}); + await upsert('test', {$setOnInsert: {c: 12}}, {_id: 'test', c: 12}); + await upsertException({a: 0}, {$setOnInsert: {$a: 12}}); + await upsertException({a: 0}, {$setOnInsert: {'\0a': 12}}); + await upsert({a: 0}, {$setOnInsert: {b: {a: 1}}}, {a: 0, b: {a: 1}}); + await upsertException({a: 0}, {$setOnInsert: {b: {$a: 1}}}); + await upsertException({a: 0}, {$setOnInsert: {b: {'a.b': 1}}}); + await upsertException({a: 0}, {$setOnInsert: {b: {'\0a': 1}}}); // Test for https://github.com/meteor/meteor/issues/8775. - upsert( + await upsert( { a: { $exists: true }}, { $setOnInsert: { a: 123 }}, { a: 123 } @@ -2980,28 +2980,28 @@ Tinytest.add('minimongo - modify', test => { // Tests for https://github.com/meteor/meteor/issues/8794. const testObjectId = new MongoID.ObjectID(); - upsert( + await upsert( { _id: testObjectId }, { $setOnInsert: { a: 123 } }, { _id: testObjectId, a: 123 }, ); - upsert( + await upsert( { someOtherId: testObjectId }, { $setOnInsert: { a: 123 } }, { someOtherId: testObjectId, a: 123 }, ); - upsert( + await upsert( { a: { $eq: testObjectId } }, { $setOnInsert: { a: 123 } }, { a: 123 }, ); const testDate = new Date('2017-01-01'); - upsert( + await upsert( { someDate: testDate }, { $setOnInsert: { a: 123 } }, { someDate: testDate, a: 123 }, ); - upsert( + await upsert( { a: Object.create(null, { $exists: { @@ -3014,84 +3014,84 @@ Tinytest.add('minimongo - modify', test => { { $setOnInsert: { a: 123 } }, { a: 123 }, ); - upsert( + await upsert( { foo: { $exists: true, $type: 2 }}, { $setOnInsert: { bar: 'baz' } }, { bar: 'baz' } ); - upsert( + await upsert( { foo: {} }, { $setOnInsert: { bar: 'baz' } }, { foo: {}, bar: 'baz' } ); // Tests for https://github.com/meteor/meteor/issues/8806 - upsert({"a": {"b": undefined, "c": null}}, {"$set": {"c": "foo"}}, {"a": {"b": undefined, "c": null}, "c": "foo"}) - upsert({"a": {"$eq": "bar" }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"a": {"b": undefined, "c": null}}, {"$set": {"c": "foo"}}, {"a": {"b": undefined, "c": null}, "c": "foo"}) + await upsert({"a": {"$eq": "bar" }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) // $all with 1 statement is similar to $eq - upsert({"a": {"$all": ["bar"] }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) - upsert({"a": {"$eq": "bar" }, "b": "baz"}, {"$set": {"c": "foo"}}, {"a": "bar", "b": "baz", "c": "foo"}) - upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"}) - upsert({"a": {"$exists": true, "$eq": "foo"}}, {"$set": {"c": "foo"}}, {"a": "foo", "c": "foo"}) - upsert({"a": {"$gt": 3, "$eq": 2}}, {"$set": {"c": "foo"}}, {"a": 2, "c": "foo"}) + await upsert({"a": {"$all": ["bar"] }}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"a": {"$eq": "bar" }, "b": "baz"}, {"$set": {"c": "foo"}}, {"a": "bar", "b": "baz", "c": "foo"}) + await upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"}) + await upsert({"a": {"$exists": true, "$eq": "foo"}}, {"$set": {"c": "foo"}}, {"a": "foo", "c": "foo"}) + await upsert({"a": {"$gt": 3, "$eq": 2}}, {"$set": {"c": "foo"}}, {"a": 2, "c": "foo"}) // $and - upsert({"$and": [{"a": {"$eq": "bar"}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) - upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) - upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"$and": [{"a": {"$eq": "bar"}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"$and": [{"a": {"$all": ["bar"]}}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) // $or with one statement is handled similar to $and - upsert({"$or": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) + await upsert({"$or": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"a": "bar", "c": "foo"}) // $or with multiple statements is ignored - upsert({"$or": [{"a": "bar"}, {"b": "baz"}]}, {"$set": {"c": "foo"}}, {"c": "foo"}) + await upsert({"$or": [{"a": "bar"}, {"b": "baz"}]}, {"$set": {"c": "foo"}}, {"c": "foo"}) // Negative logical operators are ignored - upsert({"$nor": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"c": "foo"}) + await upsert({"$nor": [{"a": "bar"}]}, {"$set": {"c": "foo"}}, {"c": "foo"}) // Filter out empty objects after filtering out operators - upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"}) + await upsert({"a": {"$exists": true}}, {"$set": {"c": "foo"}}, {"c": "foo"}) // But leave actual empty objects - upsert({"a": {}}, {"$set": {"c": "foo"}}, {"a": {}, "c": "foo"}) + await upsert({"a": {}}, {"$set": {"c": "foo"}}, {"a": {}, "c": "foo"}) // Also filter out shorthand regexp notation - upsert({"a": /a/}, {"$set": {"c": "foo"}}, {"c": "foo"}) + await upsert({"a": /a/}, {"$set": {"c": "foo"}}, {"c": "foo"}) // Test nested fields - upsert({"$and": [{"a.a": "foo"}, {"$or": [{"a.b": "baz"}]}]}, {"$set": {"c": "foo"}}, {"a": {"a": "foo", "b": "baz"}, "c": "foo"}) + await upsert({"$and": [{"a.a": "foo"}, {"$or": [{"a.b": "baz"}]}]}, {"$set": {"c": "foo"}}, {"a": {"a": "foo", "b": "baz"}, "c": "foo"}) // Test for https://github.com/meteor/meteor/issues/5294 - upsert({"a": {"$ne": 444}}, {"$push": {"a": 123}}, {"a": [123]}) + await upsert({"a": {"$ne": 444}}, {"$push": {"a": 123}}, {"a": [123]}) // Mod takes precedence over query - upsert({"a": "foo"}, {"a": "bar"}, {"a": "bar"}) - upsert({"a": "foo"}, {"$set":{"a": "bar"}}, {"a": "bar"}) + await upsert({"a": "foo"}, {"a": "bar"}, {"a": "bar"}) + await upsert({"a": "foo"}, {"$set":{"a": "bar"}}, {"a": "bar"}) // Replacement can take _id from query - upsert({"_id": "foo", "foo": "bar"}, {"bar": "foo"}, {"_id": "foo", "bar": "foo"}) + await upsert({"_id": "foo", "foo": "bar"}, {"bar": "foo"}, {"_id": "foo", "bar": "foo"}) // Replacement update keeps _id - upsertUpdate({"_id": "foo", "bar": "baz"}, {"_id":"foo"}, {"bar": "crow"}, {"_id": "foo", "bar": "crow"}); + await upsertUpdate({"_id": "foo", "bar": "baz"}, {"_id":"foo"}, {"bar": "crow"}, {"_id": "foo", "bar": "crow"}); // Test for https://github.com/meteor/meteor/issues/9167 - upsert({key: 123, keyName: '321'}, {$set: {name: 'Todo'}}, {key: 123, keyName: '321', name: 'Todo'}); - upsertException({key: 123, "key.name": '321'}, {$set:{}}); + await upsert({key: 123, keyName: '321'}, {$set: {name: 'Todo'}}, {key: 123, keyName: '321', name: 'Todo'}); + await upsertException({key: 123, "key.name": '321'}, {$set:{}}); // Nested fields don't work with literal objects - upsertException({"a": {}, "a.b": "foo"}, {}); + await upsertException({"a": {}, "a.b": "foo"}, {}); // You can't have an ambiguous ID - upsertException({"_id":"foo"}, {"_id":"bar"}); - upsertException({"_id":"foo"}, {"$set":{"_id":"bar"}}); + await upsertException({"_id":"foo"}, {"_id":"bar"}); + await upsertException({"_id":"foo"}, {"$set":{"_id":"bar"}}); // You can't set the same field twice - upsertException({"$and": [{"a": "foo"}, {"a": "foo"}]}, {}); //not even with same value - upsertException({"a": {"$all": ["foo", "bar"]}}, {}); - upsertException({"$and": [{"a": {"$eq": "foo"}}, {"$or": [{"a": {"$all": ["bar"]}}]}]}, {}); + await upsertException({"$and": [{"a": "foo"}, {"a": "foo"}]}, {}); //not even with same value + await upsertException({"a": {"$all": ["foo", "bar"]}}, {}); + await upsertException({"$and": [{"a": {"$eq": "foo"}}, {"$or": [{"a": {"$all": ["bar"]}}]}]}, {}); // You can't have nested dotted fields - upsertException({"a": {"foo.bar": "baz"}}, {}); + await upsertException({"a": {"foo.bar": "baz"}}, {}); // You can't have dollar-prefixed fields above the first level (logical operators not counted) - upsertException({"a": {"a": {"$eq": "foo"}}}, {}); - upsertException({"a": {"a": {"$exists": true}}}, {}); + await upsertException({"a": {"a": {"$eq": "foo"}}}, {}); + await upsertException({"a": {"a": {"$exists": true}}}, {}); // You can't mix operators with other fields - upsertException({"a": {"$eq": "bar", "b": "foo"}}, {}) - upsertException({"a": {"b": "foo", "$eq": "bar"}}, {}) + await upsertException({"a": {"$eq": "bar", "b": "foo"}}, {}) + await upsertException({"a": {"b": "foo", "$eq": "bar"}}, {}) const mongoIdForUpsert = new MongoID.ObjectID('44915733af80844fa1cef07a'); - upsert({_id: mongoIdForUpsert}, {$setOnInsert: {a: 123}}, {a: 123}) + await upsert({_id: mongoIdForUpsert}, {$setOnInsert: {a: 123}}, {a: 123}) // Test for https://github.com/meteor/meteor/issues/7758 - upsert({n_id: mongoIdForUpsert, c_n: "bar"}, + await upsert({n_id: mongoIdForUpsert, c_n: "bar"}, {$set: { t_t_o: "foo"}}, {n_id: mongoIdForUpsert, t_t_o: "foo", c_n: "bar"}); - exception({}, {$set: {_id: 'bad'}}); + await exception({}, {$set: {_id: 'bad'}}); // $bit // unimplemented From 40eb0223105166a4e27279d64053d587f5143d45 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:34:00 -0400 Subject: [PATCH 06/16] fix minimongo - observe ordered --- packages/minimongo/minimongo_tests_client.js | 74 ++++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 7e95490d45..7763b4ae87 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -3102,83 +3102,83 @@ Tinytest.addAsync('minimongo - modify', async test => { // XXX test update() (selecting docs, multi, upsert..) -Tinytest.add('minimongo - observe ordered', test => { +Tinytest.addAsync('minimongo - observe ordered', async test => { const operations = []; const cbs = log_callbacks(operations); let handle; const c = new LocalCollection(); - handle = c.find({}, {sort: {a: 1}}).observe(cbs); + handle = await c.find({}, {sort: {a: 1}}).observe(cbs); test.isTrue(handle.collection === c); - c.insert({_id: 'foo', a: 1}); + await c.insertAsync({_id: 'foo', a: 1}); test.equal(operations.shift(), ['added', {a: 1}, 0, null]); - c.update({a: 1}, {$set: {a: 2}}); + await c.updateAsync({a: 1}, {$set: {a: 2}}); test.equal(operations.shift(), ['changed', {a: 2}, 0, {a: 1}]); - c.insert({a: 10}); + await c.insertAsync({a: 10}); test.equal(operations.shift(), ['added', {a: 10}, 1, null]); - c.update({}, {$inc: {a: 1}}, {multi: true}); + await c.updateAsync({}, {$inc: {a: 1}}, {multi: true}); test.equal(operations.shift(), ['changed', {a: 3}, 0, {a: 2}]); test.equal(operations.shift(), ['changed', {a: 11}, 1, {a: 10}]); - c.update({a: 11}, {a: 1}); + await c.updateAsync({a: 11}, {a: 1}); test.equal(operations.shift(), ['changed', {a: 1}, 1, {a: 11}]); test.equal(operations.shift(), ['moved', {a: 1}, 1, 0, 'foo']); - c.remove({a: 2}); + await c.removeAsync({a: 2}); test.equal(operations.shift(), undefined); - c.remove({a: 3}); + await c.removeAsync({a: 3}); test.equal(operations.shift(), ['removed', 'foo', 1, {a: 3}]); // test stop handle.stop(); const idA2 = Random.id(); - c.insert({_id: idA2, a: 2}); + await c.insertAsync({_id: idA2, a: 2}); test.equal(operations.shift(), undefined); // test initial inserts (and backwards sort) - handle = c.find({}, {sort: {a: -1}}).observe(cbs); + handle = await c.find({}, {sort: {a: -1}}).observe(cbs); test.equal(operations.shift(), ['added', {a: 2}, 0, null]); test.equal(operations.shift(), ['added', {a: 1}, 1, null]); handle.stop(); // test _suppress_initial - handle = c.find({}, {sort: {a: -1}}).observe(Object.assign({ + handle = await c.find({}, {sort: {a: -1}}).observe(Object.assign({ _suppress_initial: true}, cbs)); test.equal(operations.shift(), undefined); - c.insert({a: 100}); + await c.insertAsync({a: 100}); test.equal(operations.shift(), ['added', {a: 100}, 0, idA2]); handle.stop(); // test skip and limit. - c.remove({}); - handle = c.find({}, {sort: {a: 1}, skip: 1, limit: 2}).observe(cbs); + await c.removeAsync({}); + handle = await c.find({}, {sort: {a: 1}, skip: 1, limit: 2}).observe(cbs); test.equal(operations.shift(), undefined); - c.insert({a: 1}); + await c.insertAsync({a: 1}); test.equal(operations.shift(), undefined); - c.insert({_id: 'foo', a: 2}); + await c.insertAsync({_id: 'foo', a: 2}); test.equal(operations.shift(), ['added', {a: 2}, 0, null]); - c.insert({a: 3}); + await c.insertAsync({a: 3}); test.equal(operations.shift(), ['added', {a: 3}, 1, null]); - c.insert({a: 4}); + await c.insertAsync({a: 4}); test.equal(operations.shift(), undefined); - c.update({a: 1}, {a: 0}); + await c.updateAsync({a: 1}, {a: 0}); test.equal(operations.shift(), undefined); - c.update({a: 0}, {a: 5}); + await c.updateAsync({a: 0}, {a: 5}); test.equal(operations.shift(), ['removed', 'foo', 0, {a: 2}]); test.equal(operations.shift(), ['added', {a: 4}, 1, null]); - c.update({a: 3}, {a: 3.5}); + await c.updateAsync({a: 3}, {a: 3.5}); test.equal(operations.shift(), ['changed', {a: 3.5}, 0, {a: 3}]); handle.stop(); // test observe limit with pre-existing docs - c.remove({}); - c.insert({a: 1}); - c.insert({_id: 'two', a: 2}); - c.insert({a: 3}); - handle = c.find({}, {sort: {a: 1}, limit: 2}).observe(cbs); + await c.removeAsync({}); + await c.insertAsync({a: 1}); + await c.insertAsync({_id: 'two', a: 2}); + await c.insertAsync({a: 3}); + handle = await c.find({}, {sort: {a: 1}, limit: 2}).observe(cbs); test.equal(operations.shift(), ['added', {a: 1}, 0, null]); test.equal(operations.shift(), ['added', {a: 2}, 1, null]); test.equal(operations.shift(), undefined); - c.remove({a: 2}); + await c.removeAsync({a: 2}); test.equal(operations.shift(), ['removed', 'two', 1, {a: 2}]); test.equal(operations.shift(), ['added', {a: 3}, 1, null]); test.equal(operations.shift(), undefined); @@ -3186,23 +3186,23 @@ Tinytest.add('minimongo - observe ordered', test => { // test _no_indices - c.remove({}); - handle = c.find({}, {sort: {a: 1}}).observe(Object.assign(cbs, {_no_indices: true})); - c.insert({_id: 'foo', a: 1}); + await c.removeAsync({}); + handle = await c.find({}, {sort: {a: 1}}).observe(Object.assign(cbs, {_no_indices: true})); + await c.insertAsync({_id: 'foo', a: 1}); test.equal(operations.shift(), ['added', {a: 1}, -1, null]); - c.update({a: 1}, {$set: {a: 2}}); + await c.updateAsync({a: 1}, {$set: {a: 2}}); test.equal(operations.shift(), ['changed', {a: 2}, -1, {a: 1}]); - c.insert({a: 10}); + await c.insertAsync({a: 10}); test.equal(operations.shift(), ['added', {a: 10}, -1, null]); - c.update({}, {$inc: {a: 1}}, {multi: true}); + await c.updateAsync({}, {$inc: {a: 1}}, {multi: true}); test.equal(operations.shift(), ['changed', {a: 3}, -1, {a: 2}]); test.equal(operations.shift(), ['changed', {a: 11}, -1, {a: 10}]); - c.update({a: 11}, {a: 1}); + await c.updateAsync({a: 11}, {a: 1}); test.equal(operations.shift(), ['changed', {a: 1}, -1, {a: 11}]); test.equal(operations.shift(), ['moved', {a: 1}, -1, -1, 'foo']); - c.remove({a: 2}); + await c.removeAsync({a: 2}); test.equal(operations.shift(), undefined); - c.remove({a: 3}); + await c.removeAsync({a: 3}); test.equal(operations.shift(), ['removed', 'foo', -1, {a: 3}]); handle.stop(); }); From 21dc2fb3b6e07155b3786ce26d64f8db9a3bd1a3 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:36:27 -0400 Subject: [PATCH 07/16] fix minimongo - observe ordered true/false --- packages/minimongo/minimongo_tests_client.js | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 7763b4ae87..4ceeceb5e4 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -3208,7 +3208,7 @@ Tinytest.addAsync('minimongo - observe ordered', async test => { }); [true, false].forEach(ordered => { - Tinytest.add(`minimongo - observe ordered: ${ordered}`, test => { + Tinytest.addAsync(`minimongo - observe ordered: ${ordered}`, async test => { const c = new LocalCollection(); let ev = ''; @@ -3227,42 +3227,42 @@ Tinytest.addAsync('minimongo - observe ordered', async test => { ev = ''; }; - c.insert({_id: 1, name: 'strawberry', tags: ['fruit', 'red', 'squishy']}); - c.insert({_id: 2, name: 'apple', tags: ['fruit', 'red', 'hard']}); - c.insert({_id: 3, name: 'rose', tags: ['flower', 'red', 'squishy']}); + await c.insertAsync({_id: 1, name: 'strawberry', tags: ['fruit', 'red', 'squishy']}); + await c.insertAsync({_id: 2, name: 'apple', tags: ['fruit', 'red', 'hard']}); + await c.insertAsync({_id: 3, name: 'rose', tags: ['flower', 'red', 'squishy']}); // This should work equally well for ordered and unordered observations // (because the callbacks don't look at indices and there's no 'moved' // callback). - let handle = c.find({tags: 'flower'}).observe(makecb('a')); + let handle = await c.find({tags: 'flower'}).observe(makecb('a')); expect('aa3_'); - c.update({name: 'rose'}, {$set: {tags: ['bloom', 'red', 'squishy']}}); + await c.updateAsync({name: 'rose'}, {$set: {tags: ['bloom', 'red', 'squishy']}}); expect('ra3_'); - c.update({name: 'rose'}, {$set: {tags: ['flower', 'red', 'squishy']}}); + await c.updateAsync({name: 'rose'}, {$set: {tags: ['flower', 'red', 'squishy']}}); expect('aa3_'); - c.update({name: 'rose'}, {$set: {food: false}}); + await c.updateAsync({name: 'rose'}, {$set: {food: false}}); expect('ca3_'); c.remove({}); expect('ra3_'); - c.insert({_id: 4, name: 'daisy', tags: ['flower']}); + await c.insertAsync({_id: 4, name: 'daisy', tags: ['flower']}); expect('aa4_'); handle.stop(); // After calling stop, no more callbacks are called. - c.insert({_id: 5, name: 'iris', tags: ['flower']}); + await c.insertAsync({_id: 5, name: 'iris', tags: ['flower']}); expect(''); // Test that observing a lookup by ID works. - handle = c.find(4).observe(makecb('b')); + handle = await c.find(4).observe(makecb('b')); expect('ab4_'); - c.update(4, {$set: {eek: 5}}); + await c.updateAsync(4, {$set: {eek: 5}}); expect('cb4_'); handle.stop(); // Test observe with reactive: false. - handle = c.find({tags: 'flower'}, {reactive: false}).observe(makecb('c')); + handle = await c.find({tags: 'flower'}, {reactive: false}).observe(makecb('c')); expect('ac4_ac5_'); // This insert shouldn't trigger a callback because it's not reactive. - c.insert({_id: 6, name: 'river', tags: ['flower']}); + await c.insertAsync({_id: 6, name: 'river', tags: ['flower']}); expect(''); handle.stop(); }); From 0c326b6b0cbcfe887ba6f6dcafe5631599e0d84a Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:40:29 -0400 Subject: [PATCH 08/16] fix minimongo - 'minimongo - pause' --- packages/minimongo/minimongo_tests_client.js | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 4ceeceb5e4..4b45273632 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -3360,43 +3360,43 @@ Tinytest.add('minimongo - objectid', test => { test.equal(randomOid, new MongoID.ObjectID(randomOid.valueOf())); }); -Tinytest.add('minimongo - pause', test => { +Tinytest.addAsync('minimongo - pause', async test => { const operations = []; const cbs = log_callbacks(operations); const c = new LocalCollection(); - const h = c.find({}).observe(cbs); + const h = await c.find({}).observe(cbs); // remove and add cancel out. - c.insert({_id: 1, a: 1}); + await c.insertAsync({_id: 1, a: 1}); test.equal(operations.shift(), ['added', {a: 1}, 0, null]); c.pauseObservers(); - c.remove({_id: 1}); + await c.removeAsync({_id: 1}); test.length(operations, 0); - c.insert({_id: 1, a: 1}); + await c.insertAsync({_id: 1, a: 1}); test.length(operations, 0); - c.resumeObservers(); + c.resumeObserversClient(); test.length(operations, 0); // two modifications become one c.pauseObservers(); - c.update({_id: 1}, {a: 2}); - c.update({_id: 1}, {a: 3}); + await c.updateAsync({_id: 1}, {a: 2}); + await c.updateAsync({_id: 1}, {a: 3}); - c.resumeObservers(); + c.resumeObserversClient(); test.equal(operations.shift(), ['changed', {a: 3}, 0, {a: 1}]); test.length(operations, 0); // test special case for remove({}) c.pauseObservers(); - test.equal(c.remove({}), 1); + test.equal(await c.removeAsync({}), 1); test.length(operations, 0); - c.resumeObservers(); + c.resumeObserversClient(); test.equal(operations.shift(), ['removed', 1, 0, {a: 3}]); test.length(operations, 0); From df9e4ae6f3258c3e0028ef63184b112f7e1acf2c Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 10:45:30 -0400 Subject: [PATCH 09/16] fix minimongo - $near operator tests --- packages/minimongo/minimongo_tests_client.js | 59 ++++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 4b45273632..37752c3f4c 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -3550,15 +3550,15 @@ Tinytest.add('minimongo - reactive count with cached cursor', test => { test.equal(secondAutorunCount, 3); }); -Tinytest.add('minimongo - $near operator tests', test => { +Tinytest.addAsync('minimongo - $near operator tests', async test => { let coll = new LocalCollection(); - coll.insert({ rest: { loc: [2, 3] } }); - coll.insert({ rest: { loc: [-3, 3] } }); - coll.insert({ rest: { loc: [5, 5] } }); + await coll.insertAsync({ rest: { loc: [2, 3] } }); + await coll.insertAsync({ rest: { loc: [-3, 3] } }); + await coll.insertAsync({ rest: { loc: [5, 5] } }); test.equal(coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 30 } }).count(), 3); test.equal(coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 4 } }).count(), 1); - const points = coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 6 } }).fetch(); + const points = await coll.find({ 'rest.loc': { $near: [0, 0], $maxDistance: 6 } }).fetchAsync(); points.forEach((point, i, points) => { test.isTrue(!i || distance([0, 0], point.rest.loc) >= distance([0, 0], points[i - 1].rest.loc)); }); @@ -3578,19 +3578,22 @@ Tinytest.add('minimongo - $near operator tests', test => { { category: 'OTHER OFFENSES', descript: 'POSSESSION OF BURGLARY TOOLS', address: '900 Block of MINNA ST', location: { type: 'Point', coordinates: [ -122.415386041221, 37.7747879734156 ] } }, ]; - data.forEach((x, i) => { coll.insert(Object.assign(x, { x: i })); }); + let i = 0; + for (const x of data) { + await coll.insertAsync(Object.assign(x, { x: i++ })); + } - const close15 = coll.find({ location: { $near: { + const close15 = await coll.find({ location: { $near: { $geometry: { type: 'Point', coordinates: [-122.4154282, 37.7746115] }, - $maxDistance: 15 } } }).fetch(); + $maxDistance: 15 } } }).fetchAsync(); test.length(close15, 1); test.equal(close15[0].descript, 'GRAND THEFT OF PROPERTY'); - const close20 = coll.find({ location: { $near: { + const close20 = await coll.find({ location: { $near: { $geometry: { type: 'Point', coordinates: [-122.4154282, 37.7746115] }, - $maxDistance: 20 } } }).fetch(); + $maxDistance: 20 } } }).fetchAsync(); test.length(close20, 4); test.equal(close20[0].descript, 'GRAND THEFT OF PROPERTY'); test.equal(close20[1].descript, 'PETTY THEFT FROM LOCKED AUTO'); @@ -3645,7 +3648,7 @@ Tinytest.add('minimongo - $near operator tests', test => { // array tests coll = new LocalCollection(); - coll.insert({ + await coll.insertAsync({ _id: 'x', k: 9, a: [ @@ -3653,34 +3656,44 @@ Tinytest.add('minimongo - $near operator tests', test => { [100, 100], [1, 1]]}, {b: [150, 150]}]}); - coll.insert({ + await coll.insertAsync({ _id: 'y', k: 9, a: {b: [5, 5]}}); - const testNear = (near, md, expected) => { + const testNear = async (near, md, expected) => { test.equal( - coll.find({'a.b': {$near: near, $maxDistance: md}}).fetch().map(doc => doc._id), + (await coll.find({'a.b': {$near: near, $maxDistance: md}}).fetchAsync()).map(doc => doc._id), expected); }; - testNear([149, 149], 4, ['x']); - testNear([149, 149], 1000, ['x', 'y']); + await testNear([149, 149], 4, ['x']); + await testNear([149, 149], 1000, ['x', 'y']); // It's important that we figure out that 'x' is closer than 'y' to [2,2] even // though the first within-1000 point in 'x' (ie, [100,100]) is farther than // 'y'. - testNear([2, 2], 1000, ['x', 'y']); + await testNear([2, 2], 1000, ['x', 'y']); // issue #3599 // Ensure that distance is not used as a tie-breaker for sort. test.equal( - coll.find({'a.b': {$near: [1, 1]}}, {sort: {k: 1}}).fetch().map(doc => doc._id), - ['x', 'y']); + ( + await coll + .find({ 'a.b': { $near: [1, 1] } }, { sort: { k: 1 } }) + .fetchAsync() + ).map(doc => doc._id), + ['x', 'y'] + ); test.equal( - coll.find({'a.b': {$near: [5, 5]}}, {sort: {k: 1}}).fetch().map(doc => doc._id), - ['x', 'y']); + ( + await coll + .find({ 'a.b': { $near: [5, 5] } }, { sort: { k: 1 } }) + .fetchAsync() + ).map(doc => doc._id), + ['x', 'y'] + ); const operations = []; const cbs = log_callbacks(operations); - const handle = coll.find({'a.b': {$near: [7, 7]}}).observe(cbs); + const handle = await coll.find({'a.b': {$near: [7, 7]}}).observe(cbs); test.length(operations, 2); test.equal(operations.shift(), ['added', {k: 9, a: {b: [5, 5]}}, 0, null]); @@ -3688,7 +3701,7 @@ Tinytest.add('minimongo - $near operator tests', test => { ['added', {k: 9, a: [{b: [[100, 100], [1, 1]]}, {b: [150, 150]}]}, 1, null]); // This needs to be inserted in the MIDDLE of the two existing ones. - coll.insert({a: {b: [3, 3]}}); + await coll.insertAsync({a: {b: [3, 3]}}); test.length(operations, 1); test.equal(operations.shift(), ['added', {a: {b: [3, 3]}}, 1, 'x']); From 402d81068eb04eb3d827d38b72a3a2803b6df442 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 15:26:58 -0400 Subject: [PATCH 10/16] removing queue from onMessage --- .../ddp-client/common/livedata_connection.js | 7 +------ packages/minimongo/local_collection.js | 2 +- packages/mongo/oplog_observe_driver.js | 18 ++++++++---------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index f0a36a6d3f..f014585503 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -256,8 +256,6 @@ export class Connection { } }; - self.queue = new Meteor._AsynchronousQueue(); - if (Meteor.isServer) { self._stream.on( 'message', @@ -1835,10 +1833,7 @@ export class Connection { } } - onMessage(raw_msg) { - this.queue.queueTask(() => this._onMessage(raw_msg), raw_msg); - } - async _onMessage(raw_msg) { + async onMessage(raw_msg) { let msg; try { msg = DDPCommon.parseDDP(raw_msg); diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 9be8468628..935ac06cfc 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -20,7 +20,7 @@ export default class LocalCollection { // _id -> document (also containing id) this._docs = new LocalCollection._IdMap; - this._observeQueue = new Meteor._SynchronousQueue(); + this._observeQueue = new Meteor._AsynchronousQueue(); this.next_qid = 1; // live query id generator diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index 3a988e4ba3..a02530be5d 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -826,16 +826,14 @@ _.extend(OplogObserveDriver.prototype, { 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(); - } - }); + if (self._requeryWhenDoneThisQuery) { + self._requeryWhenDoneThisQuery = false; + self._pollQuery(); + } else if (self._needToFetch.empty()) { + await self._beSteady(); + } else { + self._fetchModifiedDocuments(); + } }, _cursorForQuery: function (optionsOverwrite) { From 59a30a0fa9c4cd27c01775a2f35f5f8058676da8 Mon Sep 17 00:00:00 2001 From: denihs Date: Tue, 7 Feb 2023 15:30:04 -0400 Subject: [PATCH 11/16] fixing ddp-client tests --- .../ddp-client/common/livedata_connection.js | 69 ++----------------- packages/ddp-server/livedata_server.js | 64 +++++++++-------- packages/ddp-server/livedata_server_tests.js | 12 ++-- packages/ddp-server/writefence.js | 11 +-- packages/mongo/allow_tests.js | 9 ++- packages/mongo/mongo_driver.js | 2 +- packages/mongo/observe_multiplex.js | 8 +-- packages/mongo/oplog_observe_driver.js | 4 +- packages/mongo/polling_observe_driver.js | 18 ++--- tools/isobuild/compiler-plugin.js | 12 +++- tools/isobuild/linker.js | 22 +++--- 11 files changed, 90 insertions(+), 141 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index f014585503..b9ee59ffcb 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -523,13 +523,6 @@ export class Connection { }); } - _getIsSimulation({isFromCallAsync, alreadyInSimulation}) { - if (!isFromCallAsync) { - return alreadyInSimulation; - } - return alreadyInSimulation && DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); - } - /** * @memberOf Meteor * @importFromPackage meteor @@ -567,44 +560,9 @@ export class Connection { "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 }) - .then(result => { - DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); - resolve(result); - }) - .catch(reject); + + return this.applyAsync(name, args, { + isFromCallAsync: true }); } @@ -628,12 +586,7 @@ export class Connection { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args)); if (stubOptions.hasStub) { - if ( - !this._getIsSimulation({ - alreadyInSimulation: stubOptions.alreadyInSimulation, - isFromCallAsync: stubOptions.isFromCallAsync, - }) - ) { + if (!stubOptions.alreadyInSimulation) { this._saveOriginals(); } try { @@ -664,12 +617,7 @@ export class Connection { async applyAsync(name, args, options, callback = null) { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); if (stubOptions.hasStub) { - if ( - !this._getIsSimulation({ - alreadyInSimulation: stubOptions.alreadyInSimulation, - isFromCallAsync: stubOptions.isFromCallAsync, - }) - ) { + if (!stubOptions.alreadyInSimulation) { this._saveOriginals(); } try { @@ -732,12 +680,7 @@ export class Connection { // 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 (stubCallValue.isFromCallAsync) { if (callback) { callback(exception, stubReturnValue); return undefined; diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 51a794f505..08294e3d38 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -49,6 +49,14 @@ var SessionDocumentView = function () { DDPServer._SessionDocumentView = SessionDocumentView; +DDPServer._getCurrentFence = function () { + let currentInvocation = this._CurrentWriteFence.get(); + if (currentInvocation) { + return currentInvocation; + } + currentInvocation = DDP._CurrentPublicationInvocation.get(); + return currentInvocation ? currentInvocation.fence : undefined; +}; _.extend(SessionDocumentView.prototype, { @@ -685,7 +693,7 @@ Object.assign(Session.prototype, { self._stopSubscription(msg.id); }, - method: function (msg, unblock) { + method: async function (msg, unblock) { var self = this; // Reject malformed messages. @@ -712,20 +720,16 @@ Object.assign(Session.prototype, { // example, because the method waits for them) their // writes will be included in the fence. fence.retire(); - self.send({ - msg: 'updated', methods: [msg.id]}); + self.send({msg: 'updated', methods: [msg.id]}); }); // Find the handler var handler = self.server.method_handlers[msg.method]; if (!handler) { - fence.arm().finally(() => { - self.send({ - msg: 'result', - id: msg.id, - error: new Meteor.Error(404, `Method '${msg.method}' not found`), - }); - }); + self.send({ + msg: 'result', id: msg.id, + error: new Meteor.Error(404, `Method '${msg.method}' not found`)}); + await fence.arm(); return; } @@ -797,32 +801,28 @@ Object.assign(Session.prototype, { ); }); - function finish() { - return fence.arm().finally(() => { - unblock(); - }); + async function finish() { + await fence.arm(); + unblock(); } const payload = { msg: "result", id: msg.id }; - - promise.then(result => { - finish().finally(() => { - if (result !== undefined) { - payload.result = result; - } - self.send(payload); - }); - }, (exception) => { - finish().finally(() => { - payload.error = wrapInternalException( - exception, - `while invoking method '${msg.method}'` - ); - self.send(payload); - }); + promise.then(async result => { + await finish(); + if (result !== undefined) { + payload.result = result; + } + self.send(payload); + }, async (exception) => { + await finish(); + payload.error = wrapInternalException( + exception, + `while invoking method '${msg.method}'` + ); + self.send(payload); }); } }, @@ -1519,9 +1519,7 @@ Server = function (options = {}) { return; } - Meteor._runAsync(function() { - self._handleConnect(socket, msg); - }) + self._handleConnect(socket, msg); return; } diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js index aa4cc6f845..966594be2a 100644 --- a/packages/ddp-server/livedata_server_tests.js +++ b/packages/ddp-server/livedata_server_tests.js @@ -315,13 +315,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"; diff --git a/packages/ddp-server/writefence.js b/packages/ddp-server/writefence.js index d85f028ff8..07a81448bf 100644 --- a/packages/ddp-server/writefence.js +++ b/packages/ddp-server/writefence.js @@ -34,16 +34,15 @@ DDPServer._WriteFence = class { await this._maybeFire(); }; - const self = this; return { - committed: Meteor._isFibersEnabled ? () => Promise.await(_committedFn.apply(self)) : _committedFn, + committed: _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()) + if (this === DDPServer._getCurrentFence()) throw Error("Can't arm the current fence"); this.armed = true; return Meteor._isFibersEnabled ? Promise.await(this._maybeFire()) : this._maybeFire(); @@ -101,8 +100,10 @@ DDPServer._WriteFence = class { if (!this.outstanding_writes) { this.fired = true; - while (this.completion_callbacks.length > 0) { - const cb = this.completion_callbacks.shift(); + const callbacks = this.completion_callbacks || []; + this.completion_callbacks = []; + while (callbacks.length > 0) { + const cb = callbacks.shift(); await invokeCallback(cb); } } diff --git a/packages/mongo/allow_tests.js b/packages/mongo/allow_tests.js index d8706e83e9..70b7ec0635 100644 --- a/packages/mongo/allow_tests.js +++ b/packages/mongo/allow_tests.js @@ -35,8 +35,8 @@ if (Meteor.isServer) { needToConfigure = true; collection._insecure = insecure; var m = {}; - m["clear-collection-" + fullName] = function() { - collection.removeAsync({}); + m["clear-collection-" + fullName] = async function() { + await collection.removeAsync({}); }; Meteor.methods(m); } @@ -238,8 +238,8 @@ if (Meteor.isClient) { var collection = new Mongo.Collection( fullName, {idGeneration: idGeneration, transform: transform}); - collection.callClearMethod = function () { - return Meteor.callAsync("clear-collection-" + fullName); + collection.callClearMethod = async function () { + await Meteor.callAsync("clear-collection-" + fullName); }; collection.unnoncedName = name + idGeneration; return collection; @@ -538,7 +538,6 @@ if (Meteor.isClient) { '/' + collection._name + '/removeAsync', {updated: true}).catch(async function (err) { test.equal(err.error, 403); - console.log({err}); // unchanged test.equal(await collection.find({updated: true}).count(), 2); }); diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 913cb9a304..b52974f8ae 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -262,7 +262,7 @@ MongoConnection.prototype.createCappedCollectionAsync = async function ( // after the observer notifiers have added themselves to the write // fence), you should call 'committed()' on the object returned. MongoConnection.prototype._maybeBeginWrite = function () { - const { fence } = DDP._CurrentPublicationInvocation.get() || {}; + const fence = DDPServer._getCurrentFence(); if (fence) { return fence.beginWrite(); } else { diff --git a/packages/mongo/observe_multiplex.js b/packages/mongo/observe_multiplex.js index 43da4b141b..28c63c261e 100644 --- a/packages/mongo/observe_multiplex.js +++ b/packages/mongo/observe_multiplex.js @@ -22,8 +22,8 @@ ObserveMultiplexer = class { const self = this; this.callbackNames().forEach(callbackName => { - this[callbackName] = function(/* ... */) { - return self._applyCallback(callbackName, _.toArray(arguments)); + this[callbackName] = async function(/* ... */) { + await self._applyCallback(callbackName, _.toArray(arguments)); }; }); } @@ -95,7 +95,7 @@ ObserveMultiplexer = class { // adds have been processed. Does not block. async ready() { const self = this; - await this._queue.runTask(function () { + this._queue.queueTask(function () { if (self._ready()) throw Error("can't make ObserveMultiplex ready twice!"); @@ -146,7 +146,7 @@ ObserveMultiplexer = class { } async _applyCallback(callbackName, args) { const self = this; - await this._queue.runTask(async function () { + this._queue.queueTask(async function () { // If we stopped in the meantime, do nothing. if (!self._handles) return; diff --git a/packages/mongo/oplog_observe_driver.js b/packages/mongo/oplog_observe_driver.js index a02530be5d..00b7536bd9 100644 --- a/packages/mongo/oplog_observe_driver.js +++ b/packages/mongo/oplog_observe_driver.js @@ -140,7 +140,7 @@ OplogObserveDriver = function (options) { 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(); + var fence = DDPServer._getCurrentFence(); if (!fence || fence.fired) return; @@ -568,7 +568,7 @@ _.extend(OplogObserveDriver.prototype, { await w.committed(); } } catch (e) { - console.log({writes}, e); + console.error("_beSteady error", {writes}, e); } }); }, diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js index e64779d56b..e7469fce05 100644 --- a/packages/mongo/polling_observe_driver.js +++ b/packages/mongo/polling_observe_driver.js @@ -42,7 +42,7 @@ PollingObserveDriver = function (options) { // 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(); + var fence = DDPServer._getCurrentFence(); if (fence) self._pendingWrites.push(fence.beginWrite()); // Ensure a poll is scheduled... but if we already know that one is, @@ -126,8 +126,8 @@ _.extend(PollingObserveDriver.prototype, { self._pollsScheduledButNotStarted); // Run a poll synchronously (which will counteract the // ++_pollsScheduledButNotStarted from _suspendPolling). - self._taskQueue.runTask(function () { - self._pollMongo(); + self._taskQueue.runTask(async function () { + await self._pollMongo(); }); }, @@ -202,10 +202,10 @@ _.extend(PollingObserveDriver.prototype, { // 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(); - }); + await self._multiplexer.onFlush(async function () { + for (const w of writesForCycle) { + await w.committed(); + } }); }, @@ -218,8 +218,8 @@ _.extend(PollingObserveDriver.prototype, { _.each(self._stopCallbacks, stopCallbacksCaller); // Release any write fences that are waiting on us. - _.each(self._pendingWrites, function (w) { - w.committed(); + _.each(self._pendingWrites, async function (w) { + await w.committed(); }); Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact( "mongo-livedata", "observe-drivers-polling", -1); diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 4ae0213777..a14c5e79b6 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -1071,6 +1071,8 @@ 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 + + self.deps = []; } async init() { @@ -1093,13 +1095,17 @@ export class PackageSourceBatch { skipDebugOnly: true, skipProdOnly: true, skipTestOnly: true, - }, depUnibuild => { + }, (depUnibuild, { weak, unordered }) => { + let packageName = depUnibuild.pkg.name; + _.each(depUnibuild.declaredExports, function (symbol) { // Slightly hacky implementation of test-only exports. if (! symbol.testOnly || self.unibuild.pkg.isTest) { - self.importedSymbolToPackageName[symbol.name] = depUnibuild.pkg.name; + self.importedSymbolToPackageName[symbol.name] = packageName; } }); + + self.deps.push({ package: packageName, weak, unordered }); }); self.useMeteorInstall = @@ -1694,7 +1700,7 @@ export class PackageSourceBatch { imports: self.importedSymbolToPackageName, // XXX report an error if there is a package called global-imports includeSourceMapInstructions: isWeb, - uses: self.unibuild.uses + deps: self.deps }; const fileHashes = []; diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index 3a2700f2a5..31d6ed990e 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -941,16 +941,17 @@ var getHeader = function (options) { var isApp = options.name === null; var chunks = []; - let deps = []; - options.uses.forEach(uses => { - if (!uses.unordered) { - deps.push(JSON.stringify(uses.package)) + let orderedDeps = []; + + options.deps.forEach(dep => { + if (!dep.unordered) { + orderedDeps.push(JSON.stringify(dep.package)) } }); chunks.push( `Package["core-runtime"].queue("${options.name}", [`, - deps.join(', '), + orderedDeps.join(', '), '], function () {' ); @@ -1139,8 +1140,9 @@ export var fullLink = Profile("linker.fullLink", async function (inputFiles, { // how to use them in a browser. includeSourceMapInstructions, - // List of packages this bundle uses - uses + // List of packages this bundle directly uses, or is implied by the packages + // it uses + deps }) { buildmessage.assertInJob(); @@ -1157,7 +1159,7 @@ export var fullLink = Profile("linker.fullLink", async function (inputFiles, { // we can be sure the runtime will be available // The main situations it is not available is the core-runtime // package itself, or any build plugins with no dependencies - let hasRuntime = uses.some(entry => entry.unordered !== true); + let hasRuntime = deps.some(entry => entry.unordered !== true); _.each(inputFiles, file => module.addFile(file)); @@ -1192,7 +1194,7 @@ export var fullLink = Profile("linker.fullLink", async function (inputFiles, { imports, packageVariables: [], hasRuntime, - uses + deps }); let footer = getFooter({ name: null, @@ -1253,7 +1255,7 @@ export var fullLink = Profile("linker.fullLink", async function (inputFiles, { imports, packageVariables: _.union(assignedVariables, declaredExports), hasRuntime, - uses + deps }); var footer = getFooter({ From 82e3456e5b20d187b43eac8be56b0967b9bc7df3 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 8 Feb 2023 09:22:04 -0400 Subject: [PATCH 12/16] - fix livedata stub - methods - revert callAsync code --- .../ddp-client/common/livedata_connection.js | 73 +++++++++++++++++-- packages/ddp-server/livedata_server_tests.js | 12 +-- packages/minimongo/local_collection.js | 4 +- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index b9ee59ffcb..b7f2a46c77 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -237,8 +237,8 @@ export class Connection { // Block auto-reload while we're waiting for method responses. if (Meteor.isClient && - Package.reload && - ! options.reloadWithOutstanding) { + Package.reload && + ! options.reloadWithOutstanding) { Package.reload.Reload._onMigrate(retry => { if (! self._readyToMigrate()) { self._retryMigrate = retry; @@ -523,6 +523,13 @@ export class Connection { }); } + _getIsSimulation({isFromCallAsync, alreadyInSimulation}) { + if (!isFromCallAsync) { + return alreadyInSimulation; + } + return alreadyInSimulation && DDP._CurrentMethodInvocation._isCallAsyncMethodRunning(); + } + /** * @memberOf Meteor * @importFromPackage meteor @@ -560,9 +567,44 @@ export class Connection { "Meteor.callAsync() does not accept a callback. You should 'await' the result, or use .then()." ); } - - return this.applyAsync(name, args, { - isFromCallAsync: true + /* + * 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 }) + .then(result => { + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); + resolve(result); + }) + .catch(reject); }); } @@ -586,7 +628,12 @@ export class Connection { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args)); if (stubOptions.hasStub) { - if (!stubOptions.alreadyInSimulation) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { this._saveOriginals(); } try { @@ -617,7 +664,12 @@ export class Connection { async applyAsync(name, args, options, callback = null) { const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options); if (stubOptions.hasStub) { - if (!stubOptions.alreadyInSimulation) { + if ( + !this._getIsSimulation({ + alreadyInSimulation: stubOptions.alreadyInSimulation, + isFromCallAsync: stubOptions.isFromCallAsync, + }) + ) { this._saveOriginals(); } try { @@ -680,7 +732,12 @@ export class Connection { // 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 (stubCallValue.isFromCallAsync) { + if ( + this._getIsSimulation({ + alreadyInSimulation, + isFromCallAsync: stubCallValue.isFromCallAsync, + }) + ) { if (callback) { callback(exception, stubReturnValue); return undefined; diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js index 966594be2a..aa4cc6f845 100644 --- a/packages/ddp-server/livedata_server_tests.js +++ b/packages/ddp-server/livedata_server_tests.js @@ -315,13 +315,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"; diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 935ac06cfc..a06bf58aa1 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -20,7 +20,9 @@ export default class LocalCollection { // _id -> document (also containing id) this._docs = new LocalCollection._IdMap; - this._observeQueue = new Meteor._AsynchronousQueue(); + this._observeQueue = Meteor.isClient + ? new Meteor._SynchronousQueue() + : new Meteor._AsynchronousQueue(); this.next_qid = 1; // live query id generator From 7c1b331d6bc3c12045899b0b15050a25e74204a4 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 8 Feb 2023 12:19:21 -0400 Subject: [PATCH 13/16] - fix livedata stub - compound methods --- packages/ddp-client/common/livedata_connection.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index b7f2a46c77..9f13c930c4 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -601,10 +601,12 @@ export class Connection { return new Promise((resolve, reject) => { this.applyAsync(name, args, { isFromCallAsync: true }) .then(result => { - DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false); resolve(result); }) - .catch(reject); + .catch(reject) + .finally(() => + DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false) + ); }); } @@ -799,7 +801,7 @@ 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. + // TODO[fibers]: before this was a future, now it's a promise. // Do more tests around this. if (!options.isFromCallAsync) { @@ -861,7 +863,9 @@ export class Connection { // If we're using the default callback on the server, // block waiting for the result. if (future) { - return future; + return { + result: future, + }; } return options.returnStubValue ? stubReturnValue : undefined; } From 0067ab29ba62f27a9784367feb3f288278a2bf14 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 8 Feb 2023 12:33:43 -0400 Subject: [PATCH 14/16] - fix livedata - method call on server blocks in a fiber way --- packages/ddp-client/test/livedata_tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index 97e107a184..5fa451263c 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -1044,7 +1044,8 @@ if (Meteor.isServer) { async function(test, expect) { const self = this; if (self.conn.status().connected) { - test.equal(await self.conn.callAsync('s2s', 'foo'), 's2s foo'); + const callResult = await self.conn.callAsync('s2s', 'foo'); + test.equal(await callResult.result, 's2s foo'); } }, ]); From f7355082b6951257d87b0a4301dfd4c5d840b1ed Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 8 Feb 2023 13:56:44 -0400 Subject: [PATCH 15/16] - fix livedata - publish multiple cursors --- packages/ddp-server/livedata_server.js | 43 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index 08294e3d38..09da2ccef8 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -360,12 +360,20 @@ var Session = function (server, version, socket, options) { }; Object.assign(Session.prototype, { - + _checkPublishPromiseBeforeSend(f) { + if (!this._publishCursorPromise) { + f(); + return; + } + this._publishCursorPromise.finally(() => f()); + }, sendReady: function (subscriptionIds) { var self = this; - if (self._isSending) - self.send({msg: "ready", subs: subscriptionIds}); - else { + if (self._isSending) { + this._checkPublishPromiseBeforeSend(() => { + self.send({msg: "ready", subs: subscriptionIds}); + }); + } else { _.each(subscriptionIds, function (subscriptionId) { self._pendingReady.push(subscriptionId); }); @@ -379,13 +387,9 @@ Object.assign(Session.prototype, { sendAdded(collectionName, id, fields) { if (this._canSend(collectionName)) { - if (!this._publishCursorPromise) { + this._checkPublishPromiseBeforeSend(() => { this.send({ msg: 'added', collection: collectionName, id, fields }); - return; - } - this._publishCursorPromise.finally(() => - this.send({ msg: 'added', collection: collectionName, id, fields }) - ); + }); } }, @@ -394,18 +398,23 @@ Object.assign(Session.prototype, { return; if (this._canSend(collectionName)) { - this.send({ - msg: "changed", - collection: collectionName, - id, - fields + this._checkPublishPromiseBeforeSend(() => { + this.send({ + msg: "changed", + collection: collectionName, + id, + fields + }); }); } }, sendRemoved(collectionName, id) { - if (this._canSend(collectionName)) - this.send({msg: "removed", collection: collectionName, id}); + if (this._canSend(collectionName)) { + this._checkPublishPromiseBeforeSend(() => { + this.send({msg: "removed", collection: collectionName, id}); + }); + } }, getSendCallbacks: function () { From d9e20a0f0439ea947a74ee3210eb5681693a1ca0 Mon Sep 17 00:00:00 2001 From: denihs Date: Wed, 8 Feb 2023 14:33:43 -0400 Subject: [PATCH 16/16] - ddp-server tests adjusts --- packages/ddp-client/common/livedata_connection.js | 2 +- packages/ddp-client/test/livedata_tests.js | 2 +- packages/ddp-server/livedata_server_tests.js | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ddp-client/common/livedata_connection.js b/packages/ddp-client/common/livedata_connection.js index 9f13c930c4..d1d56f46ad 100644 --- a/packages/ddp-client/common/livedata_connection.js +++ b/packages/ddp-client/common/livedata_connection.js @@ -864,7 +864,7 @@ export class Connection { // block waiting for the result. if (future) { return { - result: future, + stubValuePromise: future, }; } return options.returnStubValue ? stubReturnValue : undefined; diff --git a/packages/ddp-client/test/livedata_tests.js b/packages/ddp-client/test/livedata_tests.js index 5fa451263c..83aab47515 100644 --- a/packages/ddp-client/test/livedata_tests.js +++ b/packages/ddp-client/test/livedata_tests.js @@ -1045,7 +1045,7 @@ if (Meteor.isServer) { const self = this; if (self.conn.status().connected) { const callResult = await self.conn.callAsync('s2s', 'foo'); - test.equal(await callResult.result, 's2s foo'); + test.equal(await callResult.stubValuePromise, 's2s foo'); } }, ]); diff --git a/packages/ddp-server/livedata_server_tests.js b/packages/ddp-server/livedata_server_tests.js index aa4cc6f845..930287100b 100644 --- a/packages/ddp-server/livedata_server_tests.js +++ b/packages/ddp-server/livedata_server_tests.js @@ -127,8 +127,9 @@ Tinytest.addAsync( makeTestConnection( test, function (clientConn, serverConn) { - clientConn.callAsync('livedata_server_test_inner').then(res => { - test.equal(res, serverConn.id); + clientConn.callAsync('livedata_server_test_inner').then(async res => { + const r = await res.stubValuePromise; + test.equal(r, serverConn.id); clientConn.disconnect(); onComplete(); }); @@ -144,9 +145,10 @@ Tinytest.addAsync( function (test, onComplete) { makeTestConnection( test, - function (clientConn, serverConn) { - clientConn.callAsync('livedata_server_test_outer').then(res => { - test.equal(res, serverConn.id); + function(clientConn, serverConn) { + clientConn.callAsync('livedata_server_test_outer').then(async res => { + const r = await res.stubValuePromise; + test.equal(r, serverConn.id); clientConn.disconnect(); onComplete(); }); @@ -351,7 +353,7 @@ Tinytest.addAsync( (test, onComplete) => makeTestConnection(test, async (clientConn, serverConn) => { const testResolvedPromiseResult = await clientConn.callAsync("testResolvedPromise", "clientConn.call"); test.equal( - testResolvedPromiseResult, + await testResolvedPromiseResult.stubValuePromise, "clientConn.call after waiting" );