@octabits-io/elysia
v0.8.0
Published
Reusable Elysia middleware & helpers: security headers, trusted-proxy client IP, error mapping, response schemas, rate limiting, health routes, app skeleton, and MCP harness
Readme
@octabits-io/elysia
Reusable Elysia middleware and helpers, extracted from
production APIs. Domain-agnostic — errors are
@octabits-io/foundation's OctError ({ key, message };
KeyedError is kept as an alias), the error handler takes foundation's
Logger, and any domain-specific key→status rules are injected via
statusOverrides. Foundation is a peer dependency — this package is part
of the octabits stack, not a standalone kit.
Contents
createSecurityHeadersPlugin(options?)— sets standard hardening response headers (X-Frame-Options,X-Content-Type-Options,Referrer-Policy,X-XSS-Protection: 0,Permissions-Policy,Cross-Origin-Opener-Policy,Cross-Origin-Resource-Policy, CSP, and HSTS in production) on every response, including error responses (headers are staged inonRequest). All values configurable/disable-able via options.createClientIpPlugin(trustedProxies)— derivesclientIpfromX-Forwarded-Foronly when the direct connection is a trusted proxy ('*', an IP allowlist, or[]for none), walking the chain right-to-left past trusted proxy hops (rightmost-untrusted, spoof-resistant); IPv6-mapped IPv4 is normalized, garbage entries fall back to the direct peer. Used to key rate limiting.createClientIpResolver/normalizeIpexpose the pure logic.- Error mapping —
getStatusCodeForError,statusErrorWithSet,mapResultError, theApiErrorclass family (NotFoundError,ForbiddenError, …),isDbConnectionError, and thecreateErrorHandlerglobal plugin. Response bodies are whitelisted to{ key, message[, fields] }, and 5xx messages are redacted in production (the stablekeyis kept). createRateLimit(options)— app-level rate limiting with a timing-safe skip-by-internal-secret seam and a skip-by-CIDR seam (skipCidrs: real IPv4 CIDR matching, e.g.10.0.0.0/24, or exact IPs; IPv6-mapped keys normalized).createElysiaApp(routes, options)— the standard app skeleton (securityHeaders → clientIp → rateLimit → [cors/swagger] → errorHandler → routes), preserving the routes' type for Eden Treaty; plusregisterGracefulShutdown.createHealthRoutes({ checkReady })—/health+/live+/readywith the readiness-failure → 503 mapping.@octabits-io/elysia/mcp—createMcpRoutes({ resolveScope, registerTools, … }): statelesselysia-mcpharness with a per-request scope correlated viaAsyncLocalStorage(interleaving-safe under concurrent requests) and disposed in afinallytied to the request.registerToolsalso runs once at startup — it must be idempotent and must only callgetContainer()inside tool handlers, at invocation time. Scope-key extraction via the requiredparseScopeKeyseam — no default URL convention. UsecreatePathSegmentScopeParser('scope')for a/scope/:scopeKey/layout,createPathSegmentScopeParser('tenant')for/tenant/:id/, or() => 'default'for single-scope deployments (it matches the segment's last occurrence and caps key length at 256). The returned plugin composes under prefixed parents (.use()it anywhere in a nested route tree); matched requests are internally re-addressed, soresolveScopeshould read the public URL from itsurlargument, not fromcontext.request.url.elysia-mcp+@modelcontextprotocol/sdkare optional peers. ⚠️ On Node runtimes (e.g. vitest),elysia-mcp≤0.1.1 callsBun.randomUUIDv7()on every stateless request — shim it in test setup after importingelysia(so Elysia's runtime detection is unaffected):globalThis.Bun = { randomUUIDv7: () => crypto.randomUUID() }.- Env-config helpers —
getEnv*,isProduction,parseCsv,parseCorsOrigins. - Response schemas —
SCHEMA_ERROR_RESPONSE,SCHEMA_VALIDATION_ERROR,SCHEMA_SUCCESS_RESPONSE, theCommonErrorResponsessuperset, and theerrorResponses(...codes)selector.
Usage
import { Elysia } from 'elysia';
import {
createSecurityHeadersPlugin,
createClientIpPlugin,
createErrorHandler,
statusErrorWithSet,
CommonErrorResponses,
} from '@octabits-io/elysia';
const app = new Elysia()
.use(createSecurityHeadersPlugin())
.use(createClientIpPlugin(['*']))
.use(createErrorHandler(logger));
// In a route, translate a Result error to an HTTP response:
if (!result.ok) return statusErrorWithSet(set, result.error, { tenant_not_found: 403 });Peer dependencies: elysia, zod.
