@beignet/core
Version:
Core framework primitives for Beignet
245 lines • 11.1 kB
TypeScript
import type { StandardSchemaV1 } from "@standard-schema/spec";
import { type HttpContractConfig } from "../contracts/index.js";
import type { AnyPorts } from "../ports/index.js";
import type { InferProviderPorts, ServiceProvider } from "../providers/index.js";
import type { ServerContextConfig, ServiceContextInputArgs } from "./context.js";
import type { ContractLike, ResolveContract } from "./contract-like.js";
import type { AddedCtxFromHooks, Handler, HttpRequestLike, HttpResponse, RouteHook, ServerCaughtErrorHook, ServerHook, ServerUnhandledErrorMapper } from "./http.js";
import type { ServerInstrumentationOptions } from "./instrumentation.js";
import type { RequestBodyOptions } from "./request-preparation.js";
import { contractsFromRoutes, createRoutes, defineRoutes, type HandlerRouteDef, type RouteDef, type RouteDefinitionBuilder, type RouteGroup, type RouteGroupBuilder, type Routes } from "./route-definitions.js";
import type { RuntimeIntegrityCheck } from "./runtime-integrity.js";
import { type TrustedProxyConfig } from "./trusted-proxy.js";
export type { HandlerRouteDef, RequestBodyOptions, RouteDef, RouteDefinitionBuilder, RouteGroup, RouteGroupBuilder, Routes, };
export { contractsFromRoutes, createRoutes, defineRoutes };
/**
* Loosely typed provider list element.
*
* Required-port, app-context, and service-input generics are erased here so
* providers created with the typed `createProvider<Requires, Context,
* ServiceInput>()` form stay assignable to server provider lists.
*/
type AnyServiceProvider = ServiceProvider<unknown, StandardSchemaV1<any, any>, AnyPorts, any, any>;
/**
* Options for creating a Beignet server instance.
*/
export type CreateServerOptions<Ctx, Ports extends AnyPorts, ServiceInput = void, Routes extends readonly RouteDef<any, any>[] = readonly RouteDef<any, any>[], Providers extends readonly AnyServiceProvider[] = readonly []> = {
/**
* App-owned ports available to context creation, hooks, and handlers.
*/
ports: Ports;
/**
* Providers installed during server startup.
*
* Provider ports are merged into `ports` before request handling and are
* stopped in reverse setup order when `server.stop()` runs.
*/
providers?: Providers;
/**
* Runtime env used by providers. Defaults to `process.env`.
*/
providerEnv?: Record<string, string | undefined>;
/**
* Provider config overrides keyed by provider name.
*/
providerConfig?: Record<string, unknown>;
/**
* Context blueprint for request and service contexts.
*
* Gate-less contexts may pass a plain request factory. Contexts with a
* `gate` property must use the blueprint form
* `{ gate: (ports) => ports.gate, request, service }` so the server owns
* gate attachment and identity changes can never go stale.
*
* The `ports` argument includes app ports plus ports provided during server
* startup.
*/
context: ServerContextConfig<Ctx, Ports & InferProviderPorts<Providers>, ServiceInput>;
/**
* Explicit policy for trusting proxy- or edge-provided request metadata.
*
* Forwarding headers are ignored by default. Configure this only when every
* request reaches the app through a trusted platform or reverse proxy that
* strips or normalizes those headers.
*/
trustedProxy?: TrustedProxyConfig;
/**
* Server hooks that wrap every registered route.
*/
hooks?: ServerHook<Ctx, Ports & InferProviderPorts<Providers>>[];
/**
* Optional pure startup check for app workflow registrations.
*
* Runtime integrity compares app-declared workflow artifacts against the
* registries passed to runtime entrypoints. It performs no filesystem,
* provider, database, network, worker, or background-loop work, so it is safe
* to run during serverless cold starts.
*/
integrity?: RuntimeIntegrityCheck;
/**
* Server-owned request instrumentation.
*
* The server resolves a request ID and W3C trace context for every request
* before user hooks and context creation, writes `x-request-id` and
* `traceparent` response headers, and records request and error events into
* the resolved provider instrumentation port (`ports.instrumentation`, then
* `ports.devtools`) when one is installed.
*
* Pass `false` to disable headers and event recording. Context factories
* still receive `requestId` and `trace` arguments, and request-scoped
* correlation remains available to ambient audit wrappers.
*/
instrumentation?: ServerInstrumentationOptions<Ctx> | false;
/**
* Whether route-owned responses are parsed against the contract's declared
* statuses and response schemas before they are sent. The parsed schema
* output becomes the response body.
*
* Disable this to send route-owned handler bodies as-is without validation,
* unknown-key stripping, or transforms, mirroring the client-side
* `validateResponses` option.
*
* @default true
*/
validateResponses?: boolean;
/**
* Request body parsing limits for JSON/text contract routes.
*/
requestBody?: RequestBodyOptions;
/**
* Route list to register up front.
*/
routes?: Routes;
/**
* How to handle ports that are still unbound after all providers have
* started.
*
* Ports declared as `deferred` in `definePorts(...)` boot as throwing
* placeholders until a provider contributes them. The default `"error"`
* fails startup and lists the unbound port keys. Apps that bind every port
* directly are unaffected.
*
* @default "error"
*/
onUnboundPorts?: "error" | "warn" | "ignore";
/**
* Global caught-error observer.
*/
onCaughtError?: ServerCaughtErrorHook<Ctx>;
/**
* Global mapper for unexpected errors not handled by app error catalogs.
*/
mapUnhandledError?: ServerUnhandledErrorMapper<Ctx>;
};
interface RouteBuilder<Ctx, C extends HttpContractConfig> {
handle: (fn: Handler<Ctx, C>) => (req: HttpRequestLike) => Promise<HttpResponse>;
}
/**
* Identity and hook metadata for a raw route.
*
* Raw routes are mounted by the adapter at their own path, so `method` and
* `path` describe the route to hooks, instrumentation, and error mapping
* instead of driving routing. `metadata` feeds metadata-driven hooks exactly
* like contract metadata does, for example `rateLimit` and `idempotency`.
*/
export type RawRouteInit = {
name: string;
method: HttpContractConfig["method"];
path: string;
metadata?: HttpContractConfig["metadata"];
};
/**
* Builder returned by `server.rawRoute(...)`.
*/
export interface RawRouteBuilder<Ctx> {
handle: (fn: Handler<Ctx, HttpContractConfig>) => (req: HttpRequestLike) => Promise<HttpResponse>;
}
/**
* Runtime server object returned by `createServer(...)`.
*/
export interface ServerInstance<Ctx, Ports extends AnyPorts = AnyPorts, ServiceInput = void> {
/**
* Catch-all request handler for platform adapters.
*/
api: (req: HttpRequestLike) => Promise<HttpResponse>;
/**
* Register and build a single route handler imperatively.
*/
route: <CLike extends ContractLike>(contractLike: CLike) => RouteBuilder<Ctx, ResolveContract<CLike>>;
/**
* Build a handler for a route that cannot be a contract — webhooks,
* third-party auth callbacks, streaming endpoints — that still runs the
* whole server pipeline: correlation, `onRequest`, route hooks,
* `beforeHandle`, `beforeSend`, `afterSend`, context creation,
* instrumentation, and framework error mapping.
*
* Request parsing and validation are skipped and the request body is left
* unconsumed, so the handler owns body reading — for example webhook
* signature verification over the exact raw bytes. Metadata-driven hooks
* such as rate limiting and idempotency read `init.metadata`. Raw routes
* are not added to the route registry; mount the returned handler at the
* route's own path.
*/
rawRoute: <const Hooks extends readonly RouteHook<Ctx, object>[] = readonly []>(init: RawRouteInit & {
hooks?: Hooks;
}) => RawRouteBuilder<Ctx & AddedCtxFromHooks<Hooks>>;
/**
* Build a fully assembled request context from a framework-neutral request.
*
* Use this for adapter entry points outside the route pipeline, such as
* server components or upload routes.
*/
createRequestContext: (req: HttpRequestLike) => Promise<Ctx>;
/**
* Build a fully assembled service context for schedules, outbox drains,
* tasks, and background work.
*
* Requires `context.service` to be declared in `createServer(...)`.
*
* Only call this from long-lived runtimes such as servers, workers, and
* `bun test`. It enters the ambient correlation context with
* `AsyncLocalStorage.enterWith`, and resuming that frame across top-level
* await crashes Bun 1.3.x in plain scripts. Seeds and one-off scripts must
* use `runServiceContext(...)` instead.
*/
createServiceContext: (...args: ServiceContextInputArgs<ServiceInput>) => Promise<Ctx>;
/**
* Build a service context and run `fn` inside a scoped ambient correlation
* frame, returning its result.
*
* This is the script-safe counterpart to `createServiceContext(...)`: the
* ambient frame is entered with `AsyncLocalStorage.run`, so plain scripts
* such as seeds and one-off maintenance work stay safe under top-level
* await. Requires `context.service` to be declared in `createServer(...)`.
*/
runServiceContext: <T>(...args: [
...ServiceContextInputArgs<ServiceInput>,
fn: (ctx: Ctx) => T | Promise<T>
]) => Promise<T>;
/**
* Contract configs registered through the `routes` option.
*/
contracts: readonly HttpContractConfig[];
/**
* Stop installed providers in reverse setup order.
*/
stop: () => Promise<void>;
/**
* Final app ports after provider setup.
*/
ports: Ports;
}
/**
* Create a Beignet server instance.
*
* The server owns route registration, provider setup/startup, request
* validation, hook execution, response validation, and framework error mapping.
* Use adapter packages such as `@beignet/next` to expose `server.api` to a
* specific runtime.
*
* @param options - Ports, providers, routes, hooks, context blueprint, and
* error mapping hooks for the server.
* @returns A started server instance with final ports and a catch-all handler.
*/
export declare function createServer<Ctx, Ports extends AnyPorts, ServiceInput = void, Routes extends readonly RouteDef<any, any>[] = readonly RouteDef<any, any>[], Providers extends readonly AnyServiceProvider[] = readonly []>(options: CreateServerOptions<Ctx, Ports, ServiceInput, Routes, Providers>): Promise<ServerInstance<Ctx, Ports & InferProviderPorts<Providers>, ServiceInput>>;
//# sourceMappingURL=server.d.ts.map