Skip to content

Limitations & roadmap

This page is intentionally honest: what does not work well yet and what we are aiming for next. It is not a dated release plan—order and timing can change. For shipped changes, see the Changelog.

AreaWhat to expect
GraphQL schema stitchingQueries, mutations and — as of v3.4.0 — subscriptions all execute. Schema stitching is still not hardened; compose schemas at the gateway rather than relying on the plugin for it.
ORM integrationsDrizzle has first-class DI helpers (registerDrizzle, @InjectDB). Prisma and TypeORM are not yet at the same level of built-in, documented integration—you can still use them directly inside your services. All three provide native count/updateMany/deleteMany; a custom repository must override them itself (the base implementations now throw NotImplementedError rather than silently scanning the whole table).
Test coverageLine coverage over src/ is ~57%, with the CI gate at 55% to prevent regressions. The thinnest remaining areas, measured: the cache and request-context middleware (~3%), the logging middleware and health plugin (~5%), the WebSocket plugin (~7%), and the OAuth and permission helpers with the RBAC decorators (~10–21%). Treat lightly covered paths with more caution than the core routing/validation surface.
  • ✅ GraphQL subscriptions execute — the largest remaining gap. They were generated into the SDL and nothing more; there was no execution transport, so subscribing did nothing. GraphQLPlugin now serves them over graphql-transport-ws (the subprotocol Apollo Client, urql, GraphiQL and the graphql-ws client speak) on Bun, Deno and Node, with a PubSub built on the event bus, per-connection auth through connectionParams, and bounded backpressure. The protocol is the framework’s own, so CI runs it against the official graphql-ws client rather than assuming interoperability. See GraphQL → Subscriptions.
  • ✅ app.listen(0) reports the port it got — it returned the requested port, so asking the OS for a free one left you with 0.
  • ✅ A WebSocketPlugin gateway and the subscription endpoint coexist on Node — each was creating its own @hono/node-ws instance, and every instance ends any upgrade it finds no waiter for, so the two destroyed each other’s connections.
  • ✅ The @On('event') decorator — declarative listeners, registered with app.include() and resolved through the DI container, so a listener injects the same services a controller can. Subscriptions attach at compile() and detach at shutdown(), which matters on the process-wide globalEvents: without it, every app built in a test suite answers the same event. new VeloceTS({ eventBus }) takes a bus of your own. See Event Bus.
  • ✅ WebSockets on Node.js — the framework’s longest-standing limitation. Node upgrades now go through the optional @hono/node-ws peer dependency; the decorators and WebSocketConnection API are identical on Bun, Deno and Node. The plugin attaches in onStart(), so the app must be served with app.listen(). All four CLI templates, websocket and fullstack included, now boot under Node in CI. See WebSockets.
  • ✅ The published package shrank 72% — 1.5 MB → 0.41 MB packed, by dropping JS sourcemaps and 351 KB of declaration maps that pointed at a src/ the package does not ship.
  • ✅ The declarations work under node16/nodenext — the published types resolved only under moduleResolution: bundler before.
  • ✅ The CLI works on Node — veloce dev and veloce build detect the runtime and accept --runtime auto|bun|node; the Node paths use tsx and tsc
  • ✅ Generated projects run on Node — they previously emitted a CORS config the framework rejects (so the app crashed on startup), extensionless imports Node’s ESM loader cannot resolve, and scripts that hardcoded bun
  • ✅ Template smoke test in CI — every template is scaffolded, installed, type-checked, built and booted under Node on each run

Still Bun/Deno only: the websocket and fullstack templates, because WebSocketPlugin has no Node upgrade path yet. veloce new warns about this at scaffold time.

  • ✅ Guard enforcement — the three affected plugins now inject their guard during install()
  • ✅ Signed session cookies — SessionConfig.secret is required and the cookie is HMAC-signed; it previously carried the raw session id
  • ✅ Atomic refresh-token rotation — two concurrent refreshes with the same token no longer both succeed
  • ✅ Request body limit — bodyLimit, 1 MiB by default
  • ✅ Per-caller cache keys — varyByHeaders / keyGenerator on @Cache(), plus a compile-time warning when a per-user route is cached under a shared key
  • ✅ Node listen() actually works — it used require() inside the ESM bundle, so it always threw
  • ✅ Exception filters see validation and DI errors; class-level @UseInterceptor runs; the DI container no longer reports false circular dependencies under concurrency
  • ✅ Distributed rate limiting — RedisRateLimitStore
  • ✅ Typed context variables — c.get('auth.user') is typed
  • ✅ Security — refresh tokens can no longer be replayed as access tokens; RS256/384/512 genuinely sign asymmetrically (they previously used the shared HMAC secret); rate limiting no longer trusts client-supplied X-Forwarded-For unless trustProxy is set
  • ✅ Node.js support actually works — the published 1.2.0 had four packaging bugs that broke it for Node consumers, including a bundled duplicate of Zod that silently broke instanceof checks against your own schemas
  • ✅ Performance — request dispatch substantially faster than v0.4.18; the package is 1.33 MB, down from 3.82 MB
  • ✅ Typed functional API — handler arguments infer from the declared Zod schema bag
  • ✅ Distributed token revocation — RedisTokenBlacklist propagates logout across replicas
  • ✅ Plugin lifecycle — onStart / onStop hooks
  • ✅ Cloudflare Workers — app.getFetchHandler() is the supported deploy path
  • ✅ WebSocket hardening (Bun/Deno) — heartbeat, idle timeout, max message size
  • ✅ CLI runs under plain Node — it previously required Bun

All P0 and P1 roadmap items are now live:

  • ✅ CLI scaffolding — veloce generate module/controller/service/resolver/dto/middleware/plugin
  • ✅ Graceful shutdown — app.onShutdown(), SIGTERM/SIGINT, configurable timeout
  • ✅ Exception filters — @Catch(), app.useFilter(), FilterManager
  • ✅ Interceptors — @UseInterceptor(), app.useInterceptor(), ExecutionContext
  • ✅ Streaming responses — @SSE(), @Stream(contentType) on AsyncGenerator handlers
  • ✅ Event bus — EventBus, globalEvents singleton
  • ✅ Extra decorators — @Throttle, @ApiVersion, @ResponseHeader, @Redirect, @Deprecated
  • ✅ Test isolation — isolate(), compileTestApp()
  • ✅ OpenAPI 3.1 — jsonSchemaDialect, nullable normalization

Priorities the maintainers care about; not a promise of order or date.

  1. ORM choice — Make Drizzle, Prisma, and TypeORM practical, documented options so teams can pick an ORM without fighting the framework.
  2. GraphQL schema stitching — Subscriptions execute as of v3.4.0; composing schemas across services is the part that is still not hardened.
  3. Raise the coverage floor — The CI gate sits at 55% to block regressions; the goal is to lift it as the thin areas above get tested.

Now published: a reproducible benchmark suite comparing Veloce-TS against Hono, Express and Fastify (BENCHMARKS.md, bun run benchmark). Honest summary: Veloce-TS costs 2–11% over raw Hono — the price of the decorator layer, widest on a trivial /hello route and narrowest on Zod validation, where Zod dominates both sides — and is roughly 1.8× faster than Express 4 and 1.3–1.4× faster than Fastify 5 under Bun. Elysia and NestJS are not included yet.

Earlier editions claimed Veloce-TS beat raw Hono on two scenarios. Those came from single-pass measurements that flipped between runs; the harness now repeats each measurement and reports a median with its spread.