@btravstack/http-server / index
index
Classes
HtmxFragmentsPort
Defined in: packages/http-server/src/htmx-route.ts:238
Every route a fragment declares, composed into one port — the answerer's own reads it through routes and calls resolvePrincipal itself, which is why requirements and authenticators ride the port rather than staying in this closure.
Extends
PortInstance<"HtmxFragments", {authenticators:Readonly<Record<string,AuthenticatorService<unknown>>>;principals:Readonly<Record<string,AnyPort>>;routes: readonlyFragmentAnswer[]; }>
Constructors
Constructor
new HtmxFragmentsPort(): HtmxFragmentsPort;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
Port("HtmxFragments")<{
readonly routes: readonly FragmentAnswer[];
readonly authenticators: Readonly<Record<string, AuthenticatorService<unknown>>>;
/ One port per scheme, so htmx() can seed the fork it opens for the caller. /
readonly principals: Readonly<Record<string, AnyPort>>;
}>.constructorProperties
| Property | Modifier | Type | Description | Inherited from | Defined in |
|---|---|---|---|---|---|
[ID] | readonly | "HtmxFragments" | - | Port("HtmxFragments").[ID] | packages/di/dist/index.d.mts:12 |
[SERVICE] | readonly | object | - | Port("HtmxFragments").[SERVICE] | packages/di/dist/index.d.mts:13 |
[SERVICE].authenticators | readonly | Readonly<Record<string, AuthenticatorService<unknown>>> | - | - | packages/http-server/src/htmx-route.ts:240 |
[SERVICE].principals | readonly | Readonly<Record<string, AnyPort>> | One port per scheme, so htmx() can seed the fork it opens for the caller. | - | packages/http-server/src/htmx-route.ts:242 |
[SERVICE].routes | readonly | readonly FragmentAnswer[] | - | - | packages/http-server/src/htmx-route.ts:239 |
portId | readonly | "HtmxFragments" | - | Port("HtmxFragments").portId | packages/di/dist/index.d.mts:17 |
HttpConfig
Defined in: packages/http-server/src/http-config.ts:9
What the transport is bound and configured with, as a service: http() binds it from PORT (default 3000; 0 lets the OS pick), HOST (default 0.0.0.0), HTTP_BODY_LIMIT, HTTP_CORS_ORIGIN and HTTP_COMPRESSION, each pinned by the matching option, and anything else in the graph may read it.
Extends
PortInstance<"HttpConfig", {bodyLimit:number;compression:boolean;corsOrigin:string;hostname:string;port:number; }>
Constructors
Constructor
new HttpConfig(): HttpConfig;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
Port("HttpConfig")<{
readonly port: number;
readonly hostname: string;
/ The largest request body a procedure reads, in bytes; 0 is unbounded. /
readonly bodyLimit: number;
/ Comma-separated allowed origins, or *. Empty is "the deployment said nothing". /
readonly corsOrigin: string;
readonly compression: boolean;
}>.constructorProperties
| Property | Modifier | Type | Description | Inherited from | Defined in |
|---|---|---|---|---|---|
[ID] | readonly | "HttpConfig" | - | Port("HttpConfig").[ID] | packages/di/dist/index.d.mts:12 |
[SERVICE] | readonly | object | - | Port("HttpConfig").[SERVICE] | packages/di/dist/index.d.mts:13 |
[SERVICE].bodyLimit | readonly | number | The largest request body a procedure reads, in bytes; 0 is unbounded. | - | packages/http-server/src/http-config.ts:13 |
[SERVICE].compression | readonly | boolean | - | - | packages/http-server/src/http-config.ts:16 |
[SERVICE].corsOrigin | readonly | string | Comma-separated allowed origins, or *. Empty is "the deployment said nothing". | - | packages/http-server/src/http-config.ts:15 |
[SERVICE].hostname | readonly | string | - | - | packages/http-server/src/http-config.ts:11 |
[SERVICE].port | readonly | number | - | - | packages/http-server/src/http-config.ts:10 |
portId | readonly | "HttpConfig" | - | Port("HttpConfig").portId | packages/di/dist/index.d.mts:17 |
HttpHandler
Defined in: packages/http-server/src/handler.ts:56
The HTTP surface as a SET port: every protocol served in this process contributes one member, and the runtime routes each request to the one whose prefix matches longest.
A set port rather than the single function it used to be, because HTTP is one transport carrying several protocols — oRPC here, GraphQL and htmx fragments beside it — while a graph holds exactly one runtime. They cannot be three runtimes; they are three answerers under one.
The runtime reaches them through Runtime.resolves rather than through di, because a member contributed by a SIBLING module is not visible from inside this one: start gates that the composition root exports this port, and HttpModule adds it to exports so an application never lists it.
Extends
PortInstance<"HttpHandler", readonlyHttpAnswerer[]> &object
Constructors
Constructor
new HttpHandler(): HttpHandler;Defined in: packages/di/dist/index.d.mts:30
Returns
Inherited from
Port.many("HttpHandler")<HttpAnswerer>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
[ID] | readonly | "HttpHandler" | Port.many("HttpHandler").[ID] | packages/di/dist/index.d.mts:12 |
[MANY] | readonly | true | Port.many("HttpHandler").[MANY] | packages/di/dist/index.d.mts:31 |
[SERVICE] | readonly | readonly HttpAnswerer[] | Port.many("HttpHandler").[SERVICE] | packages/di/dist/index.d.mts:13 |
many | readonly | true | CookieSchemes.many | packages/di/dist/index.d.mts:34 |
portId | readonly | "HttpHandler" | Port.many("HttpHandler").portId | packages/di/dist/index.d.mts:33 |
HttpRuntime
Defined in: packages/http-server/src/http-runtime.ts:142
The runtime's port: what http() provides, and what the module start boots must export.
It resolves HttpHandler, which is why a root exporting it is now part of start's gate: the answerers are contributed by sibling modules — oRPC here, a fragment or GraphQL answerer beside it — so they are not visible from inside this module and have to be read out of the application context.
Extends
RuntimePort<Runtime<typeofHttpHandler,HttpInfo>>
Constructors
Constructor
new HttpRuntime(): HttpRuntime;Defined in: packages/di/dist/index.d.mts:16
Returns
Inherited from
RuntimePort<Runtime<typeof HttpHandler, HttpInfo>>.constructorProperties
Unauthenticated
Defined in: packages/http-server/src/auth.ts:14
A caller was refused. Carries nothing: the starter surfaces no reason — a rejected caller gets an UNAUTHORIZED and oRPC's default message — so a payload here would be write-only. An authenticator that wants to record why logs it before returning this.
Extends
TaggedErrorInstance<"Unauthenticated", { }>
Constructors
Constructor
new Unauthenticated(args): Unauthenticated;Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | void |
Returns
Inherited from
TaggedError("Unauthenticated").constructorProperties
UnderScoped
Defined in: packages/http-server/src/auth.ts:21
A credential was valid but lacks one or more scopes the endpoint declared. Distinct from Unauthenticated because the answers differ: an anonymous caller gets 401, an under-scoped one 403.
Extends
TaggedErrorInstance<"UnderScoped", { }>
Constructors
Constructor
new UnderScoped(args): UnderScoped;Defined in: node_modules/.pnpm/unthrown@5.8.0/node_modules/unthrown/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | void |
Returns
Inherited from
TaggedError("UnderScoped").constructorProperties
Type Aliases
ApiKey
type ApiKey<P> = object;Defined in: packages/http-server/src/api-key.ts:16
One issued key, and what presenting it makes the caller.
scopes is the only place a scope is written: the scheme's vocabulary is the union of what its keys grant, inferred rather than declared a second time as a type argument. A vocabulary stated separately could name a scope no key grants, which passes ScopeGate and then refuses every caller with a permanent 403 — the failure that gate exists to catch, one layer up.
Type Parameters
| Type Parameter |
|---|
P |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
key | readonly | string | - | packages/http-server/src/api-key.ts:17 |
principal | readonly | P | - | packages/http-server/src/api-key.ts:18 |
scopes? | readonly | readonly string[] | What this key grants, checked against the endpoint's declared scopes by the existing 403 path. | packages/http-server/src/api-key.ts:20 |
ApiKeyOptions
type ApiKeyOptions<P, Keys> = object;Defined in: packages/http-server/src/api-key.ts:23
Type Parameters
| Type Parameter |
|---|
P |
Keys extends readonly ApiKey<P>[] |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
header? | readonly | string | Which header carries the key. Default x-api-key. | packages/http-server/src/api-key.ts:25 |
keys | readonly | Keys | - | packages/http-server/src/api-key.ts:26 |
Authenticator
type Authenticator<P, Scope, N, E> = object;Defined in: packages/http-server/src/auth.ts:132
What HttpAuthenticator hands back: a description defineHttp binds to a port once the scheme name is known, carrying its principal and scope types so the registry can be inferred rather than declared.
Type Parameters
| Type Parameter | Default type |
|---|---|
P | - |
Scope extends string | - |
N | - |
E | never |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
cookie? | readonly | true | Set by a scheme that resolves a caller from a cookie, which is what makes a deployment a CSRF target: defineHttp turns it into a CookieSchemes member, and the HTTP runtime's csrf default is "any member". Absent on a scheme reading a header, where ambient authority is not in play. | packages/http-server/src/auth.ts:145 |
error | readonly | E | - | packages/http-server/src/auth.ts:137 |
needs | readonly | N | - | packages/http-server/src/auth.ts:136 |
options | readonly | unknown | - | packages/http-server/src/auth.ts:133 |
principal | readonly | P | - | packages/http-server/src/auth.ts:134 |
scope | readonly | Scope | - | packages/http-server/src/auth.ts:135 |
Authenticators
type Authenticators = Readonly<Record<string, Authenticator<unknown, string, unknown, unknown>>>;Defined in: packages/http-server/src/define-http.ts:16
The authenticators an application declares, keyed by scheme name.
AuthenticatorService
type AuthenticatorService<P, Scope> = (headers) => AsyncResult<Granted<P, Scope>, Unauthenticated>;Defined in: packages/http-server/src/auth.ts:70
Headers, not the request: an authenticator has no business reading a body, and the narrower argument is what keeps it testable without a socket.
Type Parameters
| Type Parameter | Default type |
|---|---|
P | - |
Scope extends string | never |
Parameters
| Parameter | Type |
|---|---|
headers | IncomingHttpHeaders |
Returns
AsyncResult<Granted<P, Scope>, Unauthenticated>
ControllerKeyOf
type ControllerKeyOf<C, P> = C extends ProcedureContract<infer _I, infer _O, infer _E> ? P : string extends keyof C ? string :
| P extends "" ? never : P
| { [K in Nameable<C>]: ControllerKeyOf<C[K], P extends "" ? K : `${P}.${K}`> }[Nameable<C>];Defined in: packages/http-server/src/controller.ts:16
Every path into the contract tree — a fragment or a procedure, at any depth.
Type Parameters
| Type Parameter | Default type |
|---|---|
C | - |
P extends string | "" |
ControllerPortOf
type ControllerPortOf<C, K, Schemes> = PortClassOf<`${typeof CONTROLLER_PREFIX}${K}`, Implementation<FragmentAt<C, K>, Schemes>>;Defined in: packages/http-server/src/controller.ts:90
The port one piece targets. Its id carries the contract path, which is what makes two slices claiming one fragment di's duplicate-provider defect rather than a silent merge, and what lets the composing form recover each piece's path by stripping the prefix rather than needing it spelled again.
Type Parameters
| Type Parameter | Default type |
|---|---|
C extends Record<string, RouterContract> | - |
K extends ControllerKeyOf<C> | - |
Schemes | never |
FragmentAnswer
type FragmentAnswer = object;Defined in: packages/http-server/src/htmx-route.ts:215
What the answerer reads back for one route, principal and body erased to unknown.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
handle | readonly | (context, params, input) => AsyncResult<Html, never> | - | packages/http-server/src/htmx-route.ts:222 |
input | readonly | FragmentInputSchema | undefined | - | packages/http-server/src/htmx-route.ts:218 |
method | readonly | "GET" | "POST" | - | packages/http-server/src/htmx-route.ts:216 |
path | readonly | string | - | packages/http-server/src/htmx-route.ts:217 |
requirements | readonly | Requirements | undefined | - | packages/http-server/src/htmx-route.ts:219 |
unit | readonly | Readonly<Record<string, AnyPort>> | The declared unit: record, which the answerer resolves out of the fork it opens. | packages/http-server/src/htmx-route.ts:221 |
Grant
type Grant<P, Scope> = object;Defined in: packages/http-server/src/auth.ts:36
The scoped answer, and the reason granted() is mandatory rather than advisory: the brand is what tells it from an identity that merely happens to carry a scopes field.
Type Parameters
| Type Parameter |
|---|
P |
Scope extends string |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
[GRANT] | readonly | true | packages/http-server/src/auth.ts:39 |
identity | readonly | P | packages/http-server/src/auth.ts:37 |
scopes | readonly | readonly Scope[] | packages/http-server/src/auth.ts:38 |
Granted
type Granted<P, Scope> = [Scope] extends [never] ? P : Grant<P, Scope>;Defined in: packages/http-server/src/auth.ts:48
What an authenticator hands back. A scheme with no scope vocabulary returns the identity bare — byte-for-byte what applications write today — and one with a vocabulary reports what the credential actually granted, so the starter can compare it against what the endpoint declared.
Type Parameters
| Type Parameter |
|---|
P |
Scope extends string |
Html
type Html = object;Defined in: packages/http-server/src/html.ts:13
A rendered HTML fragment — the output of html or raw, and nothing else.
An object rather than a string, so that what this package escaped is known by the value rather than by a registry, and so a fragment handler returning a bare template literal is a compile error rather than a stored-XSS bug.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
[HTML] | readonly | true | packages/http-server/src/html.ts:13 |
value | readonly | string | packages/http-server/src/html.ts:13 |
HtmxOptions
type HtmxOptions = object;Defined in: packages/http-server/src/htmx.ts:17
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
login? | readonly | `/${string}` | The login ROUTE — the path the login answerer serves, /auth/login for an oidc({ prefix: "/auth" }), not the prefix it is mounted under. Set it and a route whose requires resolves Unauthenticated sends the caller there carrying ?return= — 303 Location for a navigating browser, 401 HX-Redirect for a request htmx made. Unset, that route answers a bare 401, and an under-scoped caller answers 403 either way. | packages/http-server/src/htmx.ts:28 |
prefix? | readonly | `/${string}` | Where fragments are mounted. Default /. | packages/http-server/src/htmx.ts:19 |
Http
type Http<A, Units> = object;Defined in: packages/http-server/src/define-http.ts:53
Everything an application mints from one call. Held as ONE binding and never destructured: each binding of a destructured member expands to a type mentioning @btravstack/contract's inaccessible unique symbol, which is TS2527 (measured). Held whole, the inferred type collapses to Http<A>, which is nameable — so an application writes no annotation at all.
Type Parameters
| Type Parameter | Default type |
|---|---|
A extends Authenticators | - |
Units extends UnitsOf<A> | Record<never, never> |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
_units? | readonly | Units | Phantom: Units is read by the piece factories' leaf typing, never at runtime. | packages/http-server/src/define-http.ts:84 |
authenticators | readonly | A | The declarations as given, for a hand-rolled composition or a custom sugar that reads the registry off them the way defineHttp does. No in-repo example needs it — HttpModule carries the bound providers on the router. | packages/http-server/src/define-http.ts:68 |
HtmxFragments | readonly | ReturnType<typeof htmxFragmentsFor> | - | packages/http-server/src/define-http.ts:58 |
HtmxGet | readonly | ReturnType<typeof htmxRouteFor>["HtmxGet"] | - | packages/http-server/src/define-http.ts:59 |
HtmxPost | readonly | ReturnType<typeof htmxRouteFor>["HtmxPost"] | - | packages/http-server/src/define-http.ts:60 |
OrpcController | readonly | ReturnType<typeof controllerFor> | - | packages/http-server/src/define-http.ts:54 |
OrpcRouter | readonly | ReturnType<typeof routerFor> | - | packages/http-server/src/define-http.ts:55 |
principals | readonly | Principals<A> | One port per scheme carrying that scheme's principal, for a unit module to name in needs and inject. | packages/http-server/src/define-http.ts:73 |
units | readonly | <U>() => Http<A, U> | The second step: the SAME object, retyped by the module each kind binds. A kind the authenticators never declared is refused here — the mapped arm demands never for every key outside Kinds<A>, which a real module can never satisfy, and it names that key in the diagnostic. | packages/http-server/src/define-http.ts:80 |
HttpAnswerer
type HttpAnswerer = object;Defined in: packages/http-server/src/handler.ts:25
One protocol's answer to HTTP, mounted under prefix.
Everything the client receives must be written from inside handle — the unit stays open until the response completes, so there is no way to be late. It returns PromiseLike<unknown> rather than void: the package needs to know when the handler is finished so it can answer a request the handler declined, and a void-returning handler writing asynchronously would draw a premature 404 over a response still in flight. unknown because oRPC's handle resolves { matched: boolean }; the value is never the unit's result, and the runtime reads "did you answer?" off the response rather than off this — which is what lets an answerer be written against node:http alone.
host is how an answerer opens the unit's scope for a request it handles — host.fork(module, seed), once, for a request this answerer is about to answer. The runtime's own 404 never forks: only an answerer that claims a request does.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
handle | readonly | (request, response, signal, host) => PromiseLike<unknown> | - | packages/http-server/src/handler.ts:33 |
prefix | readonly | `/${string}` | Where this answerer is mounted. It owns every path at or under it, and a request is routed to the LONGEST prefix that matches — so / may host a fragment answerer while /rpc hosts oRPC, and nesting is expected rather than refused. Two answerers on one prefix is a startup failure. | packages/http-server/src/handler.ts:32 |
HttpInfo
type HttpInfo = object;Defined in: packages/http-server/src/http-runtime.ts:32
What the runtime publishes once it is listening, read back through RunningApp.runtimeInfo().
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
port | readonly | number | packages/http-server/src/http-runtime.ts:32 |
HttpModuleOptions
type HttpModuleOptions<Router, Fragments, Units, I, P, X, N> = Omit<HttpOptions, "unit"> & object & NeedsGate<Imports<I, Units>, Provides<P, Router, Fragments>, EnvAnd<N>> & ServesNothingGate<Router, Fragments>;Defined in: packages/http-server/src/http-module.ts:160
Type Declaration
| Name | Type | Description | Defined in |
|---|---|---|---|
exports? | X | The application's own exports; HttpRuntime is added, since start resolves it. | packages/http-server/src/http-module.ts:216 |
fragments? | Fragments | The application's htmx fragments — what api.HtmxFragments([…]) returns. Carries its own scheme authenticators the same way router does. Optional: a root may serve router alone. An authenticator provider the two share is deduplicated by reference before it reaches provides, so this module's own array is correct on its own terms rather than relying on di's internal module-tree flattening to absorb the duplicate. | packages/http-server/src/http-module.ts:196 |
fragmentsLogin? | `/${string}` | htmx()'s login — the login route an unauthenticated fragment caller is sent to. Named for the fragment half like fragmentsPrefix is, and for the same reason: it is the htmx answerer's alone, and a bare login here would read as covering the oRPC half, which refuses a caller with UNAUTHORIZED and redirects nothing. | packages/http-server/src/http-module.ts:212 |
fragmentsPrefix? | `/${string}` | Where htmx fragments are mounted, default / — htmx()'s own default. prefix (above, from HttpOptions) stays the oRPC mount, default /rpc: one field cannot carry two independent mount points with two different defaults, so a root serving both protocols gets this second, differently-named option instead of overloading the first. | packages/http-server/src/http-module.ts:204 |
imports? | I | - | packages/http-server/src/http-module.ts:213 |
needs? | N | What this root's OWN providers expect from outside. di's gate is re-stated over the augmented tuples below, so forgetting one is an error at THIS call. Env is added to what this gate counts as declared, so a root composing a scheme that configures itself from the environment never restates it — the same hiding the starter this sugar imports already gets, since it needs Env too and no root has ever named that either. | packages/http-server/src/http-module.ts:226 |
provides? | P | - | packages/http-server/src/http-module.ts:214 |
router? | Router | The application's oRPC router — what api.OrpcRouter(contract)(…) returns. It carries the scheme authenticators defineHttp bound, which is how they reach provides without an application listing them. Optional: a root may serve fragments alone. | packages/http-server/src/http-module.ts:187 |
unit? | Units & UnitGate<Units, Router, Fragments> | The unit module each KIND binds — anonymous for a request no leaf asked to authenticate, else the scheme that resolved the caller. Every bound module's own unmet needs join this root's, less the principal the fork seeds: a composition that binds one owes the composition root the same way any other needs does. The kinds are gated against the answerers: the ones units<…>() declared, carried by the router or the fragments alike, or — for a plain defineHttp() api — anonymous and every scheme the answerers serve. | packages/http-server/src/http-module.ts:180 |
Type Parameters
| Type Parameter |
|---|
Router extends AnyRouterProvider | undefined |
Fragments extends AnyFragmentsProvider | undefined |
Units extends Readonly<Record<string, AnyUnitModule>> | undefined |
I extends readonly AnyModule[] |
P extends readonly AnyProvider[] |
X extends readonly Exportable<Imports<I, Units>, Provides<P, Router, Fragments>>[] |
N extends readonly AnyPort[] |
HttpOptions
type HttpOptions = OrpcOptions & object;Defined in: packages/http-server/src/http-runtime.ts:45
http()'s options: orpc()'s own — where the router is mounted, and the transport policy — plus what a caller pins on the socket instead of reading it from the environment. Declared as one intersection so each option is spelled ONCE across the three surfaces that take it (orpc(), http(), HttpModule), which is what the six-concerns claim drifted against when they were three parallel records.
The router itself is not an option — it is the provider the composition root supplies on the starter's router port, which this module needs.
Type Declaration
| Name | Type | Description | Defined in |
|---|---|---|---|
hostname? | string | Pins HttpConfig.hostname instead of reading HOST. | packages/http-server/src/http-runtime.ts:49 |
port? | number | Pins HttpConfig.port instead of reading PORT. | packages/http-server/src/http-runtime.ts:47 |
securityHeaders? | boolean | Readonly<Record<string, string>> | Headers set on every response, before dispatch. true (default) applies DEFAULT_SECURITY_HEADERS; false disables the feature; a record replaces the defaults outright. Not a config field, deliberately: a deployment that can silently turn x-frame-options off is a footgun the other policies are not. | packages/http-server/src/http-runtime.ts:58 |
unit? | Readonly<Record<string, AnyUnitModule>> | The unit module each KIND binds: anonymous for a request no leaf asked to authenticate, else the scheme that resolved the caller. The answerers fork the one that matches — seeded, for a scheme, with that scheme's principal — as the request is taken, and tear it down when the unit closes, after the response is flushed. A kind that binds no module of its own falls back to anonymous, so binding that one alone keeps forking on every leaf. | packages/http-server/src/http-runtime.ts:67 |
Kinds
type Kinds<A> = "anonymous" | keyof A & string;Defined in: packages/http-server/src/unit.ts:46
Every kind a unit may be opened under: no credential, or the scheme that resolved one.
Type Parameters
| Type Parameter |
|---|
A |
ParamsOf
type ParamsOf<P> = { readonly [K in ParamNames<P>]: string };Defined in: packages/http-server/src/fragments.ts:22
The parameters a path template names. "/orders/:id/row" is { readonly id: string }; a template naming none is an empty record.
Type Parameters
| Type Parameter |
|---|
P extends string |
Principal
type Principal<S, Schemes> = [S] extends [never] ? never : [S] extends [keyof Schemes] ? IsUnion<S> extends true ? Tagged<S & keyof Schemes, Schemes> : Schemes[S & keyof Schemes] : never;Defined in: packages/http-server/src/principal.ts:30
What a leaf's handler reads. Bare when its requirements name one scheme — byte-for-byte what applications write today, so the common case pays nothing for the feature — and a discriminated union when they name several.
Type Parameters
| Type Parameter |
|---|
S extends string |
Schemes |
Principals
type Principals<A> = { readonly [K in keyof A & string]: PortClassOf<`HttpPrincipal:${K}`, A[K]["principal"]> };Defined in: packages/http-server/src/define-http.ts:88
One port per scheme, typed by the principal that scheme's authenticator declared.
Type Parameters
| Type Parameter |
|---|
A extends Authenticators |
SchemesFrom
type SchemesFrom<A> = { readonly [K in keyof A]: A[K]["principal"] };Defined in: packages/http-server/src/define-http.ts:21
The scheme registry, read off the authenticators rather than declared twice.
Type Parameters
| Type Parameter |
|---|
A extends Authenticators |
SchemesOf
type SchemesOf<R> = { [I in keyof R]: keyof R[I] & string }[number];Defined in: packages/http-server/src/principal.ts:7
Type Parameters
| Type Parameter |
|---|
R extends Requirements |
UnitsOf
type UnitsOf<A> = Partial<Readonly<Record<Kinds<A>, AnyUnitModule>>>;Defined in: packages/http-server/src/unit.ts:54
The module bound per kind. Every member is optional, which makes this a weak type — so a kind the authenticators never declared is refused for having no property in common rather than passing an excess-property check that a type argument would not get.
Type Parameters
| Type Parameter |
|---|
A |
Functions
apiKeyAuthenticator()
function apiKeyAuthenticator<P>(): <Keys>(options) => Authenticator<P, ScopesOf<Keys>, never>;Defined in: packages/http-server/src/api-key.ts:74
An API-key scheme, with the constant-time compare people get wrong.
Three things it does that a hand-written one usually does not:
- It compares digests, not strings.
===on a secret leaks its prefix through timing, andtimingSafeEqualrefuses two buffers of different lengths — which would leak the key's LENGTH instead. Hashing first makes every comparison 32 bytes wide whatever was presented. - It checks every configured key, without an early return. A loop that
breaks on the first match takes longer for a key configured late, which is a slower oracle but an oracle. - A missing header takes the same path as a wrong key, so "no credential" and "bad credential" are not distinguishable by timing either. Both answer
Unauthenticated, which the starter turns into401; the endpoint's own scope check is what produces a403.
export const serviceAuth = apiKeyAuthenticator<ServiceIdentity, "reports:read">({
keys: [{ key: env.REPORTING_KEY, principal: { appId: "reporting" }, scopes: ["reports:read"] }],
});Keys come from the caller — an Env-bound config field, a secret store — because a key list in the image is a key list in the repository.
Type Parameters
| Type Parameter |
|---|
P |
Returns
<Keys>(options) => Authenticator<P, ScopesOf<Keys>, never>
authenticatorPort()
function authenticatorPort<S>(scheme): PortClassOf<`HttpAuthenticator:${S}`, AuthenticatorService<unknown, never>>;Defined in: packages/http-server/src/auth.ts:90
One port per scheme, its id carrying the scheme name. The service type is erased to AuthenticatorService<unknown>, since di identifies a port by id; the principal and scope types ride the description HttpAuthenticator returns.
The id is a LITERAL type, so a contract naming a scheme the registry has no authenticator for leaves that scheme's port unmet — di's own diagnostic, naming the port, rather than a gate this package writes.
Exported for the consumer defineHttp does not cover: a test substituting ONE scheme's authenticator provides its own on this port instead of minting a second registry.
Type Parameters
| Type Parameter |
|---|
S extends string |
Parameters
| Parameter | Type |
|---|---|
scheme | S |
Returns
PortClassOf<`HttpAuthenticator:${S}`, AuthenticatorService<unknown, never>>
defineHttp()
function defineHttp<A>(options?): Http<A>;Defined in: packages/http-server/src/define-http.ts:109
The one door to the marker-typed entities. Declaring a scheme and implementing it are the same act, so a scheme without an authenticator is not a state this can reach — there is no coverage gate because there is nothing to forget.
export const api = defineHttp({ authenticators: { user: userAuth } });
export const api = defineHttp(); // a public API: `principal` is `never`The default registry is Record<never, never>, not Record<string, never>: an index signature over string would make EVERY scheme's port look available to di, so a marked contract composed under defineHttp() would type-check and then fail at build. Empty, the port stays unmet and the composition is refused.
Type Parameters
| Type Parameter | Default type |
|---|---|
A extends Readonly<Record<string, Authenticator<unknown, string, unknown, unknown>>> | Record<never, never> |
Parameters
| Parameter | Type |
|---|---|
options? | { authenticators: A; } |
options.authenticators? | A |
Returns
Http<A>
granted()
function granted<P, Scope>(identity, scopes): Grant<P, Scope>;Defined in: packages/http-server/src/auth.ts:61
What a scoped scheme answers with:
OkAsync(granted({ userId }, ["orders:export"]));Scope is not inferred from the vocabulary — an empty grant would collapse it to never and take the return type back to the bare arm — so the array is what states it, checked against the vocabulary by the assignment.
Type Parameters
| Type Parameter | Default type |
|---|---|
P | - |
Scope extends string | never |
Parameters
| Parameter | Type |
|---|---|
identity | P |
scopes | readonly Scope[] |
Returns
Grant<P, Scope>
html()
function html(strings, ...values): Html;Defined in: packages/http-server/src/html.ts:51
An HTML fragment, with every interpolation escaped.
html`<tr id="order-${order.id}"><td>${order.customerName}</td></tr>`A nested Html is spliced as it is, and an array of them is concatenated — so a list of rows needs no join. Anything else is stringified and escaped.
The escaping is context-blind: it protects element text and a quoted attribute value, and nothing else. An unquoted attribute, an attribute name, a URL scheme (href="${url}" does not vet javascript:), and <script>/<style> contents are the caller's own responsibility.
Parameters
| Parameter | Type |
|---|---|
strings | TemplateStringsArray |
...values | readonly unknown[] |
Returns
htmx()
function htmx(options?): Provider<HttpHandler, never,
| HtmxFragmentsPort
| HttpConfig
| HttpUnit> & object;Defined in: packages/http-server/src/htmx.ts:47
The htmx starter: fragments, as ONE answerer under the HTTP runtime. A request no route claims — outside every path, or on a path whose only route names a different method — resolves unwritten and the runtime answers its own 404.
routes is matched in the ORDER the composition root's piece array gave them, first match wins — and that ordering is a SECURITY property, not only a routing one. A public route declared BEFORE a requires-carrying route whose path can also match the same request answers it itself, and no authentication ever runs: two routes are two port ids, minted from their own method and path, so di has nothing to see collide, and a specificity rule is deliberately not provided (the ordering is the composition root's, on purpose). Declare a route that requires authentication before any public route whose path could also match its requests.
Parameters
| Parameter | Type |
|---|---|
options | HtmxOptions |
Returns
Provider<HttpHandler, never, | HtmxFragmentsPort | HttpConfig | HttpUnit> & object
http()
function http<Units>(options?): Module<
| HttpHandler
| Observers
| HttpConfig
| HttpRuntime, ConfigInvalid, Env | OrpcRouterPort | UnitsNeedsOf<Units>>;Defined in: packages/http-server/src/http-runtime.ts:280
The HTTP starter: oRPC, as ONE answerer under the HTTP runtime. A module providing the runtime, its configuration (bound from PORT/HOST unless pinned) and the oRPC answerer built from the application's router — which this module NEEDS, so a root that imports the starter without providing one owes the port. A second protocol is a second module contributing its own member to HttpHandler, not a second runtime.
Pin port/hostname and the module reads nothing from the environment; pin only some and the rest still comes from it.
Type Parameters
| Type Parameter | Default type |
|---|---|
Units extends Readonly<Record<string, AnyUnitModule>> | undefined | undefined |
Parameters
| Parameter | Type |
|---|---|
options | Omit<HttpOptions, "unit"> & object |
Returns
Module< | HttpHandler | Observers | HttpConfig | HttpRuntime, ConfigInvalid, Env | OrpcRouterPort | UnitsNeedsOf<Units>>
HttpAuthenticator()
function HttpAuthenticator<P, Scope>(): <D, O>(options) => Authenticator<P, Scope, InstanceType<D[keyof D]>, ErrorOfArm<O>>;Defined in: packages/http-server/src/auth.ts:211
The authenticator for one scheme, with its principal type — and the scopes it can grant — stated at the call:
export const userAuth = HttpAuthenticator<Identity, "orders:export">()({
inject: { verify: JwtVerifier },
sync: ({ verify }) => (headers) => verify(headers.authorization),
});
// An authenticator that reads nothing but the headers declares no deps:
export const serviceAuth = HttpAuthenticator<ServiceIdentity>()({
inject: {},
sync: () => (headers) => apiKey(headers["x-api-key"]),
});
// `make` where building the scheme can FAIL, and the failure is startup's:
export const jwtAuth = HttpAuthenticator<Identity>()({
inject: { env: Env },
make: ({ env }) => Config.parse("HttpJwt", schema)(env).map(verifier),
});sync and make are the same pair di's Provider has, and naming both is refused. make answers an AsyncResult, so its Err is the graph's — a scheme whose configuration is wrong fails the boot, still typed, rather than refusing every caller at runtime.
The type arguments are explicit rather than inferred from the arm: inference through a returned function's AsyncResult is where a principal silently widens to unknown. The scheme NAME is not stated here — it is the key this authenticator sits under in defineHttp({ authenticators }).
Type Parameters
| Type Parameter | Default type |
|---|---|
P | - |
Scope extends string | never |
Returns
<D, O>(options) => Authenticator<P, Scope, InstanceType<D[keyof D]>, ErrorOfArm<O>>
HttpModule()
function HttpModule<Name>(name): <Router, Fragments, Units, I, P, X, N>(options) => Module<ResolvedExports<readonly [typeof HttpRuntime, typeof HttpHandler, X]>,
| ErrOf<
| [Router] extends [undefined] ? never :
| Provider<HttpHandler, never,
| CookieSchemes
| HttpConfig
| PortInstance<..., ...>> & object
| Exclude<Router, undefined>
| [Fragments] extends [undefined] ? never :
| Provider<HttpHandler, never,
| HtmxFragmentsPort
| HttpConfig
| HttpUnit> & object
| Exclude<Fragments, undefined>
| AuthOf<Router>
| AuthOf<Fragments>
| P[number]>
| ErrOfModule<readonly [I, HttpStarter<Units>][number]>,
| Exclude<NeedOf<
| [Router] extends [undefined] ? never :
| Provider<HttpHandler, never, ... | ... | ...> & object
| Exclude<Router, undefined>
| [Fragments] extends [undefined] ? never :
| Provider<HttpHandler, never, ... | ... | ...> & object
| Exclude<Fragments, undefined>
| AuthOf<Router>
| AuthOf<Fragments>
| P[number]>, Available<readonly [I, HttpStarter<Units>], Provides<P, Router, Fragments>>>
| Exclude<NeedsOfModule<readonly [I, HttpStarter<Units>][number]>, Available<readonly [I, HttpStarter<Units>], Provides<P, Router, Fragments>>>>;Defined in: packages/http-server/src/http-module.ts:259
Module(name)({...}) for an HTTP deployment: everything a di module takes, plus a router, fragments, or both. The sugar imports the socket half (httpServer), provides whichever answerer(s) the options name and exports HttpRuntime, handing back exactly the module Module(...) would have declared over the augmented tuples.
export const OrderApi = HttpModule("OrderApi")({
router: orderRouter,
imports: [OrderApplicationModule, OrderPersistenceModule],
exports: [Logger],
});
await runMain(OrderApi);A fragments-only root drops router and supplies fragments instead; a root serving both supplies both, and prefix/fragmentsPrefix mount them independently. Supplying neither is refused at this call.
Type Parameters
| Type Parameter |
|---|
Name extends string |
Parameters
| Parameter | Type |
|---|---|
name | Name |
Returns
<Router, Fragments, Units, I, P, X, N>(options) => Module<ResolvedExports<readonly [typeof HttpRuntime, typeof HttpHandler, X]>, | ErrOf< | [Router] extends [undefined] ? never : | Provider<HttpHandler, never, | CookieSchemes | HttpConfig | PortInstance<..., ...>> & object | Exclude<Router, undefined> | [Fragments] extends [undefined] ? never : | Provider<HttpHandler, never, | HtmxFragmentsPort | HttpConfig | HttpUnit> & object | Exclude<Fragments, undefined> | AuthOf<Router> | AuthOf<Fragments> | P[number]> | ErrOfModule<readonly [I, HttpStarter<Units>][number]>, | Exclude<NeedOf< | [Router] extends [undefined] ? never : | Provider<HttpHandler, never, ... | ... | ...> & object | Exclude<Router, undefined> | [Fragments] extends [undefined] ? never : | Provider<HttpHandler, never, ... | ... | ...> & object | Exclude<Fragments, undefined> | AuthOf<Router> | AuthOf<Fragments> | P[number]>, Available<readonly [I, HttpStarter<Units>], Provides<P, Router, Fragments>>> | Exclude<NeedsOfModule<readonly [I, HttpStarter<Units>][number]>, Available<readonly [I, HttpStarter<Units>], Provides<P, Router, Fragments>>>>
httpServer()
function httpServer<Units>(options?): Module<
| HttpHandler
| Observers
| CookieSchemes
| HttpConfig
| HttpUnit
| HttpRuntime, ConfigInvalid, Env | UnitsNeedsOf<Units>>;Defined in: packages/http-server/src/http-runtime.ts:196
The socket half: the runtime, its configuration, and nothing that answers. Answerers are contributed to HttpHandler separately — orpc() from http(), htmx() from a fragment graph — which is what lets an application serve one protocol, the other, or both.
Type Parameters
| Type Parameter | Default type |
|---|---|
Units extends Readonly<Record<string, AnyUnitModule>> | undefined | undefined |
Parameters
| Parameter | Type |
|---|---|
options | Omit<SocketOptions, "unit"> & object |
Returns
Module< | HttpHandler | Observers | CookieSchemes | HttpConfig | HttpUnit | HttpRuntime, ConfigInvalid, Env | UnitsNeedsOf<Units>>
principalPort()
function principalPort<S, P>(scheme): PortClassOf<`HttpPrincipal:${S}`, P>;Defined in: packages/http-server/src/auth.ts:115
One port per scheme carrying that scheme's PRINCIPAL, so a unit module the kind binds may name it in needs and inject the caller it was opened for. Minted from the scheme name alone — the seed lands on it per unit.
Memoised on the same map as authenticatorPort, and for the same reason: defineHttp mints, a unit module depends, and a second Port(id) call would cost di's duplicate-id warning for what is the designed pattern.
Type Parameters
| Type Parameter | Default type |
|---|---|
S extends string | - |
P | unknown |
Parameters
| Parameter | Type |
|---|---|
scheme | S |
Returns
PortClassOf<`HttpPrincipal:${S}`, P>
raw()
function raw(markup): Html;Defined in: packages/http-server/src/html.ts:63
Markup admitted whole, unescaped. The only way past html's escaping, and a visible act at the call site — which is the point.
Parameters
| Parameter | Type |
|---|---|
markup | string |
Returns
resolvePrincipal()
function resolvePrincipal(
requirements,
authenticators,
headers
): AsyncResult<unknown, UnderScoped | Unauthenticated>;Defined in: packages/http-server/src/auth.ts:255
resolveScheme folded to what a handler is injected.
Parameters
| Parameter | Type |
|---|---|
requirements | Requirements |
authenticators | Readonly<Record<string, AuthenticatorService<unknown>>> |
headers | IncomingHttpHeaders |
Returns
AsyncResult<unknown, UnderScoped | Unauthenticated>