šŸ’¾ Archived View for dioskouroi.xyz ā€ŗ thread ā€ŗ 29419928 captured on 2021-12-04 at 18:04:22. Gemini links have been rewritten to link to archived content

View Raw

More Information

āž”ļø Next capture (2021-12-05)

-=-=-=-=-=-=-

An Inconsistent Truth: Next.js and Typesafety

Author: theobr

Score: 103

Comments: 46

Date: 2021-12-02 18:07:23

Web Link

________________________________________________________________________________

mirekrusin wrote at 2021-12-02 19:53:39:

Types can be asserted at runtime (parsed) at IO boundaries (reading http request or response, websocket message, parsing json file etc). Once they enter statically type system they don't need to be asserted again.

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.

[0]

https://github.com/appliedblockchain/assert-combinators

[1]

https://github.com/preludejs/refute

the_duke wrote at 2021-12-02 20:35:40:

That's only partially true for Typescript and requires a lot of discipline.

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.

mirekrusin wrote at 2021-12-02 21:15:34:

We're lucky where no- or very shallow- dependencies for our backend services are possible.

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.

frenchyatwork wrote at 2021-12-02 20:43:50:

Some of those issues can be remedied by linting rules (preventing unsafe use of any), but typing of dependencies can be a headache. Even 1st party typing and Typescript dependencies can regularly be wrong if type safety was not important to whoever wrote them.

mirekrusin wrote at 2021-12-03 12:04:40:

Absolutely, some of linter rules are very useful, ie. type-aware ones.

jensneuse wrote at 2021-12-02 19:54:03:

Very interesting topic, actually it's one of my favourite ones.

I've been working for the last year or so on the problem to improve type-safety for NextJS. My solution works like this:

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

theobr wrote at 2021-12-02 20:01:45:

This is super cool and Iā€™m excited to dig in more

Any chance there are some demos building a full stack app on this tech?

jensneuse wrote at 2021-12-02 20:10:44:

Hey, thanks for you feedback. Yes absolutely! Here's an example using Apollo Federation, REST APIs and a standalone GraphQL API: [0]

Alternatively, you can also just run "wunderctl init --template nextjs-starter" to start with the NextJS template. (Obviously you need to install it first: yarn global add @wundergraph/wunderctl@latest)

We're going open source with this solution soon. So, any feedback is appreciated! You can also join our discord and shoot questions. =)

[0]:

https://github.com/wundergraph/wundergraph-demo

SlickStef11 wrote at 2021-12-02 20:23:52:

Super cool! Will be checking out the documentation!

kansface wrote at 2021-12-02 21:49:17:

How do you deal with multiple versions on the back end - like during a migration, or a partial migration, deploy, etc?

striking wrote at 2021-12-02 21:51:14:

If you're using GraphQL, you never make a breaking change (or make that breaking change only after all clients are proven to have stopped requesting the broken field)

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.

theobr wrote at 2021-12-02 18:11:12:

Hey yā€™all! Iā€™ve been a big nerd about all things React, Next and Typescript for awhile and wanted to formalize some of my thoughts and frustrations into this article.

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!

markharrisuk999 wrote at 2021-12-02 20:59:15:

Is there any reason why you didn't use BlitzJS? Where, I think this issue is largely solved with Typescript?

Sophistifunk wrote at 2021-12-03 00:39:09:

That's nothing, there's also implicit boundaries across which NextJS will convert your objects to JSON, and NOT CONVERT THEM BACK AFTER, while pretending there's no change. Have a date in your object? Not any more pal, it's a string. You return an instance with methods in it? Wanna bet? How about a Map or Set? Guess what? Nah.

diroussel wrote at 2021-12-03 20:18:43:

Which boundaries are these? Iā€™ve not hit them. But then I donā€™t use the api features.

SavantIdiot wrote at 2021-12-02 22:28:31:

It will happen. Eventually runtime / io typesafety will happen, just not yet. A good example of how this was solved in the past was Microsofts IUnknown interface where the type of a blob is determined after it is downloaded. It basically converts unknown to a type on access, but is just too heavy duty for bandwidth requirements in a runtime/JIT environment.

DaiPlusPlus wrote at 2021-12-02 23:09:39:

IUnknown was the base type for all COM interfaces. What does that have to do with downloads?

SavantIdiot wrote at 2021-12-03 01:03:00:

I was referring to memory bandwidth and processing bandwidth, not download bandwidth. Re-reading I can see how that is confusing.

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.

merrywhether wrote at 2021-12-03 07:20:09:

My biggest gripe with inferred types is that you wind up with errors at random usage sites instead of at where the actual type ā€œerrorā€ is. For example, with inferred return types you can wind up returning `a | b` when you thought you were returning `a` and this might actually be coincidentally fine for a bit until you use it somewhere that only takes `a` and you get a type mismatch. You can then trace backwards until you find the source of the bad assignment, which can be a few layers after a bit of inference, and this always felt antithetical to the improved ergonomics of types. As a result I always enforce explicit return type annotations with inference only within scopes. But TS happily supports a spectrum!

captainmuon wrote at 2021-12-03 09:37:49:

Yeah, type interference makes me paranoid. I'm always afraid of this kind of action at a distance. Say I have a function with inferred argument types, and I normally use it with an `int`. Then in one place I call it with a `float` argument. I worry that this will propagate back and cause a lot of variables to change to float, and thus change the semantics of my program.

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.

epolanski wrote at 2021-12-03 09:32:56:

As someone who writes fp-ts regularly: TypeScript has way too many ways to be type unsafe. Discipline and parsing API boundaries can give you typings you can 99% trust.

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.

klibertp wrote at 2021-12-02 22:51:55:

with an implicit type contract (_potentially generated_) through the creation of these files

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...)

moksly wrote at 2021-12-02 19:48:36:

Donā€™t you need some pretty solid source data for you to trust them?

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?

throwanem wrote at 2021-12-03 01:21:23:

When you don't get warned that you assigned or passed something that's a string but _not_ a UUID, because the type checker can't tell them apart.

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

terracottage wrote at 2021-12-02 21:06:50:

The one thing I would ask this person is how big and complex a system they've built like this.

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.

theobr wrote at 2021-12-02 21:11:34:

Without a strongly inferred type system, all of the things youā€™re describing around ā€œchanging everything to change one thingā€ are just as much a problem.

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 :)

chii wrote at 2021-12-03 03:48:43:

> You need to be able to upgrade parts in isolation while keeping the majority working without touching it.

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.

eurasiantiger wrote at 2021-12-02 23:10:51:

With GraphQL, the idea is to never change existing behaviour, but to use directives to flag them deprecated and direct devs to change to using the new type/field/query/whatever. This gives a guarantee about not breaking existing things.

crate_barre wrote at 2021-12-02 20:02:52:

What is the value proposition of all this for simple apis? The author is using one of the simplest examples, and almost all of the proposed solutions other than the first problematic one read like obtuse technical literature to me. All for what, so I donā€™t fuck up the ā€˜idā€™ param on a hello-world fetch request? What does this stuff look like on anything seemingly more complex (my best guess, itā€™ll look _more complex_).

theobr wrote at 2021-12-02 20:09:57:

You should check out the video I link at the end - the most complex problems have the exact same solutions when your types are inferred :)

crate_barre wrote at 2021-12-02 23:09:49:

Yeah, I still donā€™t get it. This all seems stupid, sorry. But nice post, keep at it, I donā€™t want anyone on any of my teams bringing this nonsense to work.

Type safety is not even close to delivering good software.

gitgud wrote at 2021-12-03 03:31:03:

I recommend "next-rpc" [1] which is a small library allows the nextjs client-side pages to call the API function using a type-checked function interface.

[1]

https://github.com/Janpot/next-rpc

kaufmae wrote at 2021-12-03 03:54:02:

Iā€˜m sorry but this is exactly how you should NOT do it unless youā€™ve lost your mind in type limbo. Itā€˜s ideological overarching for totalitarian TS. After that, the code looks cluttered and the amount of typing syntax compared to the benefits is ridiculous, even with inference. Donā€˜t waste your time typing the shit out of your js, use ts selectively, use it for the signatures of your own pure functions and move on.

crate_barre wrote at 2021-12-02 23:17:25:

The whole thing is not worth it. If you just read the article you can tell itā€™s nonsense. Weā€™re stuck in un-imagineland, where no one knows what to build with the tech they learned.

So they build, tech, with the tech they learned?

Weā€™ve entered post-modern programming. My Albert Camus. This is pure absurdity.

Fine, so be it, Iā€™ll be your muse and your director:

Show me how this works in typed languages (Java/.NET) how your bullshit Typescript works in actual typed languages. And then show me why you are, that you exist, to be extentialist as possible.

Does Java type shit out like that? I donā€™t think they do. Show me that. Stop whoring JavaScript into being a hoe that itā€™s not. I never wanna see Ruby with lip injections and makeup. Donā€™t whore out my languages, fuck off.

Edit: JS is a dynamic language. If you donā€™t like it, invest in your your Rust compilers and interact with your DOM that way. You wonā€™t make a UI worth half a shit, Iā€™m sure of it. But please, pick the right hoe for the job. But stop making my girl look like she got 10 plastic surgery operations. Iā€™m not down with this at all.

theobr wrote at 2021-12-03 00:01:52:

This reply alone makes all the effort Iā€™ve put into my stack worth it. Thank you :)

crate_barre wrote at 2021-12-03 00:07:45:

Youā€™d have to explain, I was certainly being vitriolic, but I hope HN hasnā€™t lost its poor taste for theatrics and poetry - granted, as a moron, I believe everything Iā€™ve said.

Edit: is it so much to ask to not want to write code that looks like gibberish? I donā€™t care what the benefits are, it looks like gibberish code. Can type safety be accomplished without gibberish code?

I think in Java it goes something like this:

Function (String s, int num)

Ok, Iā€™m on board. You Typescript bastards are writing gibberish code and I donā€™t care who disagrees.

bmeski wrote at 2021-12-03 02:03:09:

I agree that this stuff is getting out of hand.

The most important part of all that code is the part in the prisma calls, which would handle all the caching and request logic.

The rest is literally just making sure you name your stuff correctly and render it.

If you want actual type safety for your large client-side app, use rust/wasm. If your client side app is small, why do any of this?

theobr wrote at 2021-12-03 02:56:17:

Point me at one fast moving ā€œlarge client side appā€ that uses Rust via WASM over Typescript and I will move my entire company over

techpression wrote at 2021-12-03 07:56:15:

Not Rust, but Figma (which everyone seem to love) is using C++ and Web Assembly and they seem to be moving quite fast. I guess itā€™s not the entire stack but still, seems viable for them.

theobr wrote at 2021-12-03 12:45:19:

They use those things for some very specific math-heavy transforms and rendering - still all Javascript for the UI. Iā€™ve used WASM for some ffmpeg-in-browser stuff too.

The belief in WASM as a method to build client-facing software is an amusing self own. There is literally no production examples because it is not production ready for dom-heavy stuff.

stillblue wrote at 2021-12-03 02:50:36:

Hahaha. As someone who has been exhausted by both JS and TS for a long time now, I kinda agree with you man. I don't know, maybe TS is the only solution for making JS tolerable, but this is all so exhausting!

nsonha wrote at 2021-12-03 01:40:15:

> Java/.NET

> actual typed languages

lol, those type systems are garbage comparing to typescript, if you even understand what a type system is, it's not the same as making toy hierarchies in OOP

> Rust compilers and interact with your DOM

> pick the right hoe for the job

More evident of you knowing nothing of what you're talking about. There is a push for Rust to do this kind of thing, but it's far fom the right tool for the job.

Rust's focus is memory safety, not expresiveness (eventhough it can be), so it's never the best tool for the job of making applications, on the DOM or otherwise. You can try to do it but it's best fit for system programming.

The best programming language to make application on the DOM is Java/C#

/s

crate_barre wrote at 2021-12-03 01:47:41:

Yeah, some intern at Microsoft made the greatest type system ever /sarcasm

Itā€™s alright dude, Iā€™m not even hating. Donā€™t let a type system destroy the style of a language though.

nsonha wrote at 2021-12-03 01:50:38:

what intern? the same guy who created C#?

crate_barre wrote at 2021-12-03 01:54:38:

Fuck it, I donā€™t know who made it. Shit sucks in excess, just use a little bit. The OP is using it like itā€™s ketchup on old McDonaldā€™s fries. I get it, JS sucks, but your crappy JS sucks even more with drenched awful Typescript ketchup that looks like unreadable gibberish.

Nothing makes me lol more than?.a?.conditional? null check in a ā€˜type safeā€™ language. Just stop it, use C# Blazor to write your Frontends and call it a day. JS is not (your) tool for the job (because oh god, what if you donā€™t know a param is a string, lord have mercy, how did the web even make it this far without this type safety brilliance?).

Enough.

nsonha wrote at 2021-12-03 02:00:27:

that's hot garbage, you don't like JS and TS that's fine, but the only 2 types of people who think TS is worse than JS are: 1. Veteran JS devs who don't want to learn, which I don't think you are. 2. People who've never had to work on a crappy language (JS) at large scale in which development tools are blind folded in providing static analysis.

> Nothing makes me lol more than?.a?.conditional? null check in a ā€˜type safeā€™ language. Just stop it, use C#

And get null pointer exception? No one forces you to be stringent on typescript, use a nullable type if you want, but saying TS is crap because it's actually better than C# at static analysis?

crate_barre wrote at 2021-12-03 02:10:12:

All I want is that you guys take it easy on the Typescript. Keep the JavaScript recognizable, thatā€™s all. Weā€™re all friends, right?

stillblue wrote at 2021-12-03 02:51:54:

I think the whole point is to make it unrecognizable enough so it looks like a real typed language.

theobr wrote at 2021-12-03 02:51:01:

The solution I recommend (tRPC) allows you to write no typedefs whatsoever lol