I'm glad you asked. :-) For a start, number types. I want i32, i64, u32 etc. JavaScript (and therefore TypeScript) only has "number". Yes we now finally have BigInt but it's not ideal for JIT optimisation.
Object prototypes is a weird part of JS that we really could do without. If you really want that, use class syntax.
The Date object. Need I say more...
Little things like Object.keys() should return a (keyof T)[] rather than a string[] but can't due to JS edge cases.
Want first class tuples/immutable arrays.
Many other new syntaxes that can't be implemented due to the need for JS compatibility.
I'm sure there are others that I can't think of right now...
For new code, I think most JS devs have already switched to using the class syntax. Also FP in JS is alive and well if you want to avoid dealing with prototype chains altogether.
I think we can all agree the Date API sucks, but life can still be good if you just give in and include a date library. Also, there's some light at the end of the tunnel with Temporal proposal coming along [1].
Object.keys returning string[] is purely a TypeScript design decision, coming from how TypeScript chooses to model object subtyping [2].
The fact that this passes type checks is a conscious TS design decision that comes with both advantages and disadvantages. It wouldn't have to be this way; Exact types [3] could potentially be used to describe that if the type is exactly Foo, it's safe to assume Object.keys(foo) is (keyof Foo)[].
For first class tuples (and records), there's a stage 2 EcmaScript proposal coming along [4]. There's of course also the readonly [] type in TypeScript if you only need the safety of not accidentally pushing to an "immutable" array. First class language support could have nice additional features though, like strict equality.
Anyways, if these are the things you don't like about JS/TS, I don't think AssemblyScript will be the answer for you, as the goals of that language seem to be entirely different from "fixing" old JS cruft. Apart from the i32, i64 stuff I guess.
> I don't think AssemblyScript will be the answer for you, as the goals of that language seem to be entirely different from "fixing" old JS cruft. Apart from the i32, i64 stuff I guess.
That pull request from 4 years ago with regards to Object.keys is worth reading if you already or are considering writing a “type wrapper” for Object.keys, especially now that better tuple types have landed and string template types are coming.
Runtime types can still get you there. I’ve been happy with myzod for this purpose, which feels like writing TypeScript while getting you everything you need to validate at runtime in a very performant package.
Another potential benefit - runtime metaprogramming.
All types are erased when converted to JS, but I can imagine a lot of utility for runtime types in Typescript.
Obligated mention of runtypes (https://github.com/pelotom/runtypes) that bridges the gap between type-land and runtime-land. It's not seamless at all but if you have a serious need for this sort of thing, it's great
Why not use a language that has the benefits of TS + everything you say and is designed to be independent from JS.
I'm thinking of OCaml for example. Why not use OCaml rather than TS?
Edit: Actually the idea is so obvious that you have ReasonML that can compile both to JS and assembly and is basically OCaml with a JS-like syntax: https://reasonml.github.io/docs/en/what-and-why
Little things like Object.keys() should return a (keyof T)[] rather than a string[] but can't due to JS edge cases.
This was something that confused me when I first started learning Typescript but is actually not an edge case but a fundamental feature of Typescript's structural typing.
A type T is only guaranteed to be at least (a subtype of) T. Which in the case of objects means it has at least those fields, but potentially more. For instance
interface FooBar {
foo: string
bar: string
}
interface Baz {
baz: number
}
const fooBarBaz: FooBar & Baz = {
foo: 'foo',
bar: 'bar',
baz: 3
}
const printFoobar = (fooBar: FooBar) => {
for (const key of Object.keys(foobar)) {
if (key !== 'foo' && key !== 'bar') {
// If Object.keys was (keyof T)[] Typescript would claim this branch is unreachable
}
}
}
printFooBar(fooBarBaz)
It's really worth thinking about Typescript types not as nominal objects like in Java or Haskell, but contracts about minimum functionality. Embracing this in terms of function arguments and return types makes testing and composing typescript code much easier. For example, if you were writing a AWS Lambda function in Typescript that only uses the body of the incoming event.
handlerTwo only requires a handlerTwo({ body: '....' }) call in a test, instead of having to fill in all the extra properties of the APIGatewayProxyEvent that aren't even required. You might be tempted to use a type coercion in the test instead e.g. handlerOne({ body: '....' } as APIGatewayProxyEvent) but the problem with the coercion is that is in not checked at all by the compiler, it's essentially like temporarily using an any type. So if handlerOne is updated in the future to depend on more of the structure of APIGatewayProxyEvent Typescript will be none the wiser that the test requires updating (although granted hopefully the test would fail).
Anyway, that's a long winded explanation of why Object.keys shouldn't return (keyof T)[]. If you want to program generically over the Type of something I'd suggest making use of a runtime type library like io-ts instead which gives you a data structure representing the type to iterate over, etc.
Object prototypes is a weird part of JS that we really could do without. If you really want that, use class syntax.
The Date object. Need I say more...
Little things like Object.keys() should return a (keyof T)[] rather than a string[] but can't due to JS edge cases.
Want first class tuples/immutable arrays.
Many other new syntaxes that can't be implemented due to the need for JS compatibility.
I'm sure there are others that I can't think of right now...