Skip to content

Search docs

Jump between documentation pages.

Browse docs

API reference: Feature modules

The per-feature subpath modules: OpenAPI generation, the typed client, contract tests, MCP, docs UIs, streaming, multipart, WebSocket primitives, tracing, metrics stores, and the CLI internals. Each module also re-exports from the root barrel unless noted. See the API reference overview for the complete module map.

One contract, many outputs
single source of truthApp route contractsmethod + path + request + responses
/openapiOpenAPI 3.1 documentgenerateOpenAPI(app, opts)
/client · /contractTyped client & contract testscreateClient(app), runContractTests(app)
/docs · /mcpDocs UIs & MCP toolsscalarHtml(), createMcpHandler()
Every module below reads the same route contracts your handlers are typed against, so the spec, client, docs, and MCP surface can never drift from the code.

@daloyjs/core/openapi

ts
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;
}

Discriminated unions (OpenAPI)

ts
discriminator(opts: DiscriminatorObject): unknown;            // { propertyName, mapping? }
discriminatedUnion(prop: string, branches: StandardSchemaV1[],
                   opts?: DiscriminatedUnionOptions): StandardSchemaV1;

@daloyjs/core/client

ts
createClient<A extends App>(app: A, opts: ClientOptions): ClientFor<A>;
createInProcessClient<A extends App>(app: A, opts?: InProcessClientOptions): ClientFor<A>;

interface ClientOptions {
  baseUrl: string;
  fetch?: typeof fetch;
  headers?: Record<string, string>;
}

interface InProcessClientOptions {
  baseUrl?: string; // default: http://daloy.local
  headers?: 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

ts
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/mcp

ts
const MCP_PROTOCOL_VERSION = "2025-11-25";
const MCP_PROTOCOL_VERSIONS: readonly string[];
const MCP_DEFAULT_MAX_BODY_BYTES = 262144;

createMcpHandler(options: McpHandlerOptions): McpHandler;
mcpRoutes(path: PathString, handler: McpHandler, options?: McpRoutesOptions):
  RouteDefinition<PathString, "GET" | "POST" | "OPTIONS">[];
validateMcpInput(schema: McpJsonSchema, value: unknown): string[];
class McpToolError extends Error {}

type McpHandler = (request: Request) => Promise<Response>;
type McpJsonValue = null | boolean | number | string | McpJsonValue[] | { [key: string]: McpJsonValue };
type McpJsonObject = { [key: string]: McpJsonValue };
type McpJsonSchema = McpJsonObject;
type McpJsonRpcId = string | number | null;

interface McpRoutesOptions {
  public?: boolean;  // default false; opt out of the production auth boot guard
}

interface McpHandlerOptions {
  serverInfo: McpServerInfo;
  instructions?: string;
  tools?: readonly McpTool[];
  resources?: readonly McpResourceDefinition[];
  resourceTemplates?: readonly McpResourceTemplateDefinition[];
  prompts?: readonly McpPromptDefinition[];
  allowedOrigins?: readonly string[];       // bare origins, plus optional "null"
  protocolVersions?: readonly string[];     // default MCP_PROTOCOL_VERSIONS
  preferredProtocolVersion?: string;        // default MCP_PROTOCOL_VERSION
  maxBodyBytes?: number;                    // default MCP_DEFAULT_MAX_BODY_BYTES
  headers?: Record<string, string>;
  exposeInternalErrors?: boolean;           // default NODE_ENV !== "production"
}

interface McpServerInfo {
  name: string;
  version: string;
  title?: string;
  description?: string;
  websiteUrl?: string;
  icons?: McpIcon[];
}

interface McpTool<TArgs extends Record<string, unknown> = Record<string, unknown>> {
  name: string;
  description: string;
  title?: string;
  inputSchema: McpJsonSchema;
  outputSchema?: McpJsonSchema;
  annotations?: McpToolAnnotations;
  icons?: McpIcon[];
  handler: McpToolHandler<TArgs>;
}

type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> =
  (args: TArgs, ctx: McpRequestContext) =>
    string | McpToolResult | Promise<string | McpToolResult>;

interface McpToolResult {
  content?: McpContent[];
  structuredContent?: McpJsonObject;
  isError?: boolean;
}

interface McpRequestContext {
  request: Request;
  protocolVersion: string;
  id: McpJsonRpcId;
  method: string;
}

// tools/call arguments are validated against inputSchema before the handler runs.
// Schema violations return JSON-RPC -32602. Unsupported keywords such as
// pattern, format, $ref, anyOf, oneOf, and allOf are advertised but not enforced.
// In production secureDefaults apps, mcpRoutes() must be covered by an auth hook
// unless mounted with mcpRoutes(path, handler, { public: true }).

@daloyjs/core/docs

ts
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;
  asyncapiScriptUrl?: string;     asyncapiScriptIntegrity?: string;
  asyncapiStyleUrl?: string;      asyncapiStyleIntegrity?: 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

ts
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

ts
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/websocket

ts
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

ts
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/rate-limit-redis

ts
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

ts
formatStartupBanner(opts: StartupBannerOptions): string;
printStartupBanner (opts: StartupBannerOptions): void;

@daloyjs/core/cli

ts
// 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;

Next up: runtime adapters.