Support for TypeScript in terms of community provided typings is great and since TypeScript 2+ and the consolidation of typings under the npm @types scope, most major libraries have great support that is pretty easy to integrate into your project. Like all things open-source occasionally, however, the typings for libraries lag ever so slightly behind the code base that they represent as gaps often only become obvious when someone tries to use a new feature. Fortunately, it's pretty easy to get around this in an elegant manner that is easy to implement.

Stepping on the landmine

So I was using Mongoose with Express when I noticed the following when starting my application:

DeprecationWarning: open() is deprecated in mongoose >= 4.11.0, use openUri() instead, or set the useMongoClient option if using connect() or createConnection()

As I'd never seen this before I made a quick trip to the Mongoose documentation. The solution was simple pass the following option when connecting:

mongoose.connect('mongodb://localhost/myapp', { useMongoClient: true, /* other options */ });

So I did this then BANG! my code would no longer compile.

Diagnosing the issue

I must admit that it was pretty frustrating being told by the compiler that I was wrong when I knew I was right. As with most frustrations when developing the problem was caused by one of my decisions. In my tsconfig.json I had set:

"strict": true,

Therefore I had instructed the compiler to reject any code with unexplained/typed properties. Fortunately, my editor (VsCode) understands TypeScript and type annotations so when I looked at the typings for the options object this property was nowhere to be seen. A quick trip on to the DefinatelyTyped GitHub repo revealed that some nice member of the community had already put in a PR for this one. This was both impressive in terms of how fast the community reacts and assuring to know, however, it didn't fix my issue.

Adding our polyfill

Fortunately, TypeScript offers a really easy way of dealing with this. The fix takes place in 2 parts.

1. Adding typings from multiple sources

The tsconfig.json of any TypeScript project has an option that takes an array of folders that are the root of any directory structure that the compiler should look in for typings. So I simply created a folder called "Typings" in the root of my project and updated the "typeRoots" property of the "compilerOptions" section to look like

"compilerOptions": { "typeRoots" : ["./typings", "./node_modules/@types/"] // many other options... }

This sets the compiler to look in our new folder for typings as well as the @types folder in the node_modules folder where npm installs our other typings.

NB: In my project, I was explicitly pointing to where my type definition files are (my preference/habit but not necessary). Removing "typeRoots" will include our typings by convention. For more info see the documentation of ts-config.json.

2. Creating our polyfill

So this is the easy part. By mirroring the structure of the actual Mongoose typings it is pretty easy to add my own typing for the "useMongoClient" setting. The missing property was on the ConnectionOptions interface in the mongoose module typing so I create a polyfills.d.ts file in my typings folder and did the following:

declare module "mongoose" { interface ConnectionOptions { useMongoClient?: boolean; } }

Job done!

The fact the TypeScript compiler merges these two separate declarations declared with the same name into a single definition is made possible through a feature of TypeScript known as Declaration Merging.

Final thoughts

TypeScript is a great asset in many ways but its not without its quirks.

Fortunately, this one like most others is really easy to get around and very much a temporary. It's a great way to allow us to use features the moment they are available without feeling like our feature set is stale or we are being restricted in any way.