Step 1: introspect your DataSources (Postgres, MySQL, GraphQL, REST, etc...) Step 2: combine them into a virtual Graph (virtual GraphQL API) Step 3: write GraphQL Operations and "compile" them into JSON RPC + generate a 100% type-safe client
Result? No more double declaration of types. No manual typing. More info on how this works with NextJS and Typescript here: [0]
Note, I'm the founder of WunderGraph and obsessed with creating the perfect developer experience for working with APIs. I appreciate any feedback.
[0]: https://wundergraph.com/docs/overview/features/generated_cli...
Any chance there are some demos building a full stack app on this tech?
We're going open source with this solution soon. So, any feedback is appreciated! You can also join our discord and shoot questions. =)
Tooling exists to prevent you from making this mistake at CI time (comparing your schema with the one currently in production) and ensuring an incompatible change is not made unless explicitly allowed.
The difference it makes is illusion of type-safety vs type-safety this article touches on.
It's basically mapping of an `unknown` type into known one, at runtime.
You can try to bind service with client (types) somehow but in many cases this will fail in production as you can't guarantee paired versioning, due to normal situations by design of your architecture or temporary mid-deployment state or other team doing something they were not suppose to do (new client is deployed connecting to old service or vice versa) etc. It's hard to avoid runtime parsing/type assertion in general.
Functional combinator approaches like [0] or faster [1] with predicate/assert semantics work very well with typescript, which is very pleasant language to work with.
TS is continuously getting better, but it's still possible to lose type safety with some constructs without the compiler warning you about it at all.
The third party typings for JS dependencies are also often not a 100% correct, especially for rare code paths and edge cases.
And due to how easy it is to fall back to any, there are also Typescript native libraries that don't uphold the guarantees they seem to give.
Typescript is great, but it's far from bullet proof, and it's extremely frustrating to deal with bugs that the type system supposedly should have prevented.
I hear from collegues it's different world on f/e.
It's true there are tons of poor quality libraries - both in pure js and ts. We try to avoid this transitive dependency explosion nonsense alltogether.
Even well known libraries like lodash, which are not typescript first – are simply too dynamic. If ts doesn't offer precise types on functions – we avoid those.
From our experience it looks like it pays off well to put an extra effort to guarantee that static types are correct and precise – no dependencies or trimmed to absolute minimum, rely on ts first code (usually ours), no dynamic fiddling that can't be expressed in ts, no any, no pretending via casting/non-null assertions, parsing/runtime-assertions on io-boundaries etc. Together with other techniques like branded types typescript can enter critial systems in enterprices – areas where js would never be allowed to exist. As you say it requires discipline – but I wouldn't call it "a lot", not in our case at least, the effort/benefit ratio is no-brainer in our case and at the end – it's honestly very pleasant language to work with.
Because if you optimize all the slack out of a system, you have no room to manoeuvre. In this case, you need to update all your code and dbs all at once if any type changes. Because it's all linked.
In my experience this is not feasible one you reach a certain size. You need to be able to upgrade parts in isolation while keeping the majority working without touching it.
In a well typed system, you can choose how deep you want your changes to go.
Let’s say you choose to rename a field in db, I.e. “imageUrl” -> “profilePicURL”. Upon making this change, you will receive type errors on the client consuming it.
At this point, you can make a choice. You can go address all the furthest end consumers, which is what you imply is necessary here. You can just as easily re-shape the data being returned though.
return { …user, imageUrl: user.profilePicUrl }
I firmly believe we’re nearing the “best of both worlds” here :)
having no slack means you get told about this problem during compile time, rather than having the need for an experienced developer who understands the entire system and is able to do the above without the help of a compiler.
It was also the base type for all DirectX interfaces. It effectively provides type info for a binary blob with implicit version information that could be used for extremely robust runtime data-typing. However there's all kinds of overhead associated with it, especially if the interface needs to be parsed constantly.
Type safety is not even close to delivering good software.
Now I think inference works in one direction only (at least in languages with HM type interference), so this is a non-issue. But I'm often uncertain.
Anyway, in languages with optional typing like TS, I usually specify the types in function signatures manually to be 100% sure I understand what is going on.
I only recently picked up typescript, coming from decades of .Net and later a Python, and the way you can turn things like json results into typed data via interfaces while also having the vast open source libraries I’ve done to love from Python, I gotta say it’s just been so easy to work in an enterprise environment where the source data is often really terrible.
I can certainly follow the points of this article. It is sort of silly to type an uuid as a string, but when does it actually break?
Nominal types fix this. Typescript doesn't precisely implement them, but its "branded types" seem to be close enough for most purposes. For further discussion and a sandbox example, see https://www.typescriptlang.org/play#example/nominal-typing
I was lucky enough to get some awesome eyes on it early, including a handful of people from Vercel. I hope this sparks some good discussion!
Racket is able to automatically convert static types of Typed Racket into contracts when values flow between typed and untyped worlds. This happens automatically and transparently, which means you don't have to worry about almost at all. One advantage Racket has over JS is the module system (well, it holds the same advantage over almost all the other languages), which allows typed and untyped code to reside in the the same file, yet have a clear boundary between them.
I can't find it right now, but there was a paper describing how it works. It's probably somewhere here: https://github.com/samth/gradual-typing-bib (if you're curious enough to read many tens of abstracts...)
Discipline is very needed because of many factors, last but not least the fact that there is an abundance of type unsafe apis (e.g. JSON.parse).
The worse part isn't even that you cannot have total type safety imho, there has to be compromise for a language willing to be a JS superset. It's how difficult it is the type system when more advanced patterns are required, often the things are completely impossible.
Still, I would never do front end on a different language, and I do love elm and other type safe alternatives.