@worker-tools/router
Version:
A router for Worker Runtimes such as Cloudflare Workers and Service Workers.
375 lines • 21.9 kB
JavaScript
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