API reference
The complete public surface of DaloyJS v1.0.0-beta.4, organized by import path. Every signature on this page is generated from the same TypeScript types your editor reads on hover, open the source files for fuller TSDoc, examples, and security rationale.
Minimal server
This page is a reference, the signatures below are the source of truth, not a step-by-step tutorial. If you are starting from scratch, the getting-started guide walks through scaffolding, validation, the typed client, and OpenAPI docs in full. The snippet here is just enough to map the types below onto a server you can actually run.
pnpm add @daloyjs/core zod
pnpm add -D tsx// index.ts
import { z } from "zod";
import { App } from "@daloyjs/core"; // root barrel
import { serve } from "@daloyjs/core/node"; // adapters are subpath-only
const app = new App({ title: "Hello API", version: "1.0.0" }).route({
method: "GET",
path: "/hello",
operationId: "hello",
responses: {
// A response `body` schema enables OWASP-API3 field stripping.
200: { description: "Greeting", body: z.object({ message: z.string() }) },
},
// The handler returns the discriminated union HandlerReturn<Res>:
// { status, body, headers? }, keyed by a status declared above.
handler: () => ({ status: 200, body: { message: "Hello from DaloyJS" } }),
});
const { port } = serve(app); // NodeServerOptions.port defaults to 3000
console.log(`listening on http://localhost:${port}`);Run it with node --import tsx index.ts (Node 24+ can also run node index.ts directly via type stripping). Every response already carries the secure-by-default headers (secureHeaders) and an x-request-id (requestId); errors serialize to RFC 9457 application/problem+json. To serve /docs and /openapi.json, pass docs: true to new App(...) (it defaults to false).
If you drop the response body schema the route still works, but DaloyJS logs a security.response.bodySchemaMissing warning at startup: response field-level stripping (OWASP API3) cannot be applied to a schema-less body. Declare the schema, or ignore the warning for routes that intentionally return no body.
Subpath modules
Quick map of subpath modules exposed by the package:
@daloyjs/core // App, routing types, errors, middleware, security, JWT/JWK, ...
@daloyjs/core/openapi // OpenAPI 3.1 document generation + security-scheme builders
@daloyjs/core/openapi-diff // Dependency-free OpenAPI 3.x breaking-change diffing
@daloyjs/core/asyncapi // AsyncAPI 3.0 generation for app.ws() WebSocket surfaces
@daloyjs/core/client // Typed in-process client + Hey API SDK glue
@daloyjs/core/contract // Contract-tests harness (assert OpenAPI parity)
@daloyjs/core/docs // Scalar / Swagger UI / Redoc HTML + CSP helper
@daloyjs/core/streaming // SSE + NDJSON helpers
@daloyjs/core/websocket // WebSocket route helper + frame primitives
@daloyjs/core/multipart // File-field + multipart object schema helpers
// Observability & ops
@daloyjs/core/tracing // OpenTelemetry tracing hook (interface-typed; no runtime dep)
@daloyjs/core/metrics // Prometheus / OpenMetrics exposition
@daloyjs/core/banner // Pretty startup banner
@daloyjs/core/cli // CLI internals (used by bin/daloy.mjs)
// Auth, sessions & crypto (also on the root barrel)
@daloyjs/core/session // Cookie sessions + signed-value helpers
@daloyjs/core/hashing // passwordHash / passwordVerify (scrypt)
@daloyjs/core/jwt // createJwtSigner / createJwtVerifier (no "alg: none")
@daloyjs/core/jwk // jwk() JWKS Bearer middleware (refuses HS*)
@daloyjs/core/cookie // Cookie serialization + attribute validation
@daloyjs/core/time-claims // assertTemporalClaims() (iat / nbf / exp)
// HTTP features & API ergonomics
@daloyjs/core/etag // etag() strong-validation 304 helper
@daloyjs/core/compression // compression() with BREACH-aware defaults
@daloyjs/core/pagination // Opaque-cursor pagination helpers
@daloyjs/core/idempotency // Idempotency-Key handling for unsafe-method retries
@daloyjs/core/response-cache // Server-side response caching (pluggable store)
@daloyjs/core/tenancy // Multitenancy: per-request tenant resolution
@daloyjs/core/scheduler // In-process scheduled (cron) tasks
// Rate limiting, concurrency & access control
@daloyjs/core/rate-limit-redis // Distributed rate-limit store
@daloyjs/core/concurrency-limit // Per-route/client concurrency limit + FIFO queue
@daloyjs/core/waf // WAF-lite inbound inspection (OWASP CRS-lite)
@daloyjs/core/auto-ban // Adaptive fail2ban-style escalating bans
@daloyjs/core/bot-guard // Bot / User-Agent management
@daloyjs/core/ip-reputation // Pluggable, refreshed IP abuse-feed denylist
@daloyjs/core/geo-block // ISO 3166-1 country allow/deny (BYO GeoIP lookup)
@daloyjs/core/request-decompression // Inbound decompression-bomb guard
@daloyjs/core/mtls // Mutual-TLS / client-certificate auth
@daloyjs/core/http-signatures // HTTP Message Signatures (RFC 9421) sign + verify
// Outbound resilience
@daloyjs/core/fetch-resilience // resilientFetch(): circuit breaker + retry + timeout
@daloyjs/core/webhook-delivery // Outbound webhook delivery (signed, retried)
// Runtime adapters
@daloyjs/core/node // Node.js (http) - serve(app, opts)
@daloyjs/core/bun // Bun.serve adapter
@daloyjs/core/deno // Deno.serve adapter
@daloyjs/core/cloudflare // Cloudflare Workers + generic { fetch } default export
@daloyjs/core/vercel // Vercel Functions / Edge / Next.js App Router
@daloyjs/core/fastly // Fastly Compute@Edge
@daloyjs/core/lambda // AWS Lambda (API Gateway v1 + v2 / Function URLs)You can import any feature two ways: from the root @daloyjs/core barrel (convenient and tree-shakeable), or from its own subpath (for example @daloyjs/core/jwt) for the smallest possible bundle without relying on a bundler's tree-shaking. Both resolve to the same code. Runtime adapters are the one exception: they are available only as subpaths (for example @daloyjs/core/node), so runtime-specific code such as node:http never leaks into an edge or Worker bundle.
@daloyjs/core (root)
class App
new App(options?: AppOptions)
createApp(options?: AppOptions): App // identical to `new App(...)`, point-free factory
interface AppOptions {
// OpenAPI document metadata
title?: string;
version?: string;
description?: string;
// Secure-by-default master switches
secureDefaults?: boolean; // default: true
acknowledgeInsecureDefaults?: boolean; // required when disabling defaults in production
preset?: "internal-service"; // service-to-service preset (browser guards off)
// Request limits
bodyLimitBytes?: number; // default: 1 MiB
allowedContentTypes?: string[]; // default: ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"]
requestTimeoutMs?: number; // default: 30_000; 0 disables
maxHeaderCount?: number; // default: 100; 0 disables (header-count flood / HTTP/2-Bomb guard)
multipart?: { maxFileBytes?: number; maxFields?: number; maxFiles?: number };
// Environment & logging
production?: boolean; // defaults from NODE_ENV
env?: "development" | "production" | "test";
logger?: Logger | { level?: LogLevel } | false;
stripServerHeaders?: boolean; // default: true
// Header / cross-origin guards (secure-by-default)
secureHeaders?: SecureHeadersOptions | false;
corsCrossOriginGuard?: boolean; // default: true
csrf?: "off"; // opt-out for the session+CSRF boot guard
trustProxy?: boolean; // legacy tri-state guard (undefined refuses X-Forwarded-*)
behindProxy?: BehindProxyConfig; // "none" | "loopback" | { hops: N } | { cidrs: [...] }
// Operational
disconnectStatusCode?: number; // default: 499 (client-disconnect log code)
crashOnUnhandledRejection?: boolean; // default: true in production
loadShedding?: boolean | LoadSheddingOptions;
// Validation, hooks, mock mode
validateResponses?: boolean; // default: true
mockMode?: boolean;
hooks?: Hooks;
// OpenAPI / docs auto-mount
openapi?: AppOpenAPIOptions;
docs?: boolean | "auto" | DocsRouteOptions; // default: false (create-daloy templates set true)
}
// Routing
app.route<P, Req, Res>(def: RouteDefinition<P, Req, Res>): App
app.ws<P, TData>(path: P, handler: WebSocketHandler<P, AppState, TData>): App
app.group(prefix, { tags?, hooks?, auth? }, register: (child: App) => void): App
app.use(hooks: Hooks): App
app.decorate<K, V>(key: K, value: V, { override? }?): App
// Plugins / lifecycle
app.register(plugin: { name?, seed?, stateful?, dependencies?, extensions?, register? }
| ((app: App) => void | Promise<void>),
{ prefix?, tags?, hooks?, auth? }?): App
app.onPluginInstalled(listener: (info: PluginInstalledEvent) => void | Promise<void>): App
app.onShutdown (listener: (info: ShutdownEvent) => void | Promise<void>): App
app.onClose (cleanup: () => void | Promise<void>): App
// Built-in routes
app.healthcheck (opts?: HealthRouteOptions): App // GET /healthz by default (opts.path to override)
app.readinesscheck (opts?: HealthRouteOptions): App // GET /readyz by default (opts.path to override)
app.cspReportRoute (opts?: CspReportRouteOptions): App
// Dispatch + introspection
app.ready(): Promise<void>
app.fetch(req: Request): Promise<Response>
app.request(input: string | URL | Request, init?: RequestInit): Promise<Response>
app.introspect(): IntrospectedRoute[]
app.shutdown(timeoutMs?: number, reason?: string): Promise<void>Route, hooks & context types
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
type PathString = `/${string}`;
type ParamsOf<P> // infers ":id" → "id" | ...
type PathParams<P> // { [K in ParamsOf<P>]: string }
interface RequestSchemas {
params?: StandardSchemaV1;
query?: StandardSchemaV1;
headers?: StandardSchemaV1;
body?: StandardSchemaV1;
}
interface ResponseSpec {
description: string;
body?: StandardSchemaV1;
headers?: Record<string, { description?: string; schema?: StandardSchemaV1 }>;
examples?: Record<string, unknown>;
}
type ResponsesMap = { [status: number]?: ResponseSpec };
interface AuthSpec {
scheme: string; // refs components.securitySchemes
scopes?: string[];
payload?: boolean; // default true; refuse to opt out when scheme requires payload auth
}
// Plugin-extensible - augment via "declare module"
interface AppState {}
type AuthScheme = "bearer" | "basic" | "jwt" | "jwk" | "webhook" | "session" | "apiKey";
interface AuthContext<TCredentials = unknown> {
readonly scheme: AuthScheme;
readonly credentials: TCredentials;
}
interface BaseContext<P extends string, R extends RequestSchemas | undefined> {
request: Request;
params: InferRequest<R, P>["params"];
query: InferRequest<R, P>["query"];
headers: InferRequest<R, P>["headers"];
body: InferRequest<R, P>["body"];
state: AppState & Record<string, unknown>;
set: { status?: number; headers: Headers };
}
// HandlerReturn<R> is a discriminated union by status code - TS enforces
// that every returned response is declared in the route's responses map.
type HandlerReturn<R extends ResponsesMap> = ...;
interface Hooks {
onRequest?: (req: Request) => void | Promise<void>;
beforeHandle?: (ctx) => void | Response | Promise<void | Response>;
afterHandle?: (ctx, result) => void | unknown | Promise<void | unknown>;
onError?: (err, ctx?) => void | Response | Promise<void | Response>;
onSend?: (res: Response, ctx?) => void | Response | Promise<void | Response>;
onResponse?: (res: Response, ctx?) => void;
}
interface RouteDefinition<P, Req, Res, S> {
method: HttpMethod;
path: P;
operationId?: string;
summary?: string;
description?: string;
tags?: string[];
deprecated?: boolean;
request?: Req;
responses: Res;
auth?: AuthSpec;
hooks?: Hooks;
meta?: RouteMeta; // AI-friendly metadata (surfaces as x-daloy-* in OpenAPI)
examples?: RouteExample[];
callbacks?: CallbackMap;
handler: (ctx) => HandlerReturn<Res> | Promise<HandlerReturn<Res>>;
}
interface IntrospectedRoute {
method: HttpMethod;
path: string;
operationId?: string;
tags?: string[];
summary?: string;
description?: string;
deprecated?: boolean;
hasBody: boolean;
hasQuery: boolean;
hasParams: boolean;
hasHeaders: boolean;
responses: number[];
auth?: { scheme: string; scopes?: string[] };
meta?: RouteMeta;
}Hook dispatch order & when the body is read
Per matched request, hooks fire in this order: onRequest(req) → route match → validate params/query/headers and read & parse the request body when the route declares a request.body schema → beforeHandle(ctx) → handler(ctx) → afterHandle(ctx, result) → onSend(res) → onResponse(res).
The body is read and validated before beforeHandle by design: body-aware guards that run in beforeHandle need the parsed ctx.body; waf() inspects it for NoSQL-operator injection and other inbound attack signatures, and idempotency() derives its dedup key from it. Deferring the read would silently turn those into no-ops.
Consequence: a cheap beforeHandle guard (e.g. bearerAuth(), rateLimit()) on a route with a body schema runs after the body has been read, so an unauthenticated client still pays for the (bounded) body read before being rejected. The cost is capped by bodyLimitBytes (1 MiB default). To gate traffic before any body I/O, put the guard in App({ hooks }) onRequest, which runs before routing and before the body is touched, or run that surface in an app with a stricter bodyLimitBytes setting.
Errors
// All errors extend HttpError and serialize to RFC 9457 application/problem+json.
class HttpError extends Error {
status: number; title: string;
type?: string; detail?: string; instance?: string;
headers?: Record<string, string>;
}
interface ProblemDetails { type?: string; title: string; status: number; detail?: string; instance?: string; [ext: string]: unknown }
class BadRequestError extends HttpError {} // 400
class UnauthorizedError extends HttpError {} // 401 - sets WWW-Authenticate
class ForbiddenError extends HttpError {} // 403
class NotFoundError extends HttpError {} // 404
class MethodNotAllowedError extends HttpError {} // 405 - sets Allow
class RequestTimeoutError extends HttpError {} // 408
class ConflictError extends HttpError {} // 409 - sets cache-control: no-store
class PayloadTooLargeError extends HttpError {} // 413
class UnsupportedMediaTypeError extends HttpError {} // 415
class ValidationError extends HttpError {} // 422 - carries StandardSchema issues
class TooManyRequestsError extends HttpError {} // 429 - sets Retry-After
class RequestHeaderFieldsTooLargeError extends HttpError {} // 431 - maxHeaderCount guard
class InternalError extends HttpError {} // 500 - detail redacted in production
// Defensive guard: throws MessageLeakError when a custom error response
// would set a header outside the safe allowlist.
const SAFE_CUSTOM_ERROR_RESPONSE_HEADERS: ReadonlySet<string>;
class MessageLeakError extends Error {}
function checkCustomErrorResponseHeaders(headers: Headers | Record<string, string>): void;
function httpError(opts: HttpErrorOptions): HttpError; // typed factorySchema validation
interface StandardSchemaV1<Input = unknown, Output = Input> { ... } // Standard Schema spec
function isStandardSchema(value: unknown): value is StandardSchemaV1;
function validate<S extends StandardSchemaV1>(schema: S, input: unknown):
| { ok: true; value: StandardSchemaV1.InferOutput<S> }
| { ok: false; issues: ReadonlyArray<StandardSchemaV1.Issue> };Security primitives
// Body & parser hardening
readBodyLimited(req: Request, limit: number): Promise<Uint8Array>;
safeJsonParse(text: string | Uint8Array): unknown; // refuses __proto__, constructor, prototype keys
isForbiddenObjectKey(key: string): boolean;
hasMongoOperatorKeys(value: unknown): boolean;
assertNoMongoOperators(value: unknown, where?: string): void; // refuses $-prefixed keys on user input
// Headers
sanitizeHeaderName(name: string): string;
sanitizeHeaderValue(value: string): string;
assertNoDuplicateSingletonHeaders(headers: Headers): void;
assertNoReservedInternalHeaders(headers: Headers): void;
const RESERVED_INBOUND_HEADER_PREFIXES: readonly string[];
const SMUGGLING_SINGLETON_HEADERS: readonly string[];
// Comparisons & tokens
timingSafeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean;
randomId(): string;
// Secrets
assertStrongSecret(value: string | Uint8Array, where: string): void;
const MIN_PROD_SECRET_BYTES = 32;
const WEAK_SECRET_STRINGS: ReadonlyArray<string>;
// Webhook HMAC
type WebhookHmacAlgorithm = "sha256" | "sha384" | "sha512";
const WEBHOOK_DEFAULT_TOLERANCE_SECONDS = 300;
signWebhookPayload(opts: { secret; payload; algorithm?; timestamp?; }): Promise<string>;
verifyWebhookSignature(opts: {
secret; payload; signature; algorithm?;
timestamp?: string | number;
toleranceSeconds?: number;
now?: () => number;
}): Promise<boolean>;
// Filesystem
sanitizeFilename(name: string): string;
assertSafeRelativePath(p: string, where?: string): void; // refuses .. escape, absolute, NULBuilt-in middleware
requestId(opts?: RequestIdOptions): Hooks
secureHeaders(opts?: SecureHeadersOptions): Hooks
cors(opts: CorsOptions): Hooks
rateLimit(opts: RateLimitOptions): Hooks
loginThrottle(opts?: LoginThrottleOptions): Hooks
timing(headerName?: string): Hooks
compression(opts?: CompressionOptions): Hooks
bearerAuth(opts: BearerAuthOptions): Hooks
basicAuth(opts: BasicAuthOptions): Hooks
csrf(opts?: CsrfOptions): Hooks
fetchMetadata(opts?: FetchMetadataOptions): Hooks // Sec-Fetch-Site/Mode/Dest enforcement
requireScopes(scopes: string | string[]
| { all?: string[]; any?: string[] }): Hooks
ipRestriction(opts: IpRestrictionOptions): Hooks // CIDR allow/deny
loadShedding(opts?: LoadSheddingOptions): Hooks
etag(opts?: ETagOptions): Hooks // 304 + Set-Cookie / Cache-Control skip
interface RateLimitOptions {
windowMs: number;
max: number;
keyGenerator?: (ctx) => string;
store?: RateLimitStore; // default in-memory; use redisRateLimitStore for clusters
trustProxyHeaders?: boolean;
retryAfter?: boolean;
groupId?: string;
}
interface BearerAuthOptions {
validate: (token: string) => boolean | Promise<boolean>; // static check; token only
verify?: BearerAuthVerifyHook; // (token, ctx) => boolean | void; per-request revalidation
realm?: string;
}Composition primitives
every(...layers: Hooks[]): Hooks // run every layer in order, pipeline-style
some (...layers: Hooks[]): Hooks // pass on first non-throwing beforeHandle (auth fallback chains)
except(when: ExceptPredicate, hooks: Hooks): Hooks // exempt matching paths from a beforeHandle gate
type ExceptPredicate =
| string // path glob: "*" = one segment, "**" = any suffix
| string[] // any-of globs
| ((ctx) => boolean | Promise<boolean>);Dependencies (typed DI chain)
defineDependency<TName, TValue, TStateKey>(opts: {
name: TName;
dependsOn?: readonly string[]; // refuses cycles at registration
stateKey?: TStateKey;
resolve: (ctx) => TValue | Promise<TValue>;
}): DependencyHooks // per-request cached; runs once per dependency per requestConnection info & proxy posture
type BehindProxyConfig = "none" | "loopback" | { hops: number } | { cidrs: readonly string[] };
interface ConnInfo { remoteAddress?: string; remotePort?: number; tls?: boolean }
getConnInfo(req: Request): ConnInfo | undefined;
setConnInfo(req: Request, info: ConnInfo): void; // adapter helper
assertBehindProxy(cfg: BehindProxyConfig | undefined): void;
resolveClientIp(ctx, cfg?: BehindProxyConfig): string | undefined;
readRemoteAddress(ctx): string | undefined;
readRemotePort(ctx): number | undefined;
pickForwardedForByHops(header: string, hops: number): string | undefined;Subdomains (Public-Suffix-aware)
subdomains(hostname: string, opts?: SubdomainsOptions): SubdomainsResult;
interface SubdomainsResult {
subdomain: string | undefined; // e.g. "api" for "api.example.co.uk"
registrableDomain: string | undefined;
publicSuffix: string | undefined;
}
const PSL_SNAPSHOT_DATE: string; // ISO date of the bundled PSL snapshot
const MAX_SNAPSHOT_AGE_DAYS: number; // refuses to use a stale snapshot
const PSL_PUBLIC_SUFFIXES: ReadonlySet<string>;SSRF guard
fetchGuard(opts?: FetchGuardOptions): typeof fetch;
// returns a fetch-compatible wrapper that refuses loopback / RFC1918 /
// link-local / cloud-metadata addresses unless explicitly allowed.
interface FetchGuardOptions {
fetch?: typeof fetch;
allowLoopback?: boolean;
allowPrivate?: boolean;
allowLinkLocal?: boolean;
allowUniqueLocal?: boolean;
allowAddresses?: readonly string[]; // CIDR or single IP
denyAddresses?: readonly string[]; // wins over allow + class flags
allowHosts?: readonly string[];
allowProtocols?: readonly string[]; // default: ["http:", "https:"]
maxRedirects?: number; // default: 5; each hop re-validated
resolve?: (host: string) => Promise<string[]>;
}
type SsrfBlockReason =
| "protocol-not-allowed" | "host-not-allowed" | "dns-resolution-failed"
| "address-not-allowed" | "too-many-redirects" | "invalid-url";
class SsrfBlockedError extends Error { readonly url; readonly reason: SsrfBlockReason; readonly address?: string }Open-redirect guard
safeRedirect(target: string, opts: SafeRedirectOptions): Response;
interface SafeRedirectOptions {
allowedPaths?: readonly string[]; // exact-match same-origin paths
allowedOrigins?: readonly string[]; // strict origin equality
fallback?: string; // returned instead of throwing on rejection
status?: 301 | 302 | 303 | 307 | 308; // default: 303
headers?: HeadersInit;
}
type SafeRedirectBlockReason =
| "empty-target" | "invalid-control-characters" | "protocol-relative"
| "backslash-path" | "path-not-allowed" | "origin-not-allowed"
| "scheme-not-allowed" | "parse-failed";
class OpenRedirectBlockedError extends Error { readonly reason; readonly target }Cookies
type CookieSameSite = "Strict" | "Lax" | "None";
interface CookieAttributes {
sameSite?: CookieSameSite; // default: "Strict"
secure?: boolean; // default: true (required for __Secure-/__Host-)
httpOnly?: boolean; // default: true (set false for client-readable tokens)
path?: string; // default: "/" (must be "/" for __Host-)
domain?: string; // forbidden with __Host-
maxAgeSeconds?: number; // Max-Age= seconds; 0 omits it on writes
partitioned?: boolean; // Partitioned (CHIPS); default: false
}
serializeCookie(name: string, value: string, attrs?: CookieAttributes): string; // URI-encodes value
serializeClearCookie(name: string, attrs?: CookieAttributes): string; // Max-Age=0
readRequestCookie(header: string | null | undefined, name: string): string | null;
// null if absent OR the name appears more than once (cookie-tossing defense)
assertCookieAttributes(opts: {
scope: string; name: string; attributes: CookieAttributes; isProduction?: boolean;
}): void;JWT signer & verifier
type JwtAlgorithm =
| "HS256" | "HS384" | "HS512"
| "RS256" | "RS384" | "RS512"
| "PS256" | "PS384" | "PS512"
| "ES256" | "ES384" | "ES512"
| "EdDSA"; // "none" deliberately absent
type JwtKeyMaterial = CryptoKey | Uint8Array | JsonWebKey;
createJwtSigner(opts: JwtSignerOptions): {
sign(payload: Record<string, unknown>, opts?): Promise<string>;
};
createJwtVerifier(opts: JwtVerifierOptions): {
verify(token: string, opts?): Promise<JwtVerified>;
};
interface JwtVerified { readonly header: Record<string, unknown>; readonly payload: Record<string, unknown> }
class JwtError extends Error { readonly code: string }
const DEFAULT_JWT_MAX_LIFETIME_SECONDS = 30 * 24 * 60 * 60; // 30dJWK / JWKS verification
jwk(opts: JwkOptions): Hooks;
// refuses HS* (confused-deputy), caches JWKS by TTL, honors kid, enforces
// issuer/audience + clock skew, then stamps ctx.state.user = { sub, scopes, claims }.
type JwkAlgorithm = Exclude<JwtAlgorithm, "HS256" | "HS384" | "HS512">;
type JwkSource = JwkSet | string | (() => JwkSet | Promise<JwkSet>); // object | https URL | resolver
interface JwkSet { keys: JsonWebKey[] }
type JwkVerifyHook = (payload: Record<string, unknown>, ctx) =>
boolean | void | Promise<boolean | void>; // return false to reject (403)
interface JwkOptions {
jwks: JwkSource; // object, https:// URL, or resolver (http:// refused)
algorithms: JwkAlgorithm[]; // required, non-empty; HS* refused at construction
issuer?: string | string[];
audience?: string | string[];
clockSkewSeconds?: number; // default: 0
realm?: string; // WWW-Authenticate realm; default: "api"
fetchTtlSeconds?: number; // default: 300; URL sources only
maxStaleSeconds?: number; // default: 3600; 0 disables; URL sources only
fetch?: typeof fetch; // pair with fetchGuard()
verify?: JwkVerifyHook;
}Temporal claim assertions
interface TemporalClaims { iat?: number; nbf?: number; exp?: number }
type TemporalClaimErrorCode =
| "missing-exp" | "expired" | "not-before" | "issued-in-future"
| "invalid-exp" | "invalid-nbf" | "invalid-iat"
| "lifetime-too-long";
assertTemporalClaims(claims: TemporalClaims, opts?: AssertTemporalClaimsOptions): void;
class TemporalClaimError extends Error { readonly code: TemporalClaimErrorCode }Configuration
defineConfig<S extends StandardSchemaV1>(opts: {
schema: S;
source?: ConfigSource; // default: "env" (process.env)
stderr?: { write(chunk: string): void } | false;
}): Promise<StandardSchemaV1.InferOutput<S>>;
// Async. Validates once at startup; throws ConfigValidationError on missing/invalid values.
type ConfigSource =
| "env"
| { kind: "env"; env: Record<string, string | undefined> }
| { kind: "file"; path: string; parse?: (text: string) => unknown }
| { kind: "object"; data: Record<string, unknown> }
| { kind: "custom"; resolve: () => Promise<Record<string, unknown>> };
class ConfigValidationError extends Error {
readonly issues: ReadonlyArray<{ key: string; message: string }>;
}Logging
type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
createLogger(opts?: ConsoleLoggerOptions): Logger;
const noopLogger: Logger;
const DEFAULT_REDACT_KEYS: ReadonlyArray<string>; // password, token, secret, authorization, ...
interface ConsoleLoggerOptions {
level?: LogLevel;
bindings?: Record<string, unknown>;
write?: (line: string) => void;
redact?: LoggerRedactionOptions; // { keys?, replacer? }
}
interface Logger {
trace(obj?, msg?): void;
debug(obj?, msg?): void;
info (obj?, msg?): void;
warn (obj?, msg?): void;
error(obj?, msg?): void;
fatal(obj?, msg?): void;
child(bindings: Record<string, unknown>): Logger;
}Startup banner
interface StartupBannerLink { label: string; url: string }
interface StartupBannerOptions {
name?: string; // default: "DaloyJS"
version?: string;
url: string;
runtime?: string; // e.g. "Node.js", "Bun"
links?: StartupBannerLink[];
color?: boolean;
ascii?: boolean;
}
formatStartupBanner(opts: StartupBannerOptions): string;
printStartupBanner(opts: StartupBannerOptions): void;Security-scheme builders (OpenAPI 3.1)
// Re-exported from @daloyjs/core for convenience (also live in /openapi).
httpBearerScheme(opts?: HttpBearerSchemeOptions): HttpBearerScheme;
httpBasicScheme(opts?: HttpBasicSchemeOptions): HttpBasicScheme;
apiKeyScheme(opts: ApiKeySchemeOptions): ApiKeyScheme;
oauth2Scheme(opts: OAuth2SchemeOptions): OAuth2Scheme;
openIdConnectScheme(opts: OpenIdConnectSchemeOptions): OpenIdConnectScheme;
type ApiKeyLocation = "header" | "query" | "cookie";
interface OAuth2Flows {
authorizationCode?: OAuth2AuthorizationCodeFlow;
clientCredentials?: OAuth2ClientCredentialsFlow;
implicit?: OAuth2ImplicitFlow;
password?: OAuth2PasswordFlow;
}
type SecurityScheme = HttpBearerScheme | HttpBasicScheme | ApiKeyScheme | OAuth2Scheme | OpenIdConnectScheme;
const REQUIRE_PAYLOAD_AUTH_EXTENSION = "x-daloy-require-payload-auth";
securitySchemeRequiresPayloadAuth(scheme: SecurityScheme): boolean;
toOpenAPISecurityScheme(scheme: SecurityScheme): unknown;Discriminated unions (OpenAPI)
discriminator(opts: DiscriminatorObject): unknown; // { propertyName, mapping? }
discriminatedUnion(prop: string, branches: StandardSchemaV1[],
opts?: DiscriminatedUnionOptions): StandardSchemaV1;@daloyjs/core/openapi
generateOpenAPI(app: App, opts: OpenAPIOptions): Record<string, unknown>;
openapiToYAML(doc: Record<string, unknown>): string;
interface OpenAPIOptions {
info: OpenAPIInfo;
servers?: { url: string; description?: string }[];
securitySchemes?: SecuritySchemeMap;
webhooks?: Record<string, WebhookDefinition | WebhookDefinition[]>;
}
interface OpenAPIInfo {
title: string;
version: string;
description?: string;
termsOfService?: string;
contact?: { name?: string; email?: string; url?: string };
license?: { name: string; identifier?: string; url?: string };
summary?: string;
}
// OpenAPI 3.1 top-level webhooks. Mirrors RouteDefinition minus path + handler.
interface WebhookDefinition {
method: HttpMethod;
operationId?: string;
summary?: string;
description?: string;
tags?: string[];
deprecated?: boolean;
request?: RequestSchemas;
responses: ResponsesMap;
auth?: AuthSpec;
}@daloyjs/core/client
createClient<A extends App>(app: A, opts: ClientOptions): ClientFor<A>;
interface ClientOptions {
baseUrl: string;
fetch?: typeof fetch;
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
}
// ClientFor<A> is keyed by operationId; each method takes
// { params?, query?, headers?, body? } and returns a discriminated union
// keyed by status: { status, body, headers }.
type ClientFor<A extends App> = { /* generated from A["routes"] */ };
type RoutesOf<A extends App> = A["routes"][number];@daloyjs/core/contract
runContractTests(app: App, opts?: ContractTestOptions): Promise<ContractReport>;
interface ContractTestOptions {
requireOperationId?: boolean; // default: true
allowBodyOnSafeMethods?: boolean; // default: false
}
interface ContractReport { ok: boolean; checked: number; issues: ContractIssue[] }
interface ContractIssue { route: string; method: HttpMethod; code: string; message: string }@daloyjs/core/docs
scalarHtml(opts: ScalarHtmlOptions): string;
swaggerUiHtml(opts: SwaggerUiHtmlOptions): string;
redocHtml(opts: RedocHtmlOptions): string;
docsContentSecurityPolicy(opts?: DocsContentSecurityPolicyOptions): string;
htmlResponse(html: string, opts?: HtmlResponseOptions): Response;
interface DocsOptions { specUrl: string; title?: string; assets?: DocsAssetOptions; scriptNonce?: string }
interface ScalarHtmlOptions extends DocsOptions { configuration?: ScalarReferenceConfiguration }
interface SwaggerUiHtmlOptions extends DocsOptions { configuration?: SwaggerUiConfiguration }
interface RedocHtmlOptions extends DocsOptions { configuration?: RedocConfiguration }
interface DocsAssetOptions {
// version-pinned URL + matching SRI hash per asset (see /docs/docs-asset-integrity)
scalarScriptUrl?: string; scalarScriptIntegrity?: string;
swaggerUiCssUrl?: string; swaggerUiCssIntegrity?: string;
swaggerUiBundleUrl?: string; swaggerUiBundleIntegrity?: string;
redocScriptUrl?: string; redocScriptIntegrity?: string;
crossOrigin?: "anonymous" | "use-credentials"; // default: "anonymous"
}
// RedocConfiguration is an index-signature bag of JSON-serializable Redoc
// standalone options (disableSearch, hideDownloadButtons, sortPropsAlphabetically,
// theme, ...), forwarded verbatim to Redoc.init(specUrl, configuration, element).
interface DocsContentSecurityPolicyOptions {
assetOrigins?: readonly string[];
connectOrigins?: readonly string[];
scriptNonce?: string;
allowInlineStyles?: boolean;
allowBlobWorkers?: boolean; // append worker-src 'self' blob: (Redoc needs it)
}
interface HtmlResponseOptions extends DocsContentSecurityPolicyOptions {
contentSecurityPolicy?: string;
}@daloyjs/core/streaming
interface SSEMessage { data?: unknown; event?: string; id?: string; retry?: number; comment?: string }
sseStream (source, opts?: SSEStreamOptions): ReadableStream<Uint8Array>;
sseResponse(source, opts?: SSEResponseOptions): Response;
ndjsonStream (source, opts?: StreamOptions): ReadableStream<Uint8Array>;
ndjsonResponse(source, opts?: NDJSONResponseOptions): Response;
interface StreamOptions { signal?: AbortSignal }
interface SSEStreamOptions extends StreamOptions { keepAliveMs?: number }
interface SSEResponseOptions extends SSEStreamOptions { status?: number; headers?: HeadersInit }
interface NDJSONResponseOptions extends StreamOptions { status?: number; headers?: HeadersInit }@daloyjs/core/multipart
fileField(opts?: FileFieldOptions): FileFieldSchema<UploadedFile>;
multipartObject<S>(shape: S, opts?: MultipartObjectOptions): StandardSchemaV1;
isFileFieldSchema(value: unknown): boolean;
isMultipartObjectSchema(value: unknown): boolean;
type UploadedFile = Blob & { readonly name?: string };
interface FileFieldOptions {
maxBytes?: number;
accept?: string | readonly string[]; // MIME or extension allowlist
filename?: { maxLength?: number; pattern?: RegExp };
magicBytes?: FileMagicBytesOption; // refuses content-type spoofing
optional?: boolean;
format?: string;
}
interface MultipartObjectOptions { strict?: boolean } // refuses unknown fields by default@daloyjs/core/session
session(opts: SessionOptions): Hooks;
rotateSession(opts?: RotateSessionOptions): Hooks; // refresh ID on login/privilege change
signValue (value: string, secret: string | Uint8Array): Promise<string>;
verifySignedValue(value: string, secret: string | Uint8Array): Promise<string | null>;
class MemorySessionStore implements SessionStore {}
interface SessionStore {
get (id: string): Promise<SessionRecord | undefined>;
set (id: string, record: SessionRecord): Promise<void>;
delete(id: string): Promise<void>;
touch?(id: string, expiresAt: number): Promise<void>;
}@daloyjs/core/websocket
defineWebSocket<P, S, TData>(handler: WebSocketHandler<P, S, TData>): WebSocketHandler<P, S, TData>;
interface WebSocketHandler<P, S = AppState, TData = unknown> {
beforeUpgrade?: (ctx: WebSocketContext<P, S>) => void | Response | Promise<void | Response>;
open?: (conn: WebSocketConnection<TData>, ctx) => void | Promise<void>;
message?: (conn, msg: MessageEvent, ctx) => void | Promise<void>;
close?: (conn, code: number, reason: string, ctx) => void | Promise<void>;
error?: (conn, err: Error, ctx) => void | Promise<void>;
// limits
maxPayloadLength?: number; // default: 1 MiB
backpressureLimit?: number; // default: 1 MiB
idleTimeoutSeconds?: number; // default: 120
allowedSubprotocols?: readonly string[];
origin?: string | readonly string[] | ((origin) => boolean);
}
wsRateLimit(opts: { windowMs; max; groupId?; keyGenerator?; store? }): WebSocketBeforeUpgrade;
normalizeWebSocketOptions(handler, ctx): NormalizedWebSocketOptions;
// Constants
WS_GUID; WS_READY_STATE; WS_OPCODE; WS_CLOSE_CODE; WS_MAX_CONTROL_PAYLOAD;
DEFAULT_WS_BACKPRESSURE_LIMIT; // 1 MiB
DEFAULT_WS_MAX_PAYLOAD_LENGTH; // 1 MiB
DEFAULT_WS_IDLE_TIMEOUT_SECONDS; // 120
// Frame primitives (for custom adapters)
parseSubprotocols(header: string | null | undefined): string[];
validateSelectedSubprotocol(selected, allowed): boolean;
checkWebSocketOrigin(origin, allowed): boolean;
parseFrame(buf: Uint8Array, opts?): ParsedFrame | typeof FRAME_INCOMPLETE;
encodeFrame(opts): Uint8Array;
encodeClosePayload(code: number, reason?: string): Uint8Array;
decodeClosePayload(payload: Uint8Array): { code: number; reason: string };
encodeSendPayload(data: string | ArrayBufferLike | ArrayBufferView): Uint8Array;
computeAcceptKey(secWebSocketKey: string): string;
class WebSocketRegistry {}
class WebSocketProtocolError extends Error {}
class WebSocketPayloadTooLargeError extends WebSocketProtocolError {}
class FrameSink { /* event emitter over an async byte stream */ }@daloyjs/core/tracing
otelTracing(opts: OtelTracingOptions): Hooks; // BYO @opentelemetry/api tracer
interface OtelTracingOptions {
tracer: TracingTracer;
serviceName?: string;
includeRequestHeaders?: readonly string[];
includeResponseHeaders?: readonly string[];
recordExceptions?: boolean;
}
const TRACING_SPAN_KIND_SERVER: number;
const TRACING_SPAN_STATUS_UNSET: number;
const TRACING_SPAN_STATUS_OK: number;
const TRACING_SPAN_STATUS_ERROR: number;@daloyjs/core/hashing
passwordHash(password: string): Promise<string>;
// scrypt with random salt + per-hash params; returns a self-describing PHC string.
// Throws TypeError on empty input or passwords over 4096 UTF-8 bytes
// (the cap blocks scrypt CPU-amplification abuse).
passwordVerify(password: string, hash: string): Promise<boolean>;
// timing-safe comparison; refuses to verify when scrypt parameters are below
// the secure floor (forces a rehash via your application logic).
// Returns false (never throws) for empty or over-4096-byte passwords.@daloyjs/core/rate-limit-redis
redisRateLimitStore(opts: RedisRateLimitStoreOptions): RateLimitStore;
ioredisAdapter (client: IoredisLike): RedisCommands;
nodeRedisAdapter(client: NodeRedisLike): RedisCommands;
interface RedisRateLimitStoreOptions {
client: RedisCommands;
prefix?: string; // default: "daloy:rl:"
scriptCacheKey?: string;
}
interface RedisCommands {
evalsha?: (...) => Promise<unknown>;
eval?: (...) => Promise<unknown>;
// ... narrow subset; adapters provided for ioredis + node-redis.
}@daloyjs/core/banner
formatStartupBanner(opts: StartupBannerOptions): string;
printStartupBanner (opts: StartupBannerOptions): void;@daloyjs/core/cli
// Internals used by bin/daloy.mjs. Most users will not import this directly,
// but the surface is public-typed so wrappers can compose it.
type DevRuntime = "node" | "bun" | "deno";
detectRuntime(): DevRuntime;
buildDevCommand(runtime: DevRuntime, entry: string): { command: string; args: string[] };
parseArgs(argv: readonly string[]): { command: string; opts: CliOptions };
buildAiDump(app: App, opts: CliOptions): Record<string, unknown>;
assertSafeEntryPath(entry: string, context: string): void;
normalizeEntryArg(entry: string): string;Runtime adapters
@daloyjs/core/node
serve(app: App, opts?: NodeServerOptions): NodeServerHandle;
interface NodeServerOptions {
port?: number; // default: 3000
hostname?: string; // default: "0.0.0.0"
connectionTimeoutMs?: number; // default: 30_000
shutdownTimeoutMs?: number; // default: 10_000
handleSignals?: boolean; // default: true (SIGINT/SIGTERM)
maxHeaderBytes?: number; // default: 16 KiB
trustProxy?: boolean; // honor x-forwarded-proto/host (only behind a trusted LB)
maxConnections?: number; // cap concurrent sockets (admission control); default: unset (unbounded)
bufferedBodyMaxBytes?: number; // default: 256 KiB (pre-buffer threshold for POST hot path)
}
interface NodeServerHandle { server: Server; port: number; close(): Promise<void> }@daloyjs/core/bun
serve(app: App, opts?: BunServeOptions): BunServerHandle;
interface BunServeOptions {
port?: number;
hostname?: string;
maxRequestBodySize?: number; // default: 16 MiB
idleTimeout?: number;
development?: boolean;
unix?: string;
tls?: BunTLSOptions;
}
interface BunServerHandle { port: number; url: URL | undefined; stop(): Promise<void> }@daloyjs/core/deno
serve(app: App, opts?: DenoServeOptions): DenoServerHandle;
interface DenoServeOptions {
port?: number; hostname?: string;
signal?: AbortSignal;
cert?: string; key?: string; // HTTPS pair
onListen?: (info: { hostname: string; port: number }) => void;
onError?: (err: unknown) => Response | Promise<Response>;
handleSignals?: boolean; // default: true
shutdownTimeoutMs?: number; // default: 10_000
}
interface DenoServerHandle { shutdown(): Promise<void> }@daloyjs/core/cloudflare
toFetchHandler<Env = unknown>(app: App): ExportedFetchHandler<Env>;
// export default toFetchHandler(app);
interface ExportedFetchHandler<Env = unknown> {
fetch: (request: Request, env?: Env, ctx?: { waitUntil?; passThroughOnException? }) => Promise<Response>;
}@daloyjs/core/vercel
type WebHandler = (req: Request) => Promise<Response>;
interface FetchHandler { fetch: WebHandler }
type RouteHandlers = Record<"GET"|"POST"|"PUT"|"PATCH"|"DELETE"|"OPTIONS"|"HEAD", WebHandler>;
toWebHandler (app: App): WebHandler; // bare function (Edge, middleware)
toFetchHandler (app: App): FetchHandler; // default export for Node Functions
toRouteHandlers(app: App): RouteHandlers; // Next.js App Router route.ts
const toEdgeHandler = toWebHandler; // backwards-compat alias@daloyjs/core/fastly
toFastlyHandler(app: App): (req: Request) => Promise<Response>;
installFastlyListener(app: App): void; // wires addEventListener("fetch", ...)@daloyjs/core/lambda
toLambdaHandler(app: App): LambdaHandler;
type LambdaHandler = (event: LambdaEvent) => Promise<LambdaResponse>;
type LambdaEvent = LambdaEventV1 | LambdaEventV2; // API Gateway REST + HTTP/Function URLs
type LambdaResponse = LambdaResponseV1 | LambdaResponseV2;Test-only / internal helpers
These are exported for internal tests and tooling. They are public-typed but underscore-prefixed; they may change without a semver bump. Most application code will never need them.
_resetPackageJsonCacheForTests();
_resetCrashHandlersForTests();
_resetInsecureDefaultsLogForTests();
_resetCompressionRuntimeProbeForTests();
_resetSharedRateLimitStoresForTests();