Skip to content

Repository files navigation

Policymaker

Policymaker is a monorepo for fast, type-safe authorization policies in JavaScript and TypeScript. The synchronous core remains the default. A separate async constructor is available when a rule genuinely needs database or network I/O.

Packages

Package Responsibility
policymaker Framework-independent RBAC, ABAC, role inheritance, direct permissions, synchronous policies, and explicit async policies.
@policymaker/react Typed sync/async providers and conditional UI boundaries. These checks are for user-interface behavior only.
@policymaker/next Next.js server helpers that enforce a core policy through the framework's redirect() and notFound() primitives.

The dependency direction is intentionally one way:

@policymaker/react ──> policymaker
@policymaker/next  ──> policymaker

The core package has no React or Next.js dependency. Policymaker decides whether an action is allowed; it does not authenticate users, persist policy data, or transform sensitive data. Redactive remains a separate concern: Policymaker authorizes an operation, while Redactive detects, removes, masks, or transforms data. This monorepo contains no Redactive adapter.

Core quick starts

Use createPolicy when all authorization data is already available:

import { createPolicy } from 'policymaker';

const policy = createPolicy({
  actions: ['document:read'] as const,
  roles: {
    reader: ['document:read'],
  },
});

const allowed = policy.can({
  subject: { roles: ['reader'] },
  action: 'document:read',
});

Use createPolicyAsync only when a rule must await external data:

import { createPolicyAsync } from 'policymaker';

type Subject = { readonly id: string };
type Document = { readonly workspaceId: string };

const policy = createPolicyAsync<'document:update', Subject, Document>({
  actions: ['document:update'] as const,
  rules: [
    {
      id: 'active-membership',
      effect: 'allow',
      actions: ['document:update'],
      when: async ({ subject, resource }) =>
        resource !== undefined &&
        hasActiveMembership(subject.id, resource.workspaceId),
    },
  ],
});

const allowed = await policy.canAsync({
  subject: { id: 'user_1' },
  action: 'document:update',
  resource: { workspaceId: 'workspace_1' },
});

Do not pass an async predicate to createPolicy, and do not mix sync method names with an async engine. See the core documentation and API reference for the complete contracts.

Development

The workspace uses pnpm and requires Node.js 20 or newer.

pnpm install --frozen-lockfile
pnpm build
pnpm test
pnpm typecheck

See CONTRIBUTING.md for the complete validation matrix and RELEASING.md for the fixed-version, three-package release process.

Security

Authorization must be enforced at a trusted server boundary. React checks can hide or disable interface controls, but they do not replace server-side authorization. Report vulnerabilities using SECURITY.md.

License

MIT

About

Fast, type-safe authorization policies for JavaScript and TypeScript

Topics

Resources

Contributing

Security policy

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages