itty-router
itty-router copied to clipboard
Working nested routes without base.
const
run = (...fns) => async (...args) => {
for (const fn of fns.flat(1 / 0)) {
const result = fn && await fn(...args);
if (result !== undefined) return result;
}
},
compile = (path) => RegExp(`^${path
.replace(/\/+(\/|$)/g, '$1') // strip double & trailing splash
.replace(/(\/?\.?):(\w+)\+/g, '($1(?<$2>*))') // greedy params
.replace(/(\/?\.?):(\w+)/g, '($1(?<$2>[^$1/]+?))') // named params and image format
.replace(/\./g, '\\.') // dot in path
.replace(/(\/?)\*/g, '($1.*)?')
}/*$`),
mount = (fn) => fn.fetch || fn,
lead = x => x.startsWith('/') ? x : '/' + x,
add = (routes, method, base, route, handlers, path) =>
routes.push([method, compile(base + route), handlers.map(mount), base + path]),
use = (routes, base, route, handlers) =>
route === "/" ?
add(routes, "ALL", base, "/", handlers, "/") :
route?.call || route?.fetch ?
add(routes, "ALL", base, '/*', [route, ...handlers], "/*") :
handlers.forEach(handler =>
handler?.routes?.forEach(([method, , handles, path]) =>
add(routes, method, base, lead(route + path), handles, lead(route + path))));
export const Router = ({ base = '', routes = [], ...other } = {}) => ({
__proto__: new Proxy({}, {
get: (_, prop, receiver) => (route, ...handlers) =>
(prop.toUpperCase() === "USE" ?
use(routes, base, route, handlers) :
add(routes, prop.toUpperCase?.(), base, route, handlers, route),
receiver)
}),
routes,
...other,
async fetch(request, ...args) {
let url = new URL(request.url), match, res, query = request.query = { __proto__: null };
for (const [k, v] of url.searchParams) query[k] = query[k] ? ([]).concat(query[k], v) : v;
const r = async (hns, ...params) =>
run(hns)(...params)
.catch((e) => other.catch ? (res = other.catch(e, request.proxy ?? request, ...args)) :
Promise.reject(e));
res = await r(other.before, request.proxy ?? request, ...args);
if (!res) {
for (const [method, route, handlers, _] of routes) {
if ((method === request.method || method === "ALL") && (match = url.pathname.match(route))) {
request.params = match.groups || {};
request.route = _;
if ((res = await r(handlers, request.proxy ?? request, ...args)) !== undefined) break;
}
}
}
return await r(other.finally, res, request.proxy ?? request, ...args) ?? res;
}
});
const child = Router().get('*', (req) => req.params.bar)
const parent = Router()
.get('/', () => 'parent')
.use('/child/:bar', child)
parent.fetch({
url: '/',
method: 'GET'
})
.then(console.log)
.catch(console.error);
parent.fetch({
url: '/child/kitten',
method: 'GET'
})
.then(console.log)
.catch(console.error);