convex
Version:
Client for the Convex Cloud
135 lines (134 loc) • 4.72 kB
JavaScript
;
import { setupAuth } from "./impl/authentication_impl.js";
import { setupHttpCalls } from "./impl/http_impl.js";
import { performJsSyscall } from "./impl/syscall.js";
const routableMethods = [
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS",
"PATCH"
];
function normalizeMethod(method) {
if (method === "HEAD")
return "GET";
return method;
}
export const httpRouter = () => new HttpRouter();
export class HttpRouter {
constructor() {
this.exactRoutes = /* @__PURE__ */ new Map();
this.prefixRoutes = /* @__PURE__ */ new Map();
this.isRouter = true;
this.route = (spec) => {
if (!spec.handler)
throw new Error(`route requires handler`);
if (!spec.method)
throw new Error(`route requires method`);
const { method, handler } = spec;
if (!routableMethods.includes(method)) {
throw new Error(
`'${method}' is not an allowed HTTP method (like GET, POST, PUT etc.)`
);
}
if ("path" in spec) {
if (!spec.path.startsWith("/")) {
throw new Error(`path '${spec.path}' does not start with a /`);
}
const prefixes = this.prefixRoutes.get(method) || /* @__PURE__ */ new Map();
for (const [prefix, _] of prefixes.entries()) {
if (spec.path.startsWith(prefix)) {
throw new Error(
`${spec.method} path ${spec.path} is shadowed by pathPrefix ${prefix}`
);
}
}
const methods = this.exactRoutes.has(spec.path) ? this.exactRoutes.get(spec.path) : /* @__PURE__ */ new Map();
if (methods.has(method)) {
throw new Error(
`Path '${spec.path}' for method ${method} already in use`
);
}
methods.set(method, handler);
this.exactRoutes.set(spec.path, methods);
} else {
if (!spec.pathPrefix.startsWith("/")) {
throw new Error(`path '${spec.pathPrefix}' does not start with a /`);
}
if (!spec.pathPrefix.endsWith("/")) {
throw new Error(`pathPrefix ${spec.pathPrefix} must end with a /`);
}
const prefixes = this.prefixRoutes.get(method) || /* @__PURE__ */ new Map();
for (const [prefix, _] of prefixes.entries()) {
if (spec.pathPrefix.startsWith(prefix)) {
throw new Error(
`${spec.method} pathPrefix ${spec.pathPrefix} is shadowed by pathPrefix ${prefix}`
);
}
}
prefixes.set(spec.pathPrefix, handler);
this.prefixRoutes.set(method, prefixes);
}
};
this.getRoutes = () => {
const exactPaths = [...this.exactRoutes.keys()].sort();
const exact = exactPaths.flatMap(
(path) => [...this.exactRoutes.get(path).keys()].sort().map(
(method) => [path, method, this.exactRoutes.get(path).get(method)]
)
);
const prefixPathMethods = [...this.prefixRoutes.keys()].sort();
const prefixes = prefixPathMethods.flatMap(
(method) => [...this.prefixRoutes.get(method).keys()].sort().map(
(pathPrefix) => [
`${pathPrefix}*`,
method,
this.prefixRoutes.get(method).get(pathPrefix)
]
)
);
return [...exact, ...prefixes];
};
this.lookup = (path, method) => {
method = normalizeMethod(method);
const exactMatch = this.exactRoutes.get(path)?.get(method);
if (exactMatch)
return [exactMatch, method, path];
const prefixes = this.prefixRoutes.get(method) || /* @__PURE__ */ new Map();
for (const [pathPrefix, endpoint] of prefixes.entries()) {
if (path.startsWith(pathPrefix)) {
return [endpoint, method, `${pathPrefix}*`];
}
}
return null;
};
this.runRequest = async (argsStr) => {
const request = performJsSyscall("requestFromConvexJson", {
convexJson: JSON.parse(argsStr)
});
const pathname = new URL(request.url).pathname;
const method = request.method;
const match = this.lookup(pathname, method);
if (!match) {
const response2 = new Response(`No HttpEndpoint routed for ${pathname}`, {
status: 404
});
return JSON.stringify(
performJsSyscall("convexJsonFromResponse", { response: response2 })
);
}
const [endpoint, _method, _path] = match;
const calls = setupHttpCalls();
const ctx = {
...calls,
auth: setupAuth()
};
const response = await endpoint(ctx, request);
return JSON.stringify(
performJsSyscall("convexJsonFromResponse", { response })
);
};
}
}
//# sourceMappingURL=router.js.map