Roadmap

Ingenium is alpha (v0.0.1). The API is mostly settled but still subject to change before 0.1.0. This page tracks what's shipped, what's known-broken, and what's planned. Authoritative changelog lives in the source repo.

⚠️ Production caveats — read first

Not production-ready for multi-instance deploys. The default in-memory stores for sessions, idempotency, and rate-limit don't share state across pods. Use the Redis-backed adapters in ingenium-redis before deploying behind a load balancer.

Alpha API surface. Verb registration, ctx shape, and middleware composition are stable enough to use; everything tagged @internal may change before 0.1.0.

Version targets

MilestoneGoalStatus
v0.0.xFeature-complete framework surface; alpha API.current
v0.1.0All Redis stores shipped; plugin scoping; ExtractParams runtime narrowing; benchmark matrix on CI.in progress
v1.0.0API frozen. SemVer stability commitment. Production deployments officially supported.planned

Shipped in v0.0.1

Core surface:

  • ingenium() app factory with lazy-composed middleware pipeline and app.compose() pre-warm.
  • Router() with prefix mounting, nested routers, and deterministic precedence (static > param > wildcard).
  • IngeniumContext with params, query, headers, state, status/header setters, and terminal writers (json / text / html / send / redirect / stream).
  • Lazy body parsers - json (with optional Zod-like schema + maxBytes), text, urlencoded, buffer, stream.
  • Native multipart / file upload (ctx.body.multipart()).
  • ctx.query.parse(schema) symmetric with ctx.body.json(schema) — shallow-array-aware coercion for repeated keys.
  • Handler return-value reflection (object → JSON, string → text/html, Buffer → octet-stream, Readable → stream, undefined → 204).
  • Type-level ExtractParams<Path> narrowing on verb handlers — app.get('/users/:id', ctx => ctx.params.id) types as string.
  • Error class hierarchy (IngeniumError and friends) with default JSON error boundary; app.onError override + re-throw delegation.
  • Express compatibility shim (expressCompat) - real-stream req/res shims so (req, res, next) middleware is a genuine drop-in: cors, helmet, body-parser, multer, compression, express-session, morgan, express-rate-limit all work end-to-end.
  • Plugin system - app.register(plugin, opts?) with lifecycle hooks (onRoute, onCompose, onRequest, onResponse, onError) and per-request decorators.
  • Standard Schema v1 integration - ctx.body.json(schema) and ctx.query.parse(schema) accept any validator exposing ['~standard'] (TypeBox, Valibot, ArkType) alongside the existing Zod-style safeParse and duck-typed { parse } shapes.

Transports:

  • Node HTTP adapter with app.listen(port, host?) returning { port, close }.
  • HTTP/2 and HTTP/2 cleartext (h2c) transports.
  • WebSocket and SSE transports sharing the same dispatch entry.
  • Bun adapter (ingenium-bun) for Bun.serve() with a Web-Streams ↔ node:stream bridge.

Production primitives:

  • Static file middleware - ETag, If-None-Match, range requests, MIME detection, HEAD support, index / extensions / dotfiles / maxAge options.
  • Rate limiting - sliding-window middleware with pluggable store (in-memory ships in core; Redis adapter in ingenium-redis).
  • Sessions - cookie + server-side stores.
  • CSRF protection - native ingenium.csrf(opts) middleware, double-submit cookie or session-synchronizer pattern, HMAC-SHA-256 signed, timing-safe verification.
  • CORS - origin allowlist, preflight caching, credentials handling.
  • Trust proxy - X-Forwarded-* and Forwarded parsing with explicit-hop counting.
  • Body parsing - schema-validated JSON with hard maxRequestBytes cap enforced at the transport layer (default 2 MiB).
  • Per-request timeout (requestTimeoutMs) with IngeniumTimeoutError (503) and late-write protection via per-context _epoch counter.
  • Graceful shutdown - drain in-flight requests, refuse new ones.

Auth + integrations:

  • JWT middleware - HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, plus JWKS support with in-flight request coalescing. 'none' rejected unconditionally.
  • API-key middleware.
  • Idempotency-Key middleware - skip-caching 5xx by default via IdempotencyOptions.cacheable.
  • OpenAPI generation, RFC 7807 application/problem+json errors, content negotiation, reverse-proxy helpers, cron + background jobs primitives.

Hardening:

  • Header injection guard - \r / \n in header names or values throws IngeniumHeaderInjectionError (500, code HEADER_INJECTION).
  • ctx.json() safety - circular refs, BigInt, and unserializable values throw IngeniumUnserializableError (500, code UNSERIALIZABLE_RESPONSE) instead of bubbling a generic JSON.stringify TypeError. safeJsonStringify helper exported for lenient mode.

Tooling:

  • CLI scaffolder - ingenium new <name> [--bun|--minimal].
  • ADR docs covering load-bearing decisions (radix-trie router, lazy composition with dirty bit, return-value reflection, context pool, compat shim scope).

QOL pass (merged, awaiting next tagged release):

  • app.inject({ method, url, headers, body }) — in-process test client returning { status, headers, body, json<T>() }. Bypasses the socket entirely; ~10× faster integration tests.
  • app.route(path).get(h).put(h).delete(h).all(h) — chainable per-path verb builder. Pure registration sugar; works on both IngeniumApp and Router.
  • ctx.cookies — first-class cookie API with signed-cookie support (cookieSecrets on app options, HMAC-SHA-256, key rotation, timingSafeEqual verify).
  • Inline OpenAPI route optionsapp.get('/path', { tags, summary, response, requestBody, deprecated, ... }, handler). Built-in keys are peeled off at registration and merged into the route descriptor.
  • ctx.body.json() parse cache — multiple consumers (audit middleware then handler) can read the body without "already consumed" errors. Buffer-level cache, so different schemas across calls all validate cleanly.
  • Plugin scopingapp.scope('/api/v2', s => s.use(authPlugin)) so plugins only decorate their subtree.
  • Dev-mode footgun warnings — double-write detection, trust-proxy misuse hints, double-listen() errors, fetch-Response return-value warning. All gated by NODE_ENV !== 'production'; zero hot-path cost.

Known issues — bugs

  • ExtractParams doesn't narrow constrained params - :id(\\d+) strips the constraint and stays string. Unconstrained params (:id) do narrow correctly. The router doesn't yet honor inline constraints at runtime; types and runtime have to land together.

Known issues — gaps

  • Inline OpenAPI response/requestBody accepts raw OpenAPI Schema only - Standard Schema / Zod validators throw at registration with a clear message; convert validators to JSON Schema or use precomputed schemas. Validator→JSON-Schema bridging tracked for 0.1.0.
  • Scoped decorators are still global - app.scope(path, ...) scopes middleware and plugins, but scope.decorate(...) installs onto the shared context. A dev-mode warning explains the limitation; path-aware resolution tracked for 0.1.0.

Open questions

  • Lazy compose dirty-bit cost under heavy mutation. Apps that register routes per-request will recompose on every request. Cap it, warn after N recomposes per minute, or expose a freeze() toggle for production?
  • Compat shim long-tail strategy. Aim for "covers the top 20 middleware on npm" with documented gaps, or stay minimal and route everyone to native ports?
  • Scoped decorators. Today app.scope(...) scopes middleware but decorators remain global (a lazy decorator installs onto the context, not per-path). Worth path-checking inside the decorator factory, or document as intentional and let userland gate?

Non-goals

These come up often enough to call out explicitly:

  • A full Express drop-in. The compat shim is for the long tail of (req, res, next) middleware; it is not a goal to make Express apps work unmodified. The migration guide is the supported path.
  • A monorepo bundler / framework wrapper. Ingenium is the HTTP framework. Adding view templating, ORM, or a CLI for app structure is out of scope.
  • Multi-runtime fetch-style Response interop. Handlers return plain values or call ctx writers. We will not add return new Response(...) translation; the dev warning makes the mistake loud and the fix is one line.
  • A community plugin marketplace. Plugins are npm packages; discovery happens via npm and the docs index.

Where to next?