From ca8219b1caf156ee2f108e8cfd347e26d40bb4c8 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Tue, 29 Oct 2024 08:44:25 -0400 Subject: [PATCH] refactor to typescript --- packages/mongo/package.js | 2 +- packages/mongo/remote_collection_driver.js | 85 -------------- packages/mongo/remote_collection_driver.ts | 130 +++++++++++++++++++++ 3 files changed, 131 insertions(+), 86 deletions(-) delete mode 100644 packages/mongo/remote_collection_driver.js create mode 100644 packages/mongo/remote_collection_driver.ts diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 8b10c46813..e4a855421d 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -100,7 +100,7 @@ Package.onUse(function (api) { "server" ); api.addFiles("local_collection_driver.js", ["client", "server"]); - api.addFiles("remote_collection_driver.js", "server"); + api.addFiles("remote_collection_driver.ts", "server"); api.addFiles("collection.js", ["client", "server"]); api.addFiles("connection_options.js", "server"); api.addAssets("mongo.d.ts", "server"); diff --git a/packages/mongo/remote_collection_driver.js b/packages/mongo/remote_collection_driver.js deleted file mode 100644 index 2b1131d14f..0000000000 --- a/packages/mongo/remote_collection_driver.js +++ /dev/null @@ -1,85 +0,0 @@ -import once from 'lodash.once'; -import { - ASYNC_COLLECTION_METHODS, - getAsyncMethodName, - CLIENT_ONLY_METHODS -} from "meteor/minimongo/constants"; -import { MongoConnection } from './mongo_connection'; - -MongoInternals.RemoteCollectionDriver = function (mongo_url, options) { - this.mongo = new MongoConnection(mongo_url, options); -}; - -const REMOTE_COLLECTION_METHODS = [ - 'createCappedCollectionAsync', - 'dropIndexAsync', - 'ensureIndexAsync', - 'createIndexAsync', - 'countDocuments', - 'dropCollectionAsync', - 'estimatedDocumentCount', - 'find', - 'findOneAsync', - 'insertAsync', - 'rawCollection', - 'removeAsync', - 'updateAsync', - 'upsertAsync', -]; - -Object.assign(MongoInternals.RemoteCollectionDriver.prototype, { - open: function (name) { - var self = this; - var ret = {}; - - REMOTE_COLLECTION_METHODS.forEach(function (m) { - ret[m] = self.mongo[m].bind(self.mongo, name); - - if (!ASYNC_COLLECTION_METHODS.includes(m)) return; - const asyncMethodName = getAsyncMethodName(m); - ret[asyncMethodName] = function (...args) { - return ret[m](...args); - }; - }); - - CLIENT_ONLY_METHODS.forEach(function (m) { - ret[m] = function (...args) { - throw new Error( - `${m} is not available on the server. Please use ${getAsyncMethodName( - m - )}() instead.` - ); - }; - }); - return ret; - }, -}); - - -// Create the singleton RemoteCollectionDriver only on demand, so we -// only require Mongo configuration if it's actually used (eg, not if -// you're only trying to receive data from a remote DDP server.) -MongoInternals.defaultRemoteCollectionDriver = once(function () { - var connectionOptions = {}; - - var mongoUrl = process.env.MONGO_URL; - - if (process.env.MONGO_OPLOG_URL) { - connectionOptions.oplogUrl = process.env.MONGO_OPLOG_URL; - } - - if (! mongoUrl) - throw new Error("MONGO_URL must be set in environment"); - - const driver = new MongoInternals.RemoteCollectionDriver(mongoUrl, connectionOptions); - // As many deployment tools, including Meteor Up, send requests to the app in - // order to confirm that the deployment finished successfully, it's required - // to know about a database connection problem before the app starts. Doing so - // in a `Meteor.startup` is fine, as the `WebApp` handles requests only after - // all are finished. - Meteor.startup(async () => { - await driver.mongo.client.connect(); - }); - - return driver; -}); diff --git a/packages/mongo/remote_collection_driver.ts b/packages/mongo/remote_collection_driver.ts new file mode 100644 index 0000000000..636b93522f --- /dev/null +++ b/packages/mongo/remote_collection_driver.ts @@ -0,0 +1,130 @@ +import once from 'lodash.once'; +import { + ASYNC_COLLECTION_METHODS, + getAsyncMethodName, + CLIENT_ONLY_METHODS +} from "meteor/minimongo/constants"; +import { MongoConnection } from './mongo_connection'; + +// Define interfaces and types +interface IConnectionOptions { + oplogUrl?: string; + [key: string]: unknown; // Changed from 'any' to 'unknown' for better type safety +} + +interface IMongoInternals { + RemoteCollectionDriver: typeof RemoteCollectionDriver; + defaultRemoteCollectionDriver: () => RemoteCollectionDriver; +} + +// More specific typing for collection methods +type MongoMethodFunction = (...args: unknown[]) => unknown; +interface ICollectionMethods { + [key: string]: MongoMethodFunction; +} + +// Type for MongoConnection +interface IMongoClient { + connect: () => Promise; +} + +interface IMongoConnection { + client: IMongoClient; + [key: string]: MongoMethodFunction | IMongoClient; +} + +declare global { + namespace NodeJS { + interface ProcessEnv { + MONGO_URL: string; + MONGO_OPLOG_URL?: string; + } + } + + const MongoInternals: IMongoInternals; + const Meteor: { + startup: (callback: () => Promise) => void; + }; +} + +class RemoteCollectionDriver { + private readonly mongo: MongoConnection; + + private static readonly REMOTE_COLLECTION_METHODS = [ + 'createCappedCollectionAsync', + 'dropIndexAsync', + 'ensureIndexAsync', + 'createIndexAsync', + 'countDocuments', + 'dropCollectionAsync', + 'estimatedDocumentCount', + 'find', + 'findOneAsync', + 'insertAsync', + 'rawCollection', + 'removeAsync', + 'updateAsync', + 'upsertAsync', + ] as const; + + constructor(mongoUrl: string, options: IConnectionOptions) { + this.mongo = new MongoConnection(mongoUrl, options); + } + + public open(name: string): ICollectionMethods { + const ret: ICollectionMethods = {}; + + // Handle remote collection methods + RemoteCollectionDriver.REMOTE_COLLECTION_METHODS.forEach((method) => { + // Type assertion needed because we know these methods exist on MongoConnection + const mongoMethod = this.mongo[method] as MongoMethodFunction; + ret[method] = mongoMethod.bind(this.mongo, name); + + if (!ASYNC_COLLECTION_METHODS.includes(method)) return; + + const asyncMethodName = getAsyncMethodName(method); + ret[asyncMethodName] = (...args: unknown[]) => ret[method](...args); + }); + + // Handle client-only methods + CLIENT_ONLY_METHODS.forEach((method) => { + ret[method] = (...args: unknown[]): never => { + throw new Error( + `${method} is not available on the server. Please use ${getAsyncMethodName( + method + )}() instead.` + ); + }; + }); + + return ret; + } +} + +// Assign the class to MongoInternals +MongoInternals.RemoteCollectionDriver = RemoteCollectionDriver; + +// Create the singleton RemoteCollectionDriver only on demand +MongoInternals.defaultRemoteCollectionDriver = once((): RemoteCollectionDriver => { + const connectionOptions: IConnectionOptions = {}; + const mongoUrl = process.env.MONGO_URL; + + if (!mongoUrl) { + throw new Error("MONGO_URL must be set in environment"); + } + + if (process.env.MONGO_OPLOG_URL) { + connectionOptions.oplogUrl = process.env.MONGO_OPLOG_URL; + } + + const driver = new RemoteCollectionDriver(mongoUrl, connectionOptions); + + // Initialize database connection on startup + Meteor.startup(async (): Promise => { + await driver.mongo.client.connect(); + }); + + return driver; +}); + +export { RemoteCollectionDriver, IConnectionOptions, ICollectionMethods }; \ No newline at end of file