UNPKG

@worker-tools/router

Version:

A router for Worker Runtimes such as Cloudflare Workers and Service Workers.

375 lines 21.9 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _a, _b; var _WorkerRouter_instances, _WorkerRouter_middleware, _WorkerRouter_routes, _WorkerRouter_recoverRoutes, _WorkerRouter_fatal, _WorkerRouter_route, _WorkerRouter_fireError, _WorkerRouter_execPatterns, _WorkerRouter_pushRoute, _WorkerRouter_pushMiddlewareRoute, _WorkerRouter_registerPattern, _WorkerRouter_registerRecoverPattern, _WorkerRouter_pushRecoverRoute, _WorkerRouter_pushMiddlewareRecoverRoute, _WorkerRouter_routeHandler; // deno-lint-ignore-file no-explicit-any import { EffectsList, executeEffects } from '@worker-tools/middleware'; import { internalServerError, notFound } from '@worker-tools/response-creators'; import { ResolvablePromise } from '@worker-tools/resolvable-promise'; import { AggregateError } from "./utils/aggregate-error.js"; import { ErrorEvent } from './utils/error-event.js'; /** * Turns a pathname pattern into a `URLPattern` that works across worker runtimes. * * Specifically in the case of Service Workers, this ensures requests to external domains that happen to have the same * pathname aren't matched. * If a worker environment has a location set (e.g. deno with `--location` or CF workers with a location polyfill), * this is essentially a noop since only matching requests can reach deployed workers in the first place. */ const toPattern = ((_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.includes('Cloudflare-Workers')) && ((_b = self.location) === null || _b === void 0 ? void 0 : _b.hostname) === 'localhost' ? (pathname) => new URLPattern({ pathname }) : (pathname) => { var _a, _b, _c; const pattern = new URLPattern({ pathname, protocol: (_a = self.location) === null || _a === void 0 ? void 0 : _a.protocol, hostname: (_b = self.location) === null || _b === void 0 ? void 0 : _b.hostname, port: (_c = self.location) === null || _c === void 0 ? void 0 : _c.port, }); // Note that `undefined` becomes a `*` pattern. return pattern; }; // const anyResult = Object.freeze(toPattern('*').exec(new Request('/').url)!); // const anyPathResult = Object.freeze(toPattern('/*').exec(new Request('/').url)!); export class WorkerRouter extends EventTarget { constructor(middleware, opts = {}) { var _a; super(); _WorkerRouter_instances.add(this); _WorkerRouter_middleware.set(this, void 0); _WorkerRouter_routes.set(this, []); _WorkerRouter_recoverRoutes.set(this, []); _WorkerRouter_fatal.set(this, void 0); _WorkerRouter_routeHandler.set(this, (ctx) => { var _a, _b; // TODO: are these guaranteed to be ordered correctly?? const values = Object.values((_b = (_a = ctx.match) === null || _a === void 0 ? void 0 : _a.pathname.groups) !== null && _b !== void 0 ? _b : {}); if (values.length) { const baseURL = new URL(ctx.request.url).origin; const subURL = new URL(values.at(-1), baseURL); return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_route).call(this, subURL.href, ctx); } throw TypeError('Pattern not suitable for nested routing. Did you forget to add a wildcard (*)?'); } /** @deprecated Name/API might change */ ); /** @deprecated Name/API might change */ Object.defineProperty(this, "handle", { enumerable: true, configurable: true, writable: true, value: (request, ctx) => { var _a, _b; return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_route).call(this, request.url, { ...ctx, request, waitUntil: (_b = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.waitUntil) === null || _a === void 0 ? void 0 : _a.bind(ctx)) !== null && _b !== void 0 ? _b : ((_f) => { }) }); } }); /** * Implements the (ancient) event listener object interface to allow passing to fetch event directly, * e.g. `self.addEventListener('fetch', router)`. */ Object.defineProperty(this, "handleEvent", { enumerable: true, configurable: true, writable: true, value: (object) => { const event = object; event.respondWith(__classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_route).call(this, event.request.url, { request: event.request, waitUntil: event.waitUntil.bind(event), event, })); } }); /** * Callback compatible with Cloudflare Worker's `fetch` module export. * E.g. `export default router`. */ Object.defineProperty(this, "fetch", { enumerable: true, configurable: true, writable: true, value: (request, env, ctx) => { var _a, _b; return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_route).call(this, request.url, { request, waitUntil: (_b = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.waitUntil) === null || _a === void 0 ? void 0 : _a.bind(ctx)) !== null && _b !== void 0 ? _b : ((_f) => { }), env, ctx, }); } }); /** * Callback that is compatible with Deno's `serve` function. * E.g. `serve(router.serveCallback)`. */ Object.defineProperty(this, "serveCallback", { enumerable: true, configurable: true, writable: true, value: (request, connInfo) => { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_route).call(this, request.url, { request, waitUntil: (_f) => { }, connInfo }); } }); __classPrivateFieldSet(this, _WorkerRouter_middleware, middleware !== null && middleware !== void 0 ? middleware : (_ => _), "f"); __classPrivateFieldSet(this, _WorkerRouter_fatal, (_a = opts === null || opts === void 0 ? void 0 : opts.fatal) !== null && _a !== void 0 ? _a : false, "f"); } get fatal() { return __classPrivateFieldGet(this, _WorkerRouter_fatal, "f"); } any(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'ANY', arguments.length, toPattern(path), middlewareOrHandler, handler); } all(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'ANY', arguments.length, toPattern(path), middlewareOrHandler, handler); } get(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'GET', arguments.length, toPattern(path), middlewareOrHandler, handler); } post(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'POST', arguments.length, toPattern(path), middlewareOrHandler, handler); } put(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'PUT', arguments.length, toPattern(path), middlewareOrHandler, handler); } patch(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'PATCH', arguments.length, toPattern(path), middlewareOrHandler, handler); } delete(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'DELETE', arguments.length, toPattern(path), middlewareOrHandler, handler); } head(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'HEAD', arguments.length, toPattern(path), middlewareOrHandler, handler); } options(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'OPTIONS', arguments.length, toPattern(path), middlewareOrHandler, handler); } external(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'ANY', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalGET(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'GET', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalPOST(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'POST', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalPUT(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'PUT', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalPATCH(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'PATCH', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalDELETE(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'DELETE', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalOPTIONS(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'OPTIONS', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } externalHEAD(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerPattern).call(this, 'HEAD', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } /** * Use a different `WorkerRouter` for the provided pattern. Keep in mind that: * * - The pattern must end in a wildcard `*` * - The corresponding match is the only part used for matching in the `subRouter` * - Forwards all HTTP methods * - Does not apply any middleware * * #### Why does it not apply middleware? * * There are 2 reasons: First, it interferes with type inference of middleware. * As a developer you'd have to provide the correct types at the point of defining the sub router, * which is at least as cumbersome as providing the middleware itself. * * Second, without this there would be no way to opt a route out of the router-level middleware. * For example you might want to opt out all `/public*` urls from cookie parsing, authentication, etc. * but add a different caching policy instead. * * @param path A pattern ending in a wildcard, e.g. `/items*` * @param subRouter A `WorkerRouter` that handles the remaining part of the URL, e.g. `/:category/:id` * @deprecated The name of this method might change to avoid confusion with `use` method known from other routers. */ use(path, subRouter) { // if (this..fatal && !path.endsWith('*')) { // console.warn('Path for \'use\' does not appear to end in a wildcard (*). This is likely to produce unexpected results.'); // } __classPrivateFieldGet(this, _WorkerRouter_routes, "f").push({ method: 'ANY', pattern: toPattern(path), handler: __classPrivateFieldGet(subRouter, _WorkerRouter_routeHandler, "f"), }); return this; } /** * See `.external` and `.use`. * @deprecated Might change name/API */ useExternal(init, subRouter) { const pattern = new URLPattern(init); // if (this.#opts.fatal && !pattern.pathname.endsWith('*')) { // console.warn('Pathname pattern for \'use\' does not appear to end in a wildcard (*). This is likely to produce unexpected results.'); // } __classPrivateFieldGet(this, _WorkerRouter_routes, "f").push({ method: 'ANY', pattern, handler: __classPrivateFieldGet(subRouter, _WorkerRouter_routeHandler, "f"), }); return this; } recover(path, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerRecoverPattern).call(this, 'ANY', arguments.length, toPattern(path), middlewareOrHandler, handler); } recoverExternal(init, middlewareOrHandler, handler) { return __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_registerRecoverPattern).call(this, 'ANY', arguments.length, new URLPattern(init), middlewareOrHandler, handler); } addEventListener(...args) { super.addEventListener(...args); } removeEventListener(...args) { super.removeEventListener(...args); } } _WorkerRouter_middleware = new WeakMap(), _WorkerRouter_routes = new WeakMap(), _WorkerRouter_recoverRoutes = new WeakMap(), _WorkerRouter_fatal = new WeakMap(), _WorkerRouter_routeHandler = new WeakMap(), _WorkerRouter_instances = new WeakSet(), _WorkerRouter_route = async function _WorkerRouter_route(fqURL, ctx) { var _a, _b, _c, _d, _e, _g; const result = __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_execPatterns).call(this, fqURL, ctx.request); try { if (!result) throw notFound(); const [handler, match] = result; const handle = new ResolvablePromise(); const handled = Promise.resolve(handle); const userCtx = Object.assign(ctx, { match, handled, effects: new EffectsList() }); const response = await handler(userCtx); handle.resolve((_c = (_b = (_a = ctx.event) === null || _a === void 0 ? void 0 : _a.handled) === null || _b === void 0 ? void 0 : _b.then(() => response)) !== null && _c !== void 0 ? _c : response); return response; } catch (err) { const recoverResult = __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_execPatterns).call(this, fqURL, ctx.request, __classPrivateFieldGet(this, _WorkerRouter_recoverRoutes, "f")); if (recoverResult) { try { const [handler, match] = recoverResult; const [response, error] = err instanceof Response ? [err, undefined] : [internalServerError(), err]; const handle = new ResolvablePromise(); const handled = Promise.resolve(handle); const userCtx = Object.assign(ctx, { response, error, match, handled, effects: new EffectsList() }); const res = await handler(userCtx); handle.resolve((_g = (_e = (_d = ctx.event) === null || _d === void 0 ? void 0 : _d.handled) === null || _e === void 0 ? void 0 : _e.then(() => res)) !== null && _g !== void 0 ? _g : res); return res; } catch (recoverErr) { const aggregateErr = new AggregateError([err, recoverErr], 'Route handler and recover handler failed'); if (__classPrivateFieldGet(this, _WorkerRouter_fatal, "f")) throw aggregateErr; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_fireError).call(this, aggregateErr); if (recoverErr instanceof Response) return recoverErr; if (err instanceof Response) return err; return internalServerError(); } } if (__classPrivateFieldGet(this, _WorkerRouter_fatal, "f")) throw err; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_fireError).call(this, err); if (err instanceof Response) return err; return internalServerError(); } }, _WorkerRouter_fireError = function _WorkerRouter_fireError(error) { const message = error instanceof Response ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : '' + error; this.dispatchEvent(new ErrorEvent('error', { message, error })); }, _WorkerRouter_execPatterns = function _WorkerRouter_execPatterns(fqURL, request, routes = __classPrivateFieldGet(this, _WorkerRouter_routes, "f")) { for (const { method, pattern, handler } of routes) { if (method !== 'ANY' && method !== request.method.toUpperCase()) continue; const match = pattern.exec(fqURL); if (!match) continue; // @ts-ignore: FIXME return [handler, match]; } return null; }, _WorkerRouter_pushRoute = function _WorkerRouter_pushRoute(method, pattern, handler) { __classPrivateFieldGet(this, _WorkerRouter_routes, "f").push({ method, pattern, handler: async (event) => { const ctx = await __classPrivateFieldGet(this, _WorkerRouter_middleware, "f").call(this, event); const response = handler(event.request, ctx); return executeEffects(event.effects, response); }, }); }, _WorkerRouter_pushMiddlewareRoute = function _WorkerRouter_pushMiddlewareRoute(method, pattern, middleware, handler) { __classPrivateFieldGet(this, _WorkerRouter_routes, "f").push({ method, pattern, handler: async (event) => { const ctx = await middleware(__classPrivateFieldGet(this, _WorkerRouter_middleware, "f").call(this, event)); const response = handler(event.request, ctx); return executeEffects(event.effects, response); }, }); }, _WorkerRouter_registerPattern = function _WorkerRouter_registerPattern(method, argsN, pattern, middlewareOrHandler, handler) { if (argsN === 2) { const handler = middlewareOrHandler; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_pushRoute).call(this, method, pattern, handler); } else if (argsN === 3) { const middleware = middlewareOrHandler; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_pushMiddlewareRoute).call(this, method, pattern, middleware, handler); } else { throw Error(`Router '${method.toLowerCase()}' called with invalid number of arguments`); } return this; }, _WorkerRouter_registerRecoverPattern = function _WorkerRouter_registerRecoverPattern(method, argsN, pattern, middlewareOrHandler, handler) { if (argsN === 2) { const handler = middlewareOrHandler; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_pushRecoverRoute).call(this, method, pattern, handler); } else if (argsN === 3) { const middleware = middlewareOrHandler; __classPrivateFieldGet(this, _WorkerRouter_instances, "m", _WorkerRouter_pushMiddlewareRecoverRoute).call(this, method, pattern, middleware, handler); } else { throw Error(`Router '${method.toLowerCase()}' called with invalid number of arguments`); } return this; }, _WorkerRouter_pushRecoverRoute = function _WorkerRouter_pushRecoverRoute(method, pattern, handler) { __classPrivateFieldGet(this, _WorkerRouter_recoverRoutes, "f").push({ method, pattern, handler: (event) => { const response = handler(event.request, event); return executeEffects(event.effects, response); }, }); }, _WorkerRouter_pushMiddlewareRecoverRoute = function _WorkerRouter_pushMiddlewareRecoverRoute(method, pattern, middleware, handler) { __classPrivateFieldGet(this, _WorkerRouter_recoverRoutes, "f").push({ method, pattern, handler: async (event) => { const ctx = await middleware(event); const response = handler(event.request, ctx); return executeEffects(event.effects, response); }, }); }; //# sourceMappingURL=index.js.map