> I'm of the functional persuasion, yet I've found that classes are the ony way to write TypeScript that fits on your screen at all.

Huh? I’m of the functional persuasion too, and I use classes in TS too, but for strategic reasons (well defined value objects are easier to reason about than duck typed POJOs, and they perform better too). But I’ve never found them more space-dense than the equivalent function-only code. Often quite the opposite, as so many functions’ return types can be fully inferred. Which of course, this is how you get ~~ants~~ duck typed POJOs, but you can’t have explicit type defs without explicit defining them somewhere, and of course the syntax that collocates the field type and its value is more dense than the syntax which is wholly incompatible with that concept.

> handling of default property values and "definedness" differs

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. This:

class Foo {
constructor(readonly bar: number) {}
}

is identical to:

class Foo {
readonly bar: number;

constructor(bar: number) {
this.bar = bar;
}
}

is identical to:

class Foo {
bar;

constructor(bar) {
this.bar = bar;
}
}

And this type error:

class Foo {
readonly bar: number;

constructor(bar: number) {}
}

is identical to this type error, just caught sooner:

class Foo {
bar;

constructor(bar: number) {}
}

const foo = new Foo();

foo.bar.toFixed(2);

> Sure, the constructor signature may change in a subclass (why not disallow incompatible constructor overrides, given incompatible property/method signatures are already disallowed?)

Because the compatibility is checked on the `super` call which is required both at compile time and runtime, and because many use cases for subclasses are impossible or even invalid without different construction contracts.

I know there are many strong feelings about examples like this being “wrong”, but it’s a common enough inheritance example to illustrate the point:

class Square extends Rectangle {
constructor(/* ? */) {}
}

You cannot satisfy both Square and Rectangle with the same constructor arguments. This of course bolsters the point that this inheritance model is “wrong”, but it’s exactly right according to the domain, and the equivalent functional code to calculate eg area would similarly have to be either polymorphic over different shapes or expect a single shape constructed with different parameters.

> this.constructor

You got this one right, and it’s worse than you describe because of the weird rules for where you can’t have type parameters or explicit `this` types. The workaround is to use a static factory method, but it’s a shitty workaround with a lot of ceremony to do something that TS generally does well: model the types of real world JS code.

> Also crap like not being able to have a question mark and a default value in positional arguments so you gotta add `|undefined` there. Even the stuff it adds on top of JS is poorly thought out.

Ima help you out! Assuming your default satisfies the non-undefined type, you can just skip the union, it’s implied. This:

const foo = (bar: Bar = someBarSatisfyingValue) => {};

is identical to this:

const foo = (bar: Bar | undefined = someBarSatisfyingValue) => {};

is identical to this:

const foo = (bar?: Bar) => {
bar = bar ?? someBarSatisfyingValue;
};