Unleashing Object Proxies: Building Type-Safe Wrappers for Anything

You must or must not have heard of object proxies before, but have you ever understood how to use them? What the actual usecase of an object proxy is? In this talk, we will do a deep dive into object proxies, understanding how they work, and the kinds of things you can build using them. We will build higher-order wrappers for existing libraries, and learn how to wrap over them in a type-safe manner.

Rate this content
Bookmark
Video Summary and Transcription
The video discusses the concept of object proxies and their use in building type-safe wrappers. It explains how object proxies serve as middleware for objects, allowing developers to control input and output. The talk highlights various applications, including controlling data access, adding logging, and handling responses, such as lazy loading data. Examples like Prisma and TRPC are mentioned, showcasing how object proxies are used in real-world applications to manage database interactions and enable transparent method calls. The video also explains the implementation of a simple version of the TRPC library using a Fastify server, where object proxies handle property access and method calls. On the client side, an object proxy is defined to access properties and make method calls, utilizing TypeScript to ensure type safety. The speaker emphasizes the importance of exploring the edges of object proxies to push their limits and encourages experimentation in creating custom RPC libraries.

This talk has been presented at TypeScript Congress 2023, check out the latest edition of this JavaScript Conference.

FAQ

Object proxies serve as middleware for objects, acting as a wrapper to control both the input received by the object and the output returned when a user accesses any method or property of the object.

Object proxies can be used for input validation and implementing an access control layer. For instance, they can manage and restrict access to sensitive data within an object based on the user's or developer's permissions.

Object proxies are commonly used for controlling data access, adding logging to track and monitor access to sensitive information, and handling responses dynamically, such as lazy loading data only when it is accessed.

Prisma uses object proxies to manage database interactions. It generates types for database tables and applies them to an object proxy, which then handles real-time data fetching and operations transparently as if accessing local properties.

Object proxies can automatically log access attempts to objects, especially unauthorized ones. This helps in monitoring and auditing access to sensitive data or operations within an application.

In response handling, object proxies can manipulate the return values based on the input or conditions, such as performing API calls for data fetching when a property is accessed, thereby enabling dynamic data loading and caching mechanisms.

TypeScript can be used to define and ensure the type safety of the properties handled by object proxies. It helps in generating types that match the expected structure of the data managed by the proxies, enhancing reliability and developer experience.

Object proxies enhance security and data integrity by controlling access and validation, improve performance through lazy loading and caching, and offer flexibility in handling data interactions and transformations within applications.

1. Introduction to Object Proxies#

Short description:

I'm going to talk about unleashing object proxies, building type-safe wrappers for anything. Object proxies are middleware for your objects that allow you to control input and output. They have various use cases such as controlling data access, adding logging, and handling responses. Prisma and PRPC are examples of frameworks that use object proxies to access databases and enable transparent method calls.

Hey guys, my name is Akash Joshi. I'm a software engineer at SIGTECH and today I'm going to talk about unleashing object proxies, building type-safe wrappers for anything. Object proxies are an amazing tool that are severely under-explored but are quite widely used in all of the libraries that we use in our day-to-day. So I'm going to explain how they work, what they are and how you can use them as well.

So what are object proxies? Object proxies are basically middleware for your objects. So think of them as a wrapper around your object with which you can control what input comes into the object and what you return from the object whenever a user tries to access a method or a property. So what are some of the use cases that we see with object proxies in the real life? So one of the first use cases that one can think of is controlling data access. So doing input validation or implementing an access control layer on any sort of object which can contain privileged properties. So for example, on a bank, if you want to create an object which contains the user's data then based on whether a user has, one of your developers have, access to that property or not, you can actually do input validation on it. So if the user or the developer has access to that property, then you return it, otherwise you can return an error or whatever you want to do instead.

Similarly, you can add logging to your objects via object proxies. So if a user tries to repeatedly access an unauthorized data, or you just want to have a log of all of the activities that your developers are doing on any privileged object, then you can use, you can add logging to it by object proxies. Any accesses are logged to one of your access logs. But most importantly, what we use object proxies for is response handling. So this can involve returning something else based on what the user provides or lazy loading certain objects. So for example, just continuing with the user case. Let's say we have an object which contains a user's banking details, like their address history, their transaction history, anything. But you don't want to fetch all of it at runtime. So whenever someone tries to access their address history, then we make a network call to the address API, get all of that data, and then return it via the output. So on the on the developer side, it just looks like you made a property call, like a .call to an object. So that would be user.address. But in the backend, it will actually make an API call. And you can also implement caching and other things on top of it to actually make your object calls faster.

In the wild, object proxies are used by Prisma. So have you ever thought about how Prisma knows what all of your tables are and how it is able to access all of them at runtime, even though it actually isn't present in their codebase? What they do is they generate the types for all of your tables and then they apply them to an object proxy. So whenever you are accessing your property by using the dot method, so like prisma.analytics, prisma.user, prisma.posts, what it actually does is it makes a network call via object proxies to the actual database and then fetches those results and then returns them to your client. Of course, it isn't all hard-coded into the Prisma's codebase, but they generate it at runtime via TypeScript and then object proxies take care of the rest. Similarly for PRPC, PRPC works on a similar concept where they allow you to make object calls, make method calls, from your front end to your back end quite transparently. They use object proxies as well. They expose a router object on the back end and expose its types and then use those types on the front end.

2. Implementing Object Proxies#

Short description:

The actual proxying happens on the front end where any call to one of their queries is transpiled by the object proxy into a network call. We are going to implement a simple version of the TRPC library and explain how object proxies work. On the server, we have a Fastify server with a route that accepts a procedure name and parameters, applies them to the procedure, and returns the response. The API object provided by the developer contains the exposed APIs. On the client side, we define an object proxy to access properties and make method calls. We use the get, has, and apply properties of the proxy to handle property access and method calls.

So the actual proxying happens on the front end where any call to one of their queries is actually transpiled by the object proxy into a network call and then the network call executes on the back end to actually return you the result on the front end. So what we are going to do now is we are going to implement a very simple version of the TRPC library or just try to implement a little bit of RPC to the back end ourselves using object proxies. And I'm going to also explain how object proxies work.

This just involves a little bit of code. Firstly, let's look at what our server looks like. So this is a very simple Fastify server. If you go to Fastify's documentation page, this is similar to what you will see on the main page. In fact, this is what I've copied from there. The only route that I've added here is slash RPC. It accepts a procedure name and a set of parameters for that procedure. And it then parses this to this data to string and then it applies those parameters on those procedure and then returns the response to the front end.

Where are we getting the actual procedure from? Let's say we have an API object which the developer provides to the server and this API object contains all of the APIs that the developer actually wants to expose to the client. In this case, we are exposing hello, which just returns world and then another method called sum, which returns the summation of two numbers. And see how we have actually defined the types correctly here. This is because we are going to use the properties of TypeScript to actually pass that type to the client as well.

On the client side now what we are going to do is that we are going to define a very simple object proxy. Have a look at the main method here where I will define a few lines of code which try to access some properties of the object proxy and try to get the results of it. So the first one is that we are trying to destruct a property called hello on an object that doesn't exist yet. Next we are checking for two properties whether they exist on the object and lastly we are trying to make method calls on the object proxy and then console.logging the result of it. So let's quickly define our object proxy here. The syntax for defining object proxies is to use a new proxy to define them. The first parameter it takes is the target object that you want to build it upon. So the standard way would be to use a wrapper over an existing object. In this case we are just going to use the properties of the wrapper so we don't use any existing object so I pass in an empty object.

Next we actually look at all of the properties that a proxy accepts. So they are primarily get, has and apply. These are the properties that you will be mostly looking at while building object proxies. The get property is the dot method so when you are destructing hello or you are doing client proxy dot some or client proxy dot anything else then the get method is called and you can return whatever you want from it. Has method is sort of like has own property which actually checks for whether a property exists in object proxy or not and in this case we are just returning true so these two methods which check whether these two parameters exist in the object property should actually return true even though they don't really exist there and lastly we have apply I don't think we have enough time to go into the depth of it but it is a trap for any method calls that you make on your object proxy. In this case get should be enough we will go into the depth of it as we actually define it.

3. Handling Object Proxy Traps and Errors#

Short description:

We have a type error because the property hello does not exist on the empty object. We need to fake the object's structure. By logging the target object and the property being called, we can see the actual output. The error hello is not a function is expected as we haven't defined the code for it. We are interested in the first three lines, which show the property access and the resulting values. By changing the defined value to false, we can print whatever we want. To resolve the error, we should log the args instead of making a function call.

Let me yeah. So we have right now we have a type error here which says property hello does not exist on type empty object that is because it's actually returning a type of empty object as that is the default object that we passed here. But since we are going to create traps on this object proxy we have to now fake what it actually looks like. So let's try to console log on get here and the first parameter that it takes is the target object. In this case our target is an empty object so we just get underscore we don't really need to use that. What we are actually looking for is the property being called here. And then we will console log on property. And when we run this code here, this executes main which executes all of the lines here. We will see the actual output. So we the error that we are getting here is hello is not a function which is expected because it is calling the hello method here. We are going to define the code for that. But what we are more interested in is the first three lines here we are getting property when we are trying to console log. Sorry, when we are trying to access the client proxy property here and then we are going getting true and true for the property. We are getting true there because we have just defined true here if we change it to false and then we save it. This should run again. So that should then print false that just print whatever we want it to. And then this is actually printing the actual property that we are trying to access which is hello in this case. And yeah let's try to solve this error next. Hello is not a function. So this is because we are trying to make a function method call on the properties being written here. So to resolve this quickly we should just console.log args instead. And if we yeah now it returns correctly and it just prints the args being passed here the property and similar for some. It's printing what method we were calling which is some and then the parameters being passed in here which is 3 and 123.

4. Making API Calls and Defining the Correct Type#

Short description:

Now we are going to make an API call to our backend and return the data. We use fetch to make the call and pass the parameters correctly. When accessing properties via the calls, it calls the API on the backend. We need to define the correct type by importing the API type from the backend and applying it to the client proxy. The client proxy returns a promise when making a method call. We use the promiseify type to wrap the methods and ensure they return promises. The key takeaways for building ObjectProxies are to embrace, experiment, and explore where they can be found in the real world.

Now we are going to quickly try to make an API call to our back end and return the data based on this what our back end looks like is it accepts a procedure named parameter and a params parameter and based on the procedure name and params that makes the API call. So for hello and params being an empty array it returns world for procedure name being some and the parameters as one and two it returns three and hypothetically for other params it should return the right result as well. So let's quickly do that.

In this case what we do is we just make a fetch call here so fetch to the back end http host 3000. We need to pass in the parameters correctly as well. So I'll just make note of that add async in here. So our procedure name we need url search params. So search params is equal to new URL search params and then add in a procedure name which should be the property and then I believe the args here, params, sorry. So pass in the params as json.stringify args. I think this needs to be a toString and yeah, that resolves that and then we just add this in here into the API call. And then while returning the data we need to do response.json of course. That should return our result. So now when we are running the method again, it should return the result but I believe I am not logging here. So let me have a quick look at what went wrong. So yeah, as we can see here now, when we are trying to access these properties via the calls, this is actually calling the API on the backend. So on the server side, what is happening is that it's accepting the procedure name and params that we are passing in via our URL search params here and then it's parsing them and then returning the actual response via procedure.apply. Now in this case, yeah, that's pretty much it in terms of our object proxy but we are actually missing the actual part still which is actually defining the correct type. So I can actually import the type of API from the backend and then try to apply it to our client proxy here. So let's try to do that. If I do a type of API here, then it should hypothetically apply the correct type to my client proxy here and then if I try to do something like client proxy dot then it should also return the correct type here. But what we are missing is that it doesn't return a promise. In our case, the client proxy actually returns a promise whenever you are making a method call because it is actually making a fetch request. So we define a simple type here called promiseify which extends a record and then, based on the keys for that record, it just applies a promise to the return types of all of the existing methods. Now, in this way, if we wrap our method in this generic that we have just defined, then what we get instead when we are trying to do clientProxy. is that all of the results actually return a promise instead and so we get the correct type on this. And so we don't confuse our users anymore. So that is it for our ObjectProxy. And what are the takeaways? The takeaways here for building your own ObjectProxies are embrace, experiment, and explore. Firstly, embrace ObjectProxies. Try to see, try to find out where you can find ObjectProxies in the real world.

5. Conclusion and Thanks#

Short description:

Most ORMs and RPC libraries use ObjectProxies. Experiment, create your own RPC libraries, and share them. Explore the edges of ObjectProxies to push their limits. Thank you for being a good audience and happy coding.

You might be surprised to find that most ORMs might be using ObjectProxies and then a lot of RPC libraries use them as well. Experiment with ObjectProxies. Try to create your own RPC libraries. Share them on the internet and just try to have fun with them.

And then finally, explore. Try to push the edges of ObjectProxies. TRPC or Rocket RPC my library wouldn't have been created if you, the people behind them wouldn't have tried to explore the edges of ObjectProxies and what you can do with them. Same for Prisma and a lot of other libraries out there.

So yeah, thank you. Thanks for this talk. Thanks for being a good audience. And happy coding.

Akash Joshi
Akash Joshi
16 min
21 Sep, 2023

Comments

Sign in or register to post your comment.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

A Guide to React Rendering Behavior
React Advanced 2022React Advanced 2022
25 min
A Guide to React Rendering Behavior
Top Content
This transcription provides a brief guide to React rendering behavior. It explains the process of rendering, comparing new and old elements, and the importance of pure rendering without side effects. It also covers topics such as batching and double rendering, optimizing rendering and using context and Redux in React. Overall, it offers valuable insights for developers looking to understand and optimize React rendering.
Scaling Up with Remix and Micro Frontends
Remix Conf Europe 2022Remix Conf Europe 2022
23 min
Scaling Up with Remix and Micro Frontends
Top Content
This talk discusses the usage of Microfrontends in Remix and introduces the Tiny Frontend library. Kazoo, a used car buying platform, follows a domain-driven design approach and encountered issues with granular slicing. Tiny Frontend aims to solve the slicing problem and promotes type safety and compatibility of shared dependencies. The speaker demonstrates how Tiny Frontend works with server-side rendering and how Remix can consume and update components without redeploying the app. The talk also explores the usage of micro frontends and the future support for Webpack Module Federation in Remix.
React Concurrency, Explained
React Summit 2023React Summit 2023
23 min
React Concurrency, Explained
Top Content
Watch video: React Concurrency, Explained
React 18's concurrent rendering, specifically the useTransition hook, optimizes app performance by allowing non-urgent updates to be processed without freezing the UI. However, there are drawbacks such as longer processing time for non-urgent updates and increased CPU usage. The useTransition hook works similarly to throttling or bouncing, making it useful for addressing performance issues caused by multiple small components. Libraries like React Query may require the use of alternative APIs to handle urgent and non-urgent updates effectively.
Full Stack Components
Remix Conf Europe 2022Remix Conf Europe 2022
37 min
Full Stack Components
Top Content
RemixConf EU discussed full stack components and their benefits, such as marrying the backend and UI in the same file. The talk demonstrated the implementation of a combo box with search functionality using Remix and the Downshift library. It also highlighted the ease of creating resource routes in Remix and the importance of code organization and maintainability in full stack components. The speaker expressed gratitude towards the audience and discussed the future of Remix, including its acquisition by Shopify and the potential for collaboration with Hydrogen.
Debugging JS
React Summit 2023React Summit 2023
24 min
Debugging JS
Top Content
Watch video: Debugging JS
Debugging JavaScript is a crucial skill that is often overlooked in the industry. It is important to understand the problem, reproduce the issue, and identify the root cause. Having a variety of debugging tools and techniques, such as console methods and graphical debuggers, is beneficial. Replay is a time-traveling debugger for JavaScript that allows users to record and inspect bugs. It works with Redux, plain React, and even minified code with the help of source maps.
Making JavaScript on WebAssembly Fast
JSNation Live 2021JSNation Live 2021
29 min
Making JavaScript on WebAssembly Fast
Top Content
WebAssembly enables optimizing JavaScript performance for different environments by deploying the JavaScript engine as a portable WebAssembly module. By making JavaScript on WebAssembly fast, instances can be created for each request, reducing latency and security risks. Initialization and runtime phases can be improved with tools like Wiser and snapshotting, resulting in faster startup times. Optimizing JavaScript performance in WebAssembly can be achieved through techniques like ahead-of-time compilation and inline caching. WebAssembly usage is growing outside the web, offering benefits like isolation and portability. Build sizes and snapshotting in WebAssembly depend on the application, and more information can be found on the Mozilla Hacks website and Bike Reliance site.

Workshops on related topic

React Hooks Tips Only the Pros Know
React Summit Remote Edition 2021React Summit Remote Edition 2021
177 min
React Hooks Tips Only the Pros Know
Top Content
Featured Workshop
Maurice de Beijer
Maurice de Beijer
The addition of the hooks API to React was quite a major change. Before hooks most components had to be class based. Now, with hooks, these are often much simpler functional components. Hooks can be really simple to use. Almost deceptively simple. Because there are still plenty of ways you can mess up with hooks. And it often turns out there are many ways where you can improve your components a better understanding of how each React hook can be used.You will learn all about the pros and cons of the various hooks. You will learn when to use useState() versus useReducer(). We will look at using useContext() efficiently. You will see when to use useLayoutEffect() and when useEffect() is better.
Mastering advanced concepts in TypeScript
React Summit US 2023React Summit US 2023
132 min
Mastering advanced concepts in TypeScript
Top Content
Featured WorkshopFree
Jiri Lojda
Jiri Lojda
TypeScript is not just types and interfaces. Join this workshop to master more advanced features of TypeScript that will make your code bullet-proof. We will cover conditional types and infer notation, template strings and how to map over union types and object/array properties. Each topic will be demonstrated on a sample application that was written with basic types or no types at all and we will together improve the code so you get more familiar with each feature and can bring this new knowledge directly into your projects.
You will learn:- - What are conditional types and infer notation- What are template strings- How to map over union types and object/array properties.
Designing Effective Tests With React Testing Library
React Summit 2023React Summit 2023
151 min
Designing Effective Tests With React Testing Library
Top Content
Featured Workshop
Josh Justice
Josh Justice
React Testing Library is a great framework for React component tests because there are a lot of questions it answers for you, so you don’t need to worry about those questions. But that doesn’t mean testing is easy. There are still a lot of questions you have to figure out for yourself: How many component tests should you write vs end-to-end tests or lower-level unit tests? How can you test a certain line of code that is tricky to test? And what in the world are you supposed to do about that persistent act() warning?
In this three-hour workshop we’ll introduce React Testing Library along with a mental model for how to think about designing your component tests. This mental model will help you see how to test each bit of logic, whether or not to mock dependencies, and will help improve the design of your components. You’ll walk away with the tools, techniques, and principles you need to implement low-cost, high-value component tests.
Table of contents- The different kinds of React application tests, and where component tests fit in- A mental model for thinking about the inputs and outputs of the components you test- Options for selecting DOM elements to verify and interact with them- The value of mocks and why they shouldn’t be avoided- The challenges with asynchrony in RTL tests and how to handle them
Prerequisites- Familiarity with building applications with React- Basic experience writing automated tests with Jest or another unit testing framework- You do not need any experience with React Testing Library- Machine setup: Node LTS, Yarn
Master JavaScript Patterns
JSNation 2024JSNation 2024
145 min
Master JavaScript Patterns
Top Content
Featured Workshop
Adrian Hajdin
Adrian Hajdin
During this workshop, participants will review the essential JavaScript patterns that every developer should know. Through hands-on exercises, real-world examples, and interactive discussions, attendees will deepen their understanding of best practices for organizing code, solving common challenges, and designing scalable architectures. By the end of the workshop, participants will gain newfound confidence in their ability to write high-quality JavaScript code that stands the test of time.
Points Covered:
1. Introduction to JavaScript Patterns2. Foundational Patterns3. Object Creation Patterns4. Behavioral Patterns5. Architectural Patterns6. Hands-On Exercises and Case Studies
How It Will Help Developers:
- Gain a deep understanding of JavaScript patterns and their applications in real-world scenarios- Learn best practices for organizing code, solving common challenges, and designing scalable architectures- Enhance problem-solving skills and code readability- Improve collaboration and communication within development teams- Accelerate career growth and opportunities for advancement in the software industry
Integrating LangChain with JavaScript for Web Developers
React Summit 2024React Summit 2024
92 min
Integrating LangChain with JavaScript for Web Developers
Featured Workshop
Vivek Nayyar
Vivek Nayyar
Dive into the world of AI with our interactive workshop designed specifically for web developers. "Hands-On AI: Integrating LangChain with JavaScript for Web Developers" offers a unique opportunity to bridge the gap between AI and web development. Despite the prominence of Python in AI development, the vast potential of JavaScript remains largely untapped. This workshop aims to change that.Throughout this hands-on session, participants will learn how to leverage LangChain—a tool designed to make large language models more accessible and useful—to build dynamic AI agents directly within JavaScript environments. This approach opens up new possibilities for enhancing web applications with intelligent features, from automated customer support to content generation and beyond.We'll start with the basics of LangChain and AI models, ensuring a solid foundation even for those new to AI. From there, we'll dive into practical exercises that demonstrate how to integrate these technologies into real-world JavaScript projects. Participants will work through examples, facing and overcoming the challenges of making AI work seamlessly on the web.This workshop is more than just a learning experience; it's a chance to be at the forefront of an emerging field. By the end, attendees will not only have gained valuable skills but also created AI-enhanced features they can take back to their projects or workplaces.Whether you're a seasoned web developer curious about AI or looking to expand your skillset into new and exciting areas, "Hands-On AI: Integrating LangChain with JavaScript for Web Developers" is your gateway to the future of web development. Join us to unlock the potential of AI in your web projects, making them smarter, more interactive, and more engaging for users.
Using CodeMirror to Build a JavaScript Editor with Linting and AutoComplete
React Day Berlin 2022React Day Berlin 2022
86 min
Using CodeMirror to Build a JavaScript Editor with Linting and AutoComplete
Top Content
WorkshopFree
Hussien Khayoon
Kahvi Patel
2 authors
Using a library might seem easy at first glance, but how do you choose the right library? How do you upgrade an existing one? And how do you wade through the documentation to find what you want?
In this workshop, we’ll discuss all these finer points while going through a general example of building a code editor using CodeMirror in React. All while sharing some of the nuances our team learned about using this library and some problems we encountered.