server/app_server.ts

Server assembly factory.

Consumers provide a pre-initialized AppBackend and options (session, origins, routes); create_app_server() handles middleware, bootstrap status, surface generation, and Hono app assembly.

view source

Declarations
#

16 declarations

APP_SERVER_RATE_LIMITER_KEYS
#

server/app_server.ts view source

internal

readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

type readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

import {APP_SERVER_RATE_LIMITER_KEYS} from '@fuzdev/fuz_app/server/app_server.js';

Every AppServerOptions rate-limiter field, in build order.

AppServer
#

server/app_server.ts view source

AppServer import type {AppServer} from '@fuzdev/fuz_app/server/app_server.js';

Result of create_app_server().

app

type Hono<BlankEnv, BlankSchema, "/">

surface_spec

Surface spec — serializable surface + raw specs that produced it.

type AppSurfaceSpec

bootstrap_status

type BootstrapStatus

migration_results

Migration results from create_app_backend (auth + any migration_namespaces passed there).

type readonly MigrationResult[]

audit_sse

Factory-managed audit log SSE. Non-null when the audit_log_sse option was passed to create_app_server, null when omitted. Use require_audit_sse(server) to assert the invariant.

type AuditLogSse | null

ws_endpoints

Path-keyed map of mounted WS endpoints. Each value is the BackendWebsocketTransport create_app_server registered connections against — supplied via WsEndpointSpec.transport or auto-created when omitted. Retain for broadcast / fan-out:

app_server.ws_endpoints['/api/ws'].send_to_account(account_id, msg);

Empty when no ws_endpoints were mounted.

type Readonly<Record<string, BackendWebsocketTransport>>

close

Shut the server's resources down, in order:

  1. stop the auth cleanup schedule when the auth_cleanup option started one, waiting for a pass in progress — that wait has no bound of its own, so a shutdown with a deadline races it
  2. close every live connection on deps.connection_closer (close_all_sockets) — each WebSocket with WS_CLOSE_GOING_AWAY, each audit stream — including transports supplied through WsEndpointSpec.transport or added by hand, since they are registered on the backend's closer and the backend is going away; a close that throws is logged and the shutdown goes on. Each transport and registry is left closing, so an upgrade or stream that registers afterward is born closed — refused at once rather than outliving the server — and a closed transport hosts no other server
  3. release what assembly acquired, last acquired first: remove the audit listeners create_app_server registered on deps.audit (the audit-log SSE listener, the WS auth guards, each extra_audit_handlers entry) and the closer members it added to deps.connection_closer (each mounted transport, the audit stream registry), and dispose the rate limiters it built for omitted options — never one passed in, which its caller owns. A release that throws is logged and the rest still run. The backend's emitter and closer are left as they were before assembly, so a failed assembly can be retried on the same backend, and a backend whose close you own (a test harness) can host another server
  4. close the database connection (AppBackend.close)

Idempotent: a second call settles with the first one's outcome (a failed close is not retried), and concurrent calls share one shutdown.

Stop accepting new HTTP connections first — without awaiting the listener's full drain, since live WebSockets hold it open until close ends them — so no new request reaches a closed database.

type (): Promise<void>

returns Promise<void>

AppServerOptions
#

server/app_server.ts view source

AppServerOptions import type {AppServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Configuration for create_app_server().

Requires a pre-initialized AppBackend from create_app_backend(). Two explicit steps: init backend then assemble server.

backend

Pre-initialized backend from create_app_backend().

type AppBackend

session_options

Session options for cookie-based auth.

type SessionOptions<string>

allowed_origins

Parsed allowed origin patterns.

type RegExp[]

proxy

Trusted proxy options.

type { trusted_proxies: string[]; get_connection_ip: (c: Context<any, any, {}>) => string | undefined; }

rate_limiters?

What an omitted rate limiter becomes — 'enforced' (the default) builds a default instance for each *_rate_limiter option left out, 'disabled_for_testing' leaves each of them null. An explicit option (an instance or null) always wins over the mode. The disabled mode adds one surface warning, and prints a stderr banner once per module instance (announce_rate_limiters_disabled). Never 'disabled_for_testing' in production — see RateLimiterMode.

type RateLimiterMode

login_ip_rate_limiter?

Per-IP rate limiter for login + password change — the distributed-spray backstop. Omit or undefined to use a default limiter (5 attempts per 15 minutes) under rate_limiters: 'enforced', none under 'disabled_for_testing'. Pass null to explicitly disable rate limiting. Also available on AppServerContext for route factory callbacks.

One instance per auth surface, not one shared across all four. These buckets are monotone within their window — a success never refunds them (see RateLimiter.reset) — so a shared instance let a failure on any surface spend the budget that bounds guessing on every other one, and let one caller's exhaustion deny four routes at once. Pass the same limiter to two of these fields to opt back into a shared budget.

type RateLimiter | null

signup_ip_rate_limiter?

Per-IP rate limiter for signup. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter because an open-signup deployment lets any unauthenticated caller spend it. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

bootstrap_ip_rate_limiter?

Per-IP rate limiter for bootstrap. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter so a fumbled bootstrap token can't spend the operator's login budget. Wired directly into the factory-managed bootstrap route; also on AppServerContext for symmetry.

type RateLimiter | null

login_account_rate_limiter?

Per-account rate limiter for login attempts. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

signup_account_rate_limiter?

Per-account rate limiter for signup attempts, keyed by submitted username. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

action_ip_rate_limiter?

Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Same limiter applies across transports — one budget per action. Omit or undefined for the mode's default (600 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

action_account_rate_limiter?

Per-account rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on the authenticated account's id, so every actor and credential of one account shares a budget. Omit or undefined for the mode's default (1200 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

max_body_size?

Maximum allowed request body size in bytes. Omit or undefined to use the default (1 MiB). Pass null to explicitly disable body size limiting.

type number | null

daemon_token_state?

Daemon token state for keeper auth. Omit to disable.

type DaemonTokenState

bootstrap?

Bootstrap options. Omit to skip bootstrap status check and routes.

type BootstrapServerOptions

surface_route?

Mount GET /api/surface, which serves this server's generated AppSurface (every route, its auth, and its schemas) to an admin UI such as AdminSurface.svelte. Off by default — the Rust spine serves no such route, and a map of the API is nothing to publish unasked.

When mounted the route is admin-only (roles: ['admin'], so it takes the ?acting= selector) and a rule-3 token surface (required_scope: 'surface:app_surface'): a narrowed API token is refused it whatever its method list says. See server/surface_route.ts.

type boolean

default false

create_route_specs

Build route specs from the initialized backend. Called after all middleware is ready.

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

transform_middleware?

Optional: transform middleware specs before applying.

Receives shallow copies of the trusted-proxy spec followed by the auth stack, in a fresh array — mutating them leaves the originals untouched. Add layers anywhere (before the proxy spec, between it and the auth stack, between the auth layers, or after), but keep each of those specs — its handler, not just its name — mounted once at its original path ('*' for the proxy, AUTH_MIDDLEWARE_PATH for auth) in its original order: the endpoint auth-scope check (assert_endpoint_in_auth_scope) and client-IP rate limiting (get_client_ip) assume it, so assembly throws (assert_middleware_stack_mounted) if the transform moves, drops, duplicates, reorders, wraps, or replaces one. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access.

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

audit_log_sse?

Enable factory-managed audit log SSE.

When truthy, creates an AuditLogSse instance internally, registers the SSE listener via backend.deps.audit.add_listener (composing with the consumer's on_audit_event callback rather than rebuilding AppDeps), adds its registry to backend.deps.connection_closer so revocations close its streams, and auto-includes audit_log_event_specs in the surface. The result is exposed on AppServerContext (for route factories) and AppServer (for the caller), always typed as AuditLogSse | null — when this option is set, the field is non-null. Use require_audit_sse(ctx) to assert the invariant in route factories that depend on it.

Pass true for defaults (admin role), or {role: 'custom'} for a custom role. Omit to wire audit SSE manually.

type true | { role?: string | undefined; }

auth_cleanup?

Schedule the auth cleanup beside the server: start_auth_cleanup over backend.deps — a pass as assembly finishes, then one per interval — stopped by AppServer.close. Each pass deletes expired sessions and closes the connections they opened through deps.connection_closer (every WebSocket transport and audit stream this server mounts), and audits each newly expired role_grant offer once.

Off by default; a production server should turn it on. Without it nothing sweeps: expired session rows accumulate, a WebSocket or audit stream outlives the expiry of the session it opened on until it disconnects, and an offer's expiry is never audited. With it, a connection outlives its session's expiry by at most one interval. The Rust spine's servers all schedule the twin (spawn_auth_cleanup).

Pass true for the default interval (DEFAULT_AUTH_CLEANUP_INTERVAL_MS), or {interval_ms} for another. Leave it off in an in-process test harness — a background pass deletes rows and writes audit rows mid-test — and in a process that runs run_auth_cleanup on a schedule of its own.

type boolean | AuthCleanupScheduleOptions

event_specs?

SSE event specs for surface generation. Defaults to [] (no SSE events).

type EventSpec[]

rpc_endpoints?

RPC endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry becomes route specs via create_rpc_endpoint, applied with the other routes, so consumers don't call create_rpc_endpoint themselves inside create_route_specs.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. create_standard_rpc_actions(ctx.deps).

Each path must sit inside the auth middleware's scope (AUTH_MIDDLEWARE_PATH, '/api/*') — assembly throws otherwise, since an endpoint outside it would see every caller as unauthenticated.

type RpcEndpointSpec[] | ((context: AppServerContext) => RpcEndpointSpec[])

create_upgrade_websocket?

Returns the Hono runtime adapter's upgradeWebSocket helper for the assembled app. create_app_server calls it once, with the app it builds (the same instance as AppServer.app), after creating the app and before mounting ws_endpoints — and only when ws_endpoints resolves non-empty, so it is never called for a server with no WebSocket endpoints. Required in that case: assembly throws without it. One helper services every mounted endpoint.

A factory rather than the helper itself because Node's adapter needs the app: @hono/node-ws has no module-level helper, only createNodeWebSocket({app}), and create_app_server is what builds the app. Deno and Bun ignore the argument:

import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun' create_upgrade_websocket: () => upgradeWebSocket,

Node keeps the adapter so it can attach to the server after serve():

let node_ws: NodeWebSocket | undefined; const server = await create_app_server({ ..., create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket, }); node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));

Test harnesses pass () => stub.upgradeWebSocket with create_stub_upgrade from testing/ws_round_trip.ts.

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

ws_endpoints?

WebSocket endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry is auto-mounted via register_ws_endpoint against the assembled Hono app on every runtime, so consumers don't call register_ws_endpoint themselves.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. when spreading create_standard_rpc_actions({...ctx.deps, notification_sender}) over WS.

When non-empty, create_upgrade_websocket must be supplied (throws otherwise). A factory returning [] does NOT trip the check — feature-flag gated WS surfaces stay safe.

Every mount gets what the HTTP surface gets: the auth middleware (a path outside AUTH_MIDDLEWARE_PATH throws at assembly, like rpc_endpoints), the server's allowed_origins unless the spec narrows them, and the same action_ip_rate_limiter / action_account_rate_limiter instances the RPC endpoints charge — one budget per action across both transports. The upgrade routes register after the route specs and before post_route_middleware and static_serving, so neither runs ahead of an upgrade.

Duplicate path values across two WsEndpointSpecs, and a path a GET RouteSpec already claims, throw at assembly before any endpoint mounts (Hono would silently shadow them otherwise).

Every mounted transport is added to deps.connection_closer, so the revocation handlers' post-commit closes reach its sockets — unconditionally, since those closes are what ends a revoked credential's connections. AppServer.close removes it again.

Each spec's auth_guard? defaults to true — the factory registers create_ws_auth_guard against the mounted transport via deps.audit.add_listener, repeating the close on the audit row. Wiring is deduped by transport reference identity so two specs sharing one BackendWebsocketTransport instance get a single listener; wrapped / proxied transports dedupe as separate entries (set auth_guard: false on duplicates and compose against the underlying transport once).

type readonly WsEndpointSpec[] | ((context: AppServerContext) => readonly WsEndpointSpec[])

env_schema?

Env schema for surface generation. Defaults to BaseServerEnv — pass an extended schema (typically BaseServerEnv.extend({...})) when the consumer adds app-specific env vars.

type ZodObject<$ZodLooseShape, $strip>

post_route_middleware?

Middleware applied after routes, before static serving. Included in surface.

type MiddlewareSpec[]

static_serving?

Static file serving. Omit if not serving static files.

type { serve_static: ServeStaticFactory; root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }

await_pending_effects?

Await all pending fire-and-forget effects before returning the response. Use in tests so audit log assertions don't need polling. Default false (production: true fire-and-forget).

type boolean

on_effect_error?

Called when a pending effect rejects. Use for monitoring, metrics, or alerting in production. Only called when await_pending_effects is false (production mode).

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

env_values?

Env values for startup summary logging.

type Record<string, unknown>

AppServerRateLimiterKey
#

server/app_server.ts view source

internal

"login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

type "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

import type {AppServerRateLimiterKey} from '@fuzdev/fuz_app/server/app_server.js';

Name of an AppServerOptions rate-limiter field — also the AppServerContext field its resolved limiter lands on.

BootstrapDisabledOptions
#

server/app_server.ts view source

BootstrapDisabledOptions import type {BootstrapDisabledOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "disabled"

BootstrapLiveOptions
#

server/app_server.ts view source

BootstrapLiveOptions import type {BootstrapLiveOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "live"

token_path

type string

route_prefix?

Route prefix for bootstrap routes. Default '/api/account'; must sit under /api/.

type string

on_bootstrap?

Called after successful bootstrap (account + session created). Use for app-specific post-bootstrap work like generating API tokens.

type (result: BootstrapAccountSuccess, c: Context<any, any, {}>): Promise<void>

result

c

type Context<any, any, {}>
returns Promise<void>

BootstrapServerOptions
#

server/app_server.ts view source

BootstrapServerOptions

type BootstrapDisabledOptions | BootstrapSurfaceOnlyOptions | BootstrapLiveOptions

import type {BootstrapServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Bootstrap configuration for AppServerOptions.bootstrap.

Discriminated union over three deployment intents. Distinct from BootstrapRouteOptions in auth/bootstrap_routes.ts — that one is per-factory runtime state (mutable bootstrap_status ref, rate limiter); this one is the consumer-facing server option that create_app_server reads at startup to decide whether to mount the routes and where.

Three modes:

  • disabled — no route mounted, nothing in /api/surface. Equivalent to omitting bootstrap entirely; the explicit mode is for documentation and reviewable intent at the wiring layer.
  • surface_only — route present, permanent 403 via check_bootstrap_status. For tests asserting on the disabled-but-present wire shape.
  • live — route mounted, real token verification. Success path reachable. token_path is required (non-nullable).

BootstrapSurfaceOnlyOptions
#

server/app_server.ts view source

BootstrapSurfaceOnlyOptions import type {BootstrapSurfaceOnlyOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "surface_only"

route_prefix?

Route prefix for surface generation. Default '/api/account'; must sit under /api/.

type string

collect_config_diagnostics
#

server/app_server.ts view source

internal

(input: ConfigDiagnosticsInput): AppSurfaceDiagnostic[] import {collect_config_diagnostics} from '@fuzdev/fuz_app/server/app_server.js';

The config-level diagnostics create_app_server appends to the surface's spec-level ones: a weakened session cookie, and an open rate-limited surface — one warning for 'disabled_for_testing', otherwise one per explicitly-null limiter, so a deployment that disables login's limiter but keeps signup's reads differently from one that disables both.

input

returns

AppSurfaceDiagnostic[]

ConfigDiagnosticsInput
#

server/app_server.ts view source

internal

ConfigDiagnosticsInput import type {ConfigDiagnosticsInput} from '@fuzdev/fuz_app/server/app_server.js';

cookie_options

The session cookie overrides, from AppServerOptions.session_options.

type Partial<SessionCookieOptions> | undefined

rate_limiter_mode

The rate_limiters mode, defaulted.

type RateLimiterMode

rate_limiters

The resolved limiters, from resolve_rate_limiters.

type Record<AppServerRateLimiterKey, RateLimiter | null>

create_app_server
#

server/app_server.ts view source

(options: AppServerOptions): Promise<AppServer> import {create_app_server} from '@fuzdev/fuz_app/server/app_server.js';

Create a fully assembled Hono app with auth, middleware, and routes.

Handles the assembly lifecycle: rate limiters → audit SSE → proxy middleware → auth middleware → bootstrap status → route specs (consumer, bootstrap, RPC endpoints, surface) → surface generation → Hono app assembly (routes, then WS endpoints) → post-route middleware → static serving → the auth cleanup schedule. Database migrations belong to the backend lifecycle — pass migration_namespaces to create_app_backend.

When audit_log_sse is set, the SSE registry's listener is registered via backend.deps.audit.add_listener — no shallow-copy of AppDeps. That registry and every WebSocket transport mounted from ws_endpoints are added to backend.deps.connection_closer, so the revocation handlers built from ctx.deps close their connections with no further wiring. The audit_sse field on the returned AppServer (and the AppServerContext passed to create_route_specs) is non-null in that case; consumers can call require_audit_sse(ctx) / require_audit_sse(server) to assert the invariant.

Assembly acquires a few things that outlive it — the rate limiters it builds, the listeners it registers on deps.audit, the members it adds to deps.connection_closer — and records a release for each as it goes. When assembly throws, every release recorded so far runs, last acquired first (one that throws is logged and the rest still run), and the original error is rethrown: a failed assembly leaves the backend's emitter and closer as it found them and no limiter timer running. When auth_cleanup is set, the auth cleanup schedule starts as the last step of assembly — nothing after it can throw, so an assembly that throws leaves no schedule running.

The returned close owns shutdown: it stops that schedule, closes every live connection on the backend's closer, runs the same releases, and closes the database (AppServer.close).

options

returns

Promise<AppServer>

the assembled Hono app with its surface, bootstrap status, migration results, audit SSE, mounted WS transports, and close

throws

  • Error - from any assembly step, after releasing what assembly acquired

DEFAULT_MAX_BODY_SIZE
#

server/app_server.ts view source

number import {DEFAULT_MAX_BODY_SIZE} from '@fuzdev/fuz_app/server/app_server.js';

Default maximum request body size: 1 MiB.

EffectErrorContext
#

server/app_server.ts view source

EffectErrorContext import type {EffectErrorContext} from '@fuzdev/fuz_app/server/app_server.js';

Context passed to on_effect_error when a pending effect rejects.

method

HTTP method of the request that spawned the effect.

type string

path

URL path of the request that spawned the effect.

type string

require_audit_sse
#

server/app_server.ts view source

(source: { audit_sse: AuditLogSse | null; }): AuditLogSse import {require_audit_sse} from '@fuzdev/fuz_app/server/app_server.js';

Assert that audit_sse was wired by create_app_server and return it as a non-null AuditLogSse. Throws a labelled error when the audit_log_sse option was not passed to create_app_server.

Use in route factories that depend on factory-managed audit SSE:

create_route_specs: (ctx) => create_audit_log_route_specs({ stream: require_audit_sse(ctx), }),

Preferred over ctx.audit_sse! — ! lies to the type system and produces a downstream cannot-read-property crash if a consumer wires the route without enabling the option.

source

type { audit_sse: AuditLogSse | null; }

returns

AuditLogSse

resolve_rate_limiters
#

server/app_server.ts view source

internal

(options: Pick<AppServerOptions, "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter" | "rate_limiters">): ResolvedRateLimiters import {resolve_rate_limiters} from '@fuzdev/fuz_app/server/app_server.js';

Resolve each rate-limiter option: an explicit option (instance or null) wins; an omitted one is a default instance under 'enforced' and null under 'disabled_for_testing', which also announces the mode on stderr (announce_rate_limiters_disabled). One instance per surface — see AppServerOptions.login_ip_rate_limiter for why these are not a single shared bucket.

options

the rate-limiter fields and rate_limiters mode of AppServerOptions

type Pick<AppServerOptions, "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter" | "rate_limiters">

returns

ResolvedRateLimiters

ResolvedRateLimiters
#

server/app_server.ts view source

internal

ResolvedRateLimiters import type {ResolvedRateLimiters} from '@fuzdev/fuz_app/server/app_server.js';

The rate limiters create_app_server resolved from its options.

mode

The rate_limiters mode, defaulted to 'enforced'.

type RateLimiterMode

limiters

Each limiter by option name — null when off.

type Record<AppServerRateLimiterKey, RateLimiter | null>

built

The limiters built here for omitted options, in build order — the server's to dispose. Never one passed in, which its caller owns.

type RateLimiter[]

Depends on
#

Imported by
#