I found sweet spot for myself - flow with types in comments. As weird as it sounds it works for me on quite complex projects very well. Unlike typescript, which is language transpiler like coffeescript - flow is just typechecker with guarantee that after stripping types the output is valid js. It wouldn't work with ts, which generates runtime code ie. enums etc. Very good type inference means types appear as type declarations and when declaring functions and pretty much nowhere else. The code has interesting haskell'ish feel to it, ie. most of the code looks something like this:

  const ofId /*: (Sql, number) => Promise */ =
    (sql, id) =>
      ...
I'm very happy with it after using it on quite complex projects, writing libraries (ie. functional combinators for parsers, assertions/runtime types, template sql generators; and the rest from apis, business logic to anything that is needed).

Lack of libdefs is not a huge problem in my case. I have strong preference for shallow or no dependencies in projects and for things I use 3rd party code there are types or were converted from ts or simply added. Better support would help here but it was not a deal breaker.

Code editor support is not as good as ts but it does the job.

Another interesting effect is that arbitrarily deep npm linking is so much easier, the code is just js, doesn't need transpilation step, can be edited as is; I'm free to use any directory structure, ie. I often use root directory to drop files, they have natural require/import paths, no configuration etc.

For me, it's a joy to code like this.

typescript also supports type checking js code: https://www.typescriptlang.org/docs/handbook/type-checking-j...

I've found this very useful for gradually migrating a node.js project to a more maintainable state. Types also mean autocomplete works!

Additionally since it's typescript we can download the `@types/` packages for our dependencies and get type checking for those as well.

You can't do too many things with jsdoc, can you? Ie. generics are not supported or basic things like importing types from other file, right? Ergonomy/verbosity/look and feel of those types in jsdoc is a bit poor IMHO as well. It's absolutely not the same thing. With flow you get the whole flow, with jsdoc it seems some scraps of typechecking. Am I wrong?
Generics are supported via `@template` https://www.typescriptlang.org/docs/handbook/type-checking-j...

types can be imported either explicitly via `require()` or via the `@type {import("foo").Bar}`

There are definitely some limitations and caveats, like `Object` being aliased to `any` (I think this is being changed in a new version), but it's still way better than untyped JS, and having typescript integration is nice since our devs are already using typescript in other places.

Can even using things like https://github.com/typescript-eslint/typescript-eslint

Flow is both more advanced and faster than Typescript, but the community isn't as large, so Typescript continues to gain marketshare