From 0de91606a357e126e4ecd6e2de8db6ed2a2da243 Mon Sep 17 00:00:00 2001 From: Endel Dreyer Date: Sun, 26 Oct 2025 12:01:46 -0300 Subject: [PATCH 1/2] router: expose 'addEndpoint' to allow to dynamically add endpoints --- src/router.test.ts | 29 +++++++++++++++++++++++++++++ src/router.ts | 15 +++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/router.test.ts b/src/router.test.ts index 5e9cb13..6903f4d 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -397,3 +397,32 @@ describe("error handling", () => { expect(body.message).toBe("Resource not found"); }); }); + +describe("addEndpoint", () => { + it("should add an endpoint to existing router", async () => { + const router = createRouter({}); + + router.addEndpoint(createEndpoint( + "/dynamic_added", + { + method: "POST", + }, + async (c) => { + return { message: "dynamically added", body: c.body }; + }, + )); + + const dynamicRequest = new Request("http://localhost/dynamic_added", { + method: "POST", + body: JSON.stringify({ test: "data" }), + headers: { + "Content-Type": "application/json", + }, + }); + const dynamicResponse = await router.handler(dynamicRequest); + expect(dynamicResponse.status).toBe(200); + const jsonResponse = await dynamicResponse.json(); + expect(jsonResponse.message).toBe("dynamically added"); + expect(jsonResponse.body).toEqual({ test: "data" }); + }); +}); diff --git a/src/router.ts b/src/router.ts index 7c7f3cb..8376e9f 100644 --- a/src/router.ts +++ b/src/router.ts @@ -99,12 +99,7 @@ export const createRouter = , Config extends const router = createRou3Router(); const middlewareRouter = createRou3Router(); - for (const endpoint of Object.values(endpoints)) { - if (!endpoint.options) { - continue; - } - if (endpoint.options?.metadata?.SERVER_ONLY) continue; - + const addEndpoint = (endpoint: Endpoint) => { const methods = Array.isArray(endpoint.options?.method) ? endpoint.options.method : [endpoint.options?.method]; @@ -112,6 +107,13 @@ export const createRouter = , Config extends for (const method of methods) { addRoute(router, method, endpoint.path, endpoint); } + }; + + for (const endpoint of Object.values(endpoints)) { + if (!endpoint.options || endpoint.options?.metadata?.SERVER_ONLY) { + continue; + } + addEndpoint(endpoint); } if (config?.routerMiddleware?.length) { @@ -226,6 +228,7 @@ export const createRouter = , Config extends }; return { + addEndpoint, handler: async (request: Request) => { const onReq = await config?.onRequest?.(request); if (onReq instanceof Response) { From 3749cf794fd0e0cd3393ad50351aa534f831cbd1 Mon Sep 17 00:00:00 2001 From: Endel Dreyer Date: Sun, 26 Oct 2025 19:12:42 -0300 Subject: [PATCH 2/2] expose .extend() instead of .addEndpoint() for better type inference --- src/router.test.ts | 21 ++++++++++++++------- src/router.ts | 22 +++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/router.test.ts b/src/router.test.ts index 6903f4d..daa126b 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, assert } from "vitest"; import { createEndpoint, type Endpoint } from "./endpoint"; import { createRouter } from "./router"; import { z } from "zod"; @@ -398,11 +398,13 @@ describe("error handling", () => { }); }); -describe("addEndpoint", () => { - it("should add an endpoint to existing router", async () => { - const router = createRouter({}); +describe("extend", () => { + it("should extend the router with new endpoints", async () => { + const initialEndpoint = createEndpoint("/initial", { method: "GET" }, async () => ({})); + const router = createRouter({ initialEndpoint }); + assert.ok(router.endpoints.initialEndpoint.path === "/initial"); - router.addEndpoint(createEndpoint( + const dynamicEndpoint = createEndpoint( "/dynamic_added", { method: "POST", @@ -410,7 +412,11 @@ describe("addEndpoint", () => { async (c) => { return { message: "dynamically added", body: c.body }; }, - )); + ); + + const extendedRouter = router.extend({ dynamicEndpoint }); + assert.ok(extendedRouter.endpoints.initialEndpoint.path === "/initial"); + assert.ok(extendedRouter.endpoints.dynamicEndpoint.path === "/dynamic_added"); const dynamicRequest = new Request("http://localhost/dynamic_added", { method: "POST", @@ -419,10 +425,11 @@ describe("addEndpoint", () => { "Content-Type": "application/json", }, }); - const dynamicResponse = await router.handler(dynamicRequest); + const dynamicResponse = await extendedRouter.handler(dynamicRequest); expect(dynamicResponse.status).toBe(200); const jsonResponse = await dynamicResponse.json(); expect(jsonResponse.message).toBe("dynamically added"); expect(jsonResponse.body).toEqual({ test: "data" }); }); + }); diff --git a/src/router.ts b/src/router.ts index 8376e9f..0b37d9a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -99,7 +99,10 @@ export const createRouter = , Config extends const router = createRou3Router(); const middlewareRouter = createRou3Router(); - const addEndpoint = (endpoint: Endpoint) => { + for (const endpoint of Object.values(endpoints)) { + if (!endpoint.options || endpoint.options?.metadata?.SERVER_ONLY) { + continue; + } const methods = Array.isArray(endpoint.options?.method) ? endpoint.options.method : [endpoint.options?.method]; @@ -107,13 +110,6 @@ export const createRouter = , Config extends for (const method of methods) { addRoute(router, method, endpoint.path, endpoint); } - }; - - for (const endpoint of Object.values(endpoints)) { - if (!endpoint.options || endpoint.options?.metadata?.SERVER_ONLY) { - continue; - } - addEndpoint(endpoint); } if (config?.routerMiddleware?.length) { @@ -228,7 +224,6 @@ export const createRouter = , Config extends }; return { - addEndpoint, handler: async (request: Request) => { const onReq = await config?.onRequest?.(request); if (onReq instanceof Response) { @@ -243,6 +238,15 @@ export const createRouter = , Config extends return res; }, endpoints, + + /** + * Extend the router with new endpoints + * @param newEndpoints new endpoints to extend the router with + * @returns new router with additional endpoints + */ + extend: >(newEndpoints: NE) => { + return createRouter({ ...endpoints, ...newEndpoints } as E & NE, config); + }, }; };