Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/hono-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,12 +374,46 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
return this
}

/**
* Add a route to the router.
*
* Replaces an existing route if the method and path are the same.
* Rebuilds the router if a route is replaced.
*
* Otherwise, adds the route to the router and the routes array.
* @param method - HTTP method
* @param path - Path for the route
* @param handler - Handler for the route
*/
#addRoute(method: string, path: string, handler: H) {
method = method.toUpperCase()
path = mergePath(this._basePath, path)
const r: RouterRoute = { basePath: this._basePath, path, method, handler }
this.router.add(method, path, [handler, r])
this.routes.push(r)

const existingIndex = this.routes.findIndex(
(route) => route.method === method && route.path === path
)

if (existingIndex !== -1) {
this.routes[existingIndex] = r
this.#rebuildRouter()
} else {
this.router.add(method, path, [handler, r])
this.routes.push(r)
}
}

#rebuildRouter() {
if ('clear' in this.router && typeof this.router.clear === 'function') {
this.router.clear()
} else {
const RouterClass = this.router.constructor as new () => Router<[H, RouterRoute]>
this.router = new RouterClass()
}

this.routes.forEach((route) => {
this.router.add(route.method, route.path, [route.handler, route])
})
}

#handleError(err: unknown, c: Context<E>) {
Expand Down
5 changes: 5 additions & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export interface Router<T> {
* @returns The result of the match.
*/
match(method: string, path: string): Result<T>

/**
* Clears all routes from the router.
*/
clear?(): void
}

/**
Expand Down
74 changes: 74 additions & 0 deletions src/router/common.case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,5 +871,79 @@ export const runTest = ({
expect(res[0].handler).toBe('all')
})
})

describe('Clear method', () => {
it('should clear all routes', () => {
router.add('GET', '/hello', 'handler1')
router.add('POST', '/world', 'handler2')

let res = match('GET', '/hello')
expect(res.length).toBe(1)
expect(res[0].handler).toBe('handler1')

res = match('POST', '/world')
expect(res.length).toBe(1)
expect(res[0].handler).toBe('handler2')

if ('clear' in router && typeof router.clear === 'function') {
router.clear()

res = match('GET', '/hello')
expect(res.length).toBe(0)

res = match('POST', '/world')
expect(res.length).toBe(0)
}
})

it('should allow adding new routes after clear', () => {
router.add('GET', '/old', 'oldHandler')
let res = match('GET', '/old')
expect(res.length).toBe(1)
expect(res[0].handler).toBe('oldHandler')

if ('clear' in router && typeof router.clear === 'function') {
router.clear()
router.add('GET', '/new', 'newHandler')

res = match('GET', '/old')
expect(res.length).toBe(0)

res = match('GET', '/new')
expect(res.length).toBe(1)
expect(res[0].handler).toBe('newHandler')
}
})

it('should handle complex routes after clear', () => {
router.add('GET', '/users/:id', 'user')
router.add('POST', '/api/*', 'apiMiddleware')
router.add('ALL', '*', 'globalMiddleware')

let res = match('GET', '/users/123')
expect(res.length).toBeGreaterThan(0)

res = match('POST', '/api/posts')
expect(res.length).toBeGreaterThan(0)

if ('clear' in router && typeof router.clear === 'function') {
router.clear()

res = match('GET', '/users/123')
expect(res.length).toBe(0)

res = match('POST', '/api/posts')
expect(res.length).toBe(0)

res = match('DELETE', '/anything')
expect(res.length).toBe(0)

router.add('GET', '/fresh', 'freshHandler')
res = match('GET', '/fresh')
expect(res.length).toBe(1)
expect(res[0].handler).toBe('freshHandler')
}
})
})
})
}
4 changes: 4 additions & 0 deletions src/router/linear-router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,8 @@ export class LinearRouter<T> implements Router<T> {

return [handlers]
}

clear(): void {
this.#routes = []
}
}
4 changes: 4 additions & 0 deletions src/router/pattern-router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ export class PatternRouter<T> implements Router<T> {

return [handlers]
}

clear(): void {
this.#routes = []
}
}
5 changes: 5 additions & 0 deletions src/router/reg-exp-router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,9 @@ export class RegExpRouter<T> implements Router<T> {
return buildMatcherFromPreprocessedRoutes(routes)
}
}

clear(): void {
this.#middleware = { [METHOD_NAME_ALL]: Object.create(null) }
this.#routes = { [METHOD_NAME_ALL]: Object.create(null) }
}
}
20 changes: 19 additions & 1 deletion src/router/smart-router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { MESSAGE_MATCHER_IS_ALREADY_BUILT, UnsupportedPathError } from '../../ro
export class SmartRouter<T> implements Router<T> {
name: string = 'SmartRouter'
#routers: Router<T>[] = []
#routerConstructors: (new () => Router<T>)[] = []
#routes?: [string, string, T][] = []

constructor(init: { routers: Router<T>[] }) {
this.#routers = init.routers
this.#routers = [...init.routers]
this.#routerConstructors = init.routers.map(
(router) => router.constructor as new () => Router<T>
)
}

add(method: string, path: string, handler: T) {
Expand All @@ -26,6 +30,10 @@ export class SmartRouter<T> implements Router<T> {
const routers = this.#routers
const routes = this.#routes

if (routes.length === 0) {
return [[]]
}

const len = routers.length
let i = 0
let res
Expand Down Expand Up @@ -60,6 +68,16 @@ export class SmartRouter<T> implements Router<T> {
return res as Result<T>
}

clear(): void {
this.#routers = this.#routerConstructors.map((RouterClass) => new RouterClass())

this.match = SmartRouter.prototype.match.bind(this)

this.name = 'SmartRouter'

this.#routes = []
}

get activeRouter(): Router<T> {
if (this.#routes || this.#routers.length !== 1) {
throw new Error('No active router has been determined yet.')
Expand Down
4 changes: 4 additions & 0 deletions src/router/trie-router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ export class TrieRouter<T> implements Router<T> {
match(method: string, path: string): Result<T> {
return this.#node.search(method, path)
}

clear(): void {
this.#node = new Node()
}
}
Loading