UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

245 lines 11.1 kB
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