Installation
Install the permdock package, pick a Standard Schema validator, and learn which subpath exports exist.
PermDock is in Phase 0. Nothing is published to npm yet; the commands below describe the intended install experience so that the docs, README and skills agree before the first release. See the roadmap for what ships in which phase.
Packages
| Package | Purpose | Required |
|---|---|---|
permdock | Core plus every adapter as a subpath export (permdock/react, permdock/next, permdock/hono, ...) | Yes |
@permdock/cli | permdock collect, catalog, usage, openapi, rls, doctor, skills | Optional, dev only |
@permdock/testing | Policy matrix tests, snapshot fixtures, RLS parity runner, instant() helpers | Optional, dev only |
pnpm add permdock
pnpm add -D @permdock/cli @permdock/testingnpm and yarn work the same way; the repository itself uses pnpm workspaces.
Requirements
- TypeScript 5.9, 6 or 7. The type tests in
tests/typesrun against all three, and the public API avoids template-literal unions so TypeScript 7 stays fast. Permissions are typed object references, not string unions. - ESM only. There is no CommonJS build. Your
tsconfig.jsonneeds amoduleResolutionthat understands packageexports(bundler,node16ornodenext) so subpath imports such aspermdock/reactresolve. - A current Node.js LTS release for servers and the CLI. Browser and React Native bundles are produced from the same ESM entry points.
- Zero runtime dependencies in core.
@standard-schema/specis the only regular dependency and it is types only.
Runtimes
Core uses only the WinterTC Minimum Common API (fetch, Request, Response, URL, crypto.subtle, TextEncoder, structuredClone) and no Node built-ins, no AsyncLocalStorage, no module-level state. That is by construction (invariants 7 and 12 in AGENTS.md) and a conformance test in tests/bundle asserts it from Phase 1: every client entry and permdock/server must load in a bare WinterTC-shaped global. The table lists what each runtime adds or lacks; nothing in it changes how a decision is made.
| Runtime | Core, permdock/server, agent adapters | Notes |
|---|---|---|
| Node.js LTS | Yes | The CLI and permdock/node need Node; permdock/jwt uses jose's Node build |
| Bun | Yes | Elysia's home runtime; bun test runs the policy matrix unchanged |
| Deno, Deno Deploy | Yes | Import from npm specifiers or JSR once published; no Node built-ins to polyfill |
| Cloudflare Workers | Yes | memoryApprovalStore is per isolate: use Durable Objects or KV behind ApprovalStore and SnapshotSource (approvals); D1 has no RLS, where compilers still apply (RLS) |
| Vercel Functions (Fluid), Edge | Yes | permdock/next targets the Node runtime for React.cache and 'use cache: private'; the Fetch kernel runs on Edge too |
| Netlify Functions and Edge | Yes | Same as Vercel Edge |
| AWS Lambda | Yes | Through permdock/node or a Fetch adapter such as Hono's; approvals need an external ApprovalStore |
| Browsers, React Native (Hermes) | Client entries only | permdock/react, permdock/react-native, permdock/webmcp; no policy code ships (invariant 8) |
The one runtime-specific caveat is state: any store or sink that defaults to memory (memoryApprovalStore, memorySink) is per process or per isolate and resets on cold start. permdock doctor warns when the memory store is the configured ApprovalStore in a serverless target. Recipes for Durable Objects, KV, Redis and Postgres stores live on the approvals and audit and observability pages; none is a package.
A Standard Schema validator
Resources are defined from any validator that implements Standard Schema. Bring the one you already use:
pnpm add zod # or
pnpm add valibot # or
pnpm add arktypePermDock never imports a validator itself. resource(Post, ...) reads Post['~standard'] to infer the instance type and to validate untrusted input at trust boundaries (see validation). Boundary validation is synchronous, so a schema whose validate returns a Promise produces a typed PermDockValidationError instead of a silent deny.
Subpath exports
All adapters live in the one permdock package. Import only what a bundle needs; nothing from a server entry is reachable from a client entry.
| Import path | Contents | Phase |
|---|---|---|
permdock | definePermissions, resource, mergePermissions, listPermissions, findPermission, definePolicy, role, allow, deny, subject, createPermDock, errors | 1 |
permdock/react | PermDockProvider, usePermDock, usePermission, <Protected> | 1 |
permdock/next | createPermDock returning getPermDock, getPermission, PermDockProvider, permdockHandler | 1 |
permdock/next/plugin | createPermDockPlugin (build-time collect hook only) | 2 |
@permdock/cli/unplugin | createPermDockUnplugin (the same collect hook for Vite, Rollup, webpack, Rspack and esbuild; TanStack Start, React Router, SvelteKit, Nuxt, Astro, Solid) | 2 |
permdock/server | Fetch-first kernel shared by the HTTP adapters | 1 |
permdock/hono | createPermDock returning permdock middleware and protect | 1 |
permdock/express, permdock/fastify, permdock/elysia, permdock/nest, permdock/node | Same shape as permdock/hono | 2 |
permdock/jwt | subjectFromJwt, createJwtSubjectResolver: JWKS verification and claim mapping to a subject (jose optional peer) | 1 |
permdock/terminal | createPermDock returning permdock, protect, filterCommands, format for your own CLI | 2 |
permdock/trpc, permdock/orpc | RPC middleware with OpenAPI hooks | 2 |
permdock/mcp | createPermDock returning protectServer | 2 |
permdock/ai-sdk | createPermDock returning toolApproval, capabilityMiddleware, needsApproval | 1 |
permdock/claude-agent | createPermDock returning canUseTool, permissionRequestHook | 1 |
permdock/eve | createPermDock returning approval, approvalFor, permdock for Eve defineTool | 1 |
permdock/openai | createPermDock returning needsApproval, guardTools, resolveInterruptions, permdock for the OpenAI Agents SDK | 1 |
permdock/approvals | ApprovalStore interface, memoryApprovalStore, approvalsHandler; the store option of every agent and HTTP adapter | 1 |
permdock/cloud | cloud({ url, key }) returning approvals, sink, snapshots for PermDock Cloud; optional, server-only, never on the decision path | 2 |
permdock/webmcp | registerTools | 2 |
permdock/a2a | createPermDock returning agentCard, extendedAgentCard | 2 |
permdock/authzen | createPermDock returning the AuthZEN handler | 1 (endpoint), 2 (full) |
permdock/ssf | createPermDock returning the CAEP receiver | 3 |
permdock/openapi | OpenAPI 3.2 emission (document or Overlay) and import | 2 |
permdock/otel | Span per check and a decision counter | 2 |
permdock/react-native | React adapter plus persisted storage | 2 |
permdock/vue, permdock/svelte, permdock/solid | UI adapters | 2 |
permdock/drizzle, permdock/prisma, permdock/kysely | toWhere condition compilers | 3 |
permdock/supabase, permdock/better-auth, permdock/clerk, permdock/convex, permdock/pdp | Subject providers | 3 and 4 |
The full matrix with example apps and related standards is on the adapters index.
Optional peers
Core has no runtime dependencies. A few entries verify material or talk to a vendor SDK and declare an optional peer instead of bundling it:
| Entry | Optional peer | Why |
|---|---|---|
permdock/jwt | jose | JWS verification and JWKS fetching; see JWT |
permdock/otel | @opentelemetry/api | Span and metric emission |
permdock/mcp | @modelcontextprotocol/server 2.x (types only) | protectServer duck-types the McpServer at runtime; the peer supplies AuthInfo and registerTool option types. SDK 1.x is not supported (MCP adapter, Hosting) |
permdock/supabase, permdock/clerk, permdock/better-auth, permdock/convex | The provider SDK | Session and claim verification stays with the provider; see authentication |
permdock/terminal | An OS keychain binding | Token storage; falls back to a mode-0600 file |
TypeScript configuration
A minimal tsconfig.json that works with the subpath exports and keeps inference fast:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true
}
}strict matters: subject narrowing through assert and the actions / collection arity checks rely on strict null checks. The library itself is built with isolatedDeclarations and erasableSyntaxOnly, so its declaration files contain no inferred-only types and load quickly.
Where files go
| File | Contents | Imported by |
|---|---|---|
src/permissions.ts | definePermissions() or mergePermissions(); no rules | Everything, including client bundles |
src/policy.ts | definePolicy() with roles | Server only |
src/permdock/server.ts | createPermDock from a server adapter | Server code |
src/permissions.generated.ts | Output of permdock rls import or openapi import | src/permissions.ts |
permissions.catalog.json | Output of permdock collect; committed | CI (--check), docs, OpenAPI |
The quick start fills in the first three.
Verify the install
pnpm permdock doctordoctor checks the TypeScript version, module resolution, that exactly one copy of permdock is installed, that no server entry is imported from a client file, that memoryApprovalStore() is not the store in a serverless deployment that surfaces approvals, that PERMDOCK_CLOUD_* variables (when present) are server-only and reach the API, and that the catalog produced by permdock collect is fresh. It is the first thing the shipped wire-permdock skill runs.
Next steps
- Quick start: define, grant, check, guard.
- Larger apps: per-feature definitions,
mergePermissions,permdock collect. - Naming: why every adapter exports
createPermDock.