>you can’t have explicit type defs without explicit defining them somewhere

Sure, as long as I have to define them once and exactly once. Not always possible, as in the case of simple, garden-variety keyword arguments.

// foo is required, bar has default, baz is optional
type POJO = { foo: number, bar: number, baz?: number }

function myFn ({ foo, bar = 3, baz }: POJO = {}) {
// oh wait...

function myFn ({ foo, bar, baz }: POJO = { bar: 3 }) {
// oh wait...

function myFn ({ foo, bar, baz }: Partial<POJO> = {}) {
bar ??= 3
if (foo === undefined) throw new Error("type safety")
// ...oh.

// maybe?:
function myFn ( foo, bar, baz }: POJO = { foo: undefined as never, bar: 3 }) {
// try :D

Technically the destructuring and the type declaration are completely separate things ofc (that just happen to look about the same because they're isomorphic but that's a watchlist word).

But... it doesn't even try to infer the type of an untyped destructuring - even if it's a local function used only once!

>well defined value objects are easier to reason about than duck typed POJOs, and they perform better too

Long live those!

class BaseValueObject {
constructor (values: Partial<this> // oh wait...

> Assuming your default satisfies the non-undefined type, you can just skip the union, it’s implied

type Foo = { defaultBar?: Bar }

function main (foo: Foo, bar?: Bar = foo.defaultBar) {
// oh wait... parameter can't have question mark an initializer

function main (foo: Foo, bar?: Bar|undefined = foo.defaultBar) {
// this works but is silly and scaryish

>The only difference is that TS provides a fully optional shorthand for assigning both the type and value in constructor arguments. The actual behavior isn’t any different

That's what a sane person would assume, no? Well, allow me to disappoint you (like that ever needs permission):

$ node
> Object.getOwnPropertyNames(new class Foo { a })
[ 'a' ]

$ npx ts-node
> Object.getOwnPropertyNames(new class Foo { a: any })
[]

Probably because it compiles them to a pre-standard, ES5-compatible class implementation based on good ol' `Foo.prototype`. And since they've already handled them one way, they can't become spec-compliant without breaking backwards compatibility.

The other place where this shines through particularly egregiously is the support of ESM static import/export. Everybody's build tools been compiling that back down to CJS so hard that Node.js 16+ introduced intentional incompatibilities between CJS and ESM modes just to get people to finally switch to the standards-compliant module system. So you end up in a situation where the library is written in TypeScript with ESM syntax but the only available browser build is a CJS blob which completely defeats the main touted benefit of static imports/exports, namely dead code elimination...

So you decide what the hell, let's switch TSC to ESM and moduleResolution node16, and end up having to use something like https://github.com/antongolub/tsc-esm-fix because the only allowed fix for TSC doing the wrong thing is at the completely wrong level - https://www.typescriptlang.org/docs/handbook/esm-node.html - if you don't see what's wrong with that, you're one of today's lucky 10000...