Skip to content

feat: Shell API autocomplete type definitions MONGOSH-2031 MONGOSH-2032 MONGOSH-2173 #2434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

lerouxb
Copy link
Contributor

@lerouxb lerouxb commented Apr 17, 2025

This is a continuation of Anna's work here.

Usage:

npm run api-generate

Implements:

Changes:

  • merged in main, resolving the conflicts
  • added generics to the rs, sh and sp objects
  • added a separate api.ts entrypoint, generating api-processed.d.ts (although this needs work - trying to figure out how to reference it in package.json)
  • bundled more deps so that we don't import those types in the api-processed.d.ts file. The node.js packages are still bing imported, though. Will have to test if that works with the language server
  • fixed the tests (at least the ones I've noticed were broken so far)
  • added an api-globals.d.ts which declares the ShellAPI methods and the ShellBson functions as globals. This then gets appended to the end of api-processed.d.ts.

Notes:

@@ -294,7 +294,7 @@ describe('completer.completer', function () {

it('returns all suggestions', async function () {
const i = 'db.';
const attr = shellSignatures.Database.attributes as any;
const attr = shellSignatures.DatabaseImpl.attributes as any;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that Database and Collection are now types and the classes got replaced with DatabaseImpl and CollectionImpl is having a lot of knock-on effects and kinda ended up being responsible for most of this diff.

I'm wondering if we should keep them as Database and Collection but then rather name the new types to be something like DatabaseWithSchema and CollectionWithSchema. Then maybe the change will be smaller?

Not sure which way around would work out better ergonomically overall.

"types": "./lib/index.d.ts"
},
"./api": {
"types": "./lib/api-processed.d.ts"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what would be the best way to export/publish this and then reference it from the outside.

I think import type ShellApi from '@monogdb-js/shell-api/api' works with this and I tried it manually from a file, but I'll see shortly when I plug this into mongodb-ts-autocomplete

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I have to fs.readFile() to get the file as text so we can bundle it as a giant string, so this is kinda moot.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I have to fs.readFile() to get the file as text so we can bundle it as a giant string, so this is kinda moot.

Yeah, exactly. Do we do this as part of this PR? Put it in a .ts file that only exports that giant string?

Copy link
Contributor Author

@lerouxb lerouxb May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I generate that giant string in mongodb-ts-autocomplete (see npm run extract-types in mongodb-js/devtools-shared#520), but I suppose we can also export it here. I don't know which way is best. I don't want to accidentally bundle a giant string..

it('calls help function', async function () {
expect((await toShellResult(apiClass.help())).type).to.equal('Help');
expect((await toShellResult(apiClass.help)).type).to.equal('Help');
});
});
describe('signatures', function () {
it('type', function () {
expect(signatures.Collection.type).to.equal('Collection');
// TODO: do we want the signatures to be CollectionImpl or Collection?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a reminder for myself to remove this TODO comment before merging and after bringing it up with the team.

'Running "' + joinedCommand + '" in ' + args[2]?.cwd ?? process.cwd()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS2869 Right operand of ?? is unreachable because the left operand is never nullish.
`Running "${joinedCommand}" in ${args[2]?.cwd ?? process.cwd()}`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by. For some reason I got a compile error on this line in CI.

@lerouxb lerouxb marked this pull request as ready for review April 22, 2025 12:54
"@mongosh/service-provider-core",
"@mongosh/types",
"mongodb",
"bson"
Copy link
Contributor Author

@lerouxb lerouxb Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have bson set up in mongodb-ts-autocomplete, so I might remove it from here. We'd then just have to find/replace the line where we import from 'bson' to import from '/bson.ts' so that it will be loaded from the typescript service at runtime. (I already got that working for my stubbed mql and shell api implementations, so we can just reuse it.)

@lerouxb lerouxb changed the title feat: Shell API autocomplete type definitions MONGOSH-2032 feat: Shell API autocomplete type definitions MONGOSH-2032 MONGOSH-2173 Apr 24, 2025
@lerouxb lerouxb changed the title feat: Shell API autocomplete type definitions MONGOSH-2032 MONGOSH-2173 feat: Shell API autocomplete type definitions MONGOSH-2031 MONGOSH-2032 MONGOSH-2173 Apr 24, 2025
@@ -0,0 +1,44 @@
declare global {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is basically a snippet that gets appended at the end of api-globals.d.ts after we api-extract that. See bin/api-postprocess.ts.

I first tried to write a babel plugin to walk the ShellAPI class's methods and the ShellBson interface's methods, but that turned out to be really complicated.

There is one benefit to having them all mapped here explicitly: We don't end up accidentally leaking everything on ShellAPI into the global scope whether we want to or not. And it is arguably handy seeing them all enumerated here.

The only other variables added as globals would be db, sh, sp and rs which all get specified dynamically by mongodb-ts-autocomplete so they reference the server/database/collection schema generics.

browserslistConfigFile: false,
compact: false,
sourceType: 'module',
plugins: [applyAsyncRewriterChanges()],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We end up with these errors:

~/mongo/mongosh/packages/shell-api % npx tsc --lib es2019 --target es2018 --moduleResolution node --types node lib/api-processed.d.ts
lib/api-processed.d.ts:4412:3 - error TS2416: Property 'hasNext' in type 'Cursor' is not assignable to the same property in base type 'AggregateOrFindCursor<ServiceProviderFindCursor<Document_2>>'.
  Type '() => boolean' is not assignable to type '() => Promise<boolean>'.
    Type 'boolean' is not assignable to type 'Promise<boolean>'.

4412   hasNext(): boolean;
       ~~~~~~~

lib/api-processed.d.ts:4418:3 - error TS2416: Property 'next' in type 'Cursor' is not assignable to the same property in base type 'AggregateOrFindCursor<ServiceProviderFindCursor<Document_2>>'.
  Type '() => Document_2' is not assignable to type '() => Promise<Document_2>'.
    Type 'Document_2' is missing the following properties from type 'Promise<Document_2>': then, catch, finally, [Symbol.toStringTag]

4418   next(): Document_2 | null;
       ~~~~


Found 2 errors in the same file, starting at: lib/api-processed.d.ts:4412

Weirdly these seem to be the only two cases - if I revert those two return types manually then the file is all green.

The ts language service works around it as far as I can tell, so it doesn't impact autocomplete. If we did want to make the generated .ts valid we'll have to do something like modify the base type's methods' return types or somehow flatten the return type so that it doesn't inherit anymore?

Maybe there are other ideas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wild idea: we can just add the comment

  // @ts-expect-error The type of this function is not compatible with the type of the function in the base class

as part of the babel processing...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ultimately care about this is because it would be nice to have some smoke tests where we import this file and use it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is still an issue, would it be possible to adjust the base methods as well? That feels like the cleanest (and most correct) approach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed https://jira.mongodb.org/browse/MONGOSH-2209

Had a quick look and decided to split that into its own ticket rather.


function getHelpClassName(className: string) {
switch (className) {
case 'CollectionImpl':
Copy link
Contributor Author

@lerouxb lerouxb May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pointed this out below as well, but this is an example of the kind of fallout from renaming Collection to CollectionImpl and reusing the name for a type. And doing the same for Database.

Maybe this is OK but we could also consider leaving Collection and Database unchanged and then just having the types named something else like CollectionWithSchema and DatabaseWithSchema. Or whatever might be a better name.

browserslistConfigFile: false,
compact: false,
sourceType: 'module',
plugins: [applyAsyncRewriterChanges()],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is still an issue, would it be possible to adjust the base methods as well? That feels like the cleanest (and most correct) approach

"types": "./lib/index.d.ts"
},
"./api": {
"types": "./lib/api-processed.d.ts"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I have to fs.readFile() to get the file as text so we can bundle it as a giant string, so this is kinda moot.

Yeah, exactly. Do we do this as part of this PR? Put it in a .ts file that only exports that giant string?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants