PermDock
Decisions

0002: One umbrella package with subpath exports

Why runtime adapters ship inside the single permdock package while the CLI and testing helpers are separate scoped packages.

Status

Accepted, September 2026.

Context

PermDock ships a core plus more than thirty adapters (React, React Native, Next.js, Vue, Svelte, Solid, a Fetch server kernel, Hono, Express, Fastify, Elysia, Nest, Node, tRPC, oRPC, MCP, AI SDK, Claude Agent SDK, WebMCP, A2A, AuthZEN, SSF, OpenAPI, OTel, Drizzle, Prisma, Kysely, Supabase, Better Auth, Clerk, Convex, PDP). Two packaging models were compared:

  • Scoped packages per adapter (@permdock/core, @permdock/react, @permdock/next, ...), the CASL and Kilpi model. Independent peer dependencies and version ranges per adapter, but consumers (and agents) must discover which package they need, and version skew between core and adapters is a recurring support burden.
  • One umbrella package with subpath exports (permdock, permdock/react, permdock/next, ...), the permix model. One install, one version, and tree-shaking through exports keeps unused adapters out of bundles.

The tooling is different in kind. The catalog collector needs a JavaScript parser (oxc-parser); RLS import needs a Postgres parser (pgsql-parser, libpg_query compiled to WASM). Both are heavy, native-or-WASM dependencies that must never be pulled into an application bundle or an edge function.

Decision

  • Runtime code ships as one npm package, permdock, with subpath exports. Core is permdock; every adapter is permdock/<framework>. Framework SDKs are optional peer dependencies.
  • @permdock/cli is a separate package that owns collect, catalog, usage, openapi, rls, doctor and skills, and depends on oxc-parser and pgsql-parser. The Next.js build hook createPermDockPlugin lives with the CLI code and is exposed as permdock/next/plugin.
  • @permdock/testing is a separate package for policy matrix tests, snapshot fixtures, the RLS parity runner and instant() helpers, so Vitest and Playwright dependencies stay out of permdock.
  • Agent Skills (wire-permdock, audit-permissions) ship inside permdock under skills/.

Consequences

  • An agent needs exactly one install line (pnpm add permdock) to wire any adapter; the skill can state import paths without a package lookup.
  • Core and adapters are always version-aligned; there is no "adapter X requires core Y" matrix.
  • Bundle budgets are enforced per entry point in tests/bundle, because a single package makes accidental cross-imports easy. Client entries are checked for server-only imports by permdock doctor.
  • Peer dependency declarations grow with every adapter; all are optional, and publint and arethetypeswrong run in CI to keep the exports map correct.
  • The import path names the framework, which is why identifiers never do (see 0005).

Alternatives considered

  • @permdock/* per adapter. Cleaner peer dependencies and independent release cadence, rejected for install friction, version skew and worse agent ergonomics. Kilpi's separate @kilpi/client and @kilpi/react-server packages are the cautionary example.
  • Everything in one package, CLI included. Rejected: the parsers would land in every node_modules, and bundlers would need to be told to ignore them.
  • CLI as a dev dependency of the umbrella. Rejected for the same reason; npx @permdock/cli and pnpm add -D @permdock/cli are explicit.
  • Testing helpers inside permdock/testing. Rejected because they depend on Vitest and Playwright types, which must not appear in the runtime package.

On this page