hts/packages/rbac/src/queries.ts

60 lines
1.6 KiB
TypeScript

import { z } from "zod";
import { unkeyPermissionValidation } from "./permissions";
type Rule = "and" | "or";
export type PermissionQuery<R extends string = string> =
| R
| {
and: PermissionQuery<R>[];
or?: never;
}
| {
and?: never;
or: PermissionQuery<R>[];
};
export const permissionQuerySchema: z.ZodType<PermissionQuery> = z.union([
z.string(),
z.object({
and: z.array(z.lazy(() => permissionQuerySchema)).min(1, "provide at least one permission"),
}),
z.object({
or: z.array(z.lazy(() => permissionQuerySchema)).min(1, "provide at least one permission"),
}),
]);
function merge<R extends string>(rule: Rule, ...args: PermissionQuery<R>[]): PermissionQuery<R> {
return args.reduce(
(acc: PermissionQuery<R>, arg) => {
if (typeof acc === "string") {
throw new Error("Cannot merge into a string");
}
if (!acc[rule]) {
acc[rule] = [];
}
acc[rule]!.push(arg);
return acc;
},
{} as PermissionQuery<R>,
);
}
export function or<R extends string = string>(...args: PermissionQuery<R>[]): PermissionQuery<R> {
return merge("or", ...args);
}
export function and<R extends string = string>(...args: PermissionQuery<R>[]): PermissionQuery<R> {
return merge("and", ...args);
}
export function buildQuery<R extends string = string>(
fn: (ops: { or: typeof or<R>; and: typeof and<R> }) => PermissionQuery<R>,
): PermissionQuery {
return fn({ or, and });
}
/**
* buildUnkeyQuery is preloaded with out available roles and ensures typesafety for root key validation
*/
export const buildUnkeyQuery = buildQuery<z.infer<typeof unkeyPermissionValidation>>;