Changelog
Changelog
Section titled “Changelog”All notable changes to Veloce-TS are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Section titled “[Unreleased]”[3.5.0] - 2026-09-07
Section titled “[3.5.0] - 2026-09-07”Fixed — mixing import specifiers silently broke decorators
Section titled “Fixed — mixing import specifiers silently broke decorators”veloce-ts and veloce-ts/plugins were separate bundles, so importing from both put two copies of the framework in one process. Metadata keys were plain Symbol(), which is unique per module instance — so a class decorated through one specifier was invisible to code loaded through the other, with no error anywhere.
What that looked like: the fullstack template imported GraphQLPlugin from veloce-ts/plugins while its resolver used decorators from veloce-ts. The plugin found no resolver metadata, generated an empty schema, and the first query died on buildSchema('') with “Syntax Error: Unexpected <EOF>” — an error pointing at the query, which was fine. Routes and guards registered across a specifier boundary would have failed the same way.
- The ESM build now uses code splitting, so every subpath shares one copy of the framework. Class identity works again too —
instanceofacross specifiers had the same problem. Deduplicating across the 15 entrypoints also took the package from 0.45 MB to 0.39 MB. - Every metadata key and DI token is
Symbol.for('veloce-ts:…'), resolved through the global registry. CommonJS cannot be split, so this is what fixes it forrequire()consumers.
An empty schema now explains itself instead of failing inside graphql-js, and names this as a likely cause.
Added — CLI
Section titled “Added — CLI”veloce new --installruns the package manager (Bun when available, npm otherwise);--gitinitialises a repository with the first commit, skipping it when the target is already inside one.veloce generate gateway <name>scaffolds a@WebSocketgateway andveloce generate listener <name>an@Onlistener class. Both surfaces existed with no scaffolding behind them.- Generated projects get a
.env.examplelisting the variables the code actually reads. The templates have configured CORS fromCORS_ORIGINSsince 3.1.0, documented nowhere the reader would look.
Changed — the templates teach the framework now
Section titled “Changed — the templates teach the framework now”- Dependencies are injected, not constructed. The example controller and resolver ask for a
UserServicewith@Inject(UserService); swapping the in-memory array for a database leaves every caller untouched. app.listen()is awaited. It has been async since the Node path landed, and without the await a failure to bind became an unhandled rejection instead of the error the template prints.- The GraphQL and fullstack templates enable subscriptions and demonstrate one end to end with
PubSub. - The GraphQL templates now depend on
graphql. It is an optional peer, so nothing executed without it — and its absence is not a type error, only a 501 at the first query. - The README matches what was written: per-template layout, real endpoints, the scripts table, and the fact that the project runs on Node as well as Bun. It previously showed a generic tree and pointed at
/docs.html, which is not a path that is served. veloce build --sourcemapdefaults to off.- A GraphQL project no longer ships an empty
src/controllers/directory.
Changed — the template smoke test was too weak to catch any of this
Section titled “Changed — the template smoke test was too weak to catch any of this”It asserted only that a route answered with a non-5xx status, and the GraphQL endpoint returns 200 with an errors payload when the graphql package is missing — so a template shipping without that dependency looked healthy. It now runs a real mutation and query, runs the probe exactly once (retrying it applied the mutation twice), and refuses to start when port 3000 is already held.
[3.4.0] - 2026-09-07
Section titled “[3.4.0] - 2026-09-07”Added — GraphQL subscriptions actually run
Section titled “Added — GraphQL subscriptions actually run”Subscriptions were generated into the SDL and nothing more: there was no execution transport, so subscribe did nothing and this documentation said so. It was the framework’s largest remaining gap.
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.
const pubsub = new PubSub();
@Resolver()class ChatResolver { @GQLSubscription() @Returns(MessageSchema, { name: 'ChatMessage' }) messageAdded() { return pubsub.subscribe('MESSAGE_ADDED'); }
@GQLMutation() async send(@Arg('input', SendMessage) input: SendMessage) { await pubsub.publish('MESSAGE_ADDED', input); return input; }}
app.usePlugin(new GraphQLPlugin({ resolvers: [ChatResolver], subscriptions: { // A browser WebSocket cannot send an Authorization header, so credentials // arrive in connectionParams instead. onConnect: ({ connectionParams }) => verify(connectionParams?.token), },}));- Off by default. Opening a WebSocket endpoint should be a deliberate choice;
subscriptions: truetakes the defaults. - Shares the GraphQL path, which is what clients assume — an upgrade and a POST can coexist there because they are different methods.
PubSubis the source:publish(topic, payload)/subscribe(topic), returning theAsyncIterableIteratorthatgraphql’ssubscribe()wants. It is built onEventBus, so passingglobalEventsmakes an@On('user.created')listener and a GraphQL subscription react to the same publish. A resolver can equally return a plain async generator.- Backpressure is bounded.
maxQueueSize(1000) withdrop-oldest,drop-newestorerrordecides what gives when a subscriber cannot keep up. - Nothing leaks. Ending an operation, closing the socket and
app.shutdown()all run the iterator’s teardown, which detaches its bus listener. - Requires the
graphqlpackage, now declared as an optional peer dependency. On Node it also needs@hono/node-ws, and the app must be served withapp.listen().
The protocol implementation is the framework’s own — no runtime dependency on graphql-ws — so the interoperability claim is verified rather than assumed: CI runs the server against the official graphql-ws client under Node. Close codes follow the spec: 4400 bad request, 4401 unauthorized, 4403 forbidden, 4408 init timeout, 4409 duplicate subscriber, 4429 too many init requests.
app.listen(0)reported port 0. The returned server instance carried the requested port, so a caller asking the OS for a free one had no way to learn which it got — the spread meant to pick it up copies own enumerable properties, andportlives on the server’s prototype under both Bun and Node. Fixed on all three runtimes.- Two WebSocket plugins on one Node app destroyed each other’s connections.
WebSocketPluginand the subscription endpoint each created their own@hono/node-wsinstance, and every instance attaches an'upgrade'listener that ends any socket it finds no waiter for. They now share one adapter per application. - A resolver set with no queries produced an unusable schema. GraphQL requires a query root on every schema, so subscriptions or mutations alone failed on the first operation with
Query root type must be provided. A placeholderQueryis emitted when nothing else declares one. graphql’svalidate()throws rather than returning errors when the schema itself is invalid. Inside a socket’s message handler that took the process down; it now reaches the client as anerrormessage.- GraphQL resolvers could not be resolved over a WebSocket. They were always resolved in
requestscope against acontext.requestthat does not exist on a connection outliving any single request. HTTP keeps request scope; a socket resolves as a singleton.
Changed
Section titled “Changed”- Subscription resolvers are attached to the executable schema, so introspection now describes a Subscription type whose fields can actually run. They were deliberately left off before.
- A subscription resolver returning something that is not an async iterable fails with a message naming the class and method, instead of graphql-js’s generic “Subscription field must return Async Iterable”.
[3.3.0] - 2026-09-07
Section titled “[3.3.0] - 2026-09-07”Added — the @On('event') decorator
Section titled “Added — the @On('event') decorator”Declarative event listeners, the last item this documentation listed as missing from the event bus. A class with @On methods is registered with app.include() like a controller, and its instance comes from the DI container — so a listener injects the same services a controller can.
class NotificationListeners { constructor(@Inject(Mailer) private mailer: Mailer) {}
@On('user.created') async welcome(payload: { email: string }) { await this.mailer.send(payload.email, 'Welcome!'); }
@On('app.ready', { once: true }) warmCaches() { /* … */ }}
app.include(NotificationListeners);- Subscriptions attach at
compile()and are removed atshutdown(). Without that, listeners bound to the process-wideglobalEventsoutlive the app that created them, and every app built in a test suite answers the same event. - A class can be both a controller and a listener —
include()no longer stops at the first match. new VeloceTS({ eventBus })takes a bus of your own; it defaults toglobalEvents.app.getEventBus()returns the one in use.@Onnaming something that is not a method fails atcompile(), with the class and property in the message, rather than silently at emit time.
See Event Bus.
Changed — benchmark methodology, and a correction
Section titled “Changed — benchmark methodology, and a correction”The published claim that Veloce-TS beat raw Hono was wrong. The benchmarks page and the docs stated Veloce-TS was faster than raw Hono on JSON body parsing (+16.7%) and Zod validation (+43.8%). Those came from a single timed pass per framework, and a single pass is not a measurement: three consecutive runs of the identical scenario put Hono ahead by 2%, then 17%, then level — and a fourth put Veloce ahead by 14%.
With repeat rounds and a median, Hono leads in every scenario, which is the only sensible answer for a layer built on top of Hono. Measured cost of that layer: 2–11%. Against Express 4 it is ~1.8× faster (the README claimed 10×) and ~1.3–1.4× faster than Fastify 5.
The harness now runs 7 rounds, reports a median with its interquartile spread, calibrates iteration counts against wall time, and flags results too noisy to compare. The HTTP suite rotates server order between rounds so warm-up does not favour whichever framework ran first.
Changed — CLI housekeeping
Section titled “Changed — CLI housekeeping”- The version fallbacks said
0.3.0.veloce --versionand, worse, the dependency rangeveloce newwrites into a scaffoldedpackage.jsonboth fell back to a hardcoded version that had not been current for a long time.veloce newalso consulted the current directory’spackage.jsonfirst — whatever project you happened to be standing in, which is not veloce-ts. veloce newcould hang on the npm lookup. The registry request had no timeout; it now gives up after 5 seconds and scaffolds with the installed version, saying so.
- A missing file was served as 200 with an empty body under Bun.
Bun.file()is lazy and never throws for a nonexistent path, soResponse.file()handed it straight to the response. The Node branch, which stats the file first, correctly returned 404 — the same call answered differently depending on the runtime.
Added — tests
Section titled “Added — tests”Coverage over src/ went from 50.17% to ~57%, concentrated on code that had almost none and carries real risk: the Redis-backed stores (cache, session, token blacklist, rate limit, previously ~4%), WebSocketConnection, Response.file() root containment, and request-context. The CI coverage gate moves from 50% to 55% — it had been set at exactly the measured value, which made it a tripwire rather than a ratchet.
[3.2.0] - 2026-09-07
Section titled “[3.2.0] - 2026-09-07”Added — WebSockets work on Node
Section titled “Added — WebSockets work on Node”WebSocketPlugin threw at startup on Node (“requires Bun or Deno runtime”), which made the @WebSocket / @OnConnect / @OnMessage decorators — and the websocket and fullstack templates — unusable there. It was the framework’s number-one documented limitation.
Node now upgrades through @hono/node-ws, an optional peer dependency. Bun and Deno are untouched: they still upgrade natively and need no extra package.
npm install @hono/node-ws # only needed on NodeThe whole gateway surface is verified end-to-end on Node in CI: upgrade handshake, @OnConnect, @OnMessage, rooms, broadcast() and @OnDisconnect. The template smoke test now boots all four templates instead of skipping the two that could not run.
authorizeUpgrade() still runs, with one difference worth knowing: on Bun and Deno an unauthorized client is refused during the handshake with 401, while @hono/node-ws owns the route and hands the socket over already open — so on Node the connection is accepted and then closed with 1008 Policy Violation.
The plugin attaches the WebSocket handler in onStart(), because it needs the real http.Server, which only exists once app.listen() has run. Serve with app.listen(); handing app.fetch to a server you created yourself leaves the plugin with nothing to attach to.
WebSocketConnectionreadWebSocket.OPENoff the global, which only became stable in Node 22 — on Node 20, whichenginesstill supports, touching it threw aReferenceError. It now uses the numeric constant fixed by the standard.- Socket parameters are typed structurally (
WebSocketLike) instead of against the DOMWebSocket, so a Bun socket, a Deno one and node-ws’sWSContextall fit.
VeloceTS.getServer()exposes the running server, andServerInstance.rawcarries the runtime’s own object.
[3.1.2] - 2026-09-07
Section titled “[3.1.2] - 2026-09-07”Changed — the published package shrank by 72%
Section titled “Changed — the published package shrank by 72%”Production builds no longer emit sourcemaps. Bun embeds each source file’s full text in the map through sourcesContent, and with both an ESM and a CJS build that meant shipping the entire TypeScript source twice: sourcemaps were 73% of the tarball. Every install paid for it to serve the rare case of stepping into framework internals with a debugger — clone the repo for that instead.
| Packed | Unpacked | |
|---|---|---|
| 3.1.1 | 1.47 MB | 6.9 MB |
| 3.1.2 | 0.41 MB | 1.73 MB |
Development builds keep inline sourcemaps, which is where they are actually used.
Declaration maps are no longer emitted either. The 256 .d.ts.map files resolved to ../../src/*.ts, and files does not publish src/ — so they were 351 KB of maps pointing at nothing in an installed package.
[3.1.1] - 2026-09-07
Section titled “[3.1.1] - 2026-09-07”Fixed — the published types were unusable on node16/nodenext
Section titled “Fixed — the published types were unusable on node16/nodenext”- Every relative specifier in the declarations lacked a file extension, so any consumer on
moduleResolution: node16ornodenextgot nothing butTS2834(“Relative import paths need explicit file extensions”) fromnode_modules/veloce-ts, and lost all types. The 332 relative specifiers acrosssrc/now carry an explicit.js. require('veloce-ts')was rejected by TypeScript withTS1479even though it works at runtime. One declaration tree was shared by both conditions, and TypeScript infers a.d.tsfile’s module kind from the nearestpackage.json— the root says"type": "module", so the types claimed ESM whilerequireresolved todist/cjs. The build now emits a separate CommonJS declaration tree and eachexportscondition carries its owntypes.WebSocketManager.handleMessageBuntyped a parameter asBuffer, which forced consumers to install@types/nodejust to type-check an import of the package. It isUint8Arraynow.
[3.1.0] - 2026-09-07
Section titled “[3.1.0] - 2026-09-07”Node parity for the CLI. Everything veloce new produces now scaffolds, type-checks, builds and runs under plain Node, not only Bun.
Fixed — the CLI produced projects that could not run
Section titled “Fixed — the CLI produced projects that could not run”veloce newscaffolded an app that crashed on startup. Therestandfullstacktemplates emittedcors: { origin: '*', credentials: true }— the combination 3.0.0 began rejecting at construction time — so a freshly created project died before serving a single request.veloce buildfailed under Node withReferenceError: Bun is not defined; it calledBun.buildunconditionally. It now compiles with the project’s owntscwhen not running under Bun.veloce devonly ever spawnedbun. It now detects the runtime and falls back totsx, which transpiles the Stage-2 decorators Node’s own type stripping cannot handle.- Compiled output was unloadable by Node. Templates imported relative modules without a file extension, which
tscemits verbatim and Node’s ESM loader rejects —node dist/index.jsdied withERR_MODULE_NOT_FOUND. Templates andveloce generateoutput now write the.jsextension, which Bun resolves back to the.tssource. - The
websocketandfullstacktemplates passed ahandlersoption that does not exist onWebSocketPluginConfig, so those projects failed to type-check. Gateways are registered withapp.include(), like controllers. - Generated projects lacked
@hono/node-server, soapp.listen()threw under Node.
--runtime <auto|bun|node>onveloce devandveloce build.autoprefers Bun when it is usable and otherwise takes the Node path.- Generated projects are runtime-agnostic: scripts route through the
velocebinary instead of hardcodingbun,engines.nodeis>=20,tsxis a devDependency for the Node dev server, and atypecheckscript is included. veloce newwarns when scaffolding a WebSocket template without Bun installed, instead of leaving you with an app that throws on first start.- Template smoke test in CI (
npm run test:templates): packs the working tree, scaffolds all four templates against that tarball, then installs, type-checks, builds and boots each one under Node. This is the check that was missing when the broken templates shipped.
Bun.build is an in-process API, so the Bun build path requires the CLI itself to be running under Bun. npm run build on a machine with Bun installed still takes the Node path, because npm starts the CLI with Node.
[3.0.1] - 2026-09-05
Section titled “[3.0.1] - 2026-09-05”MemoryRateLimitStoreandRedisRateLimitStorewere not exported. 3.0.0 introduced the pluggable rate-limit store and documented it, but neither class was re-exported fromveloce-tsorveloce-ts/middleware, soRateLimitOptions.storecould not actually be supplied by a consumer. Both are now exported from both entry points; theRateLimitStoreandRateLimitHittypes were already reachable viaveloce-ts/types.
[3.0.0] - 2026-09-05
Section titled “[3.0.0] - 2026-09-05”Full-codebase review. Upgrade if you use @Auth(), @Session(), @RequireCSRF() or the @CanAccess family — those guards were never enforced in 2.x, so the routes they were meant to protect answered anonymous requests. See MIGRATION.md.
Security
Section titled “Security”- Route guards were never enforced.
AuthPlugin,SessionPluginandPermissionPlugineach replacedapp.compilefrom inside their owninstall()— butinstall()runs from within the already-executingcompile(), so the replacement never ran. In practice an@Auth()route without a@CurrentUser()parameter answered 200 to anonymous requests,@Auth({ roles: ['admin'] })admitted any authenticated user, and@Session({ required: true })/@RequireCSRF()did nothing at all. All three plugins now inject their guard into the route’s middleware duringinstall(), the same wayRBACPluginalready did.@Roles()was unaffected — RBAC was the one plugin wired correctly. - Session cookies are now signed with
SessionConfig.secret(HMAC-SHA256, verified in constant time). The cookie previously carried the raw session id, so anyone could swap in another id and be handed that session.secretis now required, and cookies default tosecure: trueunderNODE_ENV=production. - Refresh-token rotation is atomic.
refreshAccessToken()verified the token and only then blacklisted it, so two concurrent requests carrying the same refresh token both succeeded and each received a valid new pair. The old token is now claimed before a new pair is minted; the loser fails. - Request bodies are size-limited. New
VeloceTSConfig.bodyLimit(default 1 MiB,0disables) rejects oversized bodies with 413 before any handler reads them. - Cached responses can vary by caller.
@Cache()now honoursvaryByHeadersand a newkeyGenerator(c); previously the key was method + path + params only, so a cached per-user route could serve one user’s body to another. Caching a route that reads@CurrentUser()under a caller-independent key now logs a warning at compile time. - Auth flows no longer report internal failures as 401. A database outage inside
login/register/refreshwas wrapped intoAuthenticationException(error.message), reporting infrastructure faults as bad credentials and leaking the internal message. - WebSocket handler errors are no longer echoed to the client. Only parse/schema problems are returned; everything else is logged server-side.
- JWT revocation entries are keyed by
jtiinstead of the full token, so the blacklist no longer stores replayable credentials. - CORS:
Vary: Originis emitted whenever the allowed origin depends on the request, andorigin: '*'combined withcredentials: truenow throws at configuration time instead of producing responses browsers silently reject. Response.file()gained arootoption (plusResponse.fileFrom(root, path)) that confines the lookup to a directory; paths escaping it get 403.
Changed — breaking
Section titled “Changed — breaking”SessionConfig.secretis required. Session cookies change format, so sessions issued by 2.x are rejected and those users log in again.bodyLimitdefaults to 1 MiB. Raise it if you accept uploads:new Veloce({ bodyLimit: 25 * 1024 * 1024 }).docsdefaults totrueand now actually mounts OpenAPI at/openapi.jsonand/docs. RegisteringOpenAPIPluginyourself still wins; passdocs: falseto disable.engines.noderaised to>= 20(Node 18 is end-of-life).Adapter.listen()may return aPromise<ServerInstance>— relevant only for custom adapters.TokenBlacklistreceives token ids (jti) instead of raw tokens; custom stores may implement the optionalclaim()for atomic rotation.- The response builder is exported as
VeloceResponse(andRes). The oldResponseexport shadowed the global Web APIResponsein any file that imported it; it remains as a deprecated alias.
- Class-level
@UseInterceptornever ran — the lookup readtarget.constructoron a value that was already the class. - Exception filters never saw validation or dependency-injection errors. Errors thrown by route middleware and guards now reach filters too.
DIContainerreported false circular dependencies under concurrency. Resolution paths are now per async context; concurrent first-time singleton resolves share one in-flight promise, so exactly one instance is created.- Two controllers with the same class name silently overwrote each other’s routes. The registry keyed on
target.name; it now keys on class identity. docsandpluginsconfig options were accepted and ignored.- A malformed JSON body returned 422 “Expected object, received null” — it is now a 400 naming the JSON problem.
AuthService.refresh()was missing anawait, so itscatchnever ran and callers got a bareErrorinstead ofInvalidTokenException.- Node’s
listen()could never work. It usedrequire('@hono/node-server'), and the published ESM bundle has norequirein scope, so it always threw and reported the package as missing. It now uses a dynamic import. - SSE/streaming generators that throw now error the stream and log, instead of producing an unhandled rejection.
process.on('SIGTERM'/'SIGINT')listeners registered by the rate limiter andMemorySessionStoresuppressed Node’s default signal handling for the whole application.
RateLimitStorewithMemoryRateLimitStore(default) andRedisRateLimitStore, so a rate limit can be enforced across instances rather than per process.- Typed context variables —
c.get('auth.user')is typed and a mistyped key is a compile error. VeloceTSConfig.bodyLimitand@Cache({ keyGenerator }).
[2.0.0] - 2026-07-26
Section titled “[2.0.0] - 2026-07-26”Security, packaging and performance overhaul. If you deploy on Node.js, upgrade — the published 1.2.0 was broken there. See MIGRATION.md for the two breaking changes.
Security
Section titled “Security”- Refresh tokens could be replayed as access tokens.
JWTProvider.verifyAccessToken()ignored the payload’stypeclaim, and sincerefreshSecretfalls back tosecretwhen unset, a refresh token — default lifetime 7 days — verified successfully as an access token. It now rejects any payload withtype === 'refresh'. RS256/RS384/RS512never actually worked. They were accepted asJWTConfig.algorithm, but signing always used the sharedsecret, so tokens were HMAC-signed and the asymmetric guarantee did not exist. AddedprivateKey/publicKey; the constructor now throws if an RS* algorithm is set without both.- Rate limiting trusted spoofable headers. The default
keyGeneratorreadX-Forwarded-For/X-Real-IPunconditionally, so any client could forge its rate-limit identity. These are now ignored unless the newtrustProxy: trueoption is set; the untrusted default uses the real peer IP. @ResponseSchemafailures were swallowed, letting malformed handler output reach the client. They now log and return 500.
Changed — breaking
Section titled “Changed — breaking”JWTProviderverification and revocation methods are nowasync:verifyAccessToken,verifyRefreshToken,refreshAccessToken,blacklistToken,isBlacklisted,cleanupBlacklist. This enables pluggable revocation stores. A missingawaitis dangerous, not merely wrong — the returned Promise is always truthy.- Decorator controllers now resolve as
singleton, not a new instance per request. Per-request state in instance fields is now shared. Opt out with@Controller(prefix, { scope: 'transient' }).
Type safety
Section titled “Type safety”- Typed functional API —
app.get/post/put/delete/patchandapp.route()are generic over the declared Zod schema bag, so handler arguments infer end-to-end with no casts. Injection order is body, query, params, headers; only declared schemas contribute an argument. - Purged
anyfrom the public surface: plugin config, cache store config, parameter metadata, the auth/RBAC/session metadata registry, and the Express adapter’s return types.
New APIs
Section titled “New APIs”@Controller(prefix, { scope })— explicit instantiation scope (singleton|request|transient).RedisTokenBlacklist/MemoryTokenBlacklistand theTokenBlacklistinterface — token revocation that propagates across replicas. The in-memory blacklist never did, so logout silently failed to revoke on other instances.Plugin.onStart(app)/Plugin.onStop(app)— lifecycle hooks. The contract was install-only, so a plugin holding resources had no teardown path.onStopruns in reverse install order with errors collected.app.getFetchHandler()— thefetch(request, env, ctx)export shape serverless/edge runtimes expect. This is the supported Cloudflare Workers deploy path, sincelisten()cannot work there.CacheManager.destroy()— releases every store’s timers and connections.- WebSocket hardening —
heartbeatIntervalMs,heartbeatTimeoutMs,idleTimeoutMs,maxMessageSizeBytes. Oversized messages get an error frame and close code 1009. @Returns()for GraphQL return types, andcreateTimeoutMiddleware()exported fromveloce-ts/middleware.LoggerConfig.prettyis now honored via apino-prettytransport — it was previously accepted and ignored.
Tooling & CI
Section titled “Tooling & CI”- CI now runs a real multi-runtime matrix: pinned + latest Bun across ubuntu/windows/macos, a Node 20/22 smoke job that imports the built ESM and CJS artifacts, an allow-failure Deno leg, isolated package validation, and a coverage gate that uploads its report.
- The CLI runs under plain Node. The
binentry pointed at a#!/usr/bin/env bunTypeScript file, so it could not run without Bun despite the package advertising Node >= 18. .d.tsgeneration failure is now fatal in production builds; it previously warned and continued, so a release could ship broken types with a green pipeline.- Package validation verifies every
exportscondition resolves to a real file and type-checks a generated consumer againstdist/types. This is what caught the brokenadapters/*paths. veloce generate openapinow uses the realOpenAPIGeneratorinstead of a hand-rolled builder that ignored Zod, and the generated client emits real interfaces derived from the schemas instead ofPromise<any>.
Fixed — packaging
Section titled “Fixed — packaging”The published 1.2.0 did not work for Node consumers:
veloce-ts/adapters/*did not resolve — theexportsmap pointed atdist/{esm,cjs}/adapters/*.jsbut the build emitsdist/{esm,cjs}/src/adapters/*.js.- Runtime dependencies are no longer bundled into
dist. Bundling shipped a second copy of each, which broke Zod identity (the framework checks schemas built with your zod; a bundled copy is a different class) and re-transpiled third-party code, manglingsemverinto an invalid RegExp that threw on require. The package is now 1.33 MB, down from 3.82 MB. - The ESM bundle emitted
import.meta.require, a Bun-only global; switched to thenodetarget. import { sign } from 'jsonwebtoken'broke Node’s ESM loader — it is a CJS-only package. Now imported via its default export.dist/cjs/*.jswas parsed as ESM because the rootpackage.jsondeclares"type": "module". The build now emitsdist/cjs/package.jsonwith{"type":"commonjs"}.
- GraphQL never executed. Resolvers were passed as a nested
rootValueshapegraphql-jsdoes not read, so every query resolved tonull, and every field’s return type was hardcoded toString(object types were never emitted). Resolvers now attach directly to schema fields, and object/input types derive from Zod. Subscriptions still have no execution transport. - Request dispatch is 1.9–2.6× faster. The “compiled metadata” layer precomputed a path regex, index orderings and type flags the handler never read; controllers were re-instantiated per request; interceptor lookup did two reflect reads per request; and cache/context/auth modules were
await import()-ed inside the hot path. All resolved once atcompile()time now. - Double rollback in
BaseTransactionManager— a handler that succeeded but was markedrollbackOnlythrew, the samecatchrolled back again, and the resulting “transaction not found” masked the real error. app.shutdown()never ranonShutdown()handlers — only the SIGTERM/SIGINT path did.CacheManager.reset()leaked the previous store’s cleanup interval, accumulating timers and keeping processes alive.- In-memory cache “LRU” was actually FIFO — it evicted by insertion time, not last access.
get()now updates recency. - Redis cache and session stores used the blocking
KEYScommand forclear()/deletePattern()/length()/all(), which stalls the Redis event loop on large keyspaces. Now cursor-basedSCANwith batchedDEL. EventBus: one throwing listener aborted the rest (Promise.all). Every listener now runs; failures are collected and rethrown together as anAggregateError.Response.file()threw on non-Bun runtimes — now streams vianode:fs.- The Express adapter buffered whole response bodies, breaking
@SSE()and@Stream()(clients got nothing until the generator finished). Now streams chunk-by-chunk with backpressure handling. BaseRepository.count()/updateMany()/deleteMany()silently did full table scans (count()loaded every matching row into memory; the bulk writes did fetch-then-loop). They now throwNotImplementedError; all three adapters ship native overrides.- Two competing SIGTERM/SIGINT handlers with separate shutdown timeouts were consolidated into one.
- Query parameters were validated twice per request with inconsistent error shapes.
Benchmarks
Section titled “Benchmarks”Re-measured and re-published; the previous numbers were labelled v0.4.3 and had gone stale across several releases. Request dispatch improved 87–158% over v0.4.18. Against other frameworks Veloce-TS beats raw Hono on JSON body parsing (+16.7%) and Zod validation (+43.8%), and is ~33% slower on a trivial /hello route. Tests grew from 509 to 781.
[1.2.0] - 2026-06-27
Section titled “[1.2.0] - 2026-06-27”CLI Scaffolding
Section titled “CLI Scaffolding”veloce generate controller <name>— generates@Controllerclass with CRUD route stubsveloce generate service <name>— generates a plain service classveloce generate module <name>— generates controller + service + DTO + barrel insrc/modules/<name>/veloce generate resolver <name>— generates@Resolverclass with@GQLQueryand@GQLMutationstubsveloce generate dto <name>— generates Zod schema + inferred TypeScript typeveloce generate middleware <name>— generates Hono middleware function skeletonveloce generate plugin <name>— generatesVelocePluginclass scaffold
Graceful Shutdown
Section titled “Graceful Shutdown”app.onShutdown(handler)— registers an async shutdown handler; handlers run in reverse-registration order on SIGTERM/SIGINTapp.setShutdownTimeout(ms)— configures max wait beforeprocess.exit(1)(default: 30 000 ms)app.registerShutdownSignals()— called automatically byapp.compile(); attachesprocess.on('SIGTERM')andprocess.on('SIGINT')
Exception Filters
Section titled “Exception Filters”ExceptionFilter<T>interface — implementcatch(error: T, c: Context): Responseto produce a typed error response@Catch(...errorClasses)— binds a filter class to one or more error typesapp.useFilter(filter)— registers a global filter; checked in registration order, first match winsFilterManager— internal manager; exported for custom integrations
Interceptors
Section titled “Interceptors”Interceptorinterface — implementintercept(ctx: ExecutionContext, next: () => Promise<Response>): Promise<Response>ExecutionContext— typed context withrequest,controller,handlerfields@UseInterceptor(...interceptors)— applies interceptors on a method or class; layers around the handler like middlewareapp.useInterceptor(interceptor)— registers a global interceptor; runs before class/method interceptors
Streaming Responses
Section titled “Streaming Responses”@SSE()— marks anAsyncGeneratorhandler as a Server-Sent Events stream (text/event-stream)@Stream(contentType)— marks anAsyncGeneratorhandler as a raw stream with a custom content type (e.g.'text/csv')
Event Bus
Section titled “Event Bus”EventBus— in-process pub/sub withon,once,off,emit(async, parallel),emitSync,removeAllListeners,listenerCountglobalEvents— shared singletonEventBusfor cross-module communication without dependency injection
Extra Decorators
Section titled “Extra Decorators”@Throttle(limit, windowMs)— per-endpoint rate limit that overrides the global setting@ApiVersion(v)— prefixes the route path with/v<v>for versioned APIs@ResponseHeader(name, value)— sets a fixed response header for the route@Redirect(url, status?)— returns a redirect response (default status: 302)@Deprecated(message?)— marks the route as deprecated in OpenAPI and logs a startup warning
Test Isolation
Section titled “Test Isolation”isolate()— resets all singleton state (CacheManager, plugin registries) between parallel test runscompileTestApp(app)— compiles the app and returns the Hono instance; convenience wrapper for unit tests
OpenAPI 3.1
Section titled “OpenAPI 3.1”- Spec version bumped from
3.0.xto3.1.0 - Added
jsonSchemaDialect: 'https://spec.openapis.org/oas/3.1/dialect/base' nullable: truenormalized to{ type: ['x', 'null'] }or{ oneOf: [T, { type: 'null' }] }vianormalizeFor31()
Breaking Changes
Section titled “Breaking Changes”None — all changes are additive and backward compatible with v1.0.1.
[1.0.1] - 2026-06-26
Section titled “[1.0.1] - 2026-06-26”- GraphQL decorator pipeline:
@Resolver/@GQLQuery/@GQLMutationdecorators now work end-to-end.GraphQLSchemaBuilderwas reading from MetadataRegistry instance Maps that were never populated (decorator writes used a separate local-symbol namespace). It now reads directly from reflect-metadata viagetResolverMetadata/getFieldsMetadata. PermissionManager.revokePermission(): Filter logicresourceId ? p.resourceId !== resourceId : truewas alwaystruewhen noresourceIdwas given, so nothing was ever removed. Fixed.
GraphQLPluginresolversoption: Pass resolver classes directly —new GraphQLPlugin({ resolvers: [UserResolver] })— as an alternative toapp.include(). Both paths are merged and deduplicated.app.include()resolver recognition:include()now detects@Resolver-decorated classes and registers their metadata, soapp.include(UserResolver)feeds the GraphQL schema builder automatically.
- 89 new tests (509 total): PermissionPlugin (16), HealthCheckPlugin (25), Logger (20), OAuthPlugin (28), GraphQL decorators/pipeline (8 new, 17 total in suite).
[0.4.5] - 2026-03-29
Section titled “[0.4.5] - 2026-03-29”- CORS on error responses: error handler responses (401, 422, 500,
application/problem+json, etc.) now includeAccess-Control-Allow-Originand the rest of the configured CORS headers, consistent with successful responses (mergeVeloceCorsHeaders,VELOCE_CORS_HEADERS_KEY,VeloceCorsHeadersSnapshot).
- Same CORS fix as attempted in 0.4.4; npm does not allow republishing an unpublished version, so 0.4.5 is the installable release.
[0.4.4] - 2026-03-28
Section titled “[0.4.4] - 2026-03-28”Do not use: the npm tarball was broken (404). After unpublish, the registry blocks reusing this version number. Use 0.4.5 for the CORS fix.
[0.4.3] - 2026-03-27
Section titled “[0.4.3] - 2026-03-27”- RFC 9457 (Problem Details): error responses default to
Content-Type: application/problem+jsonwithtype,title,status,detail,instance, and extensions (violations/detailsfor validation;debugon 500 in development only). VeloceTSConfig.errorResponseFormat:'rfc9457'(default) or'legacy'for the previous{ error, statusCode, details? }JSON shape.- Exported helpers:
problemTypeUri,resolveProblemType,resolveProblemTitle,buildProblemInstance,toLegacyErrorBody,sendErrorResponse,PROBLEM_JSON_MEDIA_TYPE,DEFAULT_PROBLEM_TYPE_BASE. HTTPExceptionOptions: optionalproblemTypeandtitleonHTTPException.
Changed
Section titled “Changed”- Auth exceptions (
AuthenticationException,AuthorizationException) use the unified error format withauthentication-error/authorization-errorproblem types.
Documentation
Section titled “Documentation”@moduleheaders across core, errors, validation, DI, responses, logging, middleware, adapters, testing, and barrels.- Benchmarks refreshed for v0.4.3.
- Error JSON still includes
errorandstatusCodealongside RFC 9457 fields. Clients that requireapplication/jsonon errors can seterrorResponseFormat: 'legacy'.
[0.4.2] - 2026-03-27
Section titled “[0.4.2] - 2026-03-27”- HealthCheckPlugin: checker names are set in a Bun-safe way (
Object.defineProperty+ fallback) so assigning.nameon async checkers no longer throws. @Reqexport: the parameter decorator is now exported from the main package entry (import { Req } from 'veloce-ts').
Documentation
Section titled “Documentation”- Guides and API reference updated for
@Ctx(),@Req(), and the real HealthCheckPlugin API (checksarray,HealthCheckers.database/memory/disk).
[0.4.1] - 2026-03-27
Section titled “[0.4.1] - 2026-03-27”include()no longer drops decorator-set route fields such asstatusCode(@HttpCode) andresponseSchema(@ResponseSchema) when registering controller routes.
- Comprehensive test suite (53 tests) covering functional API routing, decorator routing, body/query validation, HTTP exceptions, and DI container.
- Console-based fallback logger: if
pinois not installed the framework falls back silently toconsoleinstead of crashing.
Changed
Section titled “Changed”pino,pino-pretty, andioredismoved tooptionalDependencies— no longer installed automatically (~7 MB lighter install by default). Install explicitly withbun add pino pino-prettyorbun add ioredisif needed.winstonremoved from dependencies (it was listed but never used).- Core bundle reduced from 444 KB → 408 KB (minified ESM).
Deprecated
Section titled “Deprecated”FastAPITSnamed export: useVeloceTSor the shorterVelocealias.FastAPITSwill be removed in v1.0.0.
[0.4.0] - 2026-03-27
Section titled “[0.4.0] - 2026-03-27”This release is the largest update since the initial launch — over 25 new features spread across three waves of work (high, medium, and low priority), covering the full chain from route generation to OpenAPI documentation, testing, ORM, and the CLI.
🚀 New Decorators
Section titled “🚀 New Decorators”OpenAPI Shorthand Decorators
Section titled “OpenAPI Shorthand Decorators”Five one-liner decorators as a concise alternative to @ApiDoc({...}):
@Summary(text)— short description visible in the Swagger UI route list@Description(text)— long description shown in the operation detail panel@Tag(name)— assigns a single tag; stackable with multiple@Tag@Tags(...names)— assigns several tags in a single decorator@Deprecated()— marks the route as deprecated (shown with strikethrough in Swagger UI)
@Get('/products')@Summary('List all products')@Description('Returns a paginated list of all available products')@Tags('Products', 'Catalog')async listProducts() { return products;}
@Delete('/products/:id')@Deprecated()@Summary('Delete a product (deprecated)')async deleteProduct(@Param('id') id: string) { return { success: true };}Response Control Decorators
Section titled “Response Control Decorators”@HttpCode(statusCode)— overrides the HTTP response code for the handler (e.g.201for creation). Also used by the OpenAPI generator as the documented success code@ResponseSchema(schema, statusCode?)— validates and sanitizes the handler response with a Zod schema; informs the response model in the OpenAPI spec
@Post('/products')@HttpCode(201)@ResponseSchema(ProductSchema)async createProduct(@Body(CreateProductSchema) data: any) { return await db.create(data);}Declarative Per-Route Middleware
Section titled “Declarative Per-Route Middleware”@Timeout(ms, message?)— aborts the request with 408 Request Timeout if the handler exceeds the limit. Automatically injects the middleware at the start of the pipeline and emits theX-Timeout-Msheader@RateLimit(options)— applies rate-limiting at the individual route level using the same configuration ascreateRateLimitMiddleware(). StandardX-RateLimit-*headers are sent automatically
@Get('/slow-operation')@Timeout(5000, 'Operation timed out')async slowOperation() { await heavyProcessing(); return result;}
@Post('/submit')@RateLimit({ windowMs: 60_000, max: 5 })async submitForm(@Body(FormSchema) data: any) { return process(data);}🛠️ Core Framework Improvements
Section titled “🛠️ Core Framework Improvements”OpenAPI Generator
Section titled “OpenAPI Generator”- Auto-tagging: automatically derives tags from the first path segment (
/products/:id→ tag"Products") without manual annotation - Bearer security scheme: adds
components.securitySchemes.bearerAuthto the spec and appliessecurity: [{ bearerAuth: [] }]to protected routes automatically - Automatic 401: protected routes receive a documented
401 Unauthorizedresponse without additional configuration @HttpCodesupport: uses the decorator’sstatusCodeas the key for the success block inresponses@ResponseSchemain spec: when present, the Zod schema is converted to JSON Schema format for the response content block
HTTP Exception System
Section titled “HTTP Exception System”Six new exception classes for common error cases:
ConflictException(409)GoneException(410)PayloadTooLargeException(413)UnprocessableEntityException(422)TooManyRequestsException(429)ServiceUnavailableException(503)
Structured Logger in ErrorHandler
Section titled “Structured Logger in ErrorHandler”- 5xx errors are logged with
getLogger().errorincluding path, method, status, and stack - 4xx errors are logged as
warnin development environment - Generic uncaught errors also pass through Pino
Decorator Order Fixes
Section titled “Decorator Order Fixes”@UseMiddleware: now always callsMetadataRegistry.defineRouteso middleware is not lost regardless of decorator execution order@Cache/@CacheInvalidate: same pattern — metadata is correctly merged regardless of stacking order
MetadataCompiler — Lazy Cache with Snapshots
Section titled “MetadataCompiler — Lazy Cache with Snapshots”- Lazy compilation: a route is only recompiled if its metadata changed (JSON snapshot comparison)
- Unique IDs for functional handlers via
WeakMap<Function, number>— prevents cache collisions when different app instances register the same path with different handlers (critical bug in parallel tests) clearCache()method exposed to clean state between tests
🧪 TestClient — Fluent API and Auth Helpers
Section titled “🧪 TestClient — Fluent API and Auth Helpers”Complete rewrite of TestClient:
TestResponse— new response class withstatus,headers,body,text,okproperties and chainable assertion methods:expectStatus(code),expectOk(),expectCreated(),expectNotFound(), etc.expectJson(partialObject)— partial body checkexpectField(field, value?)— verify a specific fieldexpectHeader(name, value?)— verify a response headerexpectArrayLength(n)— verify array length in response
withToken(token)— creates an immutable client instance with theAuthorization: Bearerheader already setwithHeaders(headers)— creates an immutable instance with additional headersloginAs(credentials, endpoint?)— logs in, extracts the JWT, and injects it into the current client for subsequent requestsregisterAndLogin(user, endpoints?)— registers and logs in with a single callclearAuth()— clears the stored token
const client = new TestClient(app);
// Fluent assertionsconst response = await client.get('/users');await response.expectOk().expectArrayLength(2);
// Auth helpersconst authClient = await client.loginAs({ email: 'user@example.com', password: 'pass' });await authClient.get('/profile').then(r => r.expectOk());
// Chain assertionsconst res = await client.post('/products', productData);await res.expectCreated().expectField('id').expectJson({ name: 'Product A' });🔌 Plugins and Middleware
Section titled “🔌 Plugins and Middleware”HealthCheckers.disk
Section titled “HealthCheckers.disk”- Uses
fs.statfs(Node 18+ / Bun) to obtain real disk metrics: total, free, used, and percentage - Graceful fallback to
"healthy"on unsupported platforms
CLI — Fixed Fullstack Template
Section titled “CLI — Fixed Fullstack Template”- The
fullstacktemplate now generatessrc/index.tswithGraphQLPluginandWebSocketPlugincorrectly imported and instantiated
Subpath Exports in Build
Section titled “Subpath Exports in Build”- Added
./src/auth/index.ts,./src/adapters/base.ts,./src/adapters/hono.ts,./src/adapters/express.tsas explicit entrypoints soveloce-ts/authandveloce-ts/adapters/*imports work correctly
🗄️ Drizzle ORM — DI Integration
Section titled “🗄️ Drizzle ORM — DI Integration”New module to connect Drizzle (or another ORM) to the dependency injection container:
import { registerDrizzle, InjectDB } from 'veloce-ts';
// Register the DB instanceregisterDrizzle(app, db);
// Inject into controllers@Get('/')async list(@InjectDB() db: DrizzleDB) { return await db.select().from(products);}DB_TOKEN— default symbol for the injection tokenregisterDrizzle(app, db, token?)— registers as singleton in theDIContainer@InjectDB(token?)— parameter decorator, alias of@Depends(DB_TOKEN)
📊 Improved Pagination
Section titled “📊 Improved Pagination”Metadata Enrichment
Section titled “Metadata Enrichment”PaginationMetaincludesfromandto(1-based range, e.g.from: 11, to: 20)CursorPaginatedResultincludescount(actual items returned in the page)
More Precise Cursor Pagination
Section titled “More Precise Cursor Pagination”createCursorPaginatedResult(data, limit, cursorField, hadPrevCursor)— the newhadPrevCursorparameter correctly activateshasPrev: truewhen navigating forward with cursorcreateMultiCursor(entity, fields[])— creates composite cursors by multiple fields for stable ordering (e.g.{ createdAt, id })decodeMultiCursor(cursor)— decodes a multi-field cursor back to an object
Standalone Helpers
Section titled “Standalone Helpers”paginate<T>(data, total, page, limit)— builds{ data, meta }in one call, without instantiatingPaginationHelperparseCursorQuery(query, defaultLimit?, maxLimit?)— extractscursorandlimitfrom query params without throwing exceptionsPaginationHelper.parsePaginationQuery(query, defaultLimit?, maxLimit?)— equivalent for offset pagination
🔌 Express Adapter — ESM Compatibility
Section titled “🔌 Express Adapter — ESM Compatibility”Complete rewrite of the adapter:
- Loads Express lazily and safely using
Function('return require')()for ESM compatibility without needingdeclare const require: any - Accepts a pre-created Express instance as the second constructor argument (to add your own middleware before the bridge)
- Correct handling of raw body (
Buffer) vs parsed body (JSON/urlencoded) - Omits the
transfer-encodingheader when forwarding responses (was a source of errors in Express) - Delegates unexpected errors to the Express error pipeline via
next(err)instead of responding with 500 directly
🐛 Bug Fixes
Section titled “🐛 Bug Fixes”ZodErrorcross-module:instanceof ZodErrorfailed when the consuming app had a different Zod instance than the framework (common withbun link). Added fallbackerror.name === 'ZodError'to guarantee 422 responses in all cases- GET routes marked public returned 401:
app.use()applied middleware to all methods; fixed usingapp.on(['POST', 'PUT', 'DELETE'], path, middleware)to restrict only write methods - Cache collision in MetadataCompiler: different functional handlers with the same path in different app instances shared the incorrect compiled result; fixed with unique IDs per function
@Cache/@UseMiddlewarelost metadata: when stacked in reverse order of decorator execution, metadata could be overwritten; fixed by explicitly updating the registry in each decorator
📋 Improved Validation Messages
Section titled “📋 Improved Validation Messages”The 422 error response now includes additional structured information:
fieldin conventional format:items[0].priceinstead ofitems.0.pricereceived— received type (when Zod reports it)expected— expected type (when applicable)minimum/maximum— numeric limits in range errors
{ "error": "Validation Error", "statusCode": 422, "details": [ { "field": "email", "message": "Invalid email", "code": "invalid_string" }, { "field": "age", "message": "Number must be ≥ 18", "code": "too_small", "minimum": 18 }, { "field": "tags[1]", "message": "String must not be empty", "code": "too_small" } ]}💥 Breaking Changes
Section titled “💥 Breaking Changes”None — all changes are backward compatible. The createCursorPaginatedResult signature has a new optional hadPrevCursor parameter (fourth argument, false by default).
[0.3.3] - 2025-10-31
Section titled “[0.3.3] - 2025-10-31”🐛 Critical Bug Fixes
Section titled “🐛 Critical Bug Fixes”- JSON Response Serialization: Fixed critical bug where JSON responses were not being serialized correctly
- CLI Version Resolution: Confirmed CLI correctly fetches and uses latest npm version
- Application Compilation: Fixed missing
await app.compile()call in generated templates
🔧 CLI Improvements
Section titled “🔧 CLI Improvements”- Version Fetching: CLI now correctly fetches latest version from npm registry
- Template Generation: All templates now include proper
await app.compile()call - Error Handling: Improved error handling in CLI operations
[0.3.2] - 2025-10-31
Section titled “[0.3.2] - 2025-10-31”Patch release with internal build fixes. No functional changes.
[0.3.1] - 2025-10-31
Section titled “[0.3.1] - 2025-10-31”🛠️ CLI Improvements
Section titled “🛠️ CLI Improvements”- Latest Version Fetching: CLI now automatically fetches the latest VeloceTS version from npm registry
- Improved Error Handling: Better error messages and cleanup on project creation failure
- Type Safety: Fixed TypeScript errors in CLI with proper type definitions for npm registry API
- Better User Experience: Enhanced progress messages and visual feedback during project creation
- Robust Fallbacks: Multiple fallback strategies for version detection when npm is unavailable
- npm Registry Integration: Fixed CLI to use correct npm registry endpoint
- Template Compilation: All generated templates include mandatory
await app.compile()call
[0.3.0] - 2025-10-29
Section titled “[0.3.0] - 2025-10-29”🚀 Major Features Added
Section titled “🚀 Major Features Added”Response Caching System
Section titled “Response Caching System”- In-Memory Cache Store: Fast LRU-based caching with automatic cleanup
- Redis Cache Store: Distributed caching support for multi-instance deployments
@Cache()Decorator: Declarative response caching with flexible TTL configuration@CacheInvalidate()Decorator: Pattern-based cache invalidation for mutations- TTL Support: Flexible time-to-live with string format (
'5m','1h','1d') or seconds - Pattern Invalidation: Wildcard pattern matching for cache invalidation (
'products:*') - Cache Headers: Automatic
X-Cacheheaders (HIT/MISS) in responses
@Get('/products')@Cache({ ttl: '5m', key: 'products:list' })async getProducts() { return await db.products.findAll();}
@Post('/products')@CacheInvalidate('products:*')async createProduct(@Body(ProductSchema) data: any) { return await db.products.create(data);}Enhanced Request Context
Section titled “Enhanced Request Context”- Automatic Request IDs: UUID generation for every request
@RequestId()Decorator: Inject request ID into controller methods@AbortSignal()Decorator: Request cancellation support for long-running operations- Request Timeouts: Configurable timeouts per route or globally
X-Request-IDheader: Automatic header in all responses
@Get('/data')async getData( @RequestId() requestId: string, @AbortSignal() signal: AbortSignal) { logger.info({ requestId }, 'Processing request'); return await longRunningQuery(signal);}Logging with Pino
Section titled “Logging with Pino”- Request Context Integration: Automatic request ID in all log entries
- Child Loggers: Enhanced contextual logging with inheritance
- Structured Logging: JSON-formatted logs for production
- Pretty Printing: Human-readable logs for development
🔧 New Middleware
Section titled “🔧 New Middleware”createRequestContextMiddleware(): Initialize request context with ID, timeout, and loggingcreateSimpleRequestIdMiddleware(): Minimal request ID middlewarecreateCacheMiddleware(): Functional API route cachingcreateCacheInvalidationMiddleware(): Functional API cache invalidation
💥 Breaking Changes
Section titled “💥 Breaking Changes”None — all changes are additive and backward compatible.
[0.2.6] - 2025-10-15
Section titled “[0.2.6] - 2025-10-15”- Query Export: Added missing
Queryexport from main index to resolve import conflicts - Parameter Decorators: HTTP
@Querydecorator now properly exported alongside GraphQL decorators - Import Resolution: Fixed “Export named ‘Query’ not found” error in applications
[0.2.5] - 2025-10-15
Section titled “[0.2.5] - 2025-10-15”- GraphQL Query Conflict: Removed conflicting alias
Queryfrom GraphQL decorators - Import Resolution: GraphQL decorators now use
GQLQueryto avoid conflicts with HTTP@Querydecorator - Type Safety: Eliminated TypeScript errors caused by decorator name conflicts
Breaking Changes
Section titled “Breaking Changes”- GraphQL queries now use
@GQLQueryinstead of@Queryto avoid conflicts with HTTP parameter decorator
[0.2.4] - 2025-10-15
Section titled “[0.2.4] - 2025-10-15”- Query Decorator: Fixed
@Querydecorator to properly handle parameters without schemas - Query Parameter Extraction: Improved query parameter handling in router compiler
- Validation: Added proper validation for query parameters with optional Zod schemas
- Error Handling: Fixed missing
ValidationErrorimport, now usingBadRequestException
Improved
Section titled “Improved”- Query Decorator Flexibility:
@Querynow supports multiple usage patterns:@Query()- Extract all query parameters@Query('param')- Extract specific parameter@Query(Schema)- Validate with Zod schema
- Router Compiler: Enhanced parameter extraction and validation logic
- Type Safety: Better TypeScript support for query parameter handling
Breaking Changes
Section titled “Breaking Changes”- None
[0.2.3] - 2025-10-14
Section titled “[0.2.3] - 2025-10-14”- WebSocket Exports: Fixed missing
WebSocketdecorator export from WebSocket module - Import Resolution: WebSocket decorators now properly exported from
veloce-ts/websocket
[0.2.2] - 2025-10-14
Section titled “[0.2.2] - 2025-10-14”- GraphQL Decorators: Added missing
Query,Mutation, andSubscriptionaliases for GraphQL decorators - Import Conflicts: Fixed naming conflicts between params and GraphQL decorators
- GraphQL Aliases: More intuitive names for GraphQL decorators:
@GQLQuery→@Query(GraphQL)@GQLMutation→@Mutation(GraphQL)@GQLSubscription→@Subscription(GraphQL)
[0.2.1] - 2025-10-14
Section titled “[0.2.1] - 2025-10-14”- CLI Version Resolution: Fixed CLI template to always use latest compatible version
- Package Generation: Improved
package.jsongeneration in CLI
[0.2.0] - 2025-10-14
Section titled “[0.2.0] - 2025-10-14”- Major Feature Release: Complete rewrite with enhanced functionality
- Authentication System: JWT-based authentication with refresh tokens
- Role-Based Access Control (RBAC): Hierarchical roles and permissions
- GraphQL Support: Full GraphQL integration with schema generation
- WebSocket Support: Real-time communication capabilities
- SQLite Integration: Built-in database support
- OpenAPI Documentation: Automatic API documentation generation
- CLI Tool: Project scaffolding and code generation
- Dependency Injection: Advanced DI container with scopes
- Middleware System: Custom middleware support
- Validation Engine: Zod-based request validation
- Error Handling: Comprehensive error handling system
Enhanced
Section titled “Enhanced”- Type Safety: Improved TypeScript support throughout
- Performance: Optimized routing and request handling
- Developer Experience: Better debugging and development tools
Breaking Changes
Section titled “Breaking Changes”- Complete API redesign from v0.1.x
- New decorator syntax and patterns
- Updated project structure
[0.1.7] - 2025-10-13
Section titled “[0.1.7] - 2025-10-13”- Minor bug fixes and improvements
[0.1.6] - 2025-10-13
Section titled “[0.1.6] - 2025-10-13”- Basic decorator support
- Simple routing system
[0.1.5] - 2025-10-12
Section titled “[0.1.5] - 2025-10-12”- Initial framework structure
- Core application class
[0.1.4] - 2025-10-12
Section titled “[0.1.4] - 2025-10-12”- Project setup and configuration
[0.1.3] - 2025-10-12
Section titled “[0.1.3] - 2025-10-12”- Basic TypeScript support
[0.1.2] - 2025-10-12
Section titled “[0.1.2] - 2025-10-12”- Initial package configuration
[0.1.1] - 2025-10-12
Section titled “[0.1.1] - 2025-10-12”- Package metadata and basic structure
[0.1.0] - 2025-10-12
Section titled “[0.1.0] - 2025-10-12”- Initial release
- Basic framework structure
- Package setup
:::tip Latest Version We recommend always using the latest version of Veloce-TS for the best experience and latest features. :::
:::note Breaking Changes Breaking changes are clearly marked in each release. Please review the changelog before upgrading to a new major version. :::