Lesson Description

The "Drizzle Helper Functions" Lesson is part of the full, Permission Systems that Scale course featured in this preview video. Here's what you'd learn in this lesson:

Kyle builds a "toDrizzleWhere" function for the CASL implementation that converts CASL's abstract syntax tree into Drizzle database queries, replacing the last remaining hardcoded permission logic and completing the fully automated pipeline where permission changes instantly propagate to database filtering.

Preview

Transcript from the "Drizzle Helper Functions" Lesson

[00:00:00]
>> Kyle Cook: Now really, if we search for to do, we should only have 2 more to dos, and they are essentially the same thing, solving this Drizzle conversion problem. And unfortunately, this is where a little bit of complicated TypeScript comes in, of course. So let's move into our Castle section here. We're going to take all of this code down here. We no longer need this, so I'm going to remove this. There we go.

[00:00:21]
I should clean up all that, and finally, I'm going to write our function that converts everything to a Drizzle where query, so we can say to Drizzle where. This is going to take in what our table is as a generic, so this is going to extend table config. So in our original example, we hard coded the tables based on the names. This is just going to force us to pass the table we want to do this on into this function to make it a little bit more type safe for us to work with.

[00:00:51]
So that's going to take our table, and then our function here is going to first take our action, and then it's going to take our subject, just like that. And then finally, it's going to take our table, which is just a PG table with columns of that generic type T for whatever we want to pass in. Let me just make sure that I get this imported from the Postgres version. There we go. Now we need to give types for our action and our subject, and this is going to be coming from a specific type that we have here.

[00:01:18]
I'll show you what I'm looking at. Let's get our permissions here from get user permissions. And if we call permissions, we've essentially seen the can function, but Drizzle gives us, or not Drizzle, Castle gives us a ton of other functions, and the main function we want to use is this rules 4. This allows us to essentially take all those complicated MongoDB queries, and it gives us essentially an abstract syntax tree that we can use to convert that to whatever we want.

[00:01:45]
In our case, it allows us to convert it to a database query. Now, since we didn't use any of the complex database stuff, all we have is equals and nothing else. That'll make writing this code a little bit more simple, but if you want to add in the ability for not equals less than greater than, it's not too hard to add on once you see how this system works. So we want to get the types for this rules 4, because if you see this rules 4 takes in an action and it takes in a subject, so we want to get the types for those.

[00:02:11]
So to get the type for this, it's essentially a really long TypeScript type that allows us to first say awaited because we want to await the value of something. We want to await the return type of our type of get user permissions. There we go. So essentially we're taking this get user permissions function and we're getting the return type from it and waiting for it since this returns to us a promise.

[00:02:33]
Then what we want to do from that is we want to get the rules 4, just like that, that's going to give us that rules for function. And specifically, I want to get a parameter from that. So let's wrap this one level further, and we know that the first parameter to this function is the action, so we can say I want to get the very first parameter from here. Now I can copy this exact same line down for my subject because this one is just the 2nd parameter.

[00:02:59]
And now essentially all I've done is I've mapped these two parameters to these 2 parameters here, so we get the exact same type safety. If I were to call this manually and for example, get the autocomplete here, I'll get the same autocomplete when I call to Drizzle where. Now we just need to implement what this code looks like. So by getting the rules for this particular thing, we can actually use these rules here as our abstract syntax tree.

[00:03:27]
There we go. That's going to give us all of our different information that we need, and I actually want to convert this to an AST so we can say rules to AST, which again is a helper function from that extra library, and this will take all these rules that we've got and actually convert them into that syntax tree that we want. And to do this, we actually don't call rules 4 directly, we just pass it our permissions, our action, and our subject, and then it'll do that conversion for it.

[00:03:54]
Essentially, it calls rules 4 and then converts it to an abstract syntax tree which has these conditioned types inside of it. Now if this abstract syntax tree is null, that means that we have no conditions at all defined on our object, so that means they have permissions to view everything, so we can just return undefined here because they have permission to view everything. We don't want to restrict them.

[00:04:20]
Otherwise, we're going to return a function called the get condition SQL, just like that, pass it in my abstract syntax tree and my table, and this is going to be the function that essentially converts that syntax tree into something that we can use with our SQL. There we go. This takes in that generic type, same one that we have up here, so I might as well just copy that. And the very first thing that we're going to have for this is going to be our condition that comes from the condition type, and this is why we actually installed that UCAST library cause if I look at this type, scroll all the way up to where we have it imported.

[00:05:00]
Actually, it looks like it's not even importing. There we go. If I scroll all the way up to where this is imported in our code, you can see we're importing it from that UCAST core library. So we're only using this library for the types to make sure our code is type safe across everything. Next we have our table, which is using this exact same type, so I'll just copy that in just like that. Now this get conditioned SQL, we want to make sure we hard code the actual return value from this because it's going to be a recursive function.

[00:05:32]
So it's going to give us SQL or it's going to give us undefined, same thing that our original to Drizzle where function was returning to us. And here we're going to use a simple check. So if our condition is an instance of a compound condition, since this library is kind of built all around classes, that's how CASL works, we can check instance of to see if it's an instance of this compound condition.

[00:05:57]
We know that means it's either an AND or an OR query. So what we can do is we can add a little switch into here on our condition operator, and in the case where we have an AND, then we want to run an AND query, and we can have a case here for OR where we would run our OR query, and this is essentially the different types we get. So if we have multiple rules, those are ORs, and if we have multiple conditions in the same rule, those get returned to us as an AND.

[00:06:25]
Now I'm just going to return a function here, we'll call this Drizzle AND. Takes our condition and our table, and this will be what our AND looks like, and we'll do the exact same thing for our OR as well. So let's create those functions, and we'll do the exact same thing for our OR function, and I'll just copy that over once we get this one written. So the very first thing we have inside of here is our condition.

[00:06:51]
We know that this is a compound condition, so we can get that type from it. There we go, compound condition, and we also need our table. So let's make sure we get that copied over as well, and the generic type as well. There we go. Now if we look at our condition, you can see that this condition has a bunch of different values inside of it. We have our value, we have our operator, and so on, and we can essentially know that if we have a compound condition, this value is an array of other conditions.

[00:07:22]
It may be more compound conditions, it may be simple equality checks, it may be less than or greater than checks. We don't know. We just know it's an array of different conditions. So what I want to do here, since I know I'm doing an AND, I'm going to be using the AND function from Drizzle. I'm going to take my condition and get the value, which we know is just a list of all the things that we want to add together, and I'm just going to take each of those conditions and get the condition SQL for them.

[00:07:49]
So I'm going to call my get condition SQL function and pass along that condition just like that, and we also need to pass along the table. So essentially what I'm doing is I'm saying, OK, I have a bunch of conditions. I don't know what they are yet. I'm going to go back to this function, call it again to figure out what those conditions should be. Could be more ANDs, could be more ORs, could be equalities, could be other things as well.

[00:08:10]
Now the nice thing is our Drizzle OR is going to be identical to this. We just change the name here to OR and we change this to an OR, otherwise they're both dealing with essentially the same object, so it's the same exact kind of thing. Now the next thing I want to work on, since we have this particular thing done, is we can work on our next switch statement and if statement for what happens if we don't have a condition query, but instead we have a field condition.

[00:08:32]
There we go. So instead of a compound condition, we have a field condition. In our case, we're only using equalities, but if you were using like not equals greater than less than, that's where you would add all those in. But in our case, we're just doing equality, so that's the only one I'm going to write a query for. So we can create a Drizzle equal function, which is going to work very similar to what we have here.

[00:09:01]
Drizzle equal, just like that takes in our field condition instead of a compound condition, and then we need to return some specific code for that. So we can return equality. There we go, and we want to specifically get that from our table. When I get our condition field, that's going to be the key or the column in our table we want to access, and then we have our condition.value, and that's the thing that we are essentially checking our value against.

[00:09:29]
So, if we scroll all the way up to where we have some conditions defined, you can see here project ID equal to this project ID. This is going to be the field, this is going to be the value for each one of those conditions. And I want to make sure I spell equality correctly here. That's going to give us our equal syntax and completely done for. Let me just make sure I'm not missing anything else for this particular section.

[00:09:59]
I think that should be everything we need. I may want to add in support here for if our condition, that value is equal to null. Well, then we know that we essentially want to check is null instead, so we can say is null table for our field. There we go. So that's going to make sure we have our null checks taken care of as well. So now we have this nice little helper function called to Drizzle where that's going to handle all those conversions for us.

[00:10:26]
So everywhere we have a to do, we can essentially convert this to our to Drizzle where function and make sure we import that, and we know that we need this to say read first and then document second. Looks like we have a little bit of a problem. Let me see what that is. Oh, of course, we need to pass in the table as well because we're not automatically inferring that. So we can say since this is a document, we'll pass in our document table just like that, and that should solve our errors.

[00:11:06]
Let me just see. Read document, that's fine. Document table, expected 2 arguments, got 3. Let's check. 1, 2, 3. I believe I was passing in 3 arguments. Expected two arguments. Definitely 3, maybe I didn't save this file. Let me just try deleting the line, re-adding it. No, that didn't work. Oh, it's because, oh, here we go. It's because I passed undefined up here as well when we were doing our testing.

[00:11:28]
If I remove that, that should fix our problem. And since this is a promise, let's just make sure we await that. There we go. That solves that problem. Now let's go to our project service to solve our last to do. We can essentially just paste in our to Drizzle where. This one is going to be for reading a project and we want to pass it in our project table. There we go. Remove the to do from that, and we no longer need this user where clause, which we should have removed earlier.

[00:11:54]
I just didn't remember to do it. So now, we should have everything in our database done and set up. You can see already over here we're filtering the actual projects they have access to. You can see we are filtering the documents they have access to, and if I were to log out and view someone with restricted permissions, you can see they don't have any draft permissions showing up. But if we change our permissions, if I go to the right file, we should see that if I add the ability for them to view draft projects or draft documents, so here let's just change this to draft.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now