Advanced TypeScript Trickery - Open Mic Session #2

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
(soft music) - [Host] Okay, yeah, so how many people are we? 19, not bad. So, welcome everybody. Today, we wanna try it in Zoom to have it more interactive because with StreamYards, the software you used last time to stream over YouTube, we have to manually lift you into the stage, but here you can just talk if you have questions. So yeah, welcome to the second episode of Advanced Typescript of Trickery. And today we, again have a bunch of people already prepared who want to share some awesome TypeScript tricks or trickery with us, but also in general, we are always still open for people to spontaneously appear on the stage, so to say and share something interesting, can just be like a five minutes trick. Doesn't have to be like a polished presentation or anything, anything that you're excited about that's awesome. So, yeah, let's start. So we have already a couple of people, so to say, on the stage. Here it's not as in the previous episode where we are a bunch of people on the stage, but still, so whom we already have here whom I know is Simon Knott. He has been here last time in our last meet up. So Simon the founder of Quirrel and working on SuperJSon Blitz and all kinds of awesome stuff. We have Pierre-Antoine here who is author of ts-toolbelt. So he's is very advanced in his TypeScript and will also later share something. Same for Simon, Simon will also share something. And then we have Emil Fugaunot or also known as Sutton for the people who know him. On GitLab, he is one of our smartest and most active community members of the Prisma community. And he is also working on his own startup called Graph Metrics. So welcome everybody and thanks for coming. More people are coming. so if you miss anything, no problem. We are recording this and we'll also upload this. I would say we just start with Simon. Simon, how are you doing? - I'm fine, thank you. Yeah, it's awesome to be here. And I was asked to bring some interesting TypeScript stuff. So I thought about problems I had and I found one which is actually has to deal with SuperJSons. So let me share my screen. SuperJSon, if you haven't heard of it, it's a project that was born from Blitz JS which is a JavaScript framework and what SuperJSon does it serializes JavaScript types to JSon. And it's basically like an extension of JSon on Stringify. And one of the things that we struggled with a lot is being compatible to pre ES6. So we want to be compatible with Internet Explorer. And I think like IE6 is the thing that's the problem, I guess. I don't know really because I haven't had to support IE6 until now. And that's where the problems come from. And the basic problem we had is we used functions like object.values and that is not available in pre ES6 JavaScript engines. - [Host] So you need to qualify basically. - [Simon] Yeah, and like the thing is we built our own wrapper function around it, which now called values of object and that just drives, if there is values and object available, it will use object.values, and if not, it will fall back. And we can do this because in our TS config, we say to include or ES Next libraries. And that's why TypeScript allows you to use object.values. If we downgraded that and just said, instead of ES Next, let's just not have ES Next in there. It would, I don't know if it notes... It's not live updating, but it would not allow this. And the basic problem that we're facing is we want to have this at a modern setting because we want TypeScript to actually know that there is object values available. But at the same time, the thing with this year now is that TypeScript will throw, like if you don't disable all this stuff. I didn't know what I did to disable it, but if you don't disable some settings and TypeScript, it will warn you because it will say, "Well, there's always values and object. It won't be missing any time because you have set it to ES Next. It will be available. And the basic problem is we can either choose to lower our target to something that's actually ES 6 compatible and that's ES 5 compatible. And that way we won't be able to use object.values at least really TypeScript compatible way. Or we raise our targets up to ES Next and that way TypeScript won't be able to correctly assess our available functions in the target. And that is really the base problem. It's about supporting multiple JavaScript targets and this is just like a question for all of you, but like have you encountered similar problems before? What did you do to solve them? Do you think there's like, is there some TypeScript feature that I could use for something like this? What are your thoughts on this? - So my naive approach would be to just lower the targets to put it to the minimum I need to support. Then I'm done- - In theory, it should transpile it to... it should transpile the object.values. - All right, but there may be cases where we don't want to use to transpiles one. Say, I think we've object.values, it's not big of a problem, but there may be new standard functions that actually implemented natively and that are much more performance while we want to use the new ones and only use our polyfilled version as a fallback. - I don't know how TypeScript... Have you checked out how TypeScript is actually polyfilling like something like object.values? Maybe they are checking for existence of that feature, and then you would be fine. - We could try that, right? - [Host] We could try that. But I totally get your point, you don't want to unnecessarily polyfill it if it's natively available. - [Simon] So you say, if we lower our target, I think target to ES 3. - [Host] You can keep the ES Next in the... Yeah, yeah. - [Simon] Yeah, I that was a problem, right? We just need to put target to ES 3 and then it won't work anymore. I think if we put it to ES 6, then it will say... We load TypeScript. That was one, then it will just say that- - [Emil] In theory, it should only warn you if your live does not have values and object, but the target doesn't affect the warnings. - [Simon] All right. Yeah, so if we lower the target to ES 3 and run everything around build, then we can see that in this slash... What was the file U2.JS, the added one. It will... Values of object, it will not really transpile that one. Because it is available. Maybe because it inferred it. If we try this entrance pilot - [Host] But we had the same problem with flatten or flatMap Their TypeScript was not successfully transpiling it, although, we had target ES 5, and then we just use the polyfill for that ourselves. So maybe it's also a TypeScript back. But from my understanding, if you set the target to lower than where are the features available, TypeScript will polyfill it for you. So it might be a back- - It's kind of a weird problem because with other compilers, you don't have to support multiple target platforms. That's very much a JavaScript thing. - Yeah. Yeah and I think in Babel, you can then say, what is it? Less than 5% or something, right? - Yeah. Yes. - [Host] Yeah, it would be interesting. I'm pretty sure that's a issue of TypeScript somewhere to make this easier. If not, then you should create one. You get my up vote for sure. And so SuperJSon, is that running in the browser and to nodes? - Yeah. Yeah, it's fully... What's the cool word for it? Isomorphic, yeah. - Yeah. Okay, nice. Yeah, that's definitely, I think a problem that many library authors are struggling with. Pierre-Antoine, in case you are here right now, how are you dealing with TypeScript versions? I guess, in ts-toolbelt, you also require like certain TypeScript versions to be there. Do you just set that in the package.JSon or how do you make sure that people use the right TypeScript version? You are muted by the way. - I actually use this code most of the time. Okay, so yeah, I let the people do as they wish. The problem is when they do that, they can break stuff. I don't want them to use the TypeScript version (indistinct). - Okay, but like, when you say for ts-toolbelt, you tell your users that they need a minimum version, right, of TypeScript that they can use it, right? - [Pierre-Antoine] Yes. - Yup. Yup. Yeah, we have the same for Prisma Client because we recently added some fancy features also with the help of ts-toolbelt. And you need a TypeScript for that one now and you get a cryptic error on 39, but is there like a standardized mechanism for a package. For example, in its package.JSon to say which minimum TypeScript version it needs? - Yeah, you can use the peer dependencies but a lot of people complain about that. - Yeah, peer dependencies would be an option maybe, yeah. - Maybe there should also be like something like an "engines" field for the TypeScript compiler. - Yeah, exactly, because that's also what we use for node, the "engines" field. but it seems like the packege.JSon is the wild West anyway. The spec there, like if specs are around there and people just add some stuff. Like recently the React team added that you can provide where you have to serve our components and so on. Like if you have a package that can expose the server components part and the client part, they also added that, for example. Anyway. Yeah, that was cool. I mean, that's really a problem that I think many people have, so you can also keep us posted if you find the solution for this. Awesome. Yeah, I would say we continue with Pierre-Antoine. So Pierre-Antoine, for the people who don't know him, he is the author of ts-toolbelt, which is one of the most advanced TypeScript libraries out there on the type system level. And he has also helped us a lot in the recent weeks with Prisma when we made our types a bit more type safe or sometimes also just more better DX and yeah. Pierre-Antoine. Do you have something you want to share with us? You are muted. - (indistinct) it's horrible. Share. Does it work? - [Host] Yes, we can see your screen. - [Pierre-Antoine] Okay, so a problem that a lot of users, people usually need to access... How can I say this? Properties that are nested within each other and actually today in TypeScript it's quite difficult to this. So someone gave me a challenge to be able to type and get utility to access properties and its type state at the same time I'm just going to put the get utility here. You can take a look at it. So there's get utility is quite simple. You give it an object of type O and you give it that path. And then they get utility returns you the value of the type of data from the figureheads. So that's go prop=get and then we have an object. A user object for instance. and then you get, automatically, you get auto complete on your objects, like on the properties, like you have a friend's property and you can look the props of that user 'cause friends or users obviously. And then you can access any kind of... maybe you have a thousand friends, so let's go look at the last friend you have. - [Host] Oh. - [Pierre-Antoine] And it tells you, okay, it's a user that can be undefined. But maybe it's not undefined, so we can as well continue. That friend that's sitting at the 1000, you can look at his name and then it continues working. Or we can even do more friends. Friends and then continue so on and so on. - [Host] This is next level shit. - [Pierre-Antoine] And the tricky part is that it's auto completes. It doesn't matter how deep you go, it will continue auto completing your types, and that was not possible before. But there's quite a few tricks. I'm going to say I spend maybe three days on this. So okay let's go through this. The actual magic is going through AutoPath this far in the utility. You give this AutoPath, you give it the object you are working on. But in this case, it's the user. And the path that you are typing is going to get passed to AutoPath. And then AutoPath is going to basically try to go to that path as you type. And when you press that dot, it will give you the next possible path. So if I type something stupid, obviously it can give me anything next, and it's even going to highlight me that it's not a valid path. So let's look at this AutoPath. I'm not going to retype it because it's so freaking complex. So it's quite simple, actually, the principle. we pass AutoPath, we give it a call the object, the user object is going to be called in here. We give it the object, we give it the path that we are busy typing, that's going to be complete. And we split that path that we're busy typing. We're going to split it in such a way that we split it on each dot. So when we go back here, we have friends.1000.friends. Then it's going to be split into an array, actually, that's going to be friends then 1000 then friends and 50 and so on. And thanks to this, we're going to be able to go in each part of the path and access it. So let's look at ExecPath. ExecPath is quite simple, it's what I explained. It uses a path utility. I'll demonstrate it here, it's going to be easier. (mumbling to himself) The path utilities are recursive type. I'll show you just now. Or you can give a split path, like I told you, and it will give you the... it will give you the type that sitting at that path. I could give them number as well, yeah. It's in an array. And then we can continue. It does exactly what the AutoPath does, but the AutoPath does something extra is that it auto completes you as you type, when you praise that dot, it auto completes. Okay. So when we look at that ExecPath, it actually gets the next path compared to typing tabs. So if your path is a valid path, this path utility is not going to return you never. And because of that, we'll be able to get the next path, which is encoded in a MetaPath that I'll show you just now. Let's take the MetaPath of a user... MetaPath, User... It's a bit abstract, but the basic principle here is that it takes the object user, one for each property, it will inline the name of the property on the property, directly - [Host] Into a union. - [Pierre-Antoine] That way, when we access the property, we can get obviously the name of the same property and we can continue deeper. Let's say, if I do MetaPath<user> and then I access friends, then the next MetaPath can be accessed, let's hope. (muffled by audio static) under the name. I don't know if I explained this, it's a bit- - [Host] What is the O again? Can you show that? - [Pierre-Antoine] What? - [Host] In line 32, what is this O? - [Pierre-Antoine] (whispering) Oh, line 32... - [Host] The last... Ah, you're just accessing the first element, sorry. - [Pierre-Antoine] But basically what MetaPath does, it goes all the way through the objects. Even if it's recursive, it inlines the path in such a way that we can dive in it and it prepares the next up by concatenating the previous path. (audio static) - [Host] We have... - [Pierre-Antoine] So yeah, that's it. (muffled by audio static) (static continues) - [Simon] We don't here you anymore just... - [Host] Ah, you also had white noise? (muffled by audio static) - [Pierre-Antoine] You don't hear me anymore? - [Host] I don't know who's- (indistinct) - [Host] Some white noise. (audio static) - [Pierre-Antoine] It is better? - [Host] Now it's better, yeah. - [Pierre-Antoine] Okay. So yeah, this is actually the complex part that processes the user and concatenates the paths replacing the actual properties by the next path. So when you access the property, you get the next path and you can go deeper and deeper. - [Host] Hmm. So are you reconstruct basically the path again for the auto completion on the other end. - [Pierre-Antoine] Yeah. - [Host] So you do two things. One thing is that you calculate the return type but also in the input type, you are typing that properly so that the auto competition works basically. - [Host] Yeah, the return type is calculated by this utility here, this whole path. It's quite simple. You give it an object that can be any object actually. I just use the extends any for documentation purposes, it's not actually used for running. And there's the interesting part. I don't know if you guys have used recursive types so far. It's been released recently, but I've been using them for quite awhile in the ts-toolbelt. So the idea of path here is that this type is going to call itself as long as this condition is matched. So the idea here is to try to go to the path that was given. So let's say type is 0 and then we go path, then we can... (mumbling to himself) Very simple. And then you put name here. Okay. But what it's going to do actually, it's going to receive the object, this one, name: string. And it's going to receive the path that's just going to name. So it's going to go through this list basically. And that's the condition here, that's the stopping condition. As long as the pos<I> is the friend of the length<P>, then it'll continue calling itself. I don't know if you guys follow, maybe I should- - [Host] Okay. I think important to notice that your extends function, line 18, is basically the true, when it returns true, it's the one, right? If it returns false, it's zero. - [Pierre-Antoine] Let's rewrite this so it looks more TypeScripty. (mumbling) Is the length of P. (mumbling to himself) So what is going to do is that the type is going to call itself, because when the position is not equal to the length of P, then this part returns zero. And when this part returns zero, it calls Path again. And when you call Path again, it calls this function that goes to the type at that part of the Path, which is in this case it's name. So we've got to At and we get the type again and then it will do this again and again. So if you put that stupid type here, path, you will just be undefined. Okay, but this- - [Host] This is smart. This is clever. - [Simon] So is this working because if it's an array, so it returns... like it narrates because it's an array on this type. Or is it just a special syntax for the... The array syntax online like starting from line 18 to like 22 is like that... For me, it's like... - [Pierre-Antoine] This array is a way for you to give the Path you want to access. Like, let's say if you At the... Let's put it again. - [Simon] No, I was just wondering like if it's a special syntax for like the stop, the recursive, or... 'cause like for me on line 15, in my head is like an object, it equals an object, an area of objects, right? The path, basically, that's what it is - [Pierre-Antoine] No. Oh, okay. Yeah, maybe they should have... - [Host] Yeah, that's a map type. - [Simon] I guess that's the map type. Oh okay, that makes sense. - [Host] So, you can access the property basically. - [Pierre-Antoine] Actually, the path is just a normal object, is a regular object here. We pass it the O object, that's this at the moment. Then we have the list of the path where we want to go to which come things name. And this is a tool of mine that helps to work with numbers. Like in TypeScript with you can't really do iteration. There's no full loops and all these things. So it's a way to have a counter basically. So what we do first is we create an object. That's the false... We're going to create a false condition that we're going to just call zero because zero os false. And when it's false, it will just call the path again. And with At, it will go to that position of the iterator, which is when you start, it's going to be zero, so it's going to access name. So it's going to go At O, and then in here instead when you're just beginning, it's going to do name basically automatically. And if it's true, it means that we are done and we can just return the type. And when you At this is going to cause the type to unfold until the condition is matched. - [Simon] Yeah. Okay, now I understand. Yeah, I didn't think it has a map, but now I see it with a map. Cool. - [Pierre-Antoine] Okay, so yeah, that's it. But, it's a lot of other types that I could go through, the split types. I don't know if you guys have seen the split types. - [Host] Maybe you want to repeat that again. So last time, we had as templates, type templates strings are still new, last time, we had someone from Spotify showing us all the crazy stuff you can do with that. And split was one of them. Maybe you want to just revisited how the split is implemented. - [Pierre-Antoine] Okay, so this one is a bit different. It doesn't involve iteration on both indexes. Swift has a new feature that is called template literals. And the amazing thing that they did is that you can use infer inside those template literals. So if you have a type... Let's do a test again. (mumbling to himself) And then you can do extends, infer... You can say here it's going to be actually a number. And here you're going to say infer. Infer A and here, it's a number again. And by doing this, you can... Let's root from A and let's root to never. By doing this, you can just... Oops. Oh no. What mistake did I do? Number. (indistinct) (mumbling indistinctly) - [Host] Syntax with a number, I haven't seen that yet. I didn't even know- - [Simon] Yeah, just try like 0- - [Pierre-Antoine] Oh yeah, I'm sorry, I'm still in the TypeScript 4.1, but it might be why. - [Host] So old. (Pierre-Antoine chuckles) - [Pierre-Antoine] Let's see. That should have done it. Nope. - [Host] Yeah. Otherwise the zero is also. - That's strange because I did this just the other day. Okay. Whatever. Anyway, but you can infer parts of those strings. So you put your delimiter here. And this case it can be any kind of delimiter. Previously, it was dot delimiter. And you can say, I infer the first part of this string and then there's going to be a delimiter and then infer the rest of the string. I've called it before string, BS, and after string. And then you just continue doing that. So you get the first path of the string to get the delimiter that you want to remove and the rest of the string, and you continue doing that until those strings empty, and that way you can... you pile all these little pieces of string into an array and you'll get the split string. So if you put the type out. I can do... Maybe let's see and your delimiter is this. Then you get the split string. - [Host] Not bad. Can you elaborate why you have this wrapper type in line 16? Why you don't directly call the underscore split one? - [Pierre-Antoine] Why did I split it like this? - [Host] Yeah. Mhm. - [Pierre-Antoine] Well, it's just for... make it easier to read. The TypeScripting has a more elaborate one, but I felt like I wanted to split the steps and I don't want to repeat this condition at every iteration of the recursive type because it (indistinct). - [Host] Okay, nice. Yeah. This is some advanced stuff that's fairly new, right? Yeah, exactly. Natalia just wrote. This is an open mic, so if anyone asks questions now to the advanced types, now would be a good time to ask. Okay. You still can ask later if you remember or if something comes to your mind. - [Natalia] I think that was so advanced people are scared maybe. (chuckles) - [Host] That could also be. That could also be. I think even the TypeScript compiler is a bit afraid of Pierre-Antoine. (all chuckle) - [Pierre-Antoine] The TypeScript team is afraid of me. (all laugh) - Yeah, no, this was really awesome. And again, I highly encourage you to check out ts-toolbelt and also an important point here again is that what Pierre-Antoine is showing here is really advanced stuff, and you don't necessarily need to do that in your all day life when you build an application with TypeScript. For the people who are not using TypeScript yet, you don't need to know this stuff. - It's a the real waste of time sometimes. - [Host] Oh, I mean, you are basically on the absolute edge and it's research, right? And we need to differentiate between library code and application code. And I think library authors are rather interested in the stuff if you want to give people a really, really nice API, then you can hide... That's the beauty of TypeScript, you can hide this complexity in the library and you can have nice readable application code with all the auto-completion, et cetera. But usually, at least in my applications, I don't do a lot of events TypeScript. It's fairly simple mostly. Just FYI that people are not scared of this now. This is rather a library code usually. Okay, so then we have another little presentation prepared from Emil. - Hello. - Hey. - [Emil] Your screen... So it's not as advanced, but I hope it will be informative to some people. - [Host] It doesn't have to be. - [Emil] So basically, I discovered the type of and the return type and I just ran with it a bit for GraphQL applications. So the first thing I use it for is data loaders, which, basically, like say here, I created a simple to-do application. So you have users which has a couple of to-do lists and each to-do list has a couple of to-do items. So usually you want to have data loaders for each of them. So let's say you have like a data loader for users. So that's my typical data loader usage. So with Prisma, basically, you just... (mumbling to himself) So you find many, you just dispatch that into a dictionary and then map the keys again. Pretty classical data loaders and my issue was I wanted some kind of object in my context that it would be automatically type for an end results all the data loaders directly. So I created this loader thing which basically generates all the data loaders for all my modules with... We're going to see like what the function actually does and then uses the type of data loader which is a function by itself, this function, and then the return type of that function. So you just extract basically the implicitly-generated return type of the function loader. And you can see here that it's fully, automatically typed for you and you don't need to do anything, which is an interesting use here. So if we go into like the user, it basically just calls this function that creates the data loader and then assigns that to an object with potentially other data loaders. So what is interesting, like here it's a simple idea where you just load your user by the ID, but if you have a to-do list where you can either load the to-do list via its ID or via a parent, a foreign key, so the user foreign key for example, Now you have like the main data loader and then you have like by user data loaders. And then you can use... Usually, it's a good idea to prime your objects, so if you have main primary key data loader, then you pass it to your children data loaders, and then you in inside of it, you call prime on the primary. So like they're already loaded in memory if you need to. So that way you can... Like how you're going to use it in your resolvers is for example, here find list, here it's already done. But if we type it again, with loader, and then you have all your loaders. And then if I just want to use the list one, ID.load and I just enter an ID. But if I want to do it by user agent, I created this by user, so if you just go by user, then you download. And then you put that here. So it's an interesting like idea to like automatically type... So we didn't have to write a lot of code basically, and then we have automatically-typed data loaders on every point. - [Host] Can you, again, repeat or show again the context type? - [Emil] Yeah. Yeah. Sorry, I was going too fast. So here is just to create the loaders. So basically goes and calls each functions in each modules that is the loaders function in the index, it's a default exported function. And that each of them, they create an object which contains the data loader. If people are not really familiar with data loader, it basically batches request together. So if you do like load... The problem with GraphQL is like you can really, the end plus one problem, which is basically if you have a user and they have a multiple lists, you just want to do one query for all those lists of like to-do list. And then each to-do list has the most items, you want to do one query for all those to-do items. So that way you like data loader them into one query to the database. So like, if we go into here, basically, you see it like user, it goes fetch one user and then fetch all the to-do list. And then all the to-do items in one go, instead of doing multiple queries to the database. - [Host] Exactly, I think this in statement. Maybe you can keep the super query, I think this In statement is important, right? Because it says user.id in, and then you can provide a list off IDs, right? And then the next level... can you show the graph query actually that you're sending there. - [Emil] Yeah, yeah. What I'm sending is this query here. So you go user and then you go list, I should have started with that, you go list and then you go items and then you can even go back to list because I created a back relationship. And since I primed it with the main data loader, it doesn't do another query here. It just uses the already loaded list since I primed it with- - [Host] Perfect. Because it has a per request caching, right. - [Emil] Yes, it's per request caching, basically- - [Host] If in the same request, you access the same thing again, then it sees, "I got that already, so done." - [Emil] But if you don't prime it in the primary key data loaders, then you're not going to see that. You're going to have another request here to the database, because like you load it through the user at first here, like you load through the user to get the all the lists of their user, but here you're using its primary key. So if you don't prime your main loaders, it's going to do another query to the database. - [Host] So, now back to the SQL queries that are generated based on- - [Emil] Yes, so that generates that query. - [Host] Yeah. To be honest, we are not in advanced meet up but still, it's- - [Emil] No, no. I mostly wanting to just show like how you can use the type of a function, and then the return type of that type to extract a type. And then I have... Like that was my base example and then for another project- - [Host] Before you move on, can you, again, show the context type, actually, the one that you have been importing? - [Emil] Yeah, so basically what it does here is loading... That's your context object that you pass around. I really like this pattern, even if you're using rest to have a general context is really interesting. And then you have like all the loaders in there. - [Host] And that means when you are now calling those loader function, line 15, you are basically creating a fresh loader context, so to say, a fresh loader instance so that it caches within that context, and we just hope that your JavaScript a garbage collector just collect that away once the request is done- - [Emil] We do hope that, but like data loaders are always scope to a request anyway. It's never a good idea. Well, in some cases it's a good idea to share with- - [Host] I think if you don't do the caching, but the batching part, you don't need to put it on a per request basis. - [Emil] Obviously. Yeah, yeah. - [Host] All right? But in your example that you just showed, it's useful to have it with the caching together- - [Emil] And, yeah, mostly what I wanted to show is how you're going to use it in your application which is... Because I don't like to type types if they can automatically be generated for me. - [Host] It's interesting because what you have here in this file that you have open here right now, find list items, the next level would be that we automatically typing the function, right? Like, because- - [Emil] That's coming. - [Host] In next JS, that is something I would like to see because then you could have, like, they get static props get server-side props automatically type. It's not that possible, but there is an issue open for that where people will want a whole module to be able to be typed from outside or the exports at least off the models. - [Emil] We have something like that. That's my next part. - [Host] Okay. - [Emil] Let's close that. So, that was like the simple part. And then for another project, we wanted to have all the functions automatically typed. So say you have like models and then... It's a different architecture, but basically the same thing. So I redid that into a Nexus application, which is the same thing, but like it's called first instead of being scheme offers for people that use that. But the important part is like say I have my models and I have to-do items, so here, what I do is instead of having a context, I have this, what I call applications. And then that's a function of my application of my model to-do items, so find many... We don't really care what it does, but like basically touches the data. And what is interesting is each of those models, they have this index, which injects a context. So we're using here, like if people want to check it, we're using contravariance. So context, if I go into context, context is my base context. It contains like Prisma or Stripe or whatever you want. And then application is a children, if you want, of context and it contains the return type of models, which itself is typed with all my models and all my functions automatically. - [Host] Mhh, it's using the return type type-off you don't have to write off this all the time. - [Emil] And there's the interesting thing is you don't have app here. Like if we go back to find many and you have you have have an application here, and then when you have it inside the application, you don't have it anymore. Like it's automatically type... like it will be passed implicitly down there. - [Host] What does application here? What is an application for you? - [Emil] Application is just another word for context, basically, but it contains... It's because you need to pass contexts to application, otherwise, you have recursive typing, right? Application needs applications to be. - [Host] Okay, but this function create application in line 20, where is that called? - [Emil] That's like the create context you have and- - [Host] Ah, okay, okay. - [Emil] I just call it create application. - [Host] Ah, okay. - [Emil] And the trick is it only works if you disable one flag in your TS config, which is the strict function types, which prevents you from using contravariance in types, basically. - [Host] So do I understand correct that is basically a little bit like a recursive condition here. - [Emil] Yeah, it is a recursive definition because basically you need... Well, contravariance works that if you have a derived type and a base type, you can pass the base type to the derive type and then it will work. That's contravariance basically. - [Host] Yeah. Yeah. - [Emil] So, here we're passing context, which is the parent to application, which is the children, and it still doesn't complain because we disabled that flag. So that's basically what our inject function does here. It takes the context and infers it as an application. You can also just do as application if you want, they don't care about the typing, but I do care about the typing. So if I pass garbage in here, it will not work basically. And then what is interesting is when you go into... into here, then you have, like you see here, the whole application is available. So you have like models and then you can go here and then you have like all your models, user, and then we need to find one. And you call it with some inputs - [Host] Out of curiosity, how do you tell Nexus to type this properly? How does Nexus type the app now properly? - [Emil] Oh, the Nexus thing is just while it's using the application, so, right? It's importing the application directly- - [Host] Ah, so really say the type is called application, and Nexus basically does static type analysis. I didn't know that Nexus is that advance. Nice. - [Emil] Yeah, so basically what it does... Like that's Nexus' stuff, so I don't want to really get into it but like they type your context... (mumbles to himself) - [Host] So they then import your type and that way everything is time safe on the context object. That's pretty awesome. - [Emil] Yeah, they import that here. - [Host] If you wouldn't have this, it's so much manual typing - [Emil] So yeah, it's a way to like... it's not perfect, but it's a way to like... And then here, if you need... Like usually you would need... Like, for example, if you have a subscription and you want to like call Stripe, then you would do like app.service.stripe which is a in services here. So you can call from other parts. Like if you have an app object, then you can call all the other methods that are in that app object that automatically. - [Host] So you basically also have a dependency injection with this, right? - [Emil] It's a kind of a dependency injection- - [Host] Which is nice. It's a very strong pattern and SJS for example, right, They have the services, and then you can inject what you need and the same here. That's awesome. So that means you can also, obviously, easily mock or stop things, right? If you want to do- - [Emil] Yeah, yeah. You can also do the same thing... Well, similar thing with classes, if you want, basically. Like you can have like a to-do item class, and then you have like inside of the to-do item class, you have like defined mini function. This is just like a functional-ish way to build it, or like building objects that are... 'Cause like here you never specify, basically, the return type it's always inferred from the injection. And the inject function is just like taking two types and then basically the function needs to respect the app, it needs to like have this app context in it as some kind of input and the return some something else, it will automatically like populate DNU with the function you pass and that's how it works basically. - [Host] Can you, again, show how you call the inject function? - [Emil] Yeah. Just here, so that's the method. - [Host] Ah, okay. That's the actual function implementation - [Emil] Yeah, it's the actually function implementation. So it does create a lot of objects on every request, but it's not that bad, like... - [Host] Okay. Okay. So I guess if people wouldn't use Nexus, they could use this pattern also with other graph (indistinct) frameworks, I guess, Type GraphQL here and whatnot, they would also allow you to type the context once without having to re type it all the time. - [Emil] It was just like... What I really found is that the type of and then the return type is a really powerful thing that you can do a lot of stuff with. There's like a return... That type of a function and the return type of that type of is pretty interesting. - [Host] Can you, again, show for example, in the models, what you're importing from the models? - [Emil] Yeah, so the model is like... Oh, I didn't show that. But, it's basically just an object which call each of those model. Basically, call the inject function of all of those model and just creates an object of that. - [Host] Can you show an example how would they implement it? Are those then resolvers for the whole type or how is that working? - [Emil] That's like the to-do like... We were in to-do lists, so that's where you inject- - [Host] Ah, that one, okay. - [Emil] That's the inject part, and then you just combine all of those injections into one bigger object just for simplicity. So if you need to add a new model, you just add it here and then you go into your index and just add here and automatically everybody can use it. Like you don't need to import anything else in your application anywhere basically. You never do basically import any more. You just go into... You just use a app and then you do stuff with app. Like all your functions are in app, basically. It's interesting different model. - [Host] Now, a stupid question. Why wouldn't you directly use, put like Prisma on the app? Why do you call through the models? - [Emil] Prisma, I use on the app. - [Host] Ah okay. - [Emil] It's just like another like... because if you need to do fancy stuff like subscriptions, it's not a database type for example. So I just need to have this, create one. - [Host] Ah that makes sense- - [Emil] Business logic- - [Host] If you have business logic that you need to attach them- - [Emil] And usually I prefer also to wrap all my database call even if they are to-do item, I could just do prisma.todoitem.findOne, but I prefer to wrap it in two methods because then I can do access control, or I can do stuff like that that is not... So for me, like if you're using the Prisma directly, it's not a really good... like I never used the Prisma directly in my resolvers, I just always pass through the models system, and then, yeah. - [Host] But I think that's a good pattern. That's what I also meant with nest. I think they use that a lot that they would never expose Prisma directly to a controller but they would always do it via models or services. And that way you have the control that if you want to reuse this logic and different resolvers maybe in your GraphQL server, then you don't need to, for example, rewrite this line 32, where you do something on Stripe. It's just always the same thing, basically. So you can reuse that logic no matter from where you use it. That's smart. - [Emil] Yeah, so those are like full examples and they are working TypeScript servers. Both of them, they are like called first and a schema first. So I put them on, on GitHub if people wanna check them out and see like the why the typing works and all that. - [Host] 'Cause I think you have a nice architecture there in your code. I really liked that, how you structured everything- - [Emil] Yeah, it's two different projects. Like it's not really TypeScript related but like it's a bit TypeScript related, it's like either you put it using resolvers or you just go in modules, it's two different architectures. - [Host] It's, let's say, TypeScript in practice. - [Emil] Yeah. So that's about what I have. - [Host] So someone just wrote, Sebastian DuBois, "I'm really interested in having a look at the code. Where will you publish it?" So yeah. Exactly. I would also like to have a look at that. Yeah, because after all, what we saw today, I hope, is that TypeScript basically allows everything... Ah, yeah. That's it. Exactly, if you can post that in Zoom, that would be nice. TypeScript` basically already today allows you to do kind of everything, but it's on us what do we do with TypeScript, right? How we structure our projects, what crazy types were building... Someone asked earlier regarding Pierre-Antoine, "I would be more interested in use cases." Pierre-Antoine, if you're still there, maybe you want to also elaborate on that. So use cases, I think there were talking about the split time or at least more advanced like typed string, literal types. So I think it's still experimental. Like I can also speak to that question, it's still all experimental and you probably don't want to use them yet today in production. But people are already, for example, building a GraphQL paza or JSon paza, with a the string literal. So you could really write down your GraphQL string as it is, and the return type is automatically typed. These kinds of things you could now build with that. It's still all, not so performant, because you're a bit, let's say abusing the type system there to write a paza, you can write a paza much more efficiently. We were joking last time I think Simon mentioned that he can imagine like in a few years the computer science paza classes where young students have to write a C paza in TypeScript. That that could have happened, who knows? It's still all early and I'm not really aware of people yet using that in production, but it's coming, it's coming. At Prisma, we use it a little bit already, the string literals, by the way, for error handling. So I'm in our... So for the people who don't know, Prisma builds tools around databases and our main tool is called Prisma Client allows you to access data and it's mainly a TypeScript client. Also it's available in Go. And what you can do, you can... we have a groupBy feature that we recently introduced and it turned out the types for a groupBy are kind of complex and we had conditions within the argument itself that we wanted to express. And we were, for example, saying that if you want to orderBy a certain field let's say that field is called... That field is called name. Then you also have to, if you want to orderBy that field, then you also have groupBy that field. So we needed to be able to express, on a type system level, to say, it's not in the groupBy statement. If it's an orderBy, it also has to be in groupBy and to pick out this name of the key, for that we used the new string literals. I can also share a link about that. I didn't prepare a demo for that now, but we already ship it in the latest Prisma Client you need with that TypeScript for that one. Good. I think if the heads haven't been smoking yet after Pierre-Antoine's demo, then I think they should smoke now in a good sense. Is there any question? Is there anything anyone wants to ask as we still have everyone here? If not, that's also fine. There's one last little announcement I want to make. I just posted the link here in Zoom which is a link to a Udemy course. Someone created an end-to-end React with Prisma course on Udemy and if you use this link, you get it 100% for free. So it's basically showing you Emil... I would say Emil is already an advanced user. He himself is able to like... He sees all the blocks and knows how to put everything together, but sometimes it's useful to just see from beginning to end how everything fits together, and that's really where this course helps with if you want to use Prisma and React, then this course is the right thing for you. One last link I would like to share about that is only for Prisma users or anyone who wants to try it out. We are right now in preview for native types at Prisma. So native types basically allow you to access things like bites or buffers from your database with a Prisma Client, instead of just, let's say an end. Native types, we'll go into GA next week. And if anyone still wants to share feedback, now is the right time, now is the last chance basically to do significant changes. Next week, Tuesday, we will already go in GA with that. Awesome. So then, thanks everyone for coming. It was, again, a pleasure to learn. My head was for sure bent. I was challenged with some of the stuff that we saw today. I hope some of you could learn something today and yeah. And again, thanks a lot to Natalia for organizing this. - [Natalia] Thank you. - [Host] Awesome. So then I think that's it have a nice evening and have a time no matter where you are, in which time zone you are on this planet. (Natalia chuckles) - Classic. - Yeah. - [Natalia] Okay, bye-bye. - Bye. (soft music)
Info
Channel: Prisma
Views: 609
Rating: undefined out of 5
Keywords:
Id: T3W4OQaEgSg
Channel Id: undefined
Length: 59min 38sec (3578 seconds)
Published: Fri Feb 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.