itty-router icon indicating copy to clipboard operation
itty-router copied to clipboard

Working nested routes without base.

Open kethan opened this issue 6 months ago • 3 comments

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);

kethan avatar Aug 07 '24 17:08 kethan