Skip to content
Merged
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
69 changes: 44 additions & 25 deletions bun.lock

Large diffs are not rendered by default.

45 changes: 24 additions & 21 deletions packages/wobe-benchmark/package.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
{
"name": "wobe-benchmark",
"version": "0.1.0",
"main": "index.ts",
"dependencies": {
"wobe": "*"
},
"devDependencies": {
"elysia": "1.0.16",
"get-port": "7.1.0",
"mitata": "0.1.11",
"hono": "4.10.4",
"koa-router": "12.0.1",
"radix3": "1.1.2",
"find-my-way": "9.3.0"
},
"scripts": {
"bench:startup": "bun run startup/benchmark.ts",
"bench:router": "bun run router/benchmark.ts",
"bench:extracter": "bun run pathExtract/benchmark.ts",
"bench:findHook": "bun run findHook/benchmark.ts"
}
"name": "wobe-benchmark",
"version": "0.1.0",
"main": "index.ts",
"dependencies": {
"wobe": "*"
},
"devDependencies": {
"@sinclair/typebox": "0.34.41",
"elysia": "1.4.18",
"get-port": "7.1.0",
"mitata": "1.0.34",
"hono": "4.10.7",
"@koa/router": "15.0.0",
"radix3": "1.1.2",
"find-my-way": "9.3.0"
},
"scripts": {
"bench:startup": "bun run startup/benchmark.ts",
"bench:router": "bun run router/benchmark.ts",
"bench:extracter": "bun run pathExtract/benchmark.ts",
"bench:findHook": "bun run findHook/benchmark.ts",
"lint": "biome lint . --no-errors-on-unmatched",
"format": "biome format --write ."
}
}
4 changes: 2 additions & 2 deletions packages/wobe-benchmark/router/koaRouter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import KoaRouter from 'koa-router'
import KoaRouter from '@koa/router'
import type { RouterInterface } from './tools'
import { routes, handler } from './tools'

Expand All @@ -7,7 +7,7 @@ const router = new KoaRouter()

for (const route of routes) {
if (route.method === 'GET') {
router.get(route.pathToCompile.replace('*', '(.*)'), handler)
router.get(route.pathToCompile.replace('*', '/*path'), handler)
} else {
router.post(route.pathToCompile, handler)
}
Expand Down
9 changes: 6 additions & 3 deletions packages/wobe-benchmark/router/wobe.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RadixTree } from 'wobe/src/router'
import { routes, type Route } from './tools'
import { UrlPatternRouter, type Router } from 'wobe'

const createWobeRouter = (name: string, radixTree: RadixTree) => {
const createWobeRouter = (name: string, radixTree: Router) => {
for (const route of routes) {
radixTree.addRoute(route.method, route.pathToCompile, () =>
Promise.resolve(),
Expand All @@ -18,4 +18,7 @@ const createWobeRouter = (name: string, radixTree: RadixTree) => {
}
}

export const wobeRouter = createWobeRouter('Radix router', new RadixTree())
export const wobeRouter = createWobeRouter(
'UrlPattern router',
new UrlPatternRouter(),
)
16 changes: 8 additions & 8 deletions packages/wobe-benchmark/startup/elysia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import getPort from 'get-port'

export const elysiaApp = async () => {
const port = await getPort()
const elysia = new Elysia({ precompile: true })
const app = new Elysia()
.get('/', 'Hi')
.post('/json', (c) => c.body, {
type: 'json',
})
.get('/id/:id', ({ set, params: { id }, query: { name } }) => {
set.headers['x-powered-by'] = 'benchmark'
.get('/id/:id', (c) => {
c.set.headers['x-powered-by'] = 'benchmark'

return id + ' ' + name
return `${c.params.id} ${c.query.name}`
})
.post('/json', (c) => c.body, {
parse: 'json',
})
.listen(port)

elysia.stop()
app.stop()
}
6 changes: 3 additions & 3 deletions packages/wobe/src/Context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { HttpMethod, WobeHandler, WobeHandlerOutput } from './Wobe'
import { WobeResponse } from './WobeResponse'
import type { RadixTree } from './router'
import type { Router } from './router'
import { extractPathnameAndSearchParams } from './utils'

export class Context {
Expand All @@ -18,14 +18,14 @@ export class Context {
public beforeHandlerHook: Array<WobeHandler<any>> = []
public afterHandlerHook: Array<WobeHandler<any>> = []

constructor(request: Request, router?: RadixTree) {
constructor(request: Request, router?: Router) {
this.request = request
this.res = new WobeResponse(request)

this._findRoute(router)
}

private _findRoute(router?: RadixTree) {
private _findRoute(router?: Router) {
const { pathName, searchParams } = extractPathnameAndSearchParams(
this.request.url,
)
Expand Down
11 changes: 8 additions & 3 deletions packages/wobe/src/Wobe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Server, ServerWebSocket } from 'bun'
import { RadixTree } from './router'
import { RadixTree, type Router } from './router'
import { BunAdapter, NodeAdapter, type RuntimeAdapter } from './adapters'
import type { Context } from './Context'

Expand Down Expand Up @@ -29,6 +29,11 @@ export interface WobeOptions {
cert: string
passphrase?: string
}
/**
* Provide a custom router implementation (RadixTree, UrlPatternRouter, or compatible).
* Defaults to UrlPatternRouter when not supplied.
*/
router?: Router
}

export type HttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'ALL' | 'OPTIONS'
Expand Down Expand Up @@ -100,7 +105,7 @@ export class Wobe<T> {
hook: Hook
method: HttpMethod
}>
private router: RadixTree
private router: Router
private runtimeAdapter: RuntimeAdapter = factoryOfRuntime()
private httpMethods: Array<HttpMethod> = [
'GET',
Expand All @@ -119,7 +124,7 @@ export class Wobe<T> {
this.wobeOptions = options
this.hooks = []
this.server = null
this.router = new RadixTree()
this.router = options?.router || new RadixTree()
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/wobe/src/adapters/bun/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { RuntimeAdapter } from '..'
import { Context } from '../../Context'
import { HttpException } from '../../HttpException'
import type { WobeOptions, WobeWebSocket } from '../../Wobe'
import type { RadixTree } from '../../router'
import type { Router } from '../../router'
import { bunWebSocket } from './websocket'
import { brotliDecompressSync, gunzipSync, inflateSync } from 'node:zlib'

Expand Down Expand Up @@ -55,7 +55,7 @@ const decompressBody = (
export const BunAdapter = (): RuntimeAdapter => ({
createServer: (
port: number,
router: RadixTree,
router: Router,
options?: WobeOptions,
webSocket?: WobeWebSocket,
) =>
Expand Down
4 changes: 2 additions & 2 deletions packages/wobe/src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { WobeOptions, WobeWebSocket } from '../Wobe'
import type { RadixTree } from '../router'
import type { Router } from '../router'

export * from './bun'
export * from './node'

export interface RuntimeAdapter {
createServer: (
port: number,
router: RadixTree,
router: Router,
options?: WobeOptions,
webSocket?: WobeWebSocket,
) => any
Expand Down
4 changes: 2 additions & 2 deletions packages/wobe/src/adapters/node/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { brotliDecompressSync, gunzipSync, inflateSync } from 'node:zlib'
import { HttpException } from '../../HttpException'
import { Context } from '../../Context'
import type { RuntimeAdapter } from '..'
import type { RadixTree } from '../../router'
import type { Router } from '../../router'
import type { WobeOptions } from '../../Wobe'

const DEFAULT_MAX_BODY_SIZE = 1024 * 1024 // 1 MiB
Expand Down Expand Up @@ -85,7 +85,7 @@ const transformResponseInstanceToValidResponse = async (response: Response) => {
}

export const NodeAdapter = (): RuntimeAdapter => ({
createServer: (port: number, router: RadixTree, options?: WobeOptions) => {
createServer: (port: number, router: Router, options?: WobeOptions) => {
// @ts-expect-error
const createServer: typeof createHttpsServer = options?.tls
? createHttpsServer
Expand Down
16 changes: 16 additions & 0 deletions packages/wobe/src/router/RadixTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,22 @@ describe('RadixTree', () => {
expect(route2?.handler).toBeDefined()
})

it('should match a param route when the value is missing but a trailing slash is present', () => {
const radixTree = new RadixTree()

radixTree.addRoute('GET', '/bucket/:filename', () =>
Promise.resolve(),
)

radixTree.optimizeTree()

const route = radixTree.findRoute('GET', '/bucket/')

expect(route).not.toBeNull()
expect(route?.handler).toBeDefined()
expect(route?.params).toEqual({ filename: '' })
})

it('should find a route by method', () => {
const radixTree = new RadixTree()

Expand Down
13 changes: 1 addition & 12 deletions packages/wobe/src/router/RadixTree.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import type { Node } from '.'
import type { Hook, HttpMethod, WobeHandler } from '../Wobe'

export interface Node {
name: string
children: Array<Node>
handler?: WobeHandler<any>
beforeHandlerHook?: Array<WobeHandler<any>>
afterHandlerHook?: Array<WobeHandler<any>>
method?: HttpMethod
isParameterNode?: boolean
isWildcardNode?: boolean
params?: Record<string, string>
}

export class RadixTree {
public root: Node = { name: '/', children: [] }
private isOptimized = false
Expand Down
Loading