This layer accepts inbound traffic on every supported protocol (HTTP/1.1, HTTP/2, HTTPS, WebSockets, MQTT, NATS) and routes it through to the Resource layer.
Read this when: you're touching request/response, protocol handling, middleware ordering, or WebSocket upgrade behavior.
Navigation convention. This guide references code by symbol name (function/const). Use your editor's go-to-symbol or
grep -n '<name>' server/<file>to jump. Line numbers drift; symbols don't.
| Stack | File | Used for |
|---|---|---|
| Native | http.ts |
Direct socket handling for application-level HTTP/1.1, HTTPS, HTTP/2, and WebSockets. Highest performance. This is the path most user requests take (REST, GraphQL, custom resource endpoints). |
| Operations API | operationsServer.ts |
Fastify-based JSON operations API ({operation: 'create_table', ...}). Internal/admin surface — not on the hot path for application data. |
| Custom Functions (legacy) | fastifyRoutes.ts |
Legacy custom functions only. Wraps Fastify with autoload. Don't add new code here. |
A request entering http.ts does not go through Fastify. The two handleApplication(scope) functions (one in each Fastify file) load independently from component config.
| File | Purpose |
|---|---|
Server.ts |
Defines the Server interface — the contract that protocol plugins use to register listeners. Has socket(), http(), ws(), upgrade(), contentTypes, getUser(), operation(), replication, etc. |
http.ts |
Native HTTP/WS server. Registration entry points (onRequest, onUpgrade, onWebSocket), per-port middleware chains, UDS support, PROXY protocol. See section map below. |
middlewareChain.ts |
Topological sort respecting before/after constraints on listener registrations (topoSort). Falls back to registration order on cycle. Also urlPath/host sub-route dispatch: a mount is a prefix with a segment boundary, the mount prefix is stripped from request.pathname before the sub-chain runs, and the root mount '/' normalizes to no path constraint (joins the default chain, nothing stripped — #1766). |
REST.ts |
Resource-routed REST handler: URL → Resource.getResource() → method dispatch + content negotiation. |
graphqlQuerying.ts |
GraphQL query/mutation/subscription execution against Resources. |
mqtt.ts |
MQTT broker (connect/sub/pub mapped onto Resource interface). |
DurableSubscriptionsSession.ts |
Persistent subscription state (resume across reconnects). |
| File | Purpose |
|---|---|
operationsServer.ts |
Boots Fastify for operations API. buildServer() constructs the server; handler() parses {operation: ...} and dispatches. |
fastifyRoutes.ts |
Legacy custom functions. Discovers routes from each component's routes/ folder. |
| File | Purpose |
|---|---|
serverHelpers/Request.ts |
Wraps IncomingMessage with Harper-specific fields (user, response, headers). |
serverHelpers/Headers.ts |
Header mutation/merge utilities. |
serverHelpers/contentTypes.ts |
(de)serialization registry; serialize, serializeMessage, getDeserializer. |
serverHelpers/serverUtilities.ts |
OperationDefinition and shared helpers. |
serverHelpers/OperationFunctionObject.ts |
Wraps an operation handler with metadata. |
serverHelpers/JSONStream.ts |
Streaming JSON output for large responses. |
nodeName.ts |
Resolves this node's name (config → hostname). |
static.ts |
Static file serving for component-bundled assets. |
throttle.ts |
Per-IP / per-user request throttling. |
storageReclamation.ts |
Disk-pressure signals to downstream consumers. |
serverRegistry.ts |
Trivial registry export. |
status/ |
Server status reporting (cluster status, per-port info). |
| File | Purpose |
|---|---|
threads/socketRouter.ts |
Routes accepted sockets to worker threads based on port. |
threads/manageThreads.js |
Thread pool lifecycle. |
threads/threadServer.js |
Worker entry point — receives sockets via IPC. |
threads/itc.js |
Inter-thread comms primitives. |
transactionLogCooling.ts |
Main-thread timer that cools transaction-log mmaps. |
Workers receive
workerData.noServerStart = true— never start the server inside a worker.
Single-instance background tasks pick their thread by what state they touch:
- Last worker (
getWorkerIndex() === getWorkerCount() - 1) — for tasks that operate on worker-resident JS state: audit cleanup (resources/auditStore.ts) and disk reclamation (storageReclamation.ts) walk per-store objects that only exist in a worker. - Main thread (
isMainThread) — for tasks that drive a process-global native singleton and need no JS state.transactionLogCooling.tsis the example: rocksdb-js's transaction-log registry is one C++ static shared across all worker threads, so any thread cools every log. The main thread is chosen because it is the only thread that lives for the whole process — a worker-driven timer would stall whenever that worker is recycled.
Every entry is a top-level function or named const. Jump via go-to-symbol or grep -n 'function <name>' server/http.ts.
| Symbol | What it does |
|---|---|
registerUdsCleanupPaths, cleanupUdsFiles, writeUdsMetadata, cleanupSocketsDirectory |
UDS socket / metadata file lifecycle. |
handleApplication(scope) |
Component entry point — captures httpOptions for the scope. |
getHttpOptions() |
Returns the current scope's HttpOptions. |
deliverSocket() |
IPC-delivered socket handoff from socketRouter. |
proxyRequest() |
Cross-port request routing. |
registerServer() |
Records a server for a port in the SERVERS map. |
getPorts() |
Resolves listener options → list of {port, secure}. |
httpServer() |
Main listener registration entry point. |
getHTTPServer(port, secure, options) |
The largest function in the file. Creates/retrieves the underlying Node HTTP/HTTPS server. Wires request, upgrade, error handlers, TLS context, and the per-port middleware chain. |
makeCallbackChain() |
Builds the per-port handler chain via middlewareChain.topoSort. |
unhandled() |
Terminal 404 handler. |
onRequest() |
Thin alias of httpServer({requestOnly: true}). |
onUpgrade() / upgradeListeners (const) |
Register HTTP upgrade listener; underlying list. |
onWebSocket() / websocketListeners (const) |
Register WebSocket listener; auto-adds default upgrade handler the first time it runs for a port. Underlying list of registrations. |
enableProxyProtocol() |
PROXY v1 parsing (Node 24+-compatible workaround). |
defaultNotFound() |
Default 404 response. |
logRequest() |
Per-request access log line. |
getRequestId() |
Generates the per-request correlation ID. |
Components register listeners with optional before: 'name' / after: 'name' options. middlewareChain.topoSort resolves order; cycles fall back to registration order with a warning. Three lists hold the registrations:
httpResponders— request handlersupgradeListeners(inhttp.ts)websocketListeners(inhttp.ts)
The default WebSocket upgrade handler is registered automatically inside onWebSocket() the first time it runs for a given port.
REST.ts → http(request, nextHandler) is the chief integration point: it takes a Request, asks the Resources registry for a match, builds a RequestTarget, and dispatches into the Resource class's static method. Cache headers are translated to request.expiresAt / onlyIfCached / noCache flags within the same function.
Three tiers, applied in two places:
- App/resource explicit — a
Cache-Controlset by the resource (or@table(cacheControl: "...")for anonymous reads, emitted inREST.ts → http()) always wins. The declaration is required: anonymous readability alone never emits shared-cache headers, because a request-attribute-gatedallowRead(IP, headers) would make inferredpublicunsound. - Identity floor —
security/auth.ts → applyResponseHeadersstampsCache-Control: private, no-cache+Vary: Authorization(+Cookiewhen sessions are on) on any response where a principal was resolved or credentials were rejected (401), unless the app opted into shared caching withpublic/s-maxage(the RFC 9111 opt-in). - CORS partitioning — when CORS is enabled, every response gets
Vary: Origin(the ACAO header is reflected per-origin, and its absence on no-Origin requests is origin-dependent too).
The @table(cacheControl:) value is persisted on the primary-key attribute (like expiration), so all threads and future boots see it; resources/databases.ts → table() treats null as "schema explicitly has none" (clears on reload) and undefined as "caller is not schema-defining" (no clobber from add_attribute/cluster schema events).
| Question | Where |
|---|---|
| Where do I register a new HTTP handler? | http.ts → httpServer() (or onRequest() for the request-only form) |
| Where do I register a WebSocket handler? | http.ts → onWebSocket() |
How does before/after middleware ordering work? |
middlewareChain.ts → topoSort |
| Where does PROXY protocol get parsed? | http.ts → enableProxyProtocol |
| Where is the REST request → Resource dispatch? | REST.ts → http() |
| Where is the operations API request handled? | operationsServer.ts → handler |
| How are content types (de)serialized? | serverHelpers/contentTypes.ts |
| Where do durable subscriptions live? | DurableSubscriptionsSession.ts |
| How are sockets dispatched to worker threads? | threads/socketRouter.ts |
| Where is the Operations API wired into Fastify? | operationsServer.ts → buildServer |
- Don't add new code to
fastifyRoutes.ts— it's the legacy custom-functions path. - New protocol plugins implement the
Serverinterface (inServer.ts) and register viaonRequest/onUpgrade/onWebSocket. - Always pass
namewhen registering a listener withbefore/after— anonymous entries can't be ordered against. - Tests live in
../unitTests/server/.