PermDock
Adapters

Node http

permdock/node wraps the Fetch kernel for raw node:http handlers (IncomingMessage and ServerResponse) and for any framework not covered by a dedicated adapter.

Purpose

Some services run on node:http directly, on a framework without a PermDock adapter (Koa, Polka, h3), or on a custom server. permdock/node converts IncomingMessage / ServerResponse to the Fetch Request / Response pair the server kernel expects, and exposes the kernel's permdock, protect and problem in a callback shape. It is also the conversion layer the Express and Nest adapters reuse. Runtimes that already expose Fetch handlers (Bun, Deno, Workers, srvx) should use permdock/server directly.

API

import { createServer } from "node:http";
import { createPermDock } from "permdock/node";
import { policy } from "./policy";
import { permissions } from "./permissions";

const { permdock, protect, send } = createPermDock(policy, {
  subject: (req) => userFromCookie(req.headers.cookie),
});

createServer(async (req, res) => {
  const instance = await permdock(req);

  if (req.method === "DELETE" && req.url?.startsWith("/posts/")) {
    const guard = await protect(permissions.post.delete, () =>
      loadPost(idFrom(req.url)),
    )(req);
    if (!guard.ok) return send(res, guard.response);
    await deletePost(guard.data);
    res.statusCode = 204;
    return res.end();
  }

  res.statusCode = 200;
  res.end(
    JSON.stringify(instance.filter(permissions.post.read, await listPosts())),
  );
}).listen(3000);
ExportRole
permdock(req)Converts the request once (headers, method, URL; body is streamed lazily), resolves the subject, builds the instance, memoises it in a WeakMap keyed by req.
protect(permission, loadData?, options?)Returns (req) => Promise<Guard>; same result shape as the kernel (ok, permdock, decision, data or response). The tenant option ((req) => ...) is resolved for each call.
send(res, response)Writes a Fetch Response to a ServerResponse (status, headers, body). Used for Problem Details and for the decision endpoint.
permdockHandler()(req, res) => Promise<void> implementing the AuthZEN-shaped decision endpoint for mounting under any path.
toRequest(req) / fromResponse(res, response)Exported converters so other adapters and custom frameworks can reuse them.

Request lifecycle

permdock/node follows the shared adapter contract through the server kernel. What differs on raw node:http:

  • permdock(req) memoises on req, so later calls with the same request return the same instance.
  • A PermDock error thrown by assert has no framework hook to catch it: wrap the handler and send the result of problemFromError(error) from permdock/server when it returns a Response.
  • The conversion is lazy. Headers, method and URL are copied when permdock(req) is first called; an unread body is exposed as a pull stream (highWaterMark: 0) on the Fetch Request and only read when a loader or the decision endpoint consumes it, so a body parser that runs later still sees every byte. A parsed req.body (Express, Nest) is reused instead of re-reading the stream. A missing Host header falls back to localhost and does not throw.
  • permdockHandler() validates decision-endpoint bodies against the AuthZEN evaluations schema.

Denials are the kernel's Problem Details responses, written through send.

Example app

No dedicated example. Covered by unit tests using node:http on an ephemeral port and by the Express and Nest examples, which reuse the same converters.

tests/integration/src/http/node.test.ts runs the testHttpAdapter scenarios on node:http with a URLPattern router, a formData() upload behind protect and problemFromError for assert.

Last updated on

On this page