Migrating to v2 of the Node.js SDK

The v2 release of the Node.js SDK represents a significant rethinking of the developer experience. The biggest new feature — generating TypeScript types from your Polar policy — isn't actually a breaking change, but there are a few other breaking changes to be aware of.

Minimum supported Node.js version: 16

The Node.js SDK now requires Node.js 16 or later.

Management API

The Management API has been condensed from 6 methods to 4.

tell to insert

To migrate from tell to insert, wrap the arguments in an array:


const user = { type: "User", id: "1" };
const repo = { type: "Repo", id: "2" };
-await oso.tell("has_role", user, "member", repo);
+await oso.insert(["has_role", user, "member", repo]);

This new array-wrapped syntax matches the representation of facts across the rest of the API.

delete

To migrate from the previous delete API, wrap the arguments in an array:


const user = { type: "User", id: "1" };
const repo = { type: "Repo", id: "2" };
-await oso.delete("has_role", user, "member", repo);
+await oso.delete(["has_role", user, "member", repo]);

This new array-wrapped syntax matches the representation of facts across the rest of the API.

Additionally, the new delete method supports deleting all facts matching a pattern:


const user = { type: "User", id: "1" };
// Remove all of User:1's roles across the entire system.
await oso.delete(["has_role", user, null, null]);

get

To migrate from the previous get API, wrap the arguments in an array:


const user = { type: "User", id: "1" };
-const roles = await oso.get("has_role", user, null, null);
+const roles = await oso.get(["has_role", user, null, null]);

bulk to batch

To migrate from bulk to batch, turn all patterns to delete into calls to tx.delete() and all facts to insert into calls to tx.insert():


const user = { type: "User", id: "1" };
const repo = { type: "Repo", id: "3" };
-await oso.bulk(
- [["has_role", user, null, null]],
- [["has_role", user, "member", repo]],
-);
+await oso.batch((tx) => {
+ tx.delete(["has_role", user, null, null]);
+ tx.insert(["has_role", user, "member", repo]);
+});

bulkTell to batch

To migrate from bulkTell to batch, turn all facts to insert into calls to tx.insert():


const facts = [
["has_role", { type: "User", id: "1" }, "member", { type: "Repo", id: "2" }],
["has_role", { type: "User", id: "1" }, "member", { type: "Repo", id: "3" }],
];
-await oso.bulkTell(facts);
+await oso.batch((tx) => facts.forEach((f) => tx.insert(f)));

bulkDelete to batch

To migrate from bulkDelete to batch, turn all facts to delete into calls to tx.delete():


const facts = [
["has_role", { type: "User", id: "1" }, "member", { type: "Repo", id: "2" }],
["has_role", { type: "User", id: "1" }, "member", { type: "Repo", id: "3" }],
];
-await oso.bulkDelete(facts);
+await oso.batch((tx) => facts.forEach((f) => tx.delete(f)));

Additionally, the new batch method supports deleting all facts matching a pattern:


const user1 = { type: "User", id: "1" };
const user2 = { type: "User", id: "2" };
// Remove all roles for User:1 and User:2 across the entire system.
await oso.batch((tx) => {
tx.delete(["has_role", user1, null, null]);
tx.delete(["has_role", user2, null, null]);
});

Query API

We've replaced the Query API with a more powerful and flexible QueryBuilder API with a fluent interface. We've also dropped the AuthorizeResources and BulkActions APIs in favor of the QueryBuilder.

If you're using the existing query API, we recommend looking through the reference docs for the new Query Builder to upgrade.

authorizeResources to buildQuery

The authorizeResources API was used to authorize many resources for a single actor and action.


const user = { type: "User", id: "1" };
const repoIds = ["acme", "anvil"];
const repos = repoIds.map((id) => {
type: "Repo", id;
});
await oso.authorizeResources(user, "read", repos);
// => ["acme"]

With the new QueryBuilder API:


const user = { type: "User", id: "1" };
const repoIds = ["acme", "anvil"];
const repoVar = typedVar("Repo");
await oso
.buildQuery(["allow", user, "read", repoVar])
.in(repoVar, repoIds)
.evaluate(repoVar);
// => ["acme"]

If you are using context facts with authorizedResources, check out the withContextFacts QueryBuilder method.

bulkActions to buildQuery

The bulkActions API was used to get the actions an actor can take on a list of resources of a single type:


const user = { type: "User", id: "1" };
const repoIds = ["acme", "anvil"];
const repos = repoIds.map((id) => {
type: "Repo", id;
});
await oso.bulkActions(user, repos);
// => [["read"], []]

The result is an in-order list, which must be mapped back to the initial resource list.

With the new QueryBuilder API, you can get this mapping returned more directly:


const user = { type: "User", id: "1" };
const repoIds = ["acme", "anvil"];
const actionVar = typedVar("String");
const repoVar = typedVar("Repo");
await oso
.buildQuery(["allow", user, actionVar, repoVar])
.in(repoVar, repoIds)
.evaluate({ [repoVar]: actionVar });
// => { "acme": ["read"] }

Note that resources which the actor has no actions on are omitted from the results.

You can even get the actions an actor can take on ALL resources by leaving out the in clause:


const user = { type: "User", id: "1" };
const repoIds = ["acme", "anvil"];
const actionVar = typedVar("String");
const repoVar = typedVar("Repo");
await oso
.buildQuery(["allow", user, actionVar, repoVar])
.evaluate({ [repoVar]: actionVar });
// => {
// "acme": ["read"],
// "boulder": ["read"],
// ...
// }