oakserver / oak

A middleware framework for handling HTTP with Deno, Node, Bun and Cloudflare Workers 🐿️ 🦕
https://oakserver.org
MIT License
5.09k stars 231 forks source link

How to pass dynamic router context type to methods function agruments #524

Open pheynnx opened 2 years ago

pheynnx commented 2 years ago

So in the example below I am making a new oak router, and then I am using the .get method with takes a path then ...middlwares. In the /:id line, there is a anonymous arrow function with the argument ctx. This ctx object has an infered type that is passed by the .get method, and it is dynamic based on the first path string arugment. So ctx in this case is excepting their to be a parameter object with the key id, which is awesome for type safetly and ide intelligence. The ctx argument also has inferred types that carry to the ctx.respone and ctx.request nested objects and this is great.

The issue is I like to split my routers in one file and my router handlers in a different file, like in the router.get('/', handler) line. The handler function has a ctx argument but now its type is any and looses the inferred router context type. Obviously I can still code but then you kinda loose the point of typescript here, and your fly somewhat blind.

I would normally just create a custom type and pass it to the handler function for the arguments, but Oaks router has a dyanmic type that is determined by the first path argument, so there is no one size fits all type of ctx.

Is there is a way to pass the inferred dynamic type over? I know the docs show the middleware being created right on the .get method like router.get('/:id', (ctx) => {}); but that just makes a lot of messy code. I perfer to seperate my router, middleware, and handlers.

const router = new oak.Router();

const handler = async (ctx) => {};

router.get('/', handler)
router.get('/:id', (ctx) => {});

Thank you! :D

pheynnx commented 2 years ago

I actually came up with a pretty good generic solution that allows for type compile time type checking.

type oakHandler<T extends string> = (
  ctx: oak.RouterContext<T>
) => Promise<void> | void;

blogRouter.get("/", getBlogList).get("/:id", getBlogById);

const getBlogById: oakHandler<"/:id"> = async (ctx) => {};

This allows me to seperate by code base and I can just pass a path string to on oak.RouterContext and it will preserve type safety and now my ctx on my handler is type aware and also ctx.params is object aware!