Building Fluent Interfaces in TypeScript
HTML-код
- Опубликовано: 17 окт 2024
- TypeScript has many great tools for building great fluent interfaces! In this video, we take a look at some of the best ways to do that.
Accompanying blog post: shaky.sh/fluen...
Mastodon: social.lol/@shaky
That's a topic I really love, it makes programming with TypeScript feel magic. Unfortunately, couldn't understand really how to implement that. I'll for sure read the blog post, and I'd love to have more content on this.
Thanks for the feedback! Is there anything specific that was confusing or that I didn't explain well?
👏 holy crap that piping explanation was great .Thank you. Subbed!
Nice explanation! Probably TypeScript is the best language for fluent interfaces because of its flexible type system
Thanks for this vid! I always forget that functions can also have properties on them and this makes for interesting patterns like this :)
Great video! I love when great typing improves the overall developer experience a lot! Keep on going!
I wish I saw this waaaaaaaay earlier. BTW, for the table function in the last example, you can actually use it with just "return this as QueryBuilder".
This however will not work for versions less than 5.1.6. Library authors seeking to support older TS versions or projects stuck on such codebases will have to create a new object unfortunately.
Great job! Where was this video when I was starting to get my mind around how Promises 'really' work? 😊
@Andrew Burgess can you make a video on types for currying and composition if it's possible.
this is a great idea, thanks!
your channel is gold
This inadvertently solidified my understanding of effect.to haha,because you “run” an effect after you defined them, and they are not executed at the time they are written.
That table method to update the type is also used in trpc’s meta(), and Zod too I believe, common pattern used in libraries
This is absolutely fantastic!
Great video and blog post. Succinct and very informative!
Great job dude !
Doesn't this risk hurting tree shaking e.g. zod vs. valibot -- i.e. if not done carefully it can blow up client-side bundles? Either way nice video thanks for sharing your knowledge :) These interfaces do have their place!
With the query builder example how would you make it show a TS error when you call more than 1 select method. And how could you limit the callable methods depending on the stage of the query. For example the only option should be the select method when you type "q." . But after you do that, then the only option should be the from() method, when you type "q.select()." ? Would be cool to see how this is done, similar to how drizzle have done it.
You would split your logic into multiple containers. You could have build the initial QueryBuilder class with only a "select" method. This select method would then return an instance of another class, lets call it SelectState. SelectState itself has only a single method as well, "from" which returns another container (FromState for example). you continue that logic and only implement the methods that you want to appear on the containers query. Through all that you propably have to pass along the previous containers or generic types from those containers. I can try to setup a minimal example and come back later.
@@xDivisionByZerox Yeh this makes sense. I assumed that how it would be built, and yes I would love to see a minimal example of this.
It doesn't necessarily need to be a separate class. Another way you could go about it is by down casting your return value with 'Pick'ing the functions you want for the next chain link. You could even use the 'extends' keyword to inspect the input to return branched chains.
isnt this pipe chaining super hard on linting performance?
just remove the linter
or
just wait a bit longer, as long as the code gets cleaner and more human readable who cares about 1 sec extra wait time
I will revisit the video again after reading blog, it was a bouncer after the pipe function xD
your damn right i want you to go through how your building that query builder with that typescript black magic! easy sub.
In Rust it's pretty straightforward to create different states within the builder object so you can for example prevent (at compile time) calling select more than once in a single query (if you haven't created a subquery). How might one do that with typescript?
It is possible in TS, but would be quite messy and complicated.
@@mk72v2oq Im pretty sure u get TS errors when you do this with drizzle
You make use of a state generic where the output is a transformed version of the input, but usually following the same specs (T extends {..}, U extends {..same}). By saving the literal value of what you change into the output generic, you can hide/show properties or methods in the chain. It's not that hard and writing it once makes you understand a lot more type definitions.
Your returned 'this' can most easily achieve this using a helper type ToggleMethods, for example.
I think the easiest way is to represent every state as a separate type.
Yes, you can construct types on the fly with Pick/Omit etc. But that makes consumer's life miserable in case they want to pass the object around and need to specify the type.
ok my comments keep getting deleted because i tried to include links :( . anyway i was linking to "The Typestate Pattern in Rust" by Cliff L. Biffle. You can search for it. What I'm realizing now is that the biggest thing that's possible in Rust but not Typescript is the ability to specify to the compiler that an object has gone out of scope or been used up or transitions to a new state.
In Typescript you can correctly transform into the next "type state" which only has the correct methods to call. However the object that you transformed is still a valid object that you can transform again. This is fine for builder type patterns where the methods aren't actually doing anything until executing the "finalize" method. However, in scenarios where the method calls are actually doing work/side affects and the "type states" represent real world state being able to "revert" to a previous state can result in bugs.
Does anyone have any thoughts how you might prevent this regression to a previous state via compiler errors in Typescript?
i recently tried to do sth similar for my own trpc and this would have helped
Nice tutorial 👍
Excellent video
The main difference between OOP and FP for "fluent interfaces" is that you should understand that in OOP most of the time you "mutate" the instance, so there's no easy way to "clone" it. In FP, each .pipe will create a new function, so it's easy to split the same chain to 2 or more cases.
The main example why "mutating" is bad is preparing `list` endpoint where you are doing COUNT() in one query, and limit/offset in another. I FP way you just add base query and splitting it to two variants: count and data. With OOP you should implement own "cloning" or create 2 queries with the same conditions
my brain is not braining right now with all the A, B and C
Definitely a mind bender
It's literally algebra. Literally.
I was a bit discouraged by the first simple example, but I had faith, and watch until he very end
The pipe brings kotlin's apply to my mind.
It is similar to builder pattern.
it IS the builder pattern!
@@CaldaronIt's the upgrade form of the pattern. With each call, your options narrow down accordingly to what's permitted.
@@parlor3115 it is a builder pattern
I think this might be the basis of, Drizzle orm
Nice video
So a builder pattern?
I would love to receive ur Blog posts as rss
Thanks! You totally can! shaky.sh/follow/
@@andrew-burgess thank you! Looking forward to more posts. Btw this was a great video, a perspective/topic I haven't seen on yt yet.
Rust is king in this pattern
Called builder pattern
Never do this if you care about load performance and bundle size. Because you can't treeshake any of the methods on a class. Just create simple standalone functions. As reference look at the benefits of `valibot` over `zod`
Interesting point. I’m curious if there’s a way to have the best of both worlds.
Perhaps a Babel-like build tool that can detect which Zod methods you actually use, and replace Zod with a stripped down version containing only those methods at runtime. I wonder if anything like this exists.
Don’t throw the baby out with the bath water. It all depends on the size of the library.
isn’t this just the builder pattern..?
Yes, but fluent. Also called fluent builder pattern
Man, does this make me feel stupid. With all the generics I am very confused. Alas. Off to go read the blog post. Maybe that will make me feel less dumb!
Oof, not what I want! If you have any feedback on how I could have explained this better, I'm always open to it! Thanks for watching & reading!💚
pipe is a monad!
Keep the facial hair. It suits you ❤
interesting
Good luck with debugging such things....
FC
f classes
So dumb lol why not just write sql in the database with stored procedures???
How about an implementation of QueryBuilder that has a strongly typed build() function? tinyurl querybuilder-stringly-typed
oh, man, that's so cool, I've never thought of that! love it, thanks for sharing!