VC: Viewer Context and Principal
Last updated
Was this helpful?
Last updated
Was this helpful?
One of the most important Ent Framework traits is that it always knows, "who" is sending some read/write query to the database, and is able to check permissions. Typically, that "who" is a user who opens a web page, or on behalf of whom a background worker job is running, but it can be any other Principal. This mechanism is quite different from traditional database abstraction layers or ORMs, which typically lack awareness of the specific user on whose behalf the queries are executed.
To send a query, you must always have an instance of class in hand (stands for Viewer Context). The most important property in a VC is principal
, it's a string which identifies the party who's acting. Typically, we store some user ID in vc.principal
.
It is intentionally not easy to create a brand new VC instance. In fact, you should only do it once in your app (this VC is called "root VC"), and all other VCs created should derive from that VC using its methods.
Below is a basic example for framework. (Of course you can use any other framework like Express or whatever. Next.js is here only for illustrative purposes, it has nothing to do with Ent Framework.)
For simplicity of the example, we'll plug in "Login with Google" feature to our Next app, and then will use the user's email as a primary method of addressing an EntUser.
Next.js exposes getServerSession()
function for server components, to allow you access the session data of the user, including their email:
You can also use getServerSession()
from inside of your API route handlers.
The same way as getServerSession()
gives us access to the user's session, let's build a function that returns a VC instance for that user. Technically, this function should work exactly the same way as getServerSession()
: it will even use session.user.email
field from there.
And in case the user is not authenticated yet, we still need a "guest VC" to be returned by this function. Such VC can still access some "public" Ents (depending on their privacy rules).
The VC instance should be "memoized" per the HTTP request, so if the VC accessor function is called multiple time, it should return the same object. This is critical: otherwise, many Ent Framework features (like queries batching and caching) will just not work as they should.
Different frameworks have different ways of attaching a property to the request object. In Next, the easiest way so far is to use WeakMap
and headers()
API function. (In Express, you would likely just assign a value to req.vc
in some middleware.)
We will discuss what loadByX()
is in the next sections. In short, it loads an Ent by unique key and throws an eXception (this is what "X" stands for) if it doesn't exist.
Here comes the catch: loadByX()
requires to pass a VC whose principal is the user loading the data. And to derive that VC, we need to call EntUser#loadByX()
. In our case, it's obviously a "chicken and egg" problem, so we just derive a new VC in "god mode" with vc.toOmniDangerous()
and allow Ent Framework to bypass privacy checks for the very 1st EntUser
loaded.
So now, everywhere you could use getServerSession()
, you can use getServerVC()
as well.
For instance, in a server component:
Or in an API route handle:
In other frameworks, you would access the per-request VC differently. For instance, in Express, you would likely just read req.vc
value that you earlier assigned in a middleware.
Now on any page, you may place a :