Releases: arkstack-hq/clear-router
Release list
2.9.3
2.9.2
This release introduces scoped dependency injection, structured providers, recursive resolution, and improved plugin isolation.
What's New
- Added
singleton,request, andtransientservice lifetimes. - Added
useValue,useClass,useFactory, anduseExistingproviders. - Added recursive constructor injection through
static inject, provider dependencies, or legacy metadata. - Added typed
InjectionToken<T>and symbol tokens. - Added
Container.resolveOrFail(). - Added circular dependency detection with resolution paths.
- Added
container.strictfor unresolved decorated arguments. - Deduplicated concurrent asynchronous scoped factories.
- Added
Container.current()for accessing the active request container. - Added adapter-owned containers and request child scopes.
- Scoped plugin installation and bindings per framework adapter.
- Added service lifetime options to plugin bindings.
- Plugin factories receive the active request, response, and framework context.
Compatibility
Existing bindings remain supported:
Container.bind(AuditService, () => new AuditService());Structured providers can be adopted incrementally:
Container.bind(AuditService, {
useClass: AuditService,
scope: 'request',
});Decorated handlers retain their existing fallback behavior unless container.strict is enabled.
Testing
Added coverage for provider lifetimes, recursive injection, token aliases, concurrent factories, request isolation, circular dependencies, adapter-scoped plugins, and request-scoped plugin bindings.
Full Changelog: 2.9.1...2.9.2
2.9.1
What's Changed
New Feature: Controller & method middleware decorators
You can now attach middleware directly to controllers and controller methods with the @middleware decorator, keeping middleware next to the code it protects instead of repeating it at every route registration.
import { middleware } from 'clear-router/decorators'
@middleware([auth]) // applies to every action
class AccountController {
@middleware(GuestMiddleware) // applies to this action only
create() {}
}
Router.get('/account', [AccountController, 'index'])
Router.post('/account', [AccountController, 'create'])Highlights
- Class-level decorators apply to every action of the controller — including routes generated by
Router.apiResource(). - Method-level decorators apply only to the decorated action.
- Accepts all supported middleware shapes: callbacks, middleware classes, and instances exposing a
handlemethod — passed variadically or as an array (@middleware(A, B)or@middleware([A, B])). - Works with both TypeScript
experimentalDecoratorsand TC39 standard decorators (same metadata mechanism as@Bind).
Execution order for a controller route:
Global → Group → @middleware (class) → @middleware (method) → Route middleware → Handler
Exports (from clear-router/decorators and clear-router/core): middleware, getControllerMiddlewares, and the MiddlewareDecorator / MiddlewareInput types.
Docs & tests: README and the Middlewares guide updated with a new "Decorator Middlewares" section; added Express test coverage for class-level, method-level scoping, combined ordering, class/instance handle middleware, and apiResource propagation.
Compatibility: Purely additive — no changes to existing routing or middleware APIs. All 129 tests pass.
2.9.0
What's Changed
Routing: domains, parameter constraints, encoded slashes & current-route accessors
Route Domains
Constrain routes to a host pattern; captured placeholders become route parameters. Matching runs on every adapter (Express, H3, Fastify, Hono, Koa).
// Group of routes under a subdomain
Router.domain('{account}.example.com').group(() => {
Router.get('/dashboard', ({ req, res }) => res.json({ account: req.params.account }));
});
// Per-route domain
Router.get('/team', TeamController).domain('{account}.example.com').name('team');The registrar mirrors Router.group and is chainable (.prefix(), .middleware()). Domain routes generate protocol-relative absolute URLs:
Router.url('team', { account: 'acme' }); // → '//acme.example.com/team'Regular-expression parameter constraints
Fluent constraints validated at request time (non-matching values fall through to a 404):
Router.get('/users/{id}', UserController).whereNumber('id');
Router.get('/posts/{slug}', PostController).where('slug', '[a-z-]+');
Router.get('/category/{name}', CategoryController).whereIn('name', ['movie', 'song']);Helpers: where, whereNumber, whereAlpha, whereAlphaNumeric, whereUuid, whereUlid, whereIn. Global defaults via Router.pattern(name, pattern) / Router.patterns({ ... }) (per-route constraints win).
Encoded forward slashes
A parameter constrained with a slash-spanning pattern (e.g. .*) now matches across segments and is normalized to a single string, using each framework's catch-all syntax under the hood:
Router.get('/search/{search}', SearchController).where('search', '.*');
// GET /search/foo/bar/baz → search === 'foo/bar/baz'Current-route accessors
Available during a request on both Router and the Route facade:
Router.current(); // matched Route instance
Router.currentRouteName(); // e.g. 'users.show'
Router.currentRouteAction(); // 'UserController@show' or 'Closure'
Route.current(); Route.currentRouteName(); Route.currentRouteAction();Notes
- Hono: resolved domain/wildcard params are exposed via the injected
clearRequest.params(Hono's native params are read through a method, not a mutable object). - Fully backward compatible. Routing docs updated.
Full Changelog: 2.6.5...2.9.0
2.6.5
What's Changed
Fixed an issue where controller actions could receive undefined instead of the default Clear Router context when the container was enabled and a plugin argument resolver returned an empty argument array.
Empty plugin argument arrays now fall back to the default handler signature, preserving normal (ctx, request) invocation while still allowing plugins to replace handler arguments when they provide one or more resolved values.
Added regression coverage for Express controller dispatch with empty plugin-resolved arguments.
Full Changelog: 2.6.4...2.6.5
2.6.4
What's Changed
- update API resource routes and enhance routing documentation
- optimise API route naming conventions
Full Changelog: 2.6.3...2.6.4
2.6.3
What's Changed
New Features
-
Named API resource routes — routes registered via
apiResource()are now automatically assigned a name derived from the route path (e.g.account.books.d.show), making them referenceable by name across the application. -
Inferred param names —
apiResource()can now infer the route parameter name from the base path (e.g./books→:book) instead of defaulting to:id. Requires@h3ravel/supportto be installed.
Configuration
Set inferParamName: true in your router config to opt in:
Router.configure({ inferParamName: true })
Router.apiResource('/account/users', UserController)Defaults to false — fully backwards compatible, existing routes are unaffected.
Full Changelog: 2.6.1...2.6.3
2.6.1
What's New
Custom Request and Response providers
You can now subclass Request and Response and register them as global providers via setRequestProvider and setResponseProvider. Once registered, every ctx.clearRequest and ctx.clearResponse across all routes will be an instance of your custom class — giving you a clean, centralised place to add app-level request utilities and response helpers.
import { Request, Response } from 'clear-router'
class AppRequest extends Request {
get bearerToken(): string | null {
const auth = this.header('Authorization')
return auth.startsWith('Bearer ') ? auth.slice(7) : null
}
}
class AppResponse extends Response {
success(data: any) {
return this.status(200).json({ success: true, data })
}
failure(message: string, code = 400) {
return this.status(code).json({ success: false, message })
}
}
Router.setRequestProvider(AppRequest)
Router.setResponseProvider(AppResponse)Changes
- Added
setRequestProvider— registers a customRequestsubclass as the global provider - Added
setResponseProvider— registers a customResponsesubclass as the global provider
Full Changelog: 2.6.0...2.6.1
2.6.0
Added
- Named routes via
Router.get(...).name(...) - Named route lookup with
Router.route(name)andRouter.allRoutes('name') - URL generation with
Router.url(name, params) - Laravel-style route parameters:
/books/{book}/books/{book?}/books/{book:profile}
- Plugin argument resolvers for replacing controller method arguments
- Better
Responseinitialization from native response-like objects
Improved
- Adapter route registration now expands optional parameters across Express, Fastify, H3, Hono, and Koa
- Core request/response objects are bound into the container during request handling
- Route metadata now includes registration paths, parameter metadata, and route names
Fixed
- Simplified handler resolution condition in
CoreRouter - Removed debug output from container bindings
Full Changelog: 2.5.8...2.6.0
2.5.8
What's Changed
- enhance plugin system to include request context in bindings
- add current request and response body to plugin context
Full Changelog: 2.5.7...2.5.8