# Installation

Source: https://permdock.com/docs/getting-started/installation

Install the permdock package, pick a Standard Schema validator, and learn which subpath exports exist.

The current release is `permdock@0.1.0`. See the [roadmap](/docs/roadmap) for what is planned and how versions are numbered.

## Packages [#packages]

| Package | Purpose | Required |
| --- | --- | --- |
| `permdock` | Core plus every adapter as a subpath export (`permdock/react`, `permdock/next`, `permdock/hono`, ...), and the `permdock` binary (`collect`, `catalog`, `diff`, `usage`, `doctor`, `skills`, `openapi`, `rls`, `arazzo`, `cloud`, `supabase`) | Yes |
| `permdock/testing` | Subpath of `permdock`: policy matrix tests, snapshot fixtures, RLS parity runner, conformance runners; Vitest is an optional peer | Tests only |

```bash
pnpm add permdock
```

`permdock` is the only package. It is published by the `scaledockhq` npm organisation.

npm and yarn work the same way; the repository itself uses pnpm workspaces.

## Requirements [#requirements]

* Node.js 24 or later for servers and the CLI. The package declares `engines.node: ">=24"`. Browser and React Native bundles are produced from the same ESM entry points and do not depend on Node.
* TypeScript 7. PermDock is written in and built with TypeScript 7, and the public API avoids template-literal unions so TypeScript 7 stays fast: permissions are typed object references, not string unions. The type tests in `tests/types` also check the public types under TypeScript 5.9 and 6, so an app that has not moved yet keeps compiling.
* ESM only. There is no CommonJS build. Your `tsconfig.json` needs a `moduleResolution` that understands package `exports` (`bundler`, `node16` or `nodenext`) so subpath imports such as `permdock/react` resolve.
* Runtime entries depend on `@standard-schema/spec` only, and it is types only. The package's other dependencies (`oxc-parser`, `citty`, `jiti`, `smol-toml`, `package-manager-detector`, `@clack/prompts`, `diff`) belong to the CLI and no runtime entry loads them.

## Runtimes [#runtimes]

Core uses only the [WinterTC Minimum Common API](https://min-common-api.proposal.wintertc.org/) (`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: 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 24 or later | 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](/docs/adapters/approvals)); D1 has no RLS, `where` compilers still apply ([RLS](/docs/adapters/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](/docs/adapters/approvals) and [audit and observability](/docs/concepts/audit-and-observability) pages; none is a package.

## A Standard Schema validator [#a-standard-schema-validator]

Resources are defined from any validator that implements [Standard Schema](/docs/standards/standard-schema). Bring the one you already use:

```bash
pnpm add zod      # or
pnpm add valibot  # or
pnpm add arktype
```

PermDock 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](/docs/concepts/validation)). Boundary validation is synchronous, so a schema whose `validate` returns a Promise produces a typed `PermDockValidationError` instead of a silent deny.

## Subpath exports [#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 |
| --- | --- |
| `permdock` | `definePermissions`, `resource`, `mergePermissions`, `listPermissions`, `findPermission`, `definePolicy`, `defineRoles`, `definePlans`, `role`, `allow`, `deny`, `principal`, `context`, `createPermDock`, errors |
| `permdock/react` | `PermDockProvider`, `usePermDock`, `usePermission`, `<Protected>` |
| `permdock/next` | `createPermDock` returning `getPermDock`, `getPermission`, `PermDockProvider`, `permdockHandler` |
| `permdock/next/plugin` | `createPermDockPlugin` (build-time `collect` hook only) |
| `permdock/unplugin` | `createPermDockUnplugin` (the same `collect` hook for Vite, Rollup, webpack, Rspack and esbuild; recipes for TanStack Start, React Router, Nuxt, Astro, Effect) |
| `permdock/server` | Fetch-first kernel shared by the HTTP adapters |
| `permdock/hono` | `createPermDock` returning `permdock` middleware and `protect` |
| `permdock/express`, `permdock/fastify`, `permdock/elysia`, `permdock/nest`, `permdock/node` | Same shape as `permdock/hono` |
| `permdock/jwt` | `subjectFromJwt`, `createJwtSubjectResolver`: JWKS verification and claim mapping to a subject (`jose` optional peer) |
| `permdock/terminal` | `createPermDock` returning `permdock`, `protect`, `filterCommands`, `format` for your own CLI |
| `permdock/trpc`, `permdock/orpc` | RPC middleware with OpenAPI hooks |
| `permdock/mcp` | `createPermDock` returning `protectServer` |
| `permdock/ai-sdk` | `createPermDock` returning `toolApproval`, `capabilityMiddleware`, `needsApproval` |
| `permdock/claude-agent` | `createPermDock` returning `canUseTool`, `permissionRequestHook` |
| `permdock/eve` | `createPermDock` returning `approval`, `approvalFor`, `permdock` for Eve `defineTool` |
| `permdock/openai` | `createPermDock` returning `needsApproval`, `guardTools`, `resolveInterruptions`, `permdock` for the OpenAI Agents SDK |
| `permdock/approvals` | `ApprovalStore` interface, `memoryApprovalStore`, `approvalsHandler`; the `store` option of every agent and HTTP adapter |
| `permdock/cloud` | `cloud({ url, key })` returning `approvals`, `sink`, `snapshots` for PermDock Cloud; optional, server-only, never on the decision path |
| `permdock/webmcp` | `registerTools` |
| `permdock/a2a` | `createPermDock` returning `agentCard`, `extendedAgentCard` |
| `permdock/authzen` | `createPermDock` returning the AuthZEN `permdockHandler` |
| `permdock/ssf` | `createPermDock` returning the CAEP `receiver` |
| `permdock/scim` | `scimHandler`, the `DirectoryStore` interface, `memoryDirectoryStore`, `directoryMembershipSource` |
| `permdock/openapi` | OpenAPI emission (document or Overlay) and import |
| `permdock/otel` | Span per check and a decision counter |
| `permdock/react-native` | React adapter plus persisted `storage` |
| `permdock/vue`, `permdock/svelte`, `permdock/solid` | UI adapters |
| `permdock/drizzle`, `permdock/prisma`, `permdock/kysely` | `toWhere` condition compilers |
| `permdock/supabase`, `permdock/better-auth`, `permdock/clerk`, `permdock/convex`, `permdock/pdp` | Subject providers |

The full matrix with example apps and related standards is on the [adapters index](/docs/adapters).

## Optional peers [#optional-peers]

Runtime entries have no dependency beyond `@standard-schema/spec`. A few entries verify material or talk to a vendor SDK and declare an optional peer instead of bundling it, and a few CLI commands do the same:

| Entry | Optional peer | Why |
| --- | --- | --- |
| `permdock/jwt` | `jose` | JWS verification and JWKS fetching; see [JWT](/docs/adapters/jwt) |
| `permdock/otel` | `@opentelemetry/api` | Span and metric emission |
| `permdock/supabase`, `permdock/clerk`, `permdock/better-auth`, `permdock/convex` | The provider SDK, at the call site | The adapters take the values the provider's own SDK returned and declare no peer themselves; session and claim verification stays with the provider; see [authentication](/docs/concepts/authentication) |
| `permdock/terminal` | An OS keychain binding | Token storage; falls back to a mode-0600 file |
| `permdock/testing` | `vitest` | `describePolicy` and the runners register Vitest tests |
| `permdock rls import` | `pgsql-parser` | Parses Postgres policies (WASM, about 2.8 MB installed); the command prints the install line when it is missing |
| `permdock rls import --db`, `permdock rls verify --db` | `pg` | Talks to a live database |
| `permdock/unplugin` | `unplugin` | The bundler adapter layer for Vite, Rollup, webpack, Rspack and esbuild |

### Why runtime entries have no dependencies [#why-runtime-entries-have-no-dependencies]

Core runs in browsers, React Native, edge functions, serverless functions, MCP servers and Node, and every dependency it carries is paid by all of them. So every runtime entry imports exactly one package, `@standard-schema/spec`. It is types only, and it is a real dependency rather than vendored types because resource definitions take a `StandardSchemaV1`, which makes it part of the public API that should version with the spec.

* No validator. A bundled Zod would lock out Valibot and ArkType users and add tens of kilobytes to clients that never call it.
* No serialiser. Conditions and snapshots are plain JSON with tagged ISO date strings, one format that works in Postgres, snapshots and catalogs, so there is no `superjson`.
* No telemetry. OpenTelemetry lives in `permdock/otel` behind a type-only `Tracer` interface rather than as a required peer.
* ESM only. The target runtimes are ESM, and a dual CommonJS build would double the test matrix and bring the dual-package hazard.
* Tooling never reaches a runtime entry: the CLI and the test runners ship in the same package, but no runtime entry imports them. The one CLI dependency, `oxc-parser` (about 3.5 MB installed with one platform binding), is what `collect`, `doctor` and the build hooks need; `yaml` and `ajv` are bundled into the lazily loaded OpenAPI command chunks, and the Postgres parser, `pg`, `unplugin` and Vitest are optional peers.

There is no size cap chosen in advance. `tests/bundle` measures the min+gzip size of every entry, and the measured size is the regression baseline: a test compares every entry against the recorded baseline, so a size change fails CI until the baseline is updated in the same PR, where review sees it. Core stays small because of the rules above, not because of a number picked from a competitor's bundle, so features such as the condition AST or the `Decision` shape are never cut to meet a guess.

## TypeScript configuration [#typescript-configuration]

A minimal `tsconfig.json` that works with the subpath exports and keeps inference fast:

```json
{
  "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 [#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](/docs/getting-started/quick-start) fills in the first three.

## Verify the install [#verify-the-install]

```bash
pnpm permdock doctor
```

`doctor` 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. The `permdock-wire` skill runs it as soon as the three files exist.

## Next steps [#next-steps]

* [Quick start](/docs/getting-started/quick-start): define, grant, check, guard.
* [Larger apps](/docs/getting-started/larger-apps): per-feature definitions, `mergePermissions`, `permdock collect`.
* [Naming](/docs/getting-started/naming): why every adapter exports `createPermDock`.
