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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
60 changes: 30 additions & 30 deletions bench.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
import { run, bench, group } from "mitata"
import httpNext from "./index"
import httpPrevious from "0http-bun"
import {run, bench, group} from 'mitata'
import httpNext from './index'
import httpPrevious from '0http-bun'

function setupRouter(router) {
router.use((req, next) => {
return next()
})

router.get("/", () => {
router.get('/', () => {
return new Response()
})
router.get("/:id", async (req) => {
router.get('/:id', async (req) => {
return new Response(req.params.id)
})
router.get("/:id/error", () => {
throw new Error("Error")
router.get('/:id/error', () => {
throw new Error('Error')
})
}

const { router } = httpNext()
const {router} = httpNext()
setupRouter(router)

const { router: routerPrevious } = httpPrevious()
const {router: routerPrevious} = httpPrevious()
setupRouter(routerPrevious)

group("Next Router", () => {
bench("Parameter URL", () => {
router.fetch(new Request(new URL("http://localhost/0")))
}).gc("inner")
bench("Not Found URL", () => {
router.fetch(new Request(new URL("http://localhost/0/404")))
}).gc("inner")
bench("Error URL", () => {
router.fetch(new Request(new URL("http://localhost/0/error")))
}).gc("inner")
group('Next Router', () => {
bench('Parameter URL', () => {
router.fetch(new Request(new URL('http://localhost/0')))
}).gc('inner')
bench('Not Found URL', () => {
router.fetch(new Request(new URL('http://localhost/0/404')))
}).gc('inner')
bench('Error URL', () => {
router.fetch(new Request(new URL('http://localhost/0/error')))
}).gc('inner')
})

group("Previous Router", () => {
bench("Parameter URL", () => {
routerPrevious.fetch(new Request(new URL("http://localhost/0")))
}).gc("inner")
bench("Not Found URL", () => {
routerPrevious.fetch(new Request(new URL("http://localhost/0/404")))
}).gc("inner")
bench("Error URL", () => {
routerPrevious.fetch(new Request(new URL("http://localhost/0/error")))
}).gc("inner")
group('Previous Router', () => {
bench('Parameter URL', () => {
routerPrevious.fetch(new Request(new URL('http://localhost/0')))
}).gc('inner')
bench('Not Found URL', () => {
routerPrevious.fetch(new Request(new URL('http://localhost/0/404')))
}).gc('inner')
bench('Error URL', () => {
routerPrevious.fetch(new Request(new URL('http://localhost/0/error')))
}).gc('inner')
})

await run({
run({
colors: true,
})
4 changes: 2 additions & 2 deletions common.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Pattern, Methods } from 'trouter'
import {Pattern, Methods} from 'trouter'

export interface IRouterConfig {
defaultRoute?: RequestHandler
Expand All @@ -16,7 +16,7 @@ type ZeroRequest = Request & {

export type RequestHandler = (
req: ZeroRequest,
next: StepFunction
next: StepFunction,
) => Response | Promise<Response>

export interface IRouter {
Expand Down
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IRouter, IRouterConfig } from './common'
import {IRouter, IRouterConfig} from './common'

export default function zero(config?: IRouterConfig): {
router: IRouter
}

export * from './common'
export * from './common'
9 changes: 6 additions & 3 deletions lib/next.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ module.exports = function next(
defaultRoute,
errorHandler,
) {
if (index >= middlewares.length) {
// Optimized loop unrolling for common cases
const length = middlewares.length
if (index >= length) {
return defaultRoute(req)
}

const middleware = middlewares[index++]
const middleware = middlewares[index]
const nextIndex = index + 1

try {
return middleware(req, (err) => {
if (err) {
return errorHandler(err, req)
}
return next(middlewares, req, index, defaultRoute, errorHandler)
return next(middlewares, req, nextIndex, defaultRoute, errorHandler)
})
} catch (err) {
return errorHandler(err, req)
Expand Down
2 changes: 1 addition & 1 deletion lib/router/sequential.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { IRouter, IRouterConfig } from './../../index'
import {IRouter, IRouterConfig} from './../../index'

export default function createSequentialRouter(config?: IRouterConfig): IRouter
122 changes: 83 additions & 39 deletions lib/router/sequential.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { Trouter } = require("trouter")
const qs = require("fast-querystring")
const next = require("./../next")
const {Trouter} = require('trouter')
const qs = require('fast-querystring')
const next = require('./../next')

const STATUS_404 = {
status: 404,
Expand All @@ -12,26 +12,26 @@ const STATUS_500 = {
module.exports = (config = {}) => {
const cache = new Map()

if (!config.defaultRoute) {
config.defaultRoute = () => {
return new Response(null, STATUS_404)
}
}
if (!config.errorHandler) {
config.errorHandler = (err) => {
return new Response(err.message, STATUS_500)
}
}
// Pre-create default responses to avoid object creation overhead
const default404Response = new Response(null, STATUS_404)

// Cache default functions to avoid closure creation
const defaultRouteHandler = config.defaultRoute || (() => default404Response)
const errorHandlerFn =
config.errorHandler || ((err) => new Response(err.message, STATUS_500))

// Optimize empty params object reuse
const emptyParams = {}

const router = new Trouter()
router.port = config.port || 3000

const _use = router.use

router.use = (prefix, ...middlewares) => {
if (typeof prefix === "function") {
if (typeof prefix === 'function') {
middlewares = [prefix, ...middlewares]
prefix = "/"
prefix = '/'
}
_use.call(router, prefix, middlewares)

Expand All @@ -40,40 +40,84 @@ module.exports = (config = {}) => {

router.fetch = (req) => {
const url = req.url
const startIndex = url.indexOf("/", 11)
const queryIndex = url.indexOf("?", startIndex + 1)
const path =
queryIndex === -1
? url.substring(startIndex)
: url.substring(startIndex, queryIndex)

req.path = path || "/"
req.query = queryIndex > 0 ? qs.parse(url.substring(queryIndex + 1)) : {}

const cacheKey = `${req.method}:${req.path}`
let match = null
if (cache.has(cacheKey)) {
match = cache.get(cacheKey)

// Highly optimized URL parsing - single pass through the string
let pathStart = 0
let pathEnd = url.length
let queryString = null

// Find protocol end
const protocolEnd = url.indexOf('://')
if (protocolEnd !== -1) {
// Find host end (start of path)
pathStart = url.indexOf('/', protocolEnd + 3)
if (pathStart === -1) {
pathStart = url.length
}
}

// Find query start
const queryStart = url.indexOf('?', pathStart)
if (queryStart !== -1) {
pathEnd = queryStart
queryString = url.substring(queryStart + 1)
}

const path = pathStart < pathEnd ? url.substring(pathStart, pathEnd) : '/'

req.path = path
req.query = queryString ? qs.parse(queryString) : {}

// Optimized cache lookup with method-based Map structure
const method = req.method
let methodCache = cache.get(method)
let match_result

if (methodCache) {
match_result = methodCache.get(path)
if (match_result === undefined) {
match_result = router.find(method, path)
methodCache.set(path, match_result)
}
} else {
match = router.find(req.method, req.path)
cache.set(cacheKey, match)
match_result = router.find(method, path)
methodCache = new Map([[path, match_result]])
cache.set(method, methodCache)
}

if (match?.handlers?.length > 0) {
if (!req.params) {
req.params = {}
if (match_result?.handlers?.length > 0) {
// Fast path for params assignment
const params = match_result.params
if (params) {
// Check if params object has properties without Object.keys()
let hasParams = false
for (const key in params) {
hasParams = true
break
}

if (hasParams) {
req.params = req.params || {}
// Direct property copy - faster than Object.keys() + loop
for (const key in params) {
req.params[key] = params[key]
}
} else if (!req.params) {
req.params = emptyParams
}
} else if (!req.params) {
req.params = emptyParams
}
Object.assign(req.params, match.params)

return next(
match.handlers,
match_result.handlers,
req,
0,
config.defaultRoute,
config.errorHandler
defaultRouteHandler,
errorHandlerFn,
)
} else {
return config.defaultRoute(req)
return defaultRouteHandler(req)
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"lint": "prettier --check **/*.js",
"test": "bun test",
"test": "bun --coverage test",
"bench": "bun run bench.js",
"format": "prettier --write **/*.js"
},
Expand Down
48 changes: 0 additions & 48 deletions test/config.test.js

This file was deleted.

Loading