v0.11.0
The consistency + fail-fast release. A repo-wide naming sweep lands one vocabulary everywhere — no abbreviations, Options never Settings, Websocket casing — as hard cuts with migration notes. Underneath it, a new OptionsValidator / OptionsError layer makes every configurable thing fail fast on invalid values, on every input path (builder, plain object, HOCON). Plus scoped HTTP error handling, a security-middleware suite, static file serving, and a broad batch of WebSocket/HTTP security hardening from the audit backlog. Pre-1.0 — this minor carries breaking changes; see below.
🚀 New features
Options validation
OptionsValidator+OptionsError(#274) — a declarative-but-code validator layer for theXOptionspattern. Validation runs once at consume time on the merged settings, so builder, plain-object, and HOCON inputs are all checked and cross-field rules see the final values.- Validators shipped for ~40 options families — every broker (MQTT, Kafka, AMQP, Redis Streams, NATS, JetStream, SSE, TCP, UDP, gRPC client) and the WebSocket client; cluster core, sharding, singleton, and downing strategies; discovery + gossip intervals; leases; caches (
RedisCache,MemcachedCache,CachedSnapshotStore,InMemoryCache);CassandraJournaland the S3 / filesystem object-storage backends; the Express/Hono HTTP backends; HTTP middleware + directives; WebSocket routes + resolved policy;WorkerCluster,ProducerController,TestProbe,CircuitBreaker, andBoundedMailbox. RateLimitOptions/IdempotencyOptionsfluent builders —rateLimitandidempotentgained the real builders they were already documented to have; the plain-object call form is unchanged.withMaxDecompressedBytesstore option — the 512 MiB decompression-bomb guard (#3) is now tunable per object-storage store (Infinityopts out).- Per-route WebSocket connection cap — opt-in
maxConnectionsonwebsocket()routes (builder oractor-ts.http.websocket.maxConnections); upgrades beyond the cap close with 1013 before an actor is wired.
HTTP
- Scoped error handling + fallback routes (#352) —
handleErrors(handler, child)catches exceptions from a subtree,fallback(handler)answers unmatched requests,ServerBuilder.withErrorHandler(...)is the server-wide last resort. Uniform precedence across Fastify/Express/Hono. - Security-middleware suite (#353) —
cors(compiler-expanded per-pattern preflight routes),strictTransportSecurity/hsts,contentSecurityPolicy,csrfProtection+requireSameOrigin,securityHeaders,requestId,BasicAuth,requestTimeout— each with anXOptionsbuilder. Plus publicparseCookies/serializeCookie. - Static file serving (#354) —
getFromFile,getFromDirectory,getFromBrowseableDirectory: MIME detection, conditional requests (weak ETag +Last-Modified→ 304), singleRange(206/416), HEAD, and XSS-safe listings. Plus a MIME-type registry (contentTypeFor) and streaming response bodies (ReadableStream<Uint8Array>) on all three backends. - HTML response utilities (#352) —
escapeHtml, an auto-escapinghtmltagged template with aSafeHtmlbrand,rawHtml,completeHtml.
⚠️ Breaking changes (pre-1.0)
- WebSocket →
Websocket(single-cap), noWsabbreviation —WebSocketServerActor/WebSocketClientActor→WebsocketServerActor/WebsocketClientActor,Ws*supporting types →Websocket*,wsSend()→websocketSend(), module movedsrc/http/ws/→src/http/websocket/. Thewebsocket()directive, the globalWebSocket, and theSec-WebSocket-Protocolheader are unchanged. - Abbreviations spelled out in type/member names:
*Cmd→*Command,*Msg→*Message,*Ack→*Acknowledgment,ByPid*→ByPersistenceId*,*Impl→*Implementation,*Ctor→*Constructor. Testkit too:TestProbe.expectMsg()→expectMessage(),expectMsgType()→expectMessageType(). Wire/discriminator string literals are unchanged. - One config vocabulary:
Options, neverSettings— remaining*Settingstypes →*OptionsType;BrokerSettings.tsfolded intoBrokerOptions.ts(BrokerSettingsError→BrokerOptionsError); theBrokerActorglue renamed (readOptionsFromConfig/requiredOptions/builtInDefaultOptions/options). - Command vs Signal unified on
kind— MQTT and WebSocket internal mailbox signals arekind-tagged plain objects; the bad-payload hook isonInvalidMessageeverywhere (MQTT'sonDecodeErroris gone);WebSocketAcceptSignal→WebsocketAcceptCommand. - Invalid option values throw
OptionsError(#274) at construction / actor start instead of a bareError— and previously-unchecked builder/plain-object paths are now checked. Missing required broker settings still throwBrokerOptionsError; malformed HOCON still throwsConfigError. InMemoryCachejoins theXOptionsfamily —InMemoryCacheOptionsbuilder + validator + HOCON defaults underactor-ts.cache.in-memory; the internalInMemoryCacheSettingsinterface is removed (a plain{ maxEntries, cleanupMs }object still works).CircuitBreaker+BoundedMailboxvalidate their options —OptionsErrorinstead of bareError;maxFailures/resetTimeoutMsandcapacityare required at runtime (a builder without them previously produced a breaker that never opened / an unbounded "bounded" mailbox);callTimeoutMs: 0now throws (omit it to disable).- HTTP structural types — the
Route/CompiledEndpointunions gainfallbackandcorsvariants (exhaustivematches must handle them);ServerBuildergains a requiredwithErrorHandler;HttpErrorgains an optional 4thheadersparameter andBearerTokenAuth401s expose the challenge onerr.headers['www-authenticate'](#352, #353).
🔒 Security
- WS-1 (HIGH) — WebSocket upgrade crash hardened — a malformed percent-escape in the upgrade path was process-fatal (unhandled rejection) on the Express backend, reachable pre-auth. Now a non-match → 404, with a last-resort socket guard.
- WS-2 (HIGH) — Cross-Site WebSocket Hijacking (CSWSH) defence — new
allowedOriginsonwebsocket()routes: a listed-but-wrongOriginis rejected with 403 before the handshake on all three backends. - HTTP-1 (MEDIUM-HIGH) — Hono body-size cap enforced before buffering — oversized
Content-Lengthnow rejects with 413 before reading the body. - HTTP-2 (MEDIUM-HIGH) —
InMemoryCacheis bounded (LRU) — attacker-chosen keys (idempotency, rate-limit) can no longer grow the default cache without limit; defaultsmaxEntries: 10_000, background sweep every 60 s. - HTTP-4 (MEDIUM) — idempotency responses can be scoped per caller — opt-in
identity: (req) => stringfolds the authenticated principal into the cache key, preventing cross-user response disclosure. - #3 (MEDIUM) — decompression-bomb cap on stored bodies —
decodeBodycaps decompressed size at 512 MiB by default, now tunable per store. - WS-3 (MEDIUM) — transport-level WebSocket frame cap (Express + Fastify) — oversized frames are rejected at the protocol level (1 MiB) instead of being buffered in full first.
- WS-4 (MEDIUM) — WebSocket backpressure works on Hono — the adapter now surfaces
bufferedAmount, somaxBufferedBytes/onBackpressureare no longer no-ops there. - WS-5 (MEDIUM, partial) — per-route connection admission cap — see New features; the handshake/idle-timeout and hub-mailbox-bounding sub-parts remain tracked follow-ups.
- WS-6 (LOW) — CRLF stripped from raw upgrade-reject headers — no response splitting via app-supplied header values on the Express reject path.
- BRK-1 / BRK-2 (MEDIUM) — inbound buffer caps for TCP
lines+ SSE — un-delimited streams now drop the connection instead of buffering forever. - #6 (LOW) — consistent SQL/CQL identifier validation — SQLite and Cassandra now validate config-sourced identifiers like Postgres/MariaDB already did.
- #9 (hardening) — JSON deserialization ignores the
__proto__setter — hostile payloads can't change a decoded object's prototype. - CORS, CSRF, and security-header middleware (#353) — correct preflight handling, HMAC-signed double-submit CSRF token, HSTS/CSP/COOP/CORP/nosniff/frame-options, constant-time secret comparisons;
WWW-Authenticatechallenges now reach the wire. - Hardened path-traversal defence for static files (#354) — full pre-validation decode, segment rejection (
.., NUL, backslash, drive/ADS), symlink confinement, uniform 404s.
📚 Documentation
- Server-WebSocket page moved from IO into the HTTP section (#351); stale API-reference pages reconciled with the shipped code (#360).
- Middleware pages corrected to actual behavior: rate-limit headers + keying on
req.remoteAddress(HTTP-3), idempotency in-flight → 409, response-cache single-flight (EN + DE). - The reference config now documents the
actor-ts.http.websocketpolicy section;*Settingsprose repointed to the*OptionsTypevocabulary (#349).
Full changelog: CHANGELOG.md · 2665 tests green, ~94 % line coverage.