From 6a931fa95d434cdc51eb10be3fc8f205a48d8c05 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 31 Mar 2025 15:49:53 +0100 Subject: [PATCH 01/21] Initial feature-flagged, draft new autocompleter. --- package-lock.json | 145 +++++++++++++++++- packages/cli-repl/src/mongosh-repl.ts | 84 +++++++++- packages/shell-api/package.json | 3 +- packages/shell-api/src/collection.ts | 24 +++ .../shell-api/src/shell-instance-state.ts | 76 +++++++++ 5 files changed, 324 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cfc6be4d..7506701f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12735,6 +12735,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "optional": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, "node_modules/cli-width": { "version": "3.0.0", "license": "ISC", @@ -12844,6 +12856,16 @@ "dev": true, "license": "MIT" }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/columnify": { "version": "1.6.0", "license": "MIT", @@ -18278,6 +18300,13 @@ "version": "2.0.0", "license": "ISC" }, + "node_modules/isnumber": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isnumber/-/isnumber-1.0.0.tgz", + "integrity": "sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw==", + "license": "MIT", + "optional": true + }, "node_modules/isobject": { "version": "3.0.1", "devOptional": true, @@ -21476,6 +21505,13 @@ "bson": "6.x" } }, + "node_modules/mongodb-ns": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.2.tgz", + "integrity": "sha512-gYJjEYG4v4a1WSXgUf81OBoBRlj+Z1SlnQVO392fC/4a1CN7CLWDITajZWPFTPh/yRozYk6sHHtZwZmQhodBEA==", + "license": "MIT", + "optional": true + }, "node_modules/mongodb-runner": { "version": "5.7.1", "dev": true, @@ -21530,6 +21566,93 @@ "node": ">=12" } }, + "node_modules/mongodb-schema": { + "version": "12.5.2", + "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.5.2.tgz", + "integrity": "sha512-gukbasp1udtpI79PDeGvbTGB14vO099Gm2/LNsJ+HunL1EqcdeWh+T6vBWy4fYmDvWx2SAeYrGLstp9aXruPJA==", + "license": "Apache-2.0", + "dependencies": { + "reservoir": "^0.1.2" + }, + "bin": { + "mongodb-schema": "bin/mongodb-schema" + }, + "optionalDependencies": { + "bson": "^6.7.0", + "cli-table": "^0.3.4", + "js-yaml": "^4.0.0", + "mongodb": "^6.6.1", + "mongodb-ns": "^2.4.0", + "numeral": "^2.0.6", + "progress": "^2.0.3", + "stats-lite": "^2.0.0", + "yargs": "^17.6.2" + } + }, + "node_modules/mongodb-schema/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "optional": true + }, + "node_modules/mongodb-schema/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-schema/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mongodb-schema/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-schema/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } + }, "node_modules/mongosh": { "resolved": "packages/mongosh", "link": true @@ -25139,6 +25262,12 @@ "dev": true, "license": "MIT" }, + "node_modules/reservoir": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reservoir/-/reservoir-0.1.2.tgz", + "integrity": "sha512-ysyw95gLBhMAzqIVrOHJ2yMrRQHAS+h97bS9r89Z7Ou10Jhl2k5KOsyjPqrxL+WfEanov0o5bAMVzQ7AKyENHA==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "license": "MIT", @@ -26363,6 +26492,19 @@ "dev": true, "license": "MIT" }, + "node_modules/stats-lite": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.2.0.tgz", + "integrity": "sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA==", + "license": "MIT", + "optional": true, + "dependencies": { + "isnumber": "~1.0.0" + }, + "engines": { + "node": ">=2.0.0" + } + }, "node_modules/statuses": { "version": "1.5.0", "dev": true, @@ -29925,7 +30067,8 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "2.9.1", "@mongosh/service-provider-core": "3.1.0", - "mongodb-redact": "^1.1.5" + "mongodb-redact": "^1.1.5", + "mongodb-schema": "^12.5.2" }, "devDependencies": { "@mongodb-js/eslint-config-mongosh": "^1.0.0", diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 4d9f9598e..6b3f843dd 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -49,6 +49,8 @@ import { Script, createContext, runInContext } from 'vm'; import { installPasteSupport } from './repl-paste-support'; import util from 'util'; +import { MongoDBAutocompleter } from '@mongodb-js/mongodb-ts-autocomplete'; + declare const __non_webpack_require__: any; /** @@ -131,6 +133,53 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; +function filterStartingWith({ + kind, + name, + trigger, +}: { + kind: string; + name: string; + trigger: string; +}): boolean { + name = name.toLocaleLowerCase(); + trigger = trigger.toLocaleLowerCase(); + + /* + 1. If the trigger was blank and the kind is not property/method filter out the + result. This way if you autocomplete db.test.find({ you don't get all the + global variables, just the known collection field names and mql but you can + still autocomplete global variables and functions if you type part of the + name. + 2. Don't filter out exact matches (where filter === name) so that we match the + behaviour of the node completer. + 3. Make sure the name starts with the trigger, otherwise it will return every + possible property/name that's available at that level. ie. all the "peer" + properties of the things that match. + */ + //console.log(name, kind); + // TODO: This can be improved further if we first see if there are any + // property/method kind completions and then just use those, then if there + // aren't return all completions. The reason is that db.test.find({m makes it + // through this filter and then you get all globals starting with m anyway. + // But to properly solve it we need more context. ie. if you're after { (ie. + // inside an object literal) and you're to the left of a : (or there isn't + // one) then you probably don't want globals regardless. If you're to the + // right of a : it is probably fine because you could be using a variable. + return ( + (trigger !== '' || kind === 'property' || kind === 'method') && + name.startsWith(trigger) + ); +} + +function transformAutocompleteResults( + line: string, + results: { result: string }[] +): [string[], string] | [string[], string, 'exclusive'] { + // TODO: actually use 'exclusive' when we should + return [results.map((result) => result.result), line]; +} + /** * An instance of a `mongosh` REPL, without any of the actual I/O. * Specifically, code called by this class should not do any @@ -430,10 +479,22 @@ class MongoshNodeRepl implements EvaluationListener { this.outputFinishString += installPasteSupport(repl); const origReplCompleter = promisify(repl.completer.bind(repl)); // repl.completer is callback-style - const mongoshCompleter = completer.bind( - null, - instanceState.getAutocompleteParameters() - ); + let newMongoshCompleter: MongoDBAutocompleter; + let oldMongoshCompleter: ( + line: string + ) => Promise<[string[], string, 'exclusive'] | [string[], string]>; + if (process.env.USE_NEW_AUTOCOMPLETE) { + const autocompletionContext = instanceState.getAutocompletionContext(); + newMongoshCompleter = new MongoDBAutocompleter({ + context: autocompletionContext, + autocompleterOptions: { filter: filterStartingWith }, + }); + } else { + oldMongoshCompleter = completer.bind( + null, + instanceState.getAutocompleteParameters() + ); + } const innerCompleter = async ( text: string ): Promise<[string[], string]> => { @@ -442,8 +503,19 @@ class MongoshNodeRepl implements EvaluationListener { [replResults, replOrig], [mongoshResults, , mongoshResultsExclusive], ] = await Promise.all([ - (async () => (await origReplCompleter(text)) || [[]])(), - (async () => await mongoshCompleter(text))(), + (async () => { + const nodeResults = (await origReplCompleter(text)) || [[]]; + return nodeResults; + })(), + (async () => { + if (process.env.USE_NEW_AUTOCOMPLETE) { + const results = await newMongoshCompleter.autocomplete(text); + const transformed = transformAutocompleteResults(text, results); + return transformed; + } else { + return oldMongoshCompleter(text); + } + })(), ]); this.bus.emit('mongosh:autocompletion-complete'); // For testing. diff --git a/packages/shell-api/package.json b/packages/shell-api/package.json index 231494be3..9e9ae0c4f 100644 --- a/packages/shell-api/package.json +++ b/packages/shell-api/package.json @@ -45,7 +45,8 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "2.9.1", "@mongosh/service-provider-core": "3.1.0", - "mongodb-redact": "^1.1.5" + "mongodb-redact": "^1.1.5", + "mongodb-schema": "^12.5.2" }, "devDependencies": { "@mongodb-js/eslint-config-mongosh": "^1.0.0", diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index 7b7432d77..94efc7a48 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -100,6 +100,7 @@ export default class Collection extends ShellApiWithMongoClass { _mongo: Mongo; _database: Database; _name: string; + _cachedSampleDocs: Document[] = []; constructor(mongo: Mongo, database: Database, name: string) { super(); this._mongo = mongo; @@ -2497,6 +2498,29 @@ export default class Collection extends ShellApiWithMongoClass { definition ); } + + async _getSampleDocs(): Promise { + this._cachedSampleDocs = await (await this.aggregate([])).toArray(); + return this._cachedSampleDocs; + } + + async _getSampleDocsForCompletion(): Promise { + return await Promise.race([ + (async () => { + return await this._getSampleDocs(); + })(), + (async () => { + // 200ms should be a good compromise between giving the server a chance + // to reply and responsiveness for human perception. It's not the end + // of the world if we end up using the cached results; usually, they + // are not going to differ from fresh ones, and even if they do, a + // subsequent autocompletion request will almost certainly have at least + // the new cached results. + await new Promise((resolve) => setTimeout(resolve, 200)?.unref?.()); + return this._cachedSampleDocs; + })(), + ]); + } } export type GetShardDistributionResult = { diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 0ce2d93fc..6d076f721 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import { CommonErrors, MongoshInvalidInputError } from '@mongosh/errors'; import type { AutoEncryptionOptions, @@ -37,6 +38,12 @@ import constructShellBson from './shell-bson'; import { Streams } from './streams'; import { ShellLog } from './shell-log'; +import type { + AutocompletionContext, + JSONSchema, +} from '@mongodb-js/mongodb-ts-autocomplete'; +import { analyzeDocuments } from 'mongodb-schema'; + /** * The subset of CLI options that is relevant for the shell API's behavior itself. */ @@ -135,6 +142,14 @@ export interface ShellPlugin { // eslint-disable-next-line no-control-regex const CONTROL_CHAR_REGEXP = /[\x00-\x1F\x7F-\x9F]/g; +function connectionIdFromURI(uri: string): string { + // turn the uri into something we can safely use as part of a "filename" + // inside autocomplete + const hash = crypto.createHash('sha256'); + hash.update(uri); + return hash.digest('hex'); +} + /** * Anything to do with the state of the shell API and API objects is stored here. * @@ -402,6 +417,67 @@ export default class ShellInstanceState { this.evaluationListener = listener; } + public getAutocompletionContext(): AutocompletionContext { + return { + currentDatabaseAndConnection: () => { + return { + connectionId: connectionIdFromURI(this.currentDb.getMongo().getURI()), + databaseName: this.currentDb.getName(), + }; + }, + databasesForConnection: async (): //connectionId: string, + Promise => { + try { + // TODO: use connectionId + const dbNames = + await this.currentDb._mongo._getDatabaseNamesForCompletion(); + return dbNames.filter((name) => !CONTROL_CHAR_REGEXP.test(name)); + } catch (err: any) { + if ( + err?.code === ShellApiErrors.NotConnected || + err?.codeName === 'Unauthorized' + ) { + return []; + } + throw err; + } + }, + collectionsForDatabase: async (): //connectionId: string, + //databaseName: string + Promise => { + try { + // TODO: use connectionId and databaseName + const collectionNames = + await this.currentDb._getCollectionNamesForCompletion(); + return collectionNames.filter( + (name) => !CONTROL_CHAR_REGEXP.test(name) + ); + } catch (err: any) { + if ( + err?.code === ShellApiErrors.NotConnected || + err?.codeName === 'Unauthorized' + ) { + return []; + } + throw err; + } + }, + schemaInformationForCollection: async ( + connectionKey: string, + databaseName: string, + collectionName: string + ): Promise => { + const docs = await this.currentDb + .getCollection(collectionName) + ._getSampleDocsForCompletion(); + const schemaAccessor = await analyzeDocuments(docs); + + const schema = await schemaAccessor.getMongoDBJsonSchema(); + return schema; + }, + }; + } + public getAutocompleteParameters(): AutocompleteParameters { return { topology: () => { From 88edcd3b69fcea117c7e1d8f1c02d4d243f9590f Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Wed, 2 Apr 2025 11:14:01 +0100 Subject: [PATCH 02/21] use the specified connection and db name --- .../shell-api/src/shell-instance-state.ts | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 6d076f721..9027b940b 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -417,6 +417,15 @@ export default class ShellInstanceState { this.evaluationListener = listener; } + public _getMongoByConnectionId(connectionId: string): Mongo { + for (const mongo of this.mongos) { + if (connectionIdFromURI(mongo.getURI()) === connectionId) { + return mongo; + } + } + throw new Error(`mongo with connection id ${connectionId} not found`); + } + public getAutocompletionContext(): AutocompletionContext { return { currentDatabaseAndConnection: () => { @@ -425,12 +434,12 @@ export default class ShellInstanceState { databaseName: this.currentDb.getName(), }; }, - databasesForConnection: async (): //connectionId: string, - Promise => { + databasesForConnection: async ( + connectionId: string + ): Promise => { + const mongo = this._getMongoByConnectionId(connectionId); try { - // TODO: use connectionId - const dbNames = - await this.currentDb._mongo._getDatabaseNamesForCompletion(); + const dbNames = await mongo._getDatabaseNamesForCompletion(); return dbNames.filter((name) => !CONTROL_CHAR_REGEXP.test(name)); } catch (err: any) { if ( @@ -442,13 +451,15 @@ export default class ShellInstanceState { throw err; } }, - collectionsForDatabase: async (): //connectionId: string, - //databaseName: string - Promise => { + collectionsForDatabase: async ( + connectionId: string, + databaseName: string + ): Promise => { + const mongo = this._getMongoByConnectionId(connectionId); try { - // TODO: use connectionId and databaseName - const collectionNames = - await this.currentDb._getCollectionNamesForCompletion(); + const collectionNames = await mongo + ._getDb(databaseName) + ._getCollectionNamesForCompletion(); return collectionNames.filter( (name) => !CONTROL_CHAR_REGEXP.test(name) ); @@ -463,11 +474,13 @@ export default class ShellInstanceState { } }, schemaInformationForCollection: async ( - connectionKey: string, + connectionId: string, databaseName: string, collectionName: string ): Promise => { - const docs = await this.currentDb + const mongo = this._getMongoByConnectionId(connectionId); + const docs = await mongo + ._getDb(databaseName) .getCollection(collectionName) ._getSampleDocsForCompletion(); const schemaAccessor = await analyzeDocuments(docs); From 70168e6e663b257dbc2e7e7cd23bb4c41f91c123 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Wed, 2 Apr 2025 13:31:36 +0100 Subject: [PATCH 03/21] port some tests to autocomplete --- .../src/open-context-runtime.ts | 1 + packages/cli-repl/src/cli-repl.spec.ts | 1 + packages/cli-repl/src/mongosh-repl.spec.ts | 203 +++++++++++------- packages/e2e-tests/test/e2e-direct.spec.ts | 1 + packages/e2e-tests/test/e2e-snippet.spec.ts | 1 + packages/shell-api/src/collection.ts | 4 +- 6 files changed, 129 insertions(+), 82 deletions(-) diff --git a/packages/browser-runtime-core/src/open-context-runtime.ts b/packages/browser-runtime-core/src/open-context-runtime.ts index 50d5ea97c..f53d015d3 100644 --- a/packages/browser-runtime-core/src/open-context-runtime.ts +++ b/packages/browser-runtime-core/src/open-context-runtime.ts @@ -29,6 +29,7 @@ export interface InterpreterEnvironment { */ export class OpenContextRuntime implements Runtime { private interpreterEnvironment: InterpreterEnvironment; + // TODO: we have to also port this to the new autocomplete private autocompleter: ShellApiAutocompleter | null = null; private shellEvaluator: ShellEvaluator; private instanceState: ShellInstanceState; diff --git a/packages/cli-repl/src/cli-repl.spec.ts b/packages/cli-repl/src/cli-repl.spec.ts index 20bec91c4..d3aa5be4e 100644 --- a/packages/cli-repl/src/cli-repl.spec.ts +++ b/packages/cli-repl/src/cli-repl.spec.ts @@ -2599,6 +2599,7 @@ describe('CliRepl', function () { expect(output).to.include('db.movies.find({year: {$gte'); }); + // TODO: port to the new autocomplete it('completes properties of shell API result types', async function () { if (!hasCollectionNames) return this.skip(); diff --git a/packages/cli-repl/src/mongosh-repl.spec.ts b/packages/cli-repl/src/mongosh-repl.spec.ts index 3227fdced..4d813b6c7 100644 --- a/packages/cli-repl/src/mongosh-repl.spec.ts +++ b/packages/cli-repl/src/mongosh-repl.spec.ts @@ -1,6 +1,9 @@ /* eslint-disable no-control-regex */ import { MongoshCommandFailed } from '@mongosh/errors'; -import type { ServiceProvider } from '@mongosh/service-provider-core'; +import type { + AggregationCursor, + ServiceProvider, +} from '@mongosh/service-provider-core'; import { bson } from '@mongosh/service-provider-core'; import { ADMIN_DB } from '@mongosh/shell-api/lib/enums'; import { CliUserConfig } from '@mongosh/types'; @@ -89,6 +92,14 @@ describe('MongoshNodeRepl', function () { }, }); sp.runCommandWithCheck.resolves({ ok: 1 }); + + if (process.env.USE_NEW_AUTOCOMPLETE) { + sp.listCollections.resolves([{ name: 'coll' }]); + const aggCursor = stubInterface(); + aggCursor.toArray.resolves([{ foo: 1, bar: 2 }]); + sp.aggregate.returns(aggCursor); + } + serviceProvider = sp; calledServiceProviderFunctions = () => Object.fromEntries( @@ -352,6 +363,14 @@ describe('MongoshNodeRepl', function () { }; const tabtab = async () => { await tab(); + if (process.env.USE_NEW_AUTOCOMPLETE) { + // TODO: This is because autocomplete() is async and will either list + // databases or collections or sample documents, any of which takes time + // to complete and can time out. There is probably a better way. + await new Promise((resolve) => { + setTimeout(resolve, 210); + }); + } await tab(); }; @@ -379,19 +398,28 @@ describe('MongoshNodeRepl', function () { expect(output).to.include('65537'); }); - it('does not stop input when autocompleting during .editor', async function () { - input.write('.editor\n'); - await tick(); - expect(output).to.include('Entering editor mode'); - output = ''; - input.write('db.'); - await tabtab(); - await tick(); - input.write('version()\n'); - input.write('\u0004'); // Ctrl+D - await waitEval(bus); - expect(output).to.include('Error running command serverBuildInfo'); - }); + context( + `autocompleting during .editor [${ + process.env.USE_NEW_AUTOCOMPLETE ?? 'old' + }]`, + function () { + it('does not stop input when autocompleting during .editor', async function () { + input.write('.editor\n'); + await tick(); + expect(output).to.include('Entering editor mode'); + output = ''; + input.write('db.'); + await tabtab(); + await tick(); + input.write('version()\n'); + input.write('\u0004'); // Ctrl+D + await waitEval(bus); + expect(output, output).to.include( + 'Error running command serverBuildInfo' + ); + }); + } + ); it('can enter multiline code', async function () { for (const line of multilineCode.split('\n')) { @@ -449,73 +477,86 @@ describe('MongoshNodeRepl', function () { expect(code).to.equal(undefined); }); - context('autocompletion', function () { - it('autocompletes collection methods', async function () { - input.write('db.coll.'); - await tabtab(); - await tick(); - expect(output).to.include('db.coll.updateOne'); - }); - it('autocompletes shell-api methods (once)', async function () { - input.write('vers'); - await tabtab(); - await tick(); - expect(output).to.include('version'); - expect(output).to.not.match(/version[ \t]+version/); - }); - it('autocompletes async shell api methods', async function () { - input.write('db.coll.find().'); - await tabtab(); - await tick(); - expect(output).to.include('db.coll.find().close'); - }); - it('autocompletes local variables', async function () { - input.write('let somelongvariable = 0\n'); - await waitEval(bus); - output = ''; - input.write('somelong'); - await tabtab(); - await tick(); - expect(output).to.include('somelongvariable'); - }); - it('autocompletes partial repl commands', async function () { - input.write('.e'); - await tabtab(); - await tick(); - expect(output).to.include('editor'); - expect(output).to.include('exit'); - }); - it('autocompletes full repl commands', async function () { - input.write('.ed'); - await tabtab(); - await tick(); - expect(output).to.include('.editor'); - expect(output).not.to.include('exit'); - }); - it('autocompletion during .editor does not reset the prompt', async function () { - input.write('.editor\n'); - await tick(); - output = ''; - expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); - input.write('db.'); - await tabtab(); - await tick(); - input.write('foo\nbar\n'); - expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); - input.write('\u0003'); // Ctrl+C for abort - await tick(); - expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal( - 'test> ' - ); - expect(stripAnsi(output)).to.equal('db.foo\r\nbar\r\n\r\ntest> '); - }); - it('does not autocomplete tab-indented code', async function () { - output = ''; - input.write('\t\tfoo'); - await tick(); - expect(output).to.equal('\t\tfoo'); - }); - }); + context( + `autocompletion [${process.env.USE_NEW_AUTOCOMPLETE ?? 'old'}]`, + function () { + it('autocompletes collection methods', async function () { + input.write('db.coll.'); + await tabtab(); + await tick(); + expect(output, output).to.include('db.coll.updateOne'); + }); + it('autocompletes collection schema fields', async function () { + if (!process.env.USE_NEW_AUTOCOMPLETE) { + // not supported in the old autocomplete + this.skip(); + } + input.write('db.coll.find({'); + await tabtab(); + await tick(); + expect(output, output).to.include('db.coll.find({foo'); + }); + it('autocompletes shell-api methods (once)', async function () { + input.write('vers'); + await tabtab(); + await tick(); + expect(output, output).to.include('version'); + expect(output, output).to.not.match(/version[ \t]+version/); + }); + it('autocompletes async shell api methods', async function () { + input.write('db.coll.find().'); + await tabtab(); + await tick(); + expect(output, output).to.include('db.coll.find().toArray'); + }); + it('autocompletes local variables', async function () { + input.write('let somelongvariable = 0\n'); + await waitEval(bus); + output = ''; + input.write('somelong'); + await tabtab(); + await tick(); + expect(output, output).to.include('somelongvariable'); + }); + it('autocompletes partial repl commands', async function () { + input.write('.e'); + await tabtab(); + await tick(); + expect(output, output).to.include('editor'); + expect(output, output).to.include('exit'); + }); + it('autocompletes full repl commands', async function () { + input.write('.ed'); + await tabtab(); + await tick(); + expect(output, output).to.include('.editor'); + expect(output, output).not.to.include('exit'); + }); + it('autocompletion during .editor does not reset the prompt', async function () { + input.write('.editor\n'); + await tick(); + output = ''; + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); + input.write('db.'); + await tabtab(); + await tick(); + input.write('foo\nbar\n'); + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal(''); + input.write('\u0003'); // Ctrl+C for abort + await tick(); + expect((mongoshRepl.runtimeState().repl as any)._prompt).to.equal( + 'test> ' + ); + expect(stripAnsi(output)).to.equal('db.foo\r\nbar\r\n\r\ntest> '); + }); + it('does not autocomplete tab-indented code', async function () { + output = ''; + input.write('\t\tfoo'); + await tick(); + expect(output, output).to.equal('\t\tfoo'); + }); + } + ); context('history support', function () { const arrowUp = '\x1b[A'; diff --git a/packages/e2e-tests/test/e2e-direct.spec.ts b/packages/e2e-tests/test/e2e-direct.spec.ts index 46a030660..c77639b00 100644 --- a/packages/e2e-tests/test/e2e-direct.spec.ts +++ b/packages/e2e-tests/test/e2e-direct.spec.ts @@ -193,6 +193,7 @@ describe('e2e direct connection', function () { expect(await shell.executeLine('show dbs')).to.include('admin'); }); + // TODO: port to the new autocomplete it('autocompletes collection names', async function () { if (process.arch === 's390x') { return this.skip(); // https://jira.mongodb.org/browse/MONGOSH-746 diff --git a/packages/e2e-tests/test/e2e-snippet.spec.ts b/packages/e2e-tests/test/e2e-snippet.spec.ts index 239e81862..536b09d04 100644 --- a/packages/e2e-tests/test/e2e-snippet.spec.ts +++ b/packages/e2e-tests/test/e2e-snippet.spec.ts @@ -72,6 +72,7 @@ describe('snippet integration tests', function () { shell.assertNoErrors(); }); + // TODO: port to the new autocomplete it('autocompletes snippet commands', async function () { if (process.arch === 's390x') { return this.skip(); // https://jira.mongodb.org/browse/MONGOSH-746 diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index 94efc7a48..bd9a766f1 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -2500,7 +2500,9 @@ export default class Collection extends ShellApiWithMongoClass { } async _getSampleDocs(): Promise { - this._cachedSampleDocs = await (await this.aggregate([])).toArray(); + this._cachedSampleDocs = await ( + await this.aggregate([{ $sample: { size: 10 } }]) + ).toArray(); return this._cachedSampleDocs; } From fd845e6b70d6197cb9feabf9215a18b85221f432 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Wed, 2 Apr 2025 15:43:42 +0100 Subject: [PATCH 04/21] this one just worked --- packages/cli-repl/src/cli-repl.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli-repl/src/cli-repl.spec.ts b/packages/cli-repl/src/cli-repl.spec.ts index d3aa5be4e..20bec91c4 100644 --- a/packages/cli-repl/src/cli-repl.spec.ts +++ b/packages/cli-repl/src/cli-repl.spec.ts @@ -2599,7 +2599,6 @@ describe('CliRepl', function () { expect(output).to.include('db.movies.find({year: {$gte'); }); - // TODO: port to the new autocomplete it('completes properties of shell API result types', async function () { if (!hasCollectionNames) return this.skip(); From b1ccc97abbc0866186fb16e8dff5531fc66d818c Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Wed, 2 Apr 2025 15:47:09 +0100 Subject: [PATCH 05/21] also just works --- packages/e2e-tests/test/e2e-direct.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/e2e-tests/test/e2e-direct.spec.ts b/packages/e2e-tests/test/e2e-direct.spec.ts index c77639b00..46a030660 100644 --- a/packages/e2e-tests/test/e2e-direct.spec.ts +++ b/packages/e2e-tests/test/e2e-direct.spec.ts @@ -193,7 +193,6 @@ describe('e2e direct connection', function () { expect(await shell.executeLine('show dbs')).to.include('admin'); }); - // TODO: port to the new autocomplete it('autocompletes collection names', async function () { if (process.arch === 's390x') { return this.skip(); // https://jira.mongodb.org/browse/MONGOSH-746 From 7fecfac6d782c07a5100a7857861bd4c51de45b5 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 4 Apr 2025 14:44:00 +0100 Subject: [PATCH 06/21] use the new mongodb-schema --- package-lock.json | 176 +++++++++--------- packages/shell-api/package.json | 2 +- .../shell-api/src/shell-instance-state.ts | 6 +- 3 files changed, 91 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7506701f7..840a323ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21566,93 +21566,6 @@ "node": ">=12" } }, - "node_modules/mongodb-schema": { - "version": "12.5.2", - "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.5.2.tgz", - "integrity": "sha512-gukbasp1udtpI79PDeGvbTGB14vO099Gm2/LNsJ+HunL1EqcdeWh+T6vBWy4fYmDvWx2SAeYrGLstp9aXruPJA==", - "license": "Apache-2.0", - "dependencies": { - "reservoir": "^0.1.2" - }, - "bin": { - "mongodb-schema": "bin/mongodb-schema" - }, - "optionalDependencies": { - "bson": "^6.7.0", - "cli-table": "^0.3.4", - "js-yaml": "^4.0.0", - "mongodb": "^6.6.1", - "mongodb-ns": "^2.4.0", - "numeral": "^2.0.6", - "progress": "^2.0.3", - "stats-lite": "^2.0.0", - "yargs": "^17.6.2" - } - }, - "node_modules/mongodb-schema/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0", - "optional": true - }, - "node_modules/mongodb-schema/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-schema/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mongodb-schema/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "optional": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-schema/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/mongosh": { "resolved": "packages/mongosh", "link": true @@ -30068,7 +29981,7 @@ "@mongosh/i18n": "2.9.1", "@mongosh/service-provider-core": "3.1.0", "mongodb-redact": "^1.1.5", - "mongodb-schema": "^12.5.2" + "mongodb-schema": "^12.6.2" }, "devDependencies": { "@mongodb-js/eslint-config-mongosh": "^1.0.0", @@ -30086,12 +29999,99 @@ "node": ">=14.15.1" } }, + "packages/shell-api/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "optional": true + }, + "packages/shell-api/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "packages/shell-api/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "packages/shell-api/node_modules/mongodb-redact": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/mongodb-redact/-/mongodb-redact-1.1.5.tgz", "integrity": "sha512-bLTHIHviJvTGJDvCECDBEDMk7beJQ4Fvoec50hgIax98ojzyTk9xIyrewFPM7yzlDVKTkkh864uxlkkTTLVsbg==", "license": "Apache-2.0" }, + "packages/shell-api/node_modules/mongodb-schema": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.2.tgz", + "integrity": "sha512-uKjkTAx6MqJi0Xj0aeYRjvYr3O7LrUQgXH1c0WQCOByPoYbNG9RAhWoc4IwriIqTHyBw1RJn0C/p7DISOPYpMg==", + "license": "Apache-2.0", + "dependencies": { + "reservoir": "^0.1.2" + }, + "bin": { + "mongodb-schema": "bin/mongodb-schema" + }, + "optionalDependencies": { + "bson": "^6.7.0", + "cli-table": "^0.3.4", + "js-yaml": "^4.0.0", + "mongodb": "^6.6.1", + "mongodb-ns": "^2.4.0", + "numeral": "^2.0.6", + "progress": "^2.0.3", + "stats-lite": "^2.0.0", + "yargs": "^17.6.2" + } + }, + "packages/shell-api/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "packages/shell-api/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } + }, "packages/shell-evaluator": { "name": "@mongosh/shell-evaluator", "version": "3.6.0", diff --git a/packages/shell-api/package.json b/packages/shell-api/package.json index 9e9ae0c4f..8e55ae883 100644 --- a/packages/shell-api/package.json +++ b/packages/shell-api/package.json @@ -46,7 +46,7 @@ "@mongosh/i18n": "2.9.1", "@mongosh/service-provider-core": "3.1.0", "mongodb-redact": "^1.1.5", - "mongodb-schema": "^12.5.2" + "mongodb-schema": "^12.6.2" }, "devDependencies": { "@mongodb-js/eslint-config-mongosh": "^1.0.0", diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 9027b940b..739a44b72 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -38,10 +38,8 @@ import constructShellBson from './shell-bson'; import { Streams } from './streams'; import { ShellLog } from './shell-log'; -import type { - AutocompletionContext, - JSONSchema, -} from '@mongodb-js/mongodb-ts-autocomplete'; +import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'; +import type { JSONSchema } from 'mongodb-schema'; import { analyzeDocuments } from 'mongodb-schema'; /** From 16242e0ba86c0a940eaccf3f7a35b750a44539e7 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 23 May 2025 16:47:23 +0100 Subject: [PATCH 07/21] backport the circular ref fix --- packages/cli-repl/package.json | 2 +- packages/cli-repl/src/mongosh-repl.ts | 98 ++++++++++++++++++- .../shell-api/src/shell-instance-state.ts | 87 ---------------- 3 files changed, 98 insertions(+), 89 deletions(-) diff --git a/packages/cli-repl/package.json b/packages/cli-repl/package.json index 4cc0d4f1d..be39d217d 100644 --- a/packages/cli-repl/package.json +++ b/packages/cli-repl/package.json @@ -77,7 +77,7 @@ "@mongosh/shell-evaluator": "^3.11.0", "@mongosh/snippet-manager": "^3.11.0", "@mongosh/types": "3.6.2", - "@mongodb-js/mongodb-ts-autocomplete": "^0.2.0", + "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@segment/analytics-node": "^1.3.0", "ansi-escape-sequences": "^5.1.2", "askcharacter": "^2.0.4", diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index fbd71db22..89a35eed5 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import completer from '@mongosh/autocomplete'; import { MongoshInternalError, MongoshWarning } from '@mongosh/errors'; import { changeHistory } from '@mongosh/history'; @@ -7,6 +8,7 @@ import type { } from '@mongosh/service-provider-core'; import type { EvaluationListener, + Mongo, OnLoadResult, ShellCliOptions, } from '@mongosh/shell-api'; @@ -50,6 +52,9 @@ import { installPasteSupport } from './repl-paste-support'; import util from 'util'; import { MongoDBAutocompleter } from '@mongodb-js/mongodb-ts-autocomplete'; +import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'; +import type { JSONSchema } from 'mongodb-schema'; +import { analyzeDocuments } from 'mongodb-schema'; declare const __non_webpack_require__: any; @@ -433,6 +438,88 @@ class MongoshNodeRepl implements EvaluationListener { this.runtimeState().context.history = history; } + // TODO: probably better to have this on instanceState + public _getMongoByConnectionId( + instanceState: ShellInstanceState, + connectionId: string + ): Mongo { + for (const mongo of instanceState.mongos) { + if (connectionIdFromURI(mongo.getURI()) === connectionId) { + return mongo; + } + } + throw new Error(`mongo with connection id ${connectionId} not found`); + } + + public getAutocompletionContext( + instanceState: ShellInstanceState + ): AutocompletionContext { + return { + currentDatabaseAndConnection: () => { + return { + connectionId: connectionIdFromURI( + instanceState.currentDb.getMongo().getURI() + ), + databaseName: instanceState.currentDb.getName(), + }; + }, + databasesForConnection: async ( + connectionId: string + ): Promise => { + const mongo = this._getMongoByConnectionId(instanceState, connectionId); + try { + const dbNames = await mongo._getDatabaseNamesForCompletion(); + return dbNames.filter( + (name: string) => !CONTROL_CHAR_REGEXP.test(name) + ); + } catch (err: any) { + // TODO: move this code to a method in the shell instance so we don't + // have to hardcode the error code or export it. + if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + return []; + } + throw err; + } + }, + collectionsForDatabase: async ( + connectionId: string, + databaseName: string + ): Promise => { + const mongo = this._getMongoByConnectionId(instanceState, connectionId); + try { + const collectionNames = await mongo + ._getDb(databaseName) + ._getCollectionNamesForCompletion(); + return collectionNames.filter( + (name: string) => !CONTROL_CHAR_REGEXP.test(name) + ); + } catch (err: any) { + // TODO: move this code to a method in the shell instance so we don't + // have to hardcode the error code or export it. + if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + return []; + } + throw err; + } + }, + schemaInformationForCollection: async ( + connectionId: string, + databaseName: string, + collectionName: string + ): Promise => { + const mongo = this._getMongoByConnectionId(instanceState, connectionId); + const docs = await mongo + ._getDb(databaseName) + .getCollection(collectionName) + ._getSampleDocsForCompletion(); + const schemaAccessor = await analyzeDocuments(docs); + + const schema = await schemaAccessor.getMongoDBJsonSchema(); + return schema; + }, + }; + } + private async finishInitializingNodeRepl(): Promise { const { repl, instanceState } = this.runtimeState(); if (!repl) return; @@ -445,7 +532,8 @@ class MongoshNodeRepl implements EvaluationListener { line: string ) => Promise<[string[], string, 'exclusive'] | [string[], string]>; if (process.env.USE_NEW_AUTOCOMPLETE) { - const autocompletionContext = instanceState.getAutocompletionContext(); + const autocompletionContext = + this.getAutocompletionContext(instanceState); newMongoshCompleter = new MongoDBAutocompleter({ context: autocompletionContext, }); @@ -1347,4 +1435,12 @@ async function enterAsynchronousExecutionForPrompt(): Promise { await new Promise(setImmediate); } +function connectionIdFromURI(uri: string): string { + // turn the uri into something we can safely use as part of a "filename" + // inside autocomplete + const hash = crypto.createHash('sha256'); + hash.update(uri); + return hash.digest('hex'); +} + export default MongoshNodeRepl; diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 2562d9e57..4d3489989 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto'; import { CommonErrors, MongoshInvalidInputError } from '@mongosh/errors'; import type { AutoEncryptionOptions, @@ -38,10 +37,6 @@ import constructShellBson from './shell-bson'; import { Streams } from './streams'; import { ShellLog } from './shell-log'; -import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'; -import type { JSONSchema } from 'mongodb-schema'; -import { analyzeDocuments } from 'mongodb-schema'; - /** * The subset of CLI options that is relevant for the shell API's behavior itself. */ @@ -140,14 +135,6 @@ export interface ShellPlugin { // eslint-disable-next-line no-control-regex const CONTROL_CHAR_REGEXP = /[\x00-\x1F\x7F-\x9F]/g; -function connectionIdFromURI(uri: string): string { - // turn the uri into something we can safely use as part of a "filename" - // inside autocomplete - const hash = crypto.createHash('sha256'); - hash.update(uri); - return hash.digest('hex'); -} - /** * Anything to do with the state of the shell API and API objects is stored here. * @@ -415,80 +402,6 @@ export class ShellInstanceState { this.evaluationListener = listener; } - public _getMongoByConnectionId(connectionId: string): Mongo { - for (const mongo of this.mongos) { - if (connectionIdFromURI(mongo.getURI()) === connectionId) { - return mongo; - } - } - throw new Error(`mongo with connection id ${connectionId} not found`); - } - - public getAutocompletionContext(): AutocompletionContext { - return { - currentDatabaseAndConnection: () => { - return { - connectionId: connectionIdFromURI(this.currentDb.getMongo().getURI()), - databaseName: this.currentDb.getName(), - }; - }, - databasesForConnection: async ( - connectionId: string - ): Promise => { - const mongo = this._getMongoByConnectionId(connectionId); - try { - const dbNames = await mongo._getDatabaseNamesForCompletion(); - return dbNames.filter((name) => !CONTROL_CHAR_REGEXP.test(name)); - } catch (err: any) { - if ( - err?.code === ShellApiErrors.NotConnected || - err?.codeName === 'Unauthorized' - ) { - return []; - } - throw err; - } - }, - collectionsForDatabase: async ( - connectionId: string, - databaseName: string - ): Promise => { - const mongo = this._getMongoByConnectionId(connectionId); - try { - const collectionNames = await mongo - ._getDb(databaseName) - ._getCollectionNamesForCompletion(); - return collectionNames.filter( - (name) => !CONTROL_CHAR_REGEXP.test(name) - ); - } catch (err: any) { - if ( - err?.code === ShellApiErrors.NotConnected || - err?.codeName === 'Unauthorized' - ) { - return []; - } - throw err; - } - }, - schemaInformationForCollection: async ( - connectionId: string, - databaseName: string, - collectionName: string - ): Promise => { - const mongo = this._getMongoByConnectionId(connectionId); - const docs = await mongo - ._getDb(databaseName) - .getCollection(collectionName) - ._getSampleDocsForCompletion(); - const schemaAccessor = await analyzeDocuments(docs); - - const schema = await schemaAccessor.getMongoDBJsonSchema(); - return schema; - }, - }; - } - public getAutocompleteParameters(): AutocompleteParameters { return { topology: () => { From 4c2a00151ec96dba899691756a004e7911b43731 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 12:12:52 +0100 Subject: [PATCH 08/21] just use an incrementing unique connection id --- package-lock.json | 77 +++++++++++++++++-- packages/cli-repl/src/mongosh-repl.ts | 33 +------- packages/shell-api/src/mongo.ts | 8 ++ .../shell-api/src/shell-instance-state.ts | 9 +++ 4 files changed, 90 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ac179846..0fa721d89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6539,12 +6539,6 @@ "tar": "^6.1.15" } }, - "node_modules/@mongodb-js/mongodb-ts-autocomplete": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-ts-autocomplete/-/mongodb-ts-autocomplete-0.2.0.tgz", - "integrity": "sha512-5GUrrGHrLXiQj+pGWeDsUywaXbuhVTrVJyHjmiSK1ETT4cGV1/drJICCH+BNa1HD1pxGj2XTnIrlC8G1OKIy2A==", - "license": "Apache-2.0" - }, "node_modules/@mongodb-js/monorepo-tools": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/@mongodb-js/monorepo-tools/-/monorepo-tools-1.1.16.tgz", @@ -6871,6 +6865,40 @@ "node": ">=0.10.0" } }, + "node_modules/@mongodb-js/ts-autocomplete": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/ts-autocomplete/-/ts-autocomplete-0.3.1.tgz", + "integrity": "sha512-2ui9y88PM+PIad/3htoGn/8kiNK8V4vVTrqicgAt1Bozt0AwCUqJFUfnpqf40eJVD20XbPWfeKPjPMPkA7SruQ==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "lodash": "^4.17.21", + "typescript": "^5.0.4" + } + }, + "node_modules/@mongodb-js/ts-autocomplete/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@mongodb-js/ts-autocomplete/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/@mongodb-js/tsconfig-mongosh": { "resolved": "configs/tsconfig-mongosh", "link": true @@ -14771,6 +14799,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -23611,7 +23648,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true, "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -25495,6 +25531,18 @@ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "license": "MIT" }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -34261,7 +34309,7 @@ "license": "Apache-2.0", "dependencies": { "@mongodb-js/devtools-proxy-support": "^0.4.2", - "@mongodb-js/mongodb-ts-autocomplete": "^0.2.0", + "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@mongosh/arg-parser": "^3.10.3", "@mongosh/autocomplete": "^3.11.0", "@mongosh/editor": "^3.11.0", @@ -34326,6 +34374,19 @@ "win-export-certificate-and-key": "^2.1.0" } }, + "packages/cli-repl/node_modules/@mongodb-js/mongodb-ts-autocomplete": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-ts-autocomplete/-/mongodb-ts-autocomplete-0.2.2.tgz", + "integrity": "sha512-5GwS2zm8OKJeWFK25PalpstMimIrnif1T07Ur+HECOCFhndTQmIV7VJve86GBwrnmM5Cy4P4Pv7FrjwvfE222A==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/ts-autocomplete": "^0.3.1", + "@mongosh/shell-api": "^3.11.0", + "mongodb-schema": "^12.6.2", + "node-cache": "^5.1.2", + "typescript": "^5.0.4" + } + }, "packages/cli-repl/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 89a35eed5..44caa446e 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto'; import completer from '@mongosh/autocomplete'; import { MongoshInternalError, MongoshWarning } from '@mongosh/errors'; import { changeHistory } from '@mongosh/history'; @@ -8,7 +7,6 @@ import type { } from '@mongosh/service-provider-core'; import type { EvaluationListener, - Mongo, OnLoadResult, ShellCliOptions, } from '@mongosh/shell-api'; @@ -438,35 +436,20 @@ class MongoshNodeRepl implements EvaluationListener { this.runtimeState().context.history = history; } - // TODO: probably better to have this on instanceState - public _getMongoByConnectionId( - instanceState: ShellInstanceState, - connectionId: string - ): Mongo { - for (const mongo of instanceState.mongos) { - if (connectionIdFromURI(mongo.getURI()) === connectionId) { - return mongo; - } - } - throw new Error(`mongo with connection id ${connectionId} not found`); - } - public getAutocompletionContext( instanceState: ShellInstanceState ): AutocompletionContext { return { currentDatabaseAndConnection: () => { return { - connectionId: connectionIdFromURI( - instanceState.currentDb.getMongo().getURI() - ), + connectionId: instanceState.currentDb.getMongo().getConnectionId(), databaseName: instanceState.currentDb.getName(), }; }, databasesForConnection: async ( connectionId: string ): Promise => { - const mongo = this._getMongoByConnectionId(instanceState, connectionId); + const mongo = instanceState.getMongoByConnectionId(connectionId); try { const dbNames = await mongo._getDatabaseNamesForCompletion(); return dbNames.filter( @@ -485,7 +468,7 @@ class MongoshNodeRepl implements EvaluationListener { connectionId: string, databaseName: string ): Promise => { - const mongo = this._getMongoByConnectionId(instanceState, connectionId); + const mongo = instanceState.getMongoByConnectionId(connectionId); try { const collectionNames = await mongo ._getDb(databaseName) @@ -507,7 +490,7 @@ class MongoshNodeRepl implements EvaluationListener { databaseName: string, collectionName: string ): Promise => { - const mongo = this._getMongoByConnectionId(instanceState, connectionId); + const mongo = instanceState.getMongoByConnectionId(connectionId); const docs = await mongo ._getDb(databaseName) .getCollection(collectionName) @@ -1435,12 +1418,4 @@ async function enterAsynchronousExecutionForPrompt(): Promise { await new Promise(setImmediate); } -function connectionIdFromURI(uri: string): string { - // turn the uri into something we can safely use as part of a "filename" - // inside autocomplete - const hash = crypto.createHash('sha256'); - hash.update(uri); - return hash.digest('hex'); -} - export default MongoshNodeRepl; diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index efaf5ab04..7f3be2b0b 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -73,6 +73,8 @@ type Mutable = { -readonly [P in keyof T]: T[P]; }; +let nextId = 1; + @shellApiClassDefault @classPlatforms(['CLI']) export default class Mongo< @@ -81,6 +83,7 @@ export default class Mongo< private __serviceProvider: ServiceProvider | null = null; public readonly _databases: Record, DatabaseWithSchema> = Object.create(null); + private _connectionId: number; public _instanceState: ShellInstanceState; public _connectionInfo: ConnectionInfo; private _explicitEncryptionOnly = false; @@ -97,6 +100,7 @@ export default class Mongo< sp?: ServiceProvider ) { super(); + this._connectionId = nextId++; this._instanceState = instanceState; if (sp) { this.__serviceProvider = sp; @@ -299,6 +303,10 @@ export default class Mongo< ) as CollectionWithSchema; } + getConnectionId(): string { + return `connection_${this._connectionId}`; + } + getURI(): string { return this._uri; } diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 4d3489989..bdcd63991 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -402,6 +402,15 @@ export class ShellInstanceState { this.evaluationListener = listener; } + public getMongoByConnectionId(connectionId: string): Mongo { + for (const mongo of this.mongos) { + if (mongo.getConnectionId() === connectionId) { + return mongo; + } + } + throw new Error(`mongo with connection id ${connectionId} not found`); + } + public getAutocompleteParameters(): AutocompleteParameters { return { topology: () => { From 7731cd85b39a844a0f9e43e46e604efbc6a1a769 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 12:51:01 +0100 Subject: [PATCH 09/21] better event based waiting --- packages/cli-repl/src/mongosh-repl.spec.ts | 8 ++------ packages/cli-repl/src/mongosh-repl.ts | 6 +++++- packages/cli-repl/test/repl-helpers.ts | 20 ++++++++++++++++++++ packages/types/src/index.ts | 5 ++++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/cli-repl/src/mongosh-repl.spec.ts b/packages/cli-repl/src/mongosh-repl.spec.ts index 16a093be8..ce1791113 100644 --- a/packages/cli-repl/src/mongosh-repl.spec.ts +++ b/packages/cli-repl/src/mongosh-repl.spec.ts @@ -20,6 +20,7 @@ import { tick, useTmpdir, waitEval, + waitMongoshCompletionResults, } from '../test/repl-helpers'; import type { MongoshIOProvider, MongoshNodeReplOptions } from './mongosh-repl'; import MongoshNodeRepl from './mongosh-repl'; @@ -364,12 +365,7 @@ describe('MongoshNodeRepl', function () { const tabtab = async () => { await tab(); if (process.env.USE_NEW_AUTOCOMPLETE) { - // TODO: This is because autocomplete() is async and will either list - // databases or collections or sample documents, any of which takes time - // to complete and can time out. There is probably a better way. - await new Promise((resolve) => { - setTimeout(resolve, 210); - }); + await waitMongoshCompletionResults(bus); } await tab(); }; diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 44caa446e..96f4c8a63 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -548,7 +548,11 @@ class MongoshNodeRepl implements EvaluationListener { } })(), ]); - this.bus.emit('mongosh:autocompletion-complete'); // For testing. + this.bus.emit( + 'mongosh:autocompletion-complete', + replResults, + mongoshResults + ); // For testing. // Sometimes the mongosh completion knows that what it is doing is right, // and that autocompletion based on inspecting the actual objects that diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 87bedb63e..5cd5f5033 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -75,6 +75,25 @@ async function waitCompletion(bus: MongoshBus) { await tick(); } +async function waitMongoshCompletionResults(bus: MongoshBus) { + // Waiting for the completion results can "time out" if an async action such + // as listing the databases or collections or loading the schema takes longer + // than 200ms (at the time of writing), but by the next try or at least + // eventually the action should complete and then the next autocomplete call + // will return the cached result. + let found = false; + while (!found) { + const [, mongoshResults] = await waitBus( + bus, + 'mongosh:autocompletion-complete' + ); + if (mongoshResults.length === 0) { + found = true; + } + } + await tick(); +} + const fakeTTYProps: Partial = { isTTY: true, isRaw: true, @@ -106,6 +125,7 @@ export { waitBus, waitEval, waitCompletion, + waitMongoshCompletionResults, fakeTTYProps, readReplLogFile, }; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index cc53bf6fc..cd5a29e3d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -335,7 +335,10 @@ export interface MongoshBusEventsMap extends ConnectEventMap { * Signals the completion of the autocomplete suggestion providers. * _ONLY AVAILABLE FOR TESTING._ */ - 'mongosh:autocompletion-complete': () => void; + 'mongosh:autocompletion-complete': ( + resplResults: string[], + mongoshResults: string[] + ) => void; /** * Signals the completion of the asynchronous interrupt handler in MongoshRepl. Not fired for interrupts of _synchronous_ code. * _ONLY AVAILABLE FOR TESTING._ From 824ea47a11d67c64091191ea0ed09f5d825aa15e Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 13:03:40 +0100 Subject: [PATCH 10/21] add ticket number --- packages/cli-repl/src/mongosh-repl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 96f4c8a63..ab99e4094 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -140,7 +140,7 @@ function transformAutocompleteResults( line: string, results: { result: string }[] ): [string[], string] | [string[], string, 'exclusive'] { - // TODO: actually use 'exclusive' when we should + // TODO(MONGOSH-2206): actually use 'exclusive' when we should return [results.map((result) => result.result), line]; } From df1c303a3363ea7294191d15a2fb0efbe817a7a1 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 13:04:25 +0100 Subject: [PATCH 11/21] add ticket number --- packages/browser-runtime-core/src/open-context-runtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-runtime-core/src/open-context-runtime.ts b/packages/browser-runtime-core/src/open-context-runtime.ts index f53d015d3..65686b503 100644 --- a/packages/browser-runtime-core/src/open-context-runtime.ts +++ b/packages/browser-runtime-core/src/open-context-runtime.ts @@ -29,7 +29,7 @@ export interface InterpreterEnvironment { */ export class OpenContextRuntime implements Runtime { private interpreterEnvironment: InterpreterEnvironment; - // TODO: we have to also port this to the new autocomplete + // TODO(MONGOSH-2205): we have to also port this to the new autocomplete private autocompleter: ShellApiAutocompleter | null = null; private shellEvaluator: ShellEvaluator; private instanceState: ShellInstanceState; From f354fe35366a692264b527090544739a520d0a50 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 13:05:12 +0100 Subject: [PATCH 12/21] add ticket number --- packages/e2e-tests/test/e2e-snippet.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/test/e2e-snippet.spec.ts b/packages/e2e-tests/test/e2e-snippet.spec.ts index 536b09d04..bf4a84307 100644 --- a/packages/e2e-tests/test/e2e-snippet.spec.ts +++ b/packages/e2e-tests/test/e2e-snippet.spec.ts @@ -72,7 +72,7 @@ describe('snippet integration tests', function () { shell.assertNoErrors(); }); - // TODO: port to the new autocomplete + // TODO(MONGOSH-2205): port to the new autocomplete it('autocompletes snippet commands', async function () { if (process.arch === 's390x') { return this.skip(); // https://jira.mongodb.org/browse/MONGOSH-746 From 729b4c17fc2563a6cd6f105ab2b4df39dda3a413 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 13:45:41 +0100 Subject: [PATCH 13/21] put mongodb-schema as a dep in the right place --- package-lock.json | 215 +--------------------------------------------- 1 file changed, 2 insertions(+), 213 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fa721d89..adc48854e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6865,40 +6865,6 @@ "node": ">=0.10.0" } }, - "node_modules/@mongodb-js/ts-autocomplete": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/ts-autocomplete/-/ts-autocomplete-0.3.1.tgz", - "integrity": "sha512-2ui9y88PM+PIad/3htoGn/8kiNK8V4vVTrqicgAt1Bozt0AwCUqJFUfnpqf40eJVD20XbPWfeKPjPMPkA7SruQ==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.0", - "lodash": "^4.17.21", - "typescript": "^5.0.4" - } - }, - "node_modules/@mongodb-js/ts-autocomplete/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@mongodb-js/ts-autocomplete/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/@mongodb-js/tsconfig-mongosh": { "resolved": "configs/tsconfig-mongosh", "link": true @@ -14754,18 +14720,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-table": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", - "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", - "optional": true, - "dependencies": { - "colors": "1.0.3" - }, - "engines": { - "node": ">= 0.2.0" - } - }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -14799,15 +14753,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -14908,16 +14853,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/columnify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", @@ -21316,13 +21251,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/isnumber": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isnumber/-/isnumber-1.0.0.tgz", - "integrity": "sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw==", - "license": "MIT", - "optional": true - }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -23648,6 +23576,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true, "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -25049,13 +24978,6 @@ "bson": "6.x" } }, - "node_modules/mongodb-ns": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.2.tgz", - "integrity": "sha512-gYJjEYG4v4a1WSXgUf81OBoBRlj+Z1SlnQVO392fC/4a1CN7CLWDITajZWPFTPh/yRozYk6sHHtZwZmQhodBEA==", - "license": "MIT", - "optional": true - }, "node_modules/mongodb-runner": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.7.1.tgz", @@ -25118,93 +25040,6 @@ "node": ">=12" } }, - "node_modules/mongodb-schema": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.2.tgz", - "integrity": "sha512-uKjkTAx6MqJi0Xj0aeYRjvYr3O7LrUQgXH1c0WQCOByPoYbNG9RAhWoc4IwriIqTHyBw1RJn0C/p7DISOPYpMg==", - "license": "Apache-2.0", - "dependencies": { - "reservoir": "^0.1.2" - }, - "bin": { - "mongodb-schema": "bin/mongodb-schema" - }, - "optionalDependencies": { - "bson": "^6.7.0", - "cli-table": "^0.3.4", - "js-yaml": "^4.0.0", - "mongodb": "^6.6.1", - "mongodb-ns": "^2.4.0", - "numeral": "^2.0.6", - "progress": "^2.0.3", - "stats-lite": "^2.0.0", - "yargs": "^17.6.2" - } - }, - "node_modules/mongodb-schema/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0", - "optional": true - }, - "node_modules/mongodb-schema/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-schema/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mongodb-schema/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "optional": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-schema/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/mongosh": { "resolved": "packages/mongosh", "link": true @@ -25531,18 +25366,6 @@ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "license": "MIT" }, - "node_modules/node-cache": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", - "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", - "license": "MIT", - "dependencies": { - "clone": "2.x" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -29481,12 +29304,6 @@ "dev": true, "license": "MIT" }, - "node_modules/reservoir": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/reservoir/-/reservoir-0.1.2.tgz", - "integrity": "sha512-ysyw95gLBhMAzqIVrOHJ2yMrRQHAS+h97bS9r89Z7Ou10Jhl2k5KOsyjPqrxL+WfEanov0o5bAMVzQ7AKyENHA==", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -30943,19 +30760,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stats-lite": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.2.0.tgz", - "integrity": "sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA==", - "license": "MIT", - "optional": true, - "dependencies": { - "isnumber": "~1.0.0" - }, - "engines": { - "node": ">=2.0.0" - } - }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -34309,7 +34113,6 @@ "license": "Apache-2.0", "dependencies": { "@mongodb-js/devtools-proxy-support": "^0.4.2", - "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@mongosh/arg-parser": "^3.10.3", "@mongosh/autocomplete": "^3.11.0", "@mongosh/editor": "^3.11.0", @@ -34374,19 +34177,6 @@ "win-export-certificate-and-key": "^2.1.0" } }, - "packages/cli-repl/node_modules/@mongodb-js/mongodb-ts-autocomplete": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-ts-autocomplete/-/mongodb-ts-autocomplete-0.2.2.tgz", - "integrity": "sha512-5GwS2zm8OKJeWFK25PalpstMimIrnif1T07Ur+HECOCFhndTQmIV7VJve86GBwrnmM5Cy4P4Pv7FrjwvfE222A==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/ts-autocomplete": "^0.3.1", - "@mongosh/shell-api": "^3.11.0", - "mongodb-schema": "^12.6.2", - "node-cache": "^5.1.2", - "typescript": "^5.0.4" - } - }, "packages/cli-repl/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -35092,8 +34882,7 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "^2.13.1", "@mongosh/service-provider-core": "3.3.3", - "mongodb-redact": "^1.1.5", - "mongodb-schema": "^12.6.2" + "mongodb-redact": "^1.1.5" }, "devDependencies": { "@microsoft/api-extractor": "^7.39.3", From 691fdfec139c6a4706ab362c153e5bf1c0eed8a6 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 13:47:56 +0100 Subject: [PATCH 14/21] put mongodb-schema as a dep in the right place --- package-lock.json | 213 +++++++++++++++++++++++++++++++- packages/cli-repl/package.json | 1 + packages/shell-api/package.json | 3 +- 3 files changed, 214 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index adc48854e..864a7a53f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6539,6 +6539,19 @@ "tar": "^6.1.15" } }, + "node_modules/@mongodb-js/mongodb-ts-autocomplete": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-ts-autocomplete/-/mongodb-ts-autocomplete-0.2.2.tgz", + "integrity": "sha512-5GwS2zm8OKJeWFK25PalpstMimIrnif1T07Ur+HECOCFhndTQmIV7VJve86GBwrnmM5Cy4P4Pv7FrjwvfE222A==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/ts-autocomplete": "^0.3.1", + "@mongosh/shell-api": "^3.11.0", + "mongodb-schema": "^12.6.2", + "node-cache": "^5.1.2", + "typescript": "^5.0.4" + } + }, "node_modules/@mongodb-js/monorepo-tools": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/@mongodb-js/monorepo-tools/-/monorepo-tools-1.1.16.tgz", @@ -6865,6 +6878,40 @@ "node": ">=0.10.0" } }, + "node_modules/@mongodb-js/ts-autocomplete": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/ts-autocomplete/-/ts-autocomplete-0.3.1.tgz", + "integrity": "sha512-2ui9y88PM+PIad/3htoGn/8kiNK8V4vVTrqicgAt1Bozt0AwCUqJFUfnpqf40eJVD20XbPWfeKPjPMPkA7SruQ==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "lodash": "^4.17.21", + "typescript": "^5.0.4" + } + }, + "node_modules/@mongodb-js/ts-autocomplete/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@mongodb-js/ts-autocomplete/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/@mongodb-js/tsconfig-mongosh": { "resolved": "configs/tsconfig-mongosh", "link": true @@ -14720,6 +14767,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "optional": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -14753,6 +14812,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -14853,6 +14921,16 @@ "dev": true, "license": "MIT" }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/columnify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", @@ -21251,6 +21329,13 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isnumber": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isnumber/-/isnumber-1.0.0.tgz", + "integrity": "sha512-JLiSz/zsZcGFXPrB4I/AGBvtStkt+8QmksyZBZnVXnnK9XdTEyz0tX8CRYljtwYDuIuZzih6DpHQdi+3Q6zHPw==", + "license": "MIT", + "optional": true + }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -23576,7 +23661,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true, "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -24978,6 +25062,13 @@ "bson": "6.x" } }, + "node_modules/mongodb-ns": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.2.tgz", + "integrity": "sha512-gYJjEYG4v4a1WSXgUf81OBoBRlj+Z1SlnQVO392fC/4a1CN7CLWDITajZWPFTPh/yRozYk6sHHtZwZmQhodBEA==", + "license": "MIT", + "optional": true + }, "node_modules/mongodb-runner": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.7.1.tgz", @@ -25040,6 +25131,93 @@ "node": ">=12" } }, + "node_modules/mongodb-schema": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.2.tgz", + "integrity": "sha512-uKjkTAx6MqJi0Xj0aeYRjvYr3O7LrUQgXH1c0WQCOByPoYbNG9RAhWoc4IwriIqTHyBw1RJn0C/p7DISOPYpMg==", + "license": "Apache-2.0", + "dependencies": { + "reservoir": "^0.1.2" + }, + "bin": { + "mongodb-schema": "bin/mongodb-schema" + }, + "optionalDependencies": { + "bson": "^6.7.0", + "cli-table": "^0.3.4", + "js-yaml": "^4.0.0", + "mongodb": "^6.6.1", + "mongodb-ns": "^2.4.0", + "numeral": "^2.0.6", + "progress": "^2.0.3", + "stats-lite": "^2.0.0", + "yargs": "^17.6.2" + } + }, + "node_modules/mongodb-schema/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "optional": true + }, + "node_modules/mongodb-schema/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-schema/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mongodb-schema/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-schema/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } + }, "node_modules/mongosh": { "resolved": "packages/mongosh", "link": true @@ -25366,6 +25544,18 @@ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "license": "MIT" }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -29304,6 +29494,12 @@ "dev": true, "license": "MIT" }, + "node_modules/reservoir": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reservoir/-/reservoir-0.1.2.tgz", + "integrity": "sha512-ysyw95gLBhMAzqIVrOHJ2yMrRQHAS+h97bS9r89Z7Ou10Jhl2k5KOsyjPqrxL+WfEanov0o5bAMVzQ7AKyENHA==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -30760,6 +30956,19 @@ "dev": true, "license": "MIT" }, + "node_modules/stats-lite": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stats-lite/-/stats-lite-2.2.0.tgz", + "integrity": "sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA==", + "license": "MIT", + "optional": true, + "dependencies": { + "isnumber": "~1.0.0" + }, + "engines": { + "node": ">=2.0.0" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -34113,6 +34322,7 @@ "license": "Apache-2.0", "dependencies": { "@mongodb-js/devtools-proxy-support": "^0.4.2", + "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@mongosh/arg-parser": "^3.10.3", "@mongosh/autocomplete": "^3.11.0", "@mongosh/editor": "^3.11.0", @@ -34136,6 +34346,7 @@ "js-yaml": "^4.1.0", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^2.3.1", + "mongodb-schema": "^12.6.2", "numeral": "^2.0.6", "pretty-repl": "^4.0.1", "semver": "^7.5.4", diff --git a/packages/cli-repl/package.json b/packages/cli-repl/package.json index be39d217d..b9269bd5e 100644 --- a/packages/cli-repl/package.json +++ b/packages/cli-repl/package.json @@ -87,6 +87,7 @@ "js-yaml": "^4.1.0", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^2.3.1", + "mongodb-schema": "^12.6.2", "numeral": "^2.0.6", "pretty-repl": "^4.0.1", "semver": "^7.5.4", diff --git a/packages/shell-api/package.json b/packages/shell-api/package.json index 8375f8cb2..302c897ad 100644 --- a/packages/shell-api/package.json +++ b/packages/shell-api/package.json @@ -58,8 +58,7 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "^2.13.1", "@mongosh/service-provider-core": "3.3.3", - "mongodb-redact": "^1.1.5", - "mongodb-schema": "^12.6.2" + "mongodb-redact": "^1.1.5" }, "devDependencies": { "@microsoft/api-extractor": "^7.39.3", From b7bba92e515bdd5b648d72b82c4e763140f2a2e5 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 14:00:54 +0100 Subject: [PATCH 15/21] don't add getConnectionId to the public interface --- packages/cli-repl/src/mongosh-repl.ts | 2 +- packages/shell-api/src/mongo.ts | 2 +- packages/shell-api/src/shell-instance-state.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index ab99e4094..e116570a0 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -442,7 +442,7 @@ class MongoshNodeRepl implements EvaluationListener { return { currentDatabaseAndConnection: () => { return { - connectionId: instanceState.currentDb.getMongo().getConnectionId(), + connectionId: instanceState.currentDb.getMongo()._getConnectionId(), databaseName: instanceState.currentDb.getName(), }; }, diff --git a/packages/shell-api/src/mongo.ts b/packages/shell-api/src/mongo.ts index 7f3be2b0b..9af7198b7 100644 --- a/packages/shell-api/src/mongo.ts +++ b/packages/shell-api/src/mongo.ts @@ -303,7 +303,7 @@ export default class Mongo< ) as CollectionWithSchema; } - getConnectionId(): string { + _getConnectionId(): string { return `connection_${this._connectionId}`; } diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index bdcd63991..e6139785f 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -404,7 +404,7 @@ export class ShellInstanceState { public getMongoByConnectionId(connectionId: string): Mongo { for (const mongo of this.mongos) { - if (mongo.getConnectionId() === connectionId) { + if (mongo._getConnectionId() === connectionId) { return mongo; } } From 1edc21270e33cab2dfa9d48c244f2c5c10cea649 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 15:40:01 +0100 Subject: [PATCH 16/21] move it back --- package-lock.json | 5 +- packages/cli-repl/package.json | 1 - packages/cli-repl/src/mongosh-repl.ts | 72 +------------------ packages/shell-api/package.json | 4 +- .../shell-api/src/shell-instance-state.ts | 71 ++++++++++++++++++ 5 files changed, 78 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 864a7a53f..a9677cccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34346,7 +34346,6 @@ "js-yaml": "^4.1.0", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^2.3.1", - "mongodb-schema": "^12.6.2", "numeral": "^2.0.6", "pretty-repl": "^4.0.1", "semver": "^7.5.4", @@ -35093,11 +35092,13 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "^2.13.1", "@mongosh/service-provider-core": "3.3.3", - "mongodb-redact": "^1.1.5" + "mongodb-redact": "^1.1.5", + "mongodb-schema": "^12.6.2" }, "devDependencies": { "@microsoft/api-extractor": "^7.39.3", "@mongodb-js/eslint-config-mongosh": "^1.0.0", + "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@mongodb-js/prettier-config-devtools": "^1.0.1", "@mongodb-js/tsconfig-mongosh": "^1.0.0", "@mongosh/types": "3.6.2", diff --git a/packages/cli-repl/package.json b/packages/cli-repl/package.json index b9269bd5e..be39d217d 100644 --- a/packages/cli-repl/package.json +++ b/packages/cli-repl/package.json @@ -87,7 +87,6 @@ "js-yaml": "^4.1.0", "mongodb-connection-string-url": "^3.0.1", "mongodb-log-writer": "^2.3.1", - "mongodb-schema": "^12.6.2", "numeral": "^2.0.6", "pretty-repl": "^4.0.1", "semver": "^7.5.4", diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index e116570a0..f442038f5 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -50,9 +50,6 @@ import { installPasteSupport } from './repl-paste-support'; import util from 'util'; import { MongoDBAutocompleter } from '@mongodb-js/mongodb-ts-autocomplete'; -import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'; -import type { JSONSchema } from 'mongodb-schema'; -import { analyzeDocuments } from 'mongodb-schema'; declare const __non_webpack_require__: any; @@ -436,73 +433,6 @@ class MongoshNodeRepl implements EvaluationListener { this.runtimeState().context.history = history; } - public getAutocompletionContext( - instanceState: ShellInstanceState - ): AutocompletionContext { - return { - currentDatabaseAndConnection: () => { - return { - connectionId: instanceState.currentDb.getMongo()._getConnectionId(), - databaseName: instanceState.currentDb.getName(), - }; - }, - databasesForConnection: async ( - connectionId: string - ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); - try { - const dbNames = await mongo._getDatabaseNamesForCompletion(); - return dbNames.filter( - (name: string) => !CONTROL_CHAR_REGEXP.test(name) - ); - } catch (err: any) { - // TODO: move this code to a method in the shell instance so we don't - // have to hardcode the error code or export it. - if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { - return []; - } - throw err; - } - }, - collectionsForDatabase: async ( - connectionId: string, - databaseName: string - ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); - try { - const collectionNames = await mongo - ._getDb(databaseName) - ._getCollectionNamesForCompletion(); - return collectionNames.filter( - (name: string) => !CONTROL_CHAR_REGEXP.test(name) - ); - } catch (err: any) { - // TODO: move this code to a method in the shell instance so we don't - // have to hardcode the error code or export it. - if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { - return []; - } - throw err; - } - }, - schemaInformationForCollection: async ( - connectionId: string, - databaseName: string, - collectionName: string - ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); - const docs = await mongo - ._getDb(databaseName) - .getCollection(collectionName) - ._getSampleDocsForCompletion(); - const schemaAccessor = await analyzeDocuments(docs); - - const schema = await schemaAccessor.getMongoDBJsonSchema(); - return schema; - }, - }; - } - private async finishInitializingNodeRepl(): Promise { const { repl, instanceState } = this.runtimeState(); if (!repl) return; @@ -516,7 +446,7 @@ class MongoshNodeRepl implements EvaluationListener { ) => Promise<[string[], string, 'exclusive'] | [string[], string]>; if (process.env.USE_NEW_AUTOCOMPLETE) { const autocompletionContext = - this.getAutocompletionContext(instanceState); + instanceState.getAutocompletionContext(instanceState); newMongoshCompleter = new MongoDBAutocompleter({ context: autocompletionContext, }); diff --git a/packages/shell-api/package.json b/packages/shell-api/package.json index 302c897ad..170acc1d2 100644 --- a/packages/shell-api/package.json +++ b/packages/shell-api/package.json @@ -58,11 +58,13 @@ "@mongosh/history": "2.4.6", "@mongosh/i18n": "^2.13.1", "@mongosh/service-provider-core": "3.3.3", - "mongodb-redact": "^1.1.5" + "mongodb-redact": "^1.1.5", + "mongodb-schema": "^12.6.2" }, "devDependencies": { "@microsoft/api-extractor": "^7.39.3", "@mongodb-js/eslint-config-mongosh": "^1.0.0", + "@mongodb-js/mongodb-ts-autocomplete": "^0.2.2", "@mongodb-js/prettier-config-devtools": "^1.0.1", "@mongodb-js/tsconfig-mongosh": "^1.0.0", "@mongosh/types": "3.6.2", diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index e6139785f..169641538 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -37,6 +37,10 @@ import constructShellBson from './shell-bson'; import { Streams } from './streams'; import { ShellLog } from './shell-log'; +import type { AutocompletionContext } from '@mongodb-js/mongodb-ts-autocomplete'; +import type { JSONSchema } from 'mongodb-schema'; +import { analyzeDocuments } from 'mongodb-schema'; + /** * The subset of CLI options that is relevant for the shell API's behavior itself. */ @@ -411,6 +415,73 @@ export class ShellInstanceState { throw new Error(`mongo with connection id ${connectionId} not found`); } + public getAutocompletionContext( + instanceState: ShellInstanceState + ): AutocompletionContext { + return { + currentDatabaseAndConnection: () => { + return { + connectionId: instanceState.currentDb.getMongo()._getConnectionId(), + databaseName: instanceState.currentDb.getName(), + }; + }, + databasesForConnection: async ( + connectionId: string + ): Promise => { + const mongo = instanceState.getMongoByConnectionId(connectionId); + try { + const dbNames = await mongo._getDatabaseNamesForCompletion(); + return dbNames.filter( + (name: string) => !CONTROL_CHAR_REGEXP.test(name) + ); + } catch (err: any) { + // TODO: move this code to a method in the shell instance so we don't + // have to hardcode the error code or export it. + if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + return []; + } + throw err; + } + }, + collectionsForDatabase: async ( + connectionId: string, + databaseName: string + ): Promise => { + const mongo = instanceState.getMongoByConnectionId(connectionId); + try { + const collectionNames = await mongo + ._getDb(databaseName) + ._getCollectionNamesForCompletion(); + return collectionNames.filter( + (name: string) => !CONTROL_CHAR_REGEXP.test(name) + ); + } catch (err: any) { + // TODO: move this code to a method in the shell instance so we don't + // have to hardcode the error code or export it. + if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + return []; + } + throw err; + } + }, + schemaInformationForCollection: async ( + connectionId: string, + databaseName: string, + collectionName: string + ): Promise => { + const mongo = instanceState.getMongoByConnectionId(connectionId); + const docs = await mongo + ._getDb(databaseName) + .getCollection(collectionName) + ._getSampleDocsForCompletion(); + const schemaAccessor = await analyzeDocuments(docs); + + const schema = await schemaAccessor.getMongoDBJsonSchema(); + return schema; + }, + }; + } + public getAutocompleteParameters(): AutocompleteParameters { return { topology: () => { From 8b859cebaf6724309a44af05b0d7e1266044caa3 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 15:43:14 +0100 Subject: [PATCH 17/21] remove the TODO again, can just use the constant --- packages/shell-api/src/shell-instance-state.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index 169641538..f0a2d7cc7 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -435,9 +435,10 @@ export class ShellInstanceState { (name: string) => !CONTROL_CHAR_REGEXP.test(name) ); } catch (err: any) { - // TODO: move this code to a method in the shell instance so we don't - // have to hardcode the error code or export it. - if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + if ( + err?.code === ShellApiErrors.NotConnected || + err?.codeName === 'Unauthorized' + ) { return []; } throw err; @@ -456,9 +457,10 @@ export class ShellInstanceState { (name: string) => !CONTROL_CHAR_REGEXP.test(name) ); } catch (err: any) { - // TODO: move this code to a method in the shell instance so we don't - // have to hardcode the error code or export it. - if (err?.code === 'SHAPI-10004' || err?.codeName === 'Unauthorized') { + if ( + err?.code === ShellApiErrors.NotConnected || + err?.codeName === 'Unauthorized' + ) { return []; } throw err; From 001c1d011fd4c0837ce3fefa4173db3b47d90f9c Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 15:49:35 +0100 Subject: [PATCH 18/21] add some options to the sample aggregate --- packages/shell-api/src/collection.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/shell-api/src/collection.ts b/packages/shell-api/src/collection.ts index 85aa2a66f..115b6e7ec 100644 --- a/packages/shell-api/src/collection.ts +++ b/packages/shell-api/src/collection.ts @@ -2389,7 +2389,7 @@ export class Collection< ): Promise { this._emitCollectionApiCall('checkMetadataConsistency', { options }); - return this._database._runCursorCommand({ + return await this._database._runCursorCommand({ checkMetadataConsistency: this._name, }); } @@ -2531,7 +2531,11 @@ export class Collection< async _getSampleDocs(): Promise { this._cachedSampleDocs = await ( - await this.aggregate([{ $sample: { size: 10 } }]) + await this.aggregate([{ $sample: { size: 10 } }], { + allowDiskUse: true, + maxTimeMS: 1000, + readPreference: 'secondaryPreferred', + }) ).toArray(); return this._cachedSampleDocs; } From 7e39d409c6283d4c9093d18aa1a1989d9df0fc18 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 16:02:28 +0100 Subject: [PATCH 19/21] 'new', not true --- packages/cli-repl/src/mongosh-repl.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli-repl/src/mongosh-repl.spec.ts b/packages/cli-repl/src/mongosh-repl.spec.ts index ce1791113..02d6ac3b5 100644 --- a/packages/cli-repl/src/mongosh-repl.spec.ts +++ b/packages/cli-repl/src/mongosh-repl.spec.ts @@ -396,7 +396,7 @@ describe('MongoshNodeRepl', function () { context( `autocompleting during .editor [${ - process.env.USE_NEW_AUTOCOMPLETE ?? 'old' + process.env.USE_NEW_AUTOCOMPLETE ? 'new' : 'old' }]`, function () { it('does not stop input when autocompleting during .editor', async function () { @@ -474,7 +474,7 @@ describe('MongoshNodeRepl', function () { }); context( - `autocompletion [${process.env.USE_NEW_AUTOCOMPLETE ?? 'old'}]`, + `autocompletion [${process.env.USE_NEW_AUTOCOMPLETE ? 'new' : 'old'}]`, function () { it('autocompletes collection methods', async function () { input.write('db.coll.'); From b3dd61bbc37ba8a9aab5c4b481933fa4226c0d41 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 16:05:22 +0100 Subject: [PATCH 20/21] unnecessary TODO --- packages/cli-repl/src/mongosh-repl.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index f442038f5..032af8ac9 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -136,8 +136,7 @@ type Mutable = { function transformAutocompleteResults( line: string, results: { result: string }[] -): [string[], string] | [string[], string, 'exclusive'] { - // TODO(MONGOSH-2206): actually use 'exclusive' when we should +): [string[], string] { return [results.map((result) => result.result), line]; } From cf99e0b12f420fa2230f9ad31fb0c311f46fe84e Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Mon, 26 May 2025 17:07:17 +0100 Subject: [PATCH 21/21] add an integration test --- packages/cli-repl/src/mongosh-repl.ts | 3 +- packages/shell-api/src/integration.spec.ts | 38 +++++++++++++++++++ .../shell-api/src/shell-instance-state.ts | 14 +++---- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index 032af8ac9..b9c9f96ef 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -444,8 +444,7 @@ class MongoshNodeRepl implements EvaluationListener { line: string ) => Promise<[string[], string, 'exclusive'] | [string[], string]>; if (process.env.USE_NEW_AUTOCOMPLETE) { - const autocompletionContext = - instanceState.getAutocompletionContext(instanceState); + const autocompletionContext = instanceState.getAutocompletionContext(); newMongoshCompleter = new MongoDBAutocompleter({ context: autocompletionContext, }); diff --git a/packages/shell-api/src/integration.spec.ts b/packages/shell-api/src/integration.spec.ts index 41eddfc28..98e3d7d9e 100644 --- a/packages/shell-api/src/integration.spec.ts +++ b/packages/shell-api/src/integration.spec.ts @@ -2772,6 +2772,44 @@ describe('Shell API (integration)', function () { }); }); + describe('getAutocompletionContext', function () { + beforeEach(async function () { + // Make sure the collection is present so it is included in autocompletion. + await collection.insertOne({}); + // Make sure 'database' is the current db in the eyes of the instance state object. + instanceState.setDbFunc(database); + }); + + it('returns information for autocomplete', async function () { + const context = instanceState.getAutocompletionContext(); + const { connectionId, databaseName } = + context.currentDatabaseAndConnection(); + const databaseNames = await context.databasesForConnection( + connectionId + ); + expect(databaseNames).to.include(database.getName()); + const collectionNames = await context.collectionsForDatabase( + connectionId, + databaseName + ); + expect(collectionNames).to.include(collection.getName()); + const schema = await context.schemaInformationForCollection( + connectionId, + database.getName(), + collection.getName() + ); + expect(schema).to.deep.equal({ + bsonType: 'object', + properties: { + _id: { + bsonType: 'objectId', + }, + }, + required: ['_id'], + }); + }); + }); + describe('getAutocompleteParameters', function () { let connectionString: string; beforeEach(async function () { diff --git a/packages/shell-api/src/shell-instance-state.ts b/packages/shell-api/src/shell-instance-state.ts index f0a2d7cc7..eb67f8a2b 100644 --- a/packages/shell-api/src/shell-instance-state.ts +++ b/packages/shell-api/src/shell-instance-state.ts @@ -415,20 +415,18 @@ export class ShellInstanceState { throw new Error(`mongo with connection id ${connectionId} not found`); } - public getAutocompletionContext( - instanceState: ShellInstanceState - ): AutocompletionContext { + public getAutocompletionContext(): AutocompletionContext { return { currentDatabaseAndConnection: () => { return { - connectionId: instanceState.currentDb.getMongo()._getConnectionId(), - databaseName: instanceState.currentDb.getName(), + connectionId: this.currentDb.getMongo()._getConnectionId(), + databaseName: this.currentDb.getName(), }; }, databasesForConnection: async ( connectionId: string ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); + const mongo = this.getMongoByConnectionId(connectionId); try { const dbNames = await mongo._getDatabaseNamesForCompletion(); return dbNames.filter( @@ -448,7 +446,7 @@ export class ShellInstanceState { connectionId: string, databaseName: string ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); + const mongo = this.getMongoByConnectionId(connectionId); try { const collectionNames = await mongo ._getDb(databaseName) @@ -471,7 +469,7 @@ export class ShellInstanceState { databaseName: string, collectionName: string ): Promise => { - const mongo = instanceState.getMongoByConnectionId(connectionId); + const mongo = this.getMongoByConnectionId(connectionId); const docs = await mongo ._getDb(databaseName) .getCollection(collectionName)