Module API

The root primate entrypoint exports the application-facing Module type used to extend Primate's build, startup, logging, and request lifecycles.

import type { Module } from "primate";

Module is type-only; there is no runtime module factory. See the Modules guide for authoring patterns and full examples.

Module

interface Module {
  name: string;
  setup(setup: Setup): void;
}
import type { Module } from "primate";

export default function example(): Module {
  return {
    name: "example",
    setup({ onInit }) {
      onInit(app => app.log.info`example initialized`);
    },
  };
}

Module.name

name: string;

Primate rejects duplicate configured module names. Module objects are also deduplicated by name during configuration validation.

Module.setup()

setup(setup: Setup): void;

Called once for each module while the application is constructed. Register hooks during this call. Asynchronous setup is not awaited; asynchronous work belongs in onInit, onBuild, or onServe.

Setup

The hook registrar passed to Module.setup(). This interface is exposed through the public Module declaration but is not a separate named export from primate.

interface Setup {
  onInit(hook: InitHook): void;
  onBuild(hook: BuildHook): void;
  onServe(hook: ServeHook): void;
  onHandle(hook: HandleHook): void;
  onRoute(hook: RouteHook): void;
  onLog(hook: LogHook): void;
  onError(hook: ErrorHook): void;
}

Each registration method returns void. Multiple callbacks of one kind run in module/configuration registration order.

setup.onInit()

type InitHook = (app: InitApp) => MaybePromise<void>;
onInit(hook: InitHook): void;

Runs during application initialization before the target and build phases. Thrown errors and rejected promises abort initialization.

InitApp is a framework-owned callback object, not an application import. Its relevant extension surface includes configuration lookup, project/build paths, the logger, runtime mode, url, secure, and symbol-keyed module state. See the Modules guide for examples.

setup.onBuild()

type BuildHook = (app: BuildApp) => MaybePromise<void>;
onBuild(hook: BuildHook): void;

Runs during the build. BuildApp extends the initialization callback surface with loader, build-root, entrypoint, post-build, and esbuild plugin registration capabilities. It is a framework-owned callback type, not a public entrypoint. Rejected promises abort the build.

setup.onServe()

type ServeHook = (app: ServeApp) => MaybePromise<void>;
onServe(hook: ServeHook): void;

Runs once as the built server initializes. ServeApp is a framework-owned callback object with the shared app surface plus server state such as secure, url, loaded views, and response/rendering facilities. Rejected promises abort server initialization.

setup.onHandle()

type HandleHook = (
  request: RequestFacade,
  next: (request: RequestFacade) => MaybePromise<Response>,
) => MaybePromise<Response>;

onHandle(hook: HandleHook): void;

Runs before routing for every request, including static assets. Return a native Response to stop processing or return next(request) to continue. next may be called at most once and receives the request passed downstream.

setup.onRoute()

type RouteHook = (
  request: RequestFacade,
  next: (request: RequestFacade) => MaybePromise<ResponseLike>,
) => MaybePromise<ResponseLike>;

onRoute(hook: RouteHook): void;

Runs after route matching and before the handler. It may stop the chain with a ResponseLike or wrap the result of next(request). The same one-call and defined-return constraints as route-file hooks apply.

setup.onLog()

type LogHook = (entry: LogEntry) => void;
onLog(hook: LogHook): void;

type LogEntry = {
  level: "error" | "warn" | "info" | "trace";
  message: string;
};

Runs synchronously for every emitted log call, including entries below the configured output threshold. A log hook cannot be asynchronous.

setup.onError()

type ErrorHook = (error: RequestError) => MaybePromise<void>;
onError(hook: ErrorHook): void;

Observes unexpected request failures before Primate invokes the error route. Observers run in registration order. An observer's result cannot replace the response; observer failures are logged and do not prevent later observers.

RequestError

Supporting input received by setup.onError(). It is not a named root primate export.

type RequestError = {
  readonly cause: unknown;
  readonly frames: readonly RequestErrorFrame[];
  readonly phase: RequestErrorPhase;
  readonly request: RequestFacade;
  readonly route?: string;
};

RequestErrorPhase

type RequestErrorPhase =
  | "match"
  | "handler"
  | "layout"
  | "render"
  | "error-handler"
  | "handle";

RequestErrorFrame

type RequestErrorFrame = {
  readonly file: string;
  readonly line: number;
  readonly column: number;
  readonly function?: string;
};

Production frames are mapped through the private server source map when available. Mapping is observational and never changes the error response.