About this talk
In this talk, the speaker, Evar, a software engineer at Meta, delves into JavaScript proxies and their application in developing a form validation framework called Vest. He explains how Vest functions similarly to unit testing libraries, allowing users to assert validation rules for forms dynamically. The session highlights how to implement a fluent assertion library named 'enforce' that utilizes JavaScript proxies for intercepting object calls. The speaker discusses the benefits of employing proxies, such as enabling infinite chaining of validation rules and lazy evaluation for performance optimization. Throughout the presentation, Evar showcases practical examples, aiming to build an understanding of schema validation and the use of proxies in modern JavaScript libraries.
Full transcript
make mistakes everywhere. >> Okay, everybody remember this handsome face and then if you have more legal related AI questions, go and find him at all the we have a few breaks today. So like just just hunt him down, you know, white shirt, black pants, white white shoes. >> Lawyer is today. >> Our next our next speaker has set up properly. Uh so please give a big round
of applause also to IATA. She will be talking about JavaScript proxies. >> Hello. Hi. That's me. No, that's not me. Uh, good morning everybody. Um, you need me to wear this? >> Yes. Yeah. Yeah, of course. >> You don't need Okay. Um, so my name is Evar. I'm a software engineer at Meta um for the past six years. Um, and today we're going to talk about JavaScript
proxies, but just contractually I am obliged to talk about my employer for just a few seconds. So there she is. her name is Indie. Um, yes, that's it. Um, okay. So, um, in my free time, I work on multiple open source libraries. One of which is vest is a form validation framework, uh, that's inspired by the syntax and style of unit testing libraries. And it gives me
many opportunities to build very cool stuff and learn cool technologies. And I'm not just trying to uh make a point about vest being the best, even though it is. Um I'm actually want to show you something about it today and we're going to build together a part of it. Now vest is basically similar to unit test. So imagine having basically a unit testing suite just for your
validations data. So something like that you have your username is required and then you have all your different tests um for your validations in your uh form or in your feature. And today we're going to focus specifically about enforce. Enforce is the assertion library. So imagine it like an expect in vest or in justest or in um mocha and chai. And it is a fluent assertion library
meaning it can allow you to chain multiple rules on top of um the main uh value that you're checking asserting. So for example, username is not blank, longer than shorter than matches something. And today we're going to focus on how I built this. And if time allows, we're actually going to build this together ourselves. Um, now the main features on Open Force, there are a bunch of
them like uh dozens of features. So we're going to focus today on three of those. Um, we have um infinite chaining fluent API jQuery style if you remember. Um, second a modifier support. So for example, I don't want to have a is string and an is not string rule. I want to have a modifier similar to what we have in um just or in vest. So that's
another one. And lastly, if you imagine zod or joy or yep, all those they have schema validation. Well, I I want schema validation as well. So um let's try to build a bit of schema validation today as well together. And it's not going to be um super complex. I promise it's going to be just a few JavaScript lines. Um, now if you'll notice here, we have two
notations of enforce. One is eager. We call it immediately. You see the the little uh um uh braces there? Um a parenthesis. And we're also having an a lazy one, one that we're not calling enforce immediately. So we have two notations. We have to take care of that specifically because this lazy evaluation of enforce that we're not passing a value to is what allows the schema validation
to actually work. Um now the main question that I'm wondering is okay we have that enforce age we have that number 10 but how do we make actually that connect so for example we have that age how do we give greater than that value? Well, there are many different ways to do it. You could use classes, you could use closures, but really all of those has different
have different trade-offs and well, I'm wondering if there's a way to actually combine it. Oh, in a nice way. Let me try again. The slide just jumped. Okay. Um, so basically I want a greater than function that takes those two, but that the syntax will remain similar to what we have there. So this is what I'm after today. Um, and the solution I took uses JavaScript proxies,
which is one of the lesser known features in JavaScript, like in the development world in general, even though it was introduced like about 10 years ago, uh, in ECMAScript uh, um, six and well, it's very widely used in very in in varying different um, libraries, but less in the development world. And what do proxies do? Well, basically they allow us to intercept calls to objects and either
replace or uh reimplement parts or parts of the implementation of those objects. Um so we have different traps. So for example I have an object I have a trap for the get property a set property. So for example what we you would uh usually do with getters and setters in classes only that this one is much more dynamic. We do not have to specify them in advance
and they're used in vest for example which you already know and love. Uh it's used in EMR for immutability. It's used in Mobox. It's used in Vue and it's used in Party Town. Um all of those are really cool libraries especially vest again. Um but uh so it's widely used in the framework and library world maybe you'll find a way to use it in your workflows as
well. And I'll give you a brief example of how to use proxies. So for example, we have this person object. Now this is not a really uh real world scenario. Don't use this for security. But here's an example. So let's proxy the person object. And we're creating a person uh proxy. We're trapping the get so any get call to any of the properties in that object and
we're saying okay let's return just whatever the user is asking for. But if we're calling the person proxy.name, then we'll get the name from the main object. That's cool. But now let's do something else. If we don't find that property, so if the user is accessing a property that doesn't exist, let's return not available. And now if we're accessing country that doesn't exist on the proxy, we're
going to get not available. Cool. And that's not calling a function. That's that's uh just accessing a property. Now let's say if the user is trying to get the password from the user well let's throw an access denied um because that's how security is really done. This is a real world uh uh scenario um don't um so just trying to access it not even console log nothing
an error is is thrown because this is how proxies work they just trap any get access to any property and they put their implementation or your implementation there. Now what I'm going to do with the short of of time I'm going to actually try to implement Oh yeah. You want to hold it? Okay. Stay here. Um yes. No. Oh my god. It's me, right? Okay. Shall be
my mic stand. Okay. So, basically, I already prepared a bunch of um enforce rules like equals, greater than, greater than, or equals, like a bunch of those, and we're going to try to implement enforce from scratch. Now, sounds complex, sounds scary, but let's try. So, first I'm going to run write a function. Let's call it enforce because that's the function we're building. And what enforce does it
first takes a value? We saw that we saw that syntax. So, whatever value we have. Now, I'm not going to uh bother with TypeScript today because um we are going to we do need to finish today. So, I'll just do uh um JavaScript and enforce returns a new proxy a new proxy object. Oh, and instead of trapping just the rules object, instead I'll just trap an empty
object because what I really care for right now is just the dynamic capability of a proxy. And what I'll do here is get and you get three arguments. One is the reference to the original object. Now here it's an empty object. I don't need it. Second is the key that you're trying to access. And lastly is the reference to the proxy itself to the proxy that you're
creating. So I I'm going to call it self. Now let's do something very very simple. If our rules object has the current key, the current rule that we're trying to access, then let's return a new function because this is what we're trying to do. return a function that takes the rest of the arguments that enforce once and calculates the result. So con result equals rules key and
it passes it both the value and the rest of the arguments that's for right. Okay. Now if not result so if the result is negative if it's policy let's do something like that. um throw new um let's say key which is the name of the rule failed with the value two spaces here um with a value um value. Okay. So now the next thing we want to
do is allow for infinite chaining. So right after we return the result or throw the result, we want to right after we calculate the result, we want to return the proxy again. So we can go again and access the same body of proxy again. So return self. Now if we did everything correctly, what happens here is that we're going to have an assertion library that's already chainable
and and it was like I don't know just a few lines. So let's try enforce one is number and what happens here is number failed with a value one. Okay. So I did fail it. It's not working. Um let's see. >> Oh, I passed the Yeah, that's correct. Yeah. Okay. So we're getting the proxy back. Now getting the proxy back doesn't seem like anything correct. So let's
just update the object here and let's say passing equals true just so it's saying passing true so we know that we passed even though it doesn't mean anything. Um and let's try to do something different. Let's try to do is string and here we're saying is string failed with a value one because one is a number it's not a string. Okay let's try now chaining another function
on top of that. Um again oh this will not work. Um let's try is number and we'll see that we're still seeing is string because we're passing the is number going to the is string and let's try to let's see what else we have less than is number less than 100 we're passing less than one we're failing because one is not less than one okay so we
already have a chainable uh assertion library just built with proxies but let's try to do now um I don't know negation rules like modifiers like that not how easy would that be? Well, we already know that we can trap arbitrary calls to our object. So any property call that we want even if that property does not exist. As you can see, we're actually accessing properties that do
not exist on our object. So what if we could just make up one property like if key equals not? We can do something different. We can for example tell it, hey, flip the next result. So let's put something in our closure here. Let's do um let negate next. So let's negate next. And it's going to be false. But if the key is not um negate next will
be true. And then we can return the proxy because nothing happens after negate next. It's just going to flip the next result. So return self. Cool. Cool. Cool. This should work now. So we have the result here. when we calculate the result. Let's not make it rep and let's say um if negate next if negate next then um result else not result. And now let's see what
happens now. So, for example, in our example, oh, and we do need to um clear up the negate next, of course, because it should only last for one So, let's do something like this. Oh, why is it okay? Negate next equals true a false. Sorry. So, re resetting that. Okay. Now let's try using not is number and what we should be seeing is is number failed with
one because we're passing not. Let's remove that not from the bottom and this is killing me. Okay, let's try to remove that not. It is passing. Let's return the not. It's failing. And let's try now um is a That's correct. It is passing because it really is not a string. Now let's do is number now. It should be passing because we reset negate next. So let's see.
Is number. It is passing. Okay. Oh wait, it is not passing. Did we not reset it? We reset What? I always reset. Oh, right. That's correct. Um, so if negate. Oh, wait. You're right. We should do it. I think we should do it here. Okay. Yes. So, we're setting negate next um after our um after we're finishing with it. Now the next thing is we discussed about
a schema validation. So in our example we said oh not me okay we said that we want um to add schema validation like in zod or yap or joy like that. And the nice thing is this is the syntax that we're going to use something like that and force shape and and whatever it is that's nested inside. And well we can pass this but how do we
make this part this is string to know of that uh uh name above. Well the nice thing about proxies or about JavaScript in general is that everything in JavaScript is an object. Functions too almost everything in JavaScript is an object. This means that even functions can be wrapped with a proxy. Um, which is going to cause us a little of a proxy inception, I would guess. So,
let's try to do something else. Let's rename that enforce to enforce eager, which means we're eagerly calling enforce here. And let's create a non-eager enforce which is just going to be enforce constant enforce equals new proxy which is going to trap not an object like an object but it's going to actually trap enforce eager and what it is going to do here is return a different trap.
So get let's do this key self. And now what I want to do is if has the current key what I want to return is a function that returns a function that calls our rule. So return and this is going to be a little bit in reverse. So return a function. So just imagine this. We're going to basically call a function. Let's imagine how it's going to
look. So, we're going to have is string that's going to be just call 10. Okay, that's going to just be called. We're going to be then passing whatever it is that we want to function coming on top of that. So, for example, whatever value to the function that's coming on top of that. So for example my string is going to be here how we're going to do
that Karen you know Karen that capability of breaking a function into multiple parts that's basically what we did here but here we're going to do that another layer so we're going to return a function that takes all of the spread arguments everything that comes later args and we're going to now return a new function that takes the specific value of a string. So for example, let's imagine
that we wouldn't be using a string. We would be using enforce or not enforce this number. Enforce greater than 100 for example and only then the actual value that we're checking. So for example five this would be failing of course because five is not greater than 100. So we're evaluating it and this is what I'm going to do here. I'm going to pass the value and only
here we're going to return the actual execution of the rule. So for example, const result equals rules key and then we're going to pass value and then the args. So again we're reversing that. Cool. So now if this works correctly, we're evaluating or having at least an enforce that can be passed or can be called with whatever function that's in our rules object and actually just return
a new function that will lazily evaluate it. And let's do return and let's make sure it's a boolean result. Now I'm not going to bother with infinite chain in here for that second one because this one is going to be more complex. You'll need to actually remember or track keep track of all the functions that you're calling. Oh, thank you. No, I like it. Okay. Um, so
let's try to see how that one works. So we have enforce which is a proxy because we're taking enforce and we're um we're taking the enforce eager and wrapping it with a proxy or trapping it in a proxy. This should work. So we're seeing an enforce here. a a proxy here and let's see if we can do something here. So let's see what a string does. Okay,
this is not something that we had before. This returns us a function. If we try to see the previous one and force um a string would be after calling the function. So we achieve the basic part of it. You see we have the same execution or the same behavior with two different notations with just a few lines. Okay. So I'm taking this part and let's see what
happens if I call enforce is string. Nothing happens. I get another function back as you can see here. Okay, I got another function back. Let's see try to put one here. Well, going to get false because one is not a string. Let's try uh a string here. It shouldn't have been a string. It should have been my name really. Yes. Okay, I'm also a string. Cool. So
this works. We have a lazily callable and force which means or a lazily evaluated. I declare what I want and only evaluate later. Cool. Now, let's turn it into a schema validation library or a basic one. We're not going to build the entirety of Zad today, even though we could um if we want to. I mean, if you want to stay late today. Um, but let's just
try to create the shape function that I showed before or a basic version of it. Of course, it's very big. It's very it's very robust in the production version. But here let's do it just in a simple way just so we can get a sense of it. So let's define the shape function which is going to take uh our object. So our rules object. Let's let's try
to first define what it is that we're trying to validate. You know what that would be easier. So let's this is here. This is not breaking anything. Okay. So, let's do enforce name editor um age um can't believe 36. Okay. And here we're going to do shape and inside of shape we're going to define um this structure. So, it's going to be um name and force is
string age and forest is number. This is what we're going to do here. This is what we're going to put inside shape. So, I'm just commenting this one out. Um, so it doesn't bother us yet. Let's just do this. Put this one here. Yes. Okay. It's all commented out now. Okay. So let's go here go implement this. So first of all the value that we're getting the
value that we're getting is the shape the actual shape that we're taking from the user. So let's take um user strct. Yeah that that's a good name. Um and the second thing that we're getting is the um basically shape value. So let's call it shape. Yes. Okay. shape calls shape. And the thing we're going to do now is iterate each of the keys and identify if they're
actually working or not or if they're actually matching or not. So if uh uh sorry for const uh key in shape let's verify if if user strct no sorry let's verify if shape key which is actually one of these values for example This actually matches what we have in user strct key not M K. If so, let's continue. That's fine. Otherwise, let's return false. And then lastly,
if we're done with all the validations, let's just return true. So, we're iterating each of the keys in our shape. So is string is number is string is number and then we're matching it to my name your name uh my age and if it's passing we're continuing otherwise we're exiting the entire loop because we're not matching and then if we all passed then we're continuing and returning
true. So let's let's try to undo that and we're seeing that we're passing but nothing changed uh at the bottom. So I just want to make sure that it's actually working correctly. Let's try to do um let's try to chain my name into a number and shape failed with the value. Now we're stringifying here an object. Um so that's why we're getting that. If you want to
see JSON JSON JSON stringify. Yeah. Okay. So we failed with that value because this is not a number. So let's try to uh not a string. So let's try to turn it into a string again. Cool. But this is not schema. This is just one layer. Let's see if it works with nested layers. So instead of name, let's try to do um first avatar. That's me. Last
alouch, that's also me. And instead here, let's do enforce.shape. And then let's do first enforce Last enforce string. Shape doc shape K is not a function. Why is that? Let me just I'm not seeing that. Anybody seeing Anybody seen why is that not a function? Okay, we'll have to try that again later because I know it works. And you know what? We'll go back to that later.
Um I don't want to I don't want to finish the ending with that. >> Sorry. >> Here the sub object. Yes. Oh, no, no, no. In this case, it should have been this way. Actually, I will show you what how it does work because I did a small backup. And let's see, let's see what what I what what I messed up because it should it should actually
work. Let's see if it's just in this part. No, the implementation is here. Interesting. I'll I'll try to I'll try to capture that in the during the break and and I'll return to any any one of you who actually is interested because we are about to finish in time. Um and I will tell you because using proxies is one of the coolest thing that I actually did
for vest and using vest and it's also one of the simpler ways of using uh of using the underline and understand understanding the underlying of the things that you use. If you are interested in actually using that's my cat by the way. If you are interested in using vest um you should check it out in vestjs.dev. It's a pretty cool uh open source form validation library. Give
it a star. Give it a like. Um and thank you so much. Another
More from this event
See all 8 talks →
Effective Data Modeling for Document Databases // Anna Henningsen
22:30
Micro-Frontend & Me, a love story // Dan Neciu
23:53
Federation of Specialists: From Module Federation to AI Orchestration // Nestor Lopez
24:52
Feyikemi Ogunsanya - AI Agent Fundamentals for JavaScript Developers
1:17:42