diff --git a/packages/accounts-base/accounts-base.d.ts b/packages/accounts-base/accounts-base.d.ts index ce91667fa02..0ab68de5764 100644 --- a/packages/accounts-base/accounts-base.d.ts +++ b/packages/accounts-base/accounts-base.d.ts @@ -47,7 +47,7 @@ export namespace Accounts { profile?: Meteor.UserProfile | undefined; }, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): string; + ): Promise; function createUserAsync( options: { @@ -113,23 +113,23 @@ export namespace Accounts { oldPassword: string, newPassword: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function forgotPassword( options: { email?: string | undefined }, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function resetPassword( token: string, newPassword: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function verifyEmail( token: string, callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function onEmailVerificationLink(callback: Function): void; @@ -143,11 +143,11 @@ export namespace Accounts { function logout( callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; function logoutOtherClients( callback?: (error?: Error | Meteor.Error | Meteor.TypedError) => void - ): void; + ): Promise; type PasswordSignupField = 'USERNAME_AND_EMAIL' | 'USERNAME_AND_OPTIONAL_EMAIL' | 'USERNAME_ONLY' | 'EMAIL_ONLY'; type PasswordlessSignupField = 'USERNAME_AND_EMAIL' | 'EMAIL_ONLY'; @@ -179,9 +179,9 @@ export interface EmailTemplates { export namespace Accounts { var emailTemplates: EmailTemplates; - function addEmail(userId: string, newEmail: string, verified?: boolean): void; + function addEmailAsync(userId: string, newEmail: string, verified?: boolean): Promise; - function removeEmail(userId: string, email: string): void; + function removeEmail(userId: string, email: string): Promise; function onCreateUser( func: (options: { profile?: {} | undefined }, user: Meteor.User) => void @@ -190,35 +190,35 @@ export namespace Accounts { function findUserByEmail( email: string, options?: { fields?: Mongo.FieldSpecifier | undefined } - ): Meteor.User | null | undefined; + ): Promise; function findUserByUsername( username: string, options?: { fields?: Mongo.FieldSpecifier | undefined } - ): Meteor.User | null | undefined; + ): Promise; function sendEnrollmentEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; function sendResetPasswordEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; function sendVerificationEmail( userId: string, email?: string, extraTokenData?: Record, extraParams?: Record - ): void; + ): Promise; - function setUsername(userId: string, newUsername: string): void; + function setUsername(userId: string, newUsername: string): Promise; function setPasswordAsync( userId: string, diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index cf8f573b1d3..64d03b1ced4 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -1265,15 +1265,14 @@ export class AccountsServer extends AccountsCommon { // that would give a lot of power to an attacker with a stolen login // token and the ability to crash the server. Meteor.startup(async () => { - const users = await this.users.find({ + await this.users.find({ "services.resume.haveLoginTokensToDelete": true }, { fields: { "services.resume.loginTokensToDelete": 1 } - }) - users.forEach(user => { - this._deleteSavedTokensForUser( + }).forEachAsync(user => { + return this._deleteSavedTokensForUser( user._id, user.services.resume.loginTokensToDelete ) diff --git a/packages/minimongo/constants.js b/packages/minimongo/constants.js index ac72e9c9a83..72270311e82 100644 --- a/packages/minimongo/constants.js +++ b/packages/minimongo/constants.js @@ -5,7 +5,7 @@ export function getAsyncMethodName(method) { return `${method.replace('_', '')}Async`; } -export const ASYNC_COLLECTION_METHODS = [ +export const COLLECTION_METHODS = [ '_createCappedCollection', 'dropCollection', 'dropIndex', @@ -90,7 +90,7 @@ export const ASYNC_COLLECTION_METHODS = [ 'upsert', ]; -export const ASYNC_CURSOR_METHODS = [ +export const CURSOR_METHODS = [ /** * @deprecated in 2.9 * @summary Returns the number of documents that match a query. This method is diff --git a/packages/minimongo/cursor.js b/packages/minimongo/cursor.js index bfdecc49f8e..f7f5e7e483f 100644 --- a/packages/minimongo/cursor.js +++ b/packages/minimongo/cursor.js @@ -1,6 +1,6 @@ import LocalCollection from './local_collection.js'; import { hasOwn } from './common.js'; -import { ASYNC_CURSOR_METHODS, getAsyncMethodName } from './constants'; +import { CURSOR_METHODS, getAsyncMethodName } from './constants'; // Cursor: a specification for a particular subset of documents, w/ a defined // order, limit, and offset. creating a Cursor with LocalCollection.find(), @@ -558,7 +558,7 @@ export default class Cursor { } // Implements async version of cursor methods to keep collections isomorphic -ASYNC_CURSOR_METHODS.forEach(method => { +CURSOR_METHODS.forEach(method => { const asyncName = getAsyncMethodName(method); Cursor.prototype[asyncName] = function(...args) { try { diff --git a/packages/mongo/collection.js b/packages/mongo/collection.js index 06bc59ae356..2a99f0916c2 100644 --- a/packages/mongo/collection.js +++ b/packages/mongo/collection.js @@ -1,10 +1,5 @@ // options.connection, if given, is a LivedataClient or LivedataServer // XXX presently there is no way to destroy/clean up a Collection -import { - ASYNC_COLLECTION_METHODS, - getAsyncMethodName, -} from 'meteor/minimongo/constants'; - import { normalizeProjection } from "./mongo_utils"; /** diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index a0e9e7fecf0..1ebd8dbdb2d 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -191,12 +191,12 @@ Tinytest.add('collection - calling find with a valid readPreference', // as the cursor options are now private // You can check on abstract_cursor.ts the exposed public getters test.equal( - defaultCursor._synchronousCursor._dbCursor.readPreference + defaultCursor._asynchronousCursor._dbCursor.readPreference .mode, defaultReadPreference ); test.equal( - customCursor._synchronousCursor._dbCursor.readPreference.mode, + customCursor._asynchronousCursor._dbCursor.readPreference.mode, customReadPreference ); } @@ -214,7 +214,7 @@ Tinytest.addAsync('collection - calling find with an invalid readPreference', ); await test.throwsAsync(async function() { - // Trigger the creation of _synchronousCursor + // Trigger the creation of _asynchronousCursor await cursor.countAsync(); }, `Invalid read preference mode "${invalidReadPreference}"`); } diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 69d9b86f7b4..860d3268ee4 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -18,7 +18,7 @@ const util = require("util"); var MongoDB = NpmModuleMongodb; import { DocFetcher } from "./doc_fetcher.js"; import { - ASYNC_CURSOR_METHODS, + CURSOR_METHODS, CLIENT_ONLY_METHODS, getAsyncMethodName } from "meteor/minimongo/constants"; @@ -200,7 +200,7 @@ MongoConnection = function (url, options) { name: 'Meteor', version: Meteor.release } - + self.client = new MongoDB.MongoClient(url, mongoOptions); self.db = self.client.db(); @@ -770,7 +770,7 @@ MongoConnection.prototype.findOneAsync = async function (collection_name, select options = options || {}; options.limit = 1; - const results = await self.find(collection_name, selector, options).fetch(); + const results = await self.find(collection_name, selector, options).fetchAsync(); return results[0]; }; @@ -836,13 +836,13 @@ CLIENT_ONLY_METHODS.forEach(function (m) { // not affect observeChanges output (eg, options.transform functions are not // stringifiable but do not affect observeChanges). // -// SynchronousCursor is a wrapper around a MongoDB cursor -// which includes fully-synchronous versions of forEach, etc. +// AsynchronousCursor is a wrapper around a MongoDB cursor +// which includes fully-asynchronous versions of forEach, etc. // // Cursor is the cursor object returned from find(), which implements the // documented Mongo.Collection cursor API. It wraps a CursorDescription and a -// SynchronousCursor (lazily: it doesn't contact Mongo until you call a method -// like fetch or forEach on it). +// AsynchronousCursor (lazily: it doesn't contact Mongo until you call a method +// like fetch/fetchAsync or forEach/forEachAsync on it). // // ObserveHandle is the "observe handle" returned from observeChanges. It has a // reference to an ObserveMultiplexer. @@ -871,30 +871,9 @@ Cursor = function (mongo, cursorDescription) { self._mongo = mongo; self._cursorDescription = cursorDescription; - self._synchronousCursor = null; + self._asynchronousCursor = null; }; -function setupSynchronousCursor(cursor, method) { - // You can only observe a tailable cursor. - if (cursor._cursorDescription.options.tailable) - throw new Error('Cannot call ' + method + ' on a tailable cursor'); - - if (!cursor._synchronousCursor) { - cursor._synchronousCursor = cursor._mongo._createSynchronousCursor( - cursor._cursorDescription, - { - // Make sure that the "cursor" argument to forEach/map callbacks is the - // Cursor, not the SynchronousCursor. - selfForIteration: cursor, - useTransform: true, - } - ); - } - - return cursor._synchronousCursor; -} - - Cursor.prototype.countAsync = async function () { const collection = this._mongo.rawCollection(this._cursorDescription.collectionName); return await collection.countDocuments( @@ -909,30 +888,29 @@ Cursor.prototype.count = function () { ); }; -[...ASYNC_CURSOR_METHODS, Symbol.iterator, Symbol.asyncIterator].forEach(methodName => { - // count is handled specially since we don't want to create a cursor. - // it is still included in ASYNC_CURSOR_METHODS because we still want an async version of it to exist. - if (methodName === 'count') { - return - } - Cursor.prototype[methodName] = function (...args) { - const cursor = setupSynchronousCursor(this, methodName); - return cursor[methodName](...args); - }; - - // These methods are handled separately. - if (methodName === Symbol.iterator || methodName === Symbol.asyncIterator) { - return; - } - - const methodNameAsync = getAsyncMethodName(methodName); - Cursor.prototype[methodNameAsync] = function (...args) { - try { - return Promise.resolve(this[methodName](...args)); - } catch (error) { - return Promise.reject(error); +// We don't handle the count method here. +[...CURSOR_METHODS, Symbol.asyncIterator].filter(method => method !== CURSOR_METHODS[0]).forEach(function(method) { + Cursor.prototype[method] = function asynchronousCursorMethod () { + // You can only observe a tailable cursor. + if (this._cursorDescription.options.tailable) + throw new Error("Cannot call " + method + " on a tailable cursor"); + + if (!this._asynchronousCursor) { + this._asynchronousCursor = this._mongo._createAsynchronousCursor( + this._cursorDescription, { + // Make sure that the "self" argument to forEach/map callbacks is the + // Cursor, not the AsynchronousCursor. + selfForIteration: this, + useTransform: true + }); } + + return this._asynchronousCursor[method].apply( + this._asynchronousCursor, arguments); }; + if (method !== Symbol.asyncIterator) { + Cursor.prototype[getAsyncMethodName(method)] = Cursor.prototype[method]; + } }); Cursor.prototype.getTransform = function () { @@ -994,7 +972,7 @@ Cursor.prototype.observeChangesAsync = async function (callbacks, options = {}) return this.observeChanges(callbacks, options); }; -MongoConnection.prototype._createSynchronousCursor = function( +MongoConnection.prototype._createAsynchronousCursor = function( cursorDescription, options = {}) { var self = this; const { selfForIteration, useTransform } = options; @@ -1084,7 +1062,7 @@ class AsynchronousCursor { // the Mongo->Meteor type replacement). async _rawNextObjectPromise() { try { - return this._dbCursor.next(); + return (await this._dbCursor.next()); } catch (e) { console.error(e); } @@ -1099,7 +1077,7 @@ class AsynchronousCursor { if (!doc) return null; doc = replaceTypes(doc, replaceMongoAtomWithMeteor); - if (!this._cursorDescription.options.tailable && _.has(doc, '_id')) { + if (!this._cursorDescription.options.tailable && has(doc, '_id')) { // Did Mongo give us duplicate documents in the same cursor? If so, // ignore this one. (Do this before the transform, since transform might // return some unrelated value.) We don't do this for tailable cursors, @@ -1174,7 +1152,7 @@ class AsynchronousCursor { } fetch() { - return this.map(_.identity); + return this.map(identity); } /** @@ -1201,204 +1179,6 @@ class AsynchronousCursor { } } -var SynchronousCursor = function (dbCursor, cursorDescription, options, collection) { - var self = this; - const { selfForIteration, useTransform } = options; - options = { selfForIteration, useTransform }; - - self._dbCursor = dbCursor; - self._cursorDescription = cursorDescription; - // The "self" argument passed to forEach/map callbacks. If we're wrapped - // inside a user-visible Cursor, we want to provide the outer cursor! - self._selfForIteration = options.selfForIteration || self; - if (options.useTransform && cursorDescription.options.transform) { - self._transform = LocalCollection.wrapTransform( - cursorDescription.options.transform); - } else { - self._transform = null; - } - - self._synchronousCount = Future.wrap( - collection.countDocuments.bind( - collection, - replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), - replaceTypes(cursorDescription.options, replaceMeteorAtomWithMongo), - ) - ); - self._visitedIds = new LocalCollection._IdMap; -}; - -Object.assign(SynchronousCursor.prototype, { - // Returns a Promise for the next object from the underlying cursor (before - // the Mongo->Meteor type replacement). - _rawNextObjectPromise: function () { - const self = this; - return new Promise((resolve, reject) => { - self._dbCursor.next((err, doc) => { - if (err) { - reject(err); - } else { - resolve(doc); - } - }); - }); - }, - - // Returns a Promise for the next object from the cursor, skipping those whose - // IDs we've already seen and replacing Mongo atoms with Meteor atoms. - _nextObjectPromise: async function () { - var self = this; - - while (true) { - var doc = await self._rawNextObjectPromise(); - - if (!doc) return null; - doc = replaceTypes(doc, replaceMongoAtomWithMeteor); - - if (!self._cursorDescription.options.tailable && has(doc, '_id')) { - // Did Mongo give us duplicate documents in the same cursor? If so, - // ignore this one. (Do this before the transform, since transform might - // return some unrelated value.) We don't do this for tailable cursors, - // because we want to maintain O(1) memory usage. And if there isn't _id - // for some reason (maybe it's the oplog), then we don't do this either. - // (Be careful to do this for falsey but existing _id, though.) - if (self._visitedIds.has(doc._id)) continue; - self._visitedIds.set(doc._id, true); - } - - if (self._transform) - doc = self._transform(doc); - - return doc; - } - }, - - // Returns a promise which is resolved with the next object (like with - // _nextObjectPromise) or rejected if the cursor doesn't return within - // timeoutMS ms. - _nextObjectPromiseWithTimeout: function (timeoutMS) { - const self = this; - if (!timeoutMS) { - return self._nextObjectPromise(); - } - const nextObjectPromise = self._nextObjectPromise(); - const timeoutErr = new Error('Client-side timeout waiting for next object'); - const timeoutPromise = new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(timeoutErr); - }, timeoutMS); - }); - return Promise.race([nextObjectPromise, timeoutPromise]) - .catch((err) => { - if (err === timeoutErr) { - self.close(); - } - throw err; - }); - }, - - _nextObject: function () { - var self = this; - return self._nextObjectPromise().await(); - }, - - forEach: function (callback, thisArg) { - var self = this; - const wrappedFn = Meteor.wrapFn(callback); - - // Get back to the beginning. - self._rewind(); - - // We implement the loop ourself instead of using self._dbCursor.each, - // because "each" will call its callback outside of a fiber which makes it - // much more complex to make this function synchronous. - var index = 0; - while (true) { - var doc = self._nextObject(); - if (!doc) return; - wrappedFn.call(thisArg, doc, index++, self._selfForIteration); - } - }, - - // XXX Allow overlapping callback executions if callback yields. - map: function (callback, thisArg) { - var self = this; - const wrappedFn = Meteor.wrapFn(callback); - var res = []; - self.forEach(function (doc, index) { - res.push(wrappedFn.call(thisArg, doc, index, self._selfForIteration)); - }); - return res; - }, - - _rewind: function () { - var self = this; - - // known to be synchronous - self._dbCursor.rewind(); - - self._visitedIds = new LocalCollection._IdMap; - }, - - // Mostly usable for tailable cursors. - close: function () { - var self = this; - - self._dbCursor.close(); - }, - - fetch: function () { - var self = this; - return self.map(identity); - }, - - count: function () { - var self = this; - return self._synchronousCount().wait(); - }, - - // This method is NOT wrapped in Cursor. - getRawObjects: function (ordered) { - var self = this; - if (ordered) { - return self.fetch(); - } else { - var results = new LocalCollection._IdMap; - self.forEach(function (doc) { - results.set(doc._id, doc); - }); - return results; - } - } -}); - -SynchronousCursor.prototype[Symbol.iterator] = function () { - var self = this; - - // Get back to the beginning. - self._rewind(); - - return { - next() { - const doc = self._nextObject(); - return doc ? { - value: doc - } : { - done: true - }; - } - }; -}; - -SynchronousCursor.prototype[Symbol.asyncIterator] = function () { - const syncResult = this[Symbol.iterator](); - return { - async next() { - return Promise.resolve(syncResult.next()); - } - }; -} - // Tails the cursor described by cursorDescription, most likely on the // oplog. Calls docCallback with each document found. Ignores errors and just // restarts the tail on error. @@ -1410,7 +1190,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo if (!cursorDescription.options.tailable) throw new Error("Can only tail a tailable cursor"); - var cursor = self._createSynchronousCursor(cursorDescription); + var cursor = self._createAsynchronousCursor(cursorDescription); var stopped = false; var lastTS; @@ -1445,7 +1225,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo if (lastTS) { newSelector.ts = {$gt: lastTS}; } - cursor = self._createSynchronousCursor(new CursorDescription( + cursor = self._createAsynchronousCursor(new CursorDescription( cursorDescription.collectionName, newSelector, cursorDescription.options)); @@ -1634,9 +1414,9 @@ forEachTrigger = async function (cursorDescription, triggerCallback) { cursorDescription.selector); if (specificIds) { for (const id of specificIds) { - await triggerCallback(_.extend({id: id}, key)); + await triggerCallback(Object.assign({id: id}, key)); } - await triggerCallback(_.extend({dropCollection: true, id: null}, key)); + await triggerCallback(Object.assign({dropCollection: true, id: null}, key)); } else { await triggerCallback(key); } diff --git a/packages/mongo/observe_changes_tests.js b/packages/mongo/observe_changes_tests.js index 22235fdfade..076fec49023 100644 --- a/packages/mongo/observe_changes_tests.js +++ b/packages/mongo/observe_changes_tests.js @@ -469,7 +469,7 @@ const getPromiseAndResolver = () => { if (Meteor.isServer) { testAsyncMulti("observeChanges - tailable", [ - async function (test, expect) { + async function (test) { var self = this; var collName = 'cap_' + Random.id(); var coll = new Mongo.Collection(collName); diff --git a/packages/mongo/polling_observe_driver.js b/packages/mongo/polling_observe_driver.js index 6b4172cd95d..9c31943381b 100644 --- a/packages/mongo/polling_observe_driver.js +++ b/packages/mongo/polling_observe_driver.js @@ -14,7 +14,7 @@ PollingObserveDriver = function (options) { self._stopCallbacks = []; self._stopped = false; - self._cursor = self._mongoHandle._createSynchronousCursor( + self._cursor = self._mongoHandle._createAsynchronousCursor( self._cursorDescription); // previous results snapshot. on each poll cycle, diffs against @@ -40,7 +40,7 @@ PollingObserveDriver = function (options) { // XXX figure out if we still need a queue self._taskQueue = new Meteor._AsynchronousQueue(); - + }; _.extend(PollingObserveDriver.prototype, { @@ -63,7 +63,7 @@ _.extend(PollingObserveDriver.prototype, { } ); self._stopCallbacks.push(async function () { await listenersHandle.stop(); }); - + // every once and a while, poll even if we don't think we're dirty, for // eventual consistency with database writes from outside the Meteor // universe. @@ -84,7 +84,7 @@ _.extend(PollingObserveDriver.prototype, { Meteor.clearInterval(intervalHandle); }); } - + // Make sure we actually poll soon! await this._unthrottledEnsurePollIsScheduled(); diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo/remote_collection_driver.js index d8c794abffc..79b647045ab 100644 --- a/packages/mongo/remote_collection_driver.js +++ b/packages/mongo/remote_collection_driver.js @@ -1,6 +1,6 @@ import once from 'lodash.once'; import { - ASYNC_COLLECTION_METHODS, + COLLECTION_METHODS, getAsyncMethodName, CLIENT_ONLY_METHODS } from "meteor/minimongo/constants"; @@ -35,7 +35,7 @@ Object.assign(MongoInternals.RemoteCollectionDriver.prototype, { REMOTE_COLLECTION_METHODS.forEach(function (m) { ret[m] = self.mongo[m].bind(self.mongo, name); - if (!ASYNC_COLLECTION_METHODS.includes(m)) return; + if (!COLLECTION_METHODS.includes(m)) return; const asyncMethodName = getAsyncMethodName(m); ret[asyncMethodName] = function (...args) { try { diff --git a/packages/service-configuration/package.js b/packages/service-configuration/package.js index 43e82a48883..611087dccd5 100644 --- a/packages/service-configuration/package.js +++ b/packages/service-configuration/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'Manage the configuration for third-party services', - version: '1.3.5', + version: '1.3.6', }); Package.onUse(function(api) { diff --git a/packages/service-configuration/service_configuration_server.js b/packages/service-configuration/service_configuration_server.js index 087f8cd6abc..228c2d2b585 100644 --- a/packages/service-configuration/service_configuration_server.js +++ b/packages/service-configuration/service_configuration_server.js @@ -1,41 +1,41 @@ import { Meteor } from 'meteor/meteor'; -// Only one configuration should ever exist for each service. -// A unique index helps avoid various race conditions which could -// otherwise lead to an inconsistent database state (when there are multiple -// configurations for a single service, which configuration is correct?) -try { - ServiceConfiguration.configurations.createIndexAsync( - { service: 1 }, - { unique: true } - ); -} catch (err) { - console.error( - 'The service-configuration package persists configuration in the ' + - 'meteor_accounts_loginServiceConfiguration collection in MongoDB. As ' + - 'each service should have exactly one configuration, Meteor ' + - 'automatically creates a MongoDB index with a unique constraint on the ' + - ' meteor_accounts_loginServiceConfiguration collection. The ' + - 'createIndex command which creates that index is failing.\n\n' + - 'Meteor versions before 1.0.4 did not create this index. If you recently ' + - 'upgraded and are seeing this error message for the first time, please ' + - 'check your meteor_accounts_loginServiceConfiguration collection for ' + - 'multiple configuration entries for the same service and delete ' + - 'configuration entries until there is no more than one configuration ' + - 'entry per service.\n\n' + - 'If the meteor_accounts_loginServiceConfiguration collection looks ' + - 'fine, the createIndex command is failing for some other reason.\n\n' + - 'For more information on this history of this issue, please see ' + - 'https://github.com/meteor/meteor/pull/3514.\n' - ); - throw err; -} +Meteor.startup(async () => { + // Only one configuration should ever exist for each service. + // A unique index helps avoid various race conditions which could + // otherwise lead to an inconsistent database state (when there are multiple + // configurations for a single service, which configuration is correct?) + try { + await ServiceConfiguration.configurations.createIndexAsync( + { service: 1 }, + { unique: true } + ); + } catch (err) { + console.error( + 'The service-configuration package persists configuration in the ' + + 'meteor_accounts_loginServiceConfiguration collection in MongoDB. As ' + + 'each service should have exactly one configuration, Meteor ' + + 'automatically creates a MongoDB index with a unique constraint on the ' + + ' meteor_accounts_loginServiceConfiguration collection. The ' + + 'createIndex command which creates that index is failing.\n\n' + + 'Meteor versions before 1.0.4 did not create this index. If you recently ' + + 'upgraded and are seeing this error message for the first time, please ' + + 'check your meteor_accounts_loginServiceConfiguration collection for ' + + 'multiple configuration entries for the same service and delete ' + + 'configuration entries until there is no more than one configuration ' + + 'entry per service.\n\n' + + 'If the meteor_accounts_loginServiceConfiguration collection looks ' + + 'fine, the createIndex command is failing for some other reason.\n\n' + + 'For more information on this history of this issue, please see ' + + 'https://github.com/meteor/meteor/pull/3514.\n' + ); + throw err; + } -Meteor.startup(() => { const settings = Meteor.settings?.packages?.['service-configuration']; if (!settings) return; for (const key of Object.keys(settings)) { - ServiceConfiguration.configurations.upsertAsync( + await ServiceConfiguration.configurations.upsertAsync( { service: key }, { $set: settings[key], diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 408654581ba..2b2834e099f 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -2951,8 +2951,9 @@ main.registerCommand({ versions = await catalog.official.getSortedVersions(name); } + var conn; try { - var conn = await packageClient.loggedInPackagesConnection(); + conn = await packageClient.loggedInPackagesConnection(); } catch (err) { packageClient.handlePackageServerConnectionError(err); return 1;