TypeScript Berlin Meetup #2: Generics, Conditional types and Mapped types

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] [Applause] okay yeah thanks for the intro young so achieving state-of-the-art developer experience by combining generics on Krishna types and MEP types you will see that there's not a seminar about concise talk titles but let's make it a little bit more concise its this is basically a deep dive into advanced types in typescript so Who am I to talk about advanced typescript I'm Tim I'm working here at Prisma and I'm choosing typescript for a couple of years now and implementing basically an database driver called photon Jas and I'm responsible for the typescript part of it so let's go back a little bit in history or very recent history actually which is January of this year we knew that we wanted to do the next version of Prisma and we knew that mapping the data based on what you're actually looking for based on the query to your programming language it's a hard problem and there's not really they're not really great solutions out there and let's assume we have a schema like this so with Prisma you basically define a schema and a scheme out of Christmas file so you we say in this case we have a model user a user has posts and pulse has certain fields like a title and fields can be optional and the question is now how can we create this data how can we map this to typescript and so we were thinking a little bit about Dream api's how we would like things to look like and one dream API was this that we basically say I just say photon fine one give me this particular user back and I have the user type but this is simply you will see this kind of examples on all kinds of RMS but what is a little bit more tricky is if we say we are only interested in the ID and what would be awesome that is the dream API here is that it would not say we only get back the ID in typescript so we have type safe access or we would say we also want the email so that basically typescript would dynamically generate the correct types for us without any type generation and while we thought this could work is because since types of to eight there's a feature called conditioner types so you can basically say when I have a specific input in my function I want to have specific return type with these conditional types you can define like an if-else on the type level and after about one week of research the answer was no it doesn't work after another week of research the answer was I works because why was that so hard to find out because there are no examples about this out there and we will dive into some more complex typescript concepts and this is like some big are nested type script statement here and my goal for today is that you will understand the statement at the end of the talk and that you are able to actually use this stuff these advanced types there and I call them advanced because in the typescript talks they're listed as advanced I hope that you will then be able to use that also in your project and see how this stuff can be useful so in order to really get the value of this let's let's start with the demo oftentimes people and the talk with the demo after they motivated why the tool is awesome we do the other way around we say the tools awesome and then we reverse engineer how can we build such a tool so in this case we for example say photon can you see that in this case we say photon please give us to use us and we are only selecting the ID and the name so we see in the resulting output type we also only get the ID enter name if we now say that we're not interested in the name anymore we see that actually a type script is totaling a type error here and it says name is not a thing anymore because we are now only getting back the ID however scalars simple and what we're actually interested in is querying relations when we have a relational database on the hood another hood and we can say now here the posts we also want them and for the poles where for example interested in the ID and the title we could also a query for something else let's say we're also interested in the content so we say content true and we also get the content of the post so like this we now have types I have access to our data and as you see we have some kind of query language in typescript we're using typescript as the query language to get types of safe access to our data so this is basically what we want and and by the way we should also be able to run this just to see that it's working yeah so we get exactly also in JavaScript and obviously back what we're defining here so depending on how this query object looks like and as we see we have this select object here which is defining the shape of the return type let's go back yeah so and in order to do this what we are pretty much doing here today is type level programming and what that means is that if you have a concept like a variable in your normal runtime and how you're programming we also have an equivalent on the type system level and this is not 100% whatever prove two people could find flaws in this but if you want to see a rough if you want to have a simpler mental model here you could say that variables are roughly achieved with generics on a type system we can have loops in the type system we would see maps types that are achieving that we can have something that is innumerable something that we can loop through which is a union type in our case we can have if-else statements and in a type system and in typescript which are conditional types and we can basically perform an object or Keys statement on a type and that is just the key of keyword that many people probably already saw so and what we're basically doing now is we're the same as in a math lecture we're stating we're starting with the basic axioms and start a step by step we're building up these more complex concepts for example what I just showed you the generic ISM generics are still a fairly simple thing but then later the I would say the most complex concepts here really are map types and Conditioner types so let's start with the most simple atomic concept here which is a string literal so what we can say we can say that we just have a general string and and the second variable here's STR is a string we typecast the snow on the string and that's it but we can also say that it has to be a particular string in this case a hello and why should this even be useful this doesn't look very useful like this however as soon as you are able to do a union over these types it gets useful so we can now have a Union and a union does a type that kind that's that's a term that comes from set theory so we you can see this basically now as a set so the string literal Union which is nothing else than just two strings this is now a set and we can now say that this variable hello it's a correct instantiation of that set because hello is part of the set and the same for world world as part of the set and so where are these string literal Union types no interesting for us for the key of a statement so let's say we have a type user like so and we now want to do this type level object at keys operation on it and what we can just do we can just say key of user and we will get back again a string literal Union I will in from now on just say we get the keys because string literal Union is a bit weird word and so this is an important ingredient for our nice soup that we cooking here which is the beautiful API so we now have we have string literals we have unions we have the key off statement let's look into generics you can think of generics there for sure a thousand definitions but you can think of them as a type parameter to your function so if I know let's say I have a function that takes something and it makes an array out of it and by the way why why is this type parameter interesting so let's say generics wouldn't exist then I would now have to define this concretely for Strings if I would like to turn a strings into an array and if we wouldn't have generics we would also have to do this for numbers and so on and in order to save this effort we obviously want to have a generic how the term says generic we won't have a generic definition and this type parameter helps us here because we don't care what kind of type that is we just say this is we just turn it into an array and examples here are we want to make 7/2 an array so we just have an array of 7 so we get an array of number or we if we pass in a string we get an array of strings so very basic concept and note the type parameters were important here and what you see this is called T so this is basically now a variable in our type system and we will later use this a lot in order to calculate more complex types now on to an task up to 8 this concept of the conditioner types have been introduced and the idea is basically so again we have our T here the idea is basically that we say if T is a you then please return X if it's not a you return Y so we can use these conditional types now on a return type of a function and to make this a little bit more real-world let's actually look into an example so let's say we have a function this is not pretty formatted but we have a function X and he can either be hello or world if it's concretely if we say t extents hello and extends in this case you can just think it off think of it as equal its the equal sign triple equal probably in in JavaScript on a type level so we say if T is hello then return Melo rather where otherwise return I don't know that word so that's a simple ternary expression that we have here it's the same as we're in FA as if in JavaScript we would say question mark and Colin so it's a if-else statement that we have here on this type level now and you can also have something that is a little bit more complex we could say let's cook a soup and we if we have a potato here if T is a potato then our soup will be a potato soup if T is as a pumpkin and we want to make a pumpkin soup out of it and now we see this keyword called never here and never is basically in typescript it says it's basically type error so that's what that means it's not a correct type that is it's nothing it's not a thing and this never type will later also be used a lot so now let's actually implement this query that we just saw earlier in the photon API and we may for example say we want to implement the find many user query and we saw earlier that with with a generic type parameter you can pass it in like this you can put it there in one of the the function definition and what we do now additionally we put in a base constraint so we say that T is shouldn't only be any T obviously we want type safety people shouldn't just provide any object there we say T concretely has to extend user arcs so now we just thought about it okay we're implementing this API we add this T extends user arcs and we think we're done with our job we can move on to the return type of the function it's not that simple problem is if we are now thinking about hierarchy and inheritance at T so as all properties of user acts are optional first of all an empty object would be a concrete would come correctly inherit from user arcs but also an object that is empty plus it has some other properties on it that's valid in type inheritance so what happens now that users can provide any arbitrary field here for example random prop and it would be valid and this obviously is not what we want what we want is that we're only allowing the properties that that make sense so how do we solve this now because we somehow need to access this t thing but this extent is not good enough and if we would now not access the T anymore but just directly say this has to be user arcs this also doesn't work so you could not say okay talk over sorry it doesn't work but we have another trick which we can use and that is this in your argument definition of your function you can again have basically a typed parameter riced type in our case we say we for example use a utility type I will later show how how it works called subset and we are now again narrowing down what T can be by this trick here so we say not only it doesn't have to extend user arcs so in case that user arcs for example would have a required field it needs to have that required field but also it must not have more than that and now we will get a proper type error but now how does the subset type work it looks like some magic here and in order to understand subset we go into the next concept which are map types as I said earlier map types are a little bit like a loop in in in typescript and let me explain this map type here called pick pick is ship in typescript and the standard lips of you for example having es5 as lip enabled the pig is just a global type you can use and how does this one work so what they basically do they basically say k equals key of T so this is pretty much again a equal sign in this case not the triple equal but a one equal sign so it's basically a variable assignment so they take something out of T in case in this case the key off which we saw earlier and in the end is really just taking the keys of T and assign them to K and what we say now is that similar to how we would define object in JavaScript we define it object type here and we have this in statement here and the in statement basically says we are now looping through all the keys of T and in our loop our variable that we are getting the key that we're assigning the key to in that moment that variable is called pp4 property so we can now look through all keys and we are packing out basically the T here and obviously K can also be overwritten and you could say I just have a subset of the keys of T and so you can now with this pick out certain properties of that type the ones that you want to use so this is a standard mapped type that you have in the standard lip and but we're interesting and interested in subsets so how does subset work this one is not in standard lip but how does it work so the idea is basically that we are again looping through the keys of T and we say that if this particular key is also in you then pick a packet pick it out otherwise put never on it and never again on the type system level means throat away of filter and if we would now write a similar code like this in JavaScript this would mean we're basically performing a dot filter operation and the if you the the equivalent of returning false in this in the filter callback is us having the never in here so like this we can now filter out the subset of a type we say give me only the keys of T that are also in you so that's basically the idea here and here's a quick example again we do the key off and but I think I already explained this how the key off works so now we get a type error and we're done with the arguments that's nice and now comes the hard part actually which is return times return types so now we will actually use conditional types and we saw earlier in the API that if we are not providing the extra select keyword we want to have a default behavior we just want the user and that basically means what we are saying is saying here now select extends key of T in other words is there is there that's the property select occur in this type if yes we do some magic we are diving into into that later if select is not there we don't care then we always know it will just be a user and we just return the user and the whole complexity now comes in this magic part and the idea is that okay we know it will definitely always be a promise of an array because we know we first need to fetch the data from the database so it is a promise it's an array and in there we now have a type and you can't just think of this type as a function because it has input and it has out output so you could just say it's basically a function on the type system level and this function will now help us to extract based on this what is defined and select in concretely the type of this is user select and this function will now help us to extract a correct return type based on what has been provided here and this user select so let's dive into how this type or utility type works so we say as we said earlier the the ester the type parameter here needs to correctly extend the user select otherwise we cannot work with this and what we're doing again here is a map type we are looping through something and what are we looping through we have something here go a called get truly keys and what we are only interested in and and what what does this mean get truly keys let me show you a quick example so what we must what we need to accomplish now is that if for example name is false it will not end up in the result if name is true we want to have it in the result and how can we now accomplish that on a type system level we use utility function or utility type called get true the keys and house get through the key is defined what we do again we live through the keys and what we do now is we say if it's false put never on it otherwise just return the key and in this last statement here we are just basically filtering out and making sure that we only filter everything that is actually a key of T so we now have this utility function defined get through the keys and this is an example so if we would have an x where for example B would be false then a B would just be filtered out and again we have a union of string literals but in the end we just filter out the keys where the values are have a true types and now this looks already a little bit like Java Script again but we are really just talking about the concrete type called true you cannot only have a boolean as a type but you can also have a type called true or a type called false these are concrete valid terms so now back to our map type we have our get through the keys a function here and so what are we doing now we say that if this property extends key of use in other words if this property occurs in user and what we saw earlier is that the user type it's these are just a scalars in case I haven't shown it just to make sure so if we have type user here we just have to scale us in this case ID email name and we're basically saying we're basically saying if this extends if this is part of this type then just pick it out of that type and we're done however if it's not part of this simple scalar types and we have to look into some more recursion here concretely we know a user can have posts so if concretely this property is called posts again the extents is nothing else as a triple equal sign if that property equals pulse then let's go into the next function and this next function would work exactly the same for the post type as we have for the user type so it would again loop through all the properties check is it scalar or not if not then it's a relation and we again need to go into more recursion here and again to understand how does it actually look like in in when we use this we are now talking about this here so for the scalars we just have a boolean true or false but for posts we now again have a select object and then you can basically go as deep as you want okay so let's go to back to the original definition that we had here so we now go into how this user gets select payload utility type works and we basically just implemented a pretty nice API with the type safe database access so this was a lot and I hope I didn't lose too many of you I know this was a lot to cover and in the short amount of time I want to recap a bit what we just saw so we saw that the condition types help us to narrow down which concrete type this is in our in our return definition we have seen that the map types are basically the loop of typescript we saw a basic set theory we could for example use a subset of properties and so on so in the entities are always just set theory then we could see that we can just define these map types and conditional types arbitrarily deep now now you may wonder okay how can I use this stuff in my project you may not want to implement this style of API but still there are very interesting advanced types out there right now so you can for example extractive return type of a function you can pack out the if a type was wrapped in a promise you can pack this put this promise away basically and get that type or you can also omit certain keys or certain properties from an object so these are some nice utility types all of them are again in the standard library of typescript they are globally available you can just use them without import good so the goal was that you get a rough overview about these advanced types and I hope that you will find nice use cases for them in your project to be more productive and have a nice developer experience Thanks [Applause] [Laughter] [Applause] questions yes please [Music] so in this case what we're basically saying is if the posts property is there then we know we have to query for it where we also knows that we already did the promise resolving earlier we know it's already an array so we don't even have to care about promise anymore so because we already entered through this type here so we already have promised a write wrapped around this whole thing and now we're going deeper in there and there we are now we know that we will query this anyways and we can get the posts yeah so but to be fair this is a simplified version of what we have we are allowing some more stuff and select is just one syntax we also have an include syntax but it doesn't matter this stuff also works you can basically just concatenate the slides and try it out and whatever editor I mean yes because so first of all what happened in beginning of the year is that typescript it an update and suddenly this frog so now we have photon code actually as unit test and the official typescript code page which is nice but actually yes you need unit tests but I mean you can really just define your type statements run the typescript compiler the programmatic API of it for example so we use that together with chest and we just see is it compiling basically so yes that's a good question what I also didn't mention is that to make this happen we need cogeneration so there are a couple of forums out there which are completely defined based on a generic definition so you really just define your user type and based on that everything is calculated that's not the case here especially if you see this select payload type that needs cogeneration so there is no way around it with the current type script today you need cogeneration so that means how it concretely works is that you are source of truth is this schema Prisma file and as soon as you edit it we will regenerate the code no so in oh that's a good question and we can also show I can show you an example because we have a recursive type here already so you can say we have an author and an author again has posts and so on and you can select them so that is what I meant with because of ok the name is not there with a deeply nested recursion on some slide it is possible to have that indefinitely yeah so I mean it's a question if it makes sense to do this particular query but you could okay [Applause] [Music]
Info
Channel: Prisma
Views: 7,296
Rating: 4.9669423 out of 5
Keywords: Databases, Prisma, TypeScript
Id: PJjeHzvi_VQ
Channel Id: undefined
Length: 30min 12sec (1812 seconds)
Published: Fri Nov 22 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.