Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended-api #1

Merged
merged 3 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,20 @@ type MyContext = { userId: string; rolesByOrg: Record<string, "user" | "admin" |
```
```typescript
// define all your policies
import { z } from 'zod';
import { matchSchema, notNull, definePolicy, definePolicies, check, assert, or } from 'comply';
import { assert, check, definePolicies, definePolicy, matchSchema, notNull, or } from "comply";
import { z } from "zod";

const OrgPolicies = definePolicies((context: MyContext) => (orgId: string) => {
const currentUserOrgRole = context.rolesByOrg[orgId];

return [
definePolicy("can administrate", () =>
or(
() => currentUserOrgRole === "admin",
() => currentUserOrgRole === "superadmin"
)
),
definePolicy("is superadmin", () => currentUserOrgRole === "superadmin"),
definePolicy("can administrate", or(currentUserOrgRole === "admin", currentUserOrgRole === "superadmin")),
definePolicy("is superadmin", currentUserOrgRole === "superadmin"),
];
});

const UserPolicies = definePolicies((context: MyContext) => (userId: string) => [
definePolicy("can edit profile", () => context.userId === userId),
definePolicy("can edit profile", context.userId === userId),
]);

// create and export a 'guard' that contains all your policies, scoped by domain
Expand Down Expand Up @@ -90,9 +85,11 @@ If the condition requires a parameter, `assert` and `check` will require it.

Finally, if the condition is a type guard, the parameter you pass will be inferred automatically.

Note: for convenience, the condition can be a boolean value but you will lose type inference.

## Defining Policies
To define policies, you create a policy set using the `definePolicies` function.
Each policy definition is created using the `definePolicy` function, which takes a policy name and a callback that defines the policy logic.
Each policy definition is created using the `definePolicy` function, which takes a policy name and a callback that defines the policy logic (or a boolean value).
The callback logic can receive a unique parameter (scalar or object) and return a boolean value or a a type predicate.

You can also provide an error factory to the policy (3rd argument) to customize the error message.
Expand All @@ -108,9 +105,11 @@ _Primary use case_: simple policies that can be defined inline and are 'self-con

```typescript
const policies = definePolicies([
// built-in type guards
definePolicy("is not null", notNull),
definePolicy('params are valid', matchSchema(z.object({ name: z.string() }))),
// type guard
definePolicy("is a string", (v: unknown): v is string => typeof v === "string"),
definePolicy('params are valid', matchSchema(z.object({ name: z.string() })))
]);
```

Expand All @@ -127,7 +126,7 @@ Here's a quick example:
type Context = { userId: string; rolesByOrg: Record<string, "user" | "admin" | "superadmin">; appRole: "admin" | "user" };

const AdminPolicies = definePolicies((context: Context) => [
definePolicy("has app admin role", () => context.appRole === "admin"),
definePolicy("has app admin role", context.appRole === "admin")
]);

// 2️⃣
Expand All @@ -142,7 +141,7 @@ const OrgPolicies = definePolicies((context: MyContext) => (orgId: string) => {
() => currentUserOrgRole === "superadmin",
() => check(adminGuard.policy("has app admin role"))
),
definePolicy("is superadmin", () => currentUserOrgRole === "superadmin"),
definePolicy("is superadmin", currentUserOrgRole === "superadmin"), // lazy evaluation
];
});

Expand Down Expand Up @@ -233,10 +232,7 @@ type Context = { userId: string; rolesByOrg: Record<string, "user" | "admin"> };

const OrgPolicies = definePolicies((context: Context) => (orgId: string) => [
definePolicy("can administrate org", (stillOrgAdmin: boolean) =>
and(
() => context.rolesByOrg[orgId] === "admin",
() => stillOrgAdmin
)
and(context.rolesByOrg[orgId] === "admin", stillOrgAdmin)
),
]);

Expand Down Expand Up @@ -277,7 +273,7 @@ type PolicyConditionTypeGuard<T = any, U extends T = T> = (arg: T) => arg is U;
type PolicyConditionTypeGuardResult<P extends PolicyCondition> = P extends PolicyConditionTypeGuard<any, infer U>
? U
: PolicyConditionArg<P>;
type PolicyConditionNoArg = () => boolean;
type PolicyConditionNoArg = (() => boolean) | boolean;
type PolicyCondition<T = any, U extends T = T> =
| PolicyConditionTypeGuard<T, U>
| PolicyConditionWithArg<T>
Expand Down Expand Up @@ -418,6 +414,9 @@ const PostPolicies = definePolicies((context: Context) => {
() => post.status === "published"
)
),
definePolicy("[lazy] all published posts or mine", (post: Post) =>
or(check(myPostPolicy, post), post.status === "published")
),
];
});
```
Expand Down Expand Up @@ -449,6 +448,9 @@ const PostPolicies = definePolicies((context: Context) => {
() => post.status === "published"
)
),
definePolicy("[lazy] my published post", (post: Post) =>
and(check(myPostPolicy, post), post.status === "published")
),
];
});
```
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "comply",
"version": "0.1.1",
"version": "0.2.0",
"description": "Comply is a tiny library to help you define policies in your app",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
Loading
Loading