Type annotation for named parameters
See original GitHub issueI’m now building my first Cloudflare Workers app, and the experience with Hono has been great so far!
During the development, I wish I could annotate types for named parameters. For example, if a named parameter is in the form of :param@number
, c.req.param("param")
evaluates to a value of type number
. If no type is annotated (i.e., :param
), its type should fall back to string
. This is inspired by a similar feature of aspida.
With this syntax, users don’t need to validate or convert number parameter on their own, so the code becomes simpler and less. Would it be possible to add this feature?
Below is a type-level PoC of my proposal (TS Playground link). I’m not familiar with the internal of Hono yet, so not sure about how easy/difficult it is to integrate this into your codebase.
type ExampleRoute = "/api/:author/:title@string/:price@number/:param@invalid";
declare const getterImpl: <
Route extends string,
Param extends EnumParams<Route>
>(
param: Param
) => InferParamTypeFromRoute<Route, Param>;
const getter = <Param extends EnumParams<ExampleRoute>>(param: Param) =>
getterImpl<ExampleRoute, Param>(param);
const author = getter("author"); // string
const title = getter("title"); // string
const price = getter("price"); // number
const param = getter("param"); // never
// @ts-expect-error
const nonExistent = getter("non-existent");
type InferParamTypeFromRoute<
Route extends string,
Param extends EnumParams<Route>
> = GetTypeFromObjectUnion<EnumParamTypes<SplitRoute<Route>>, Param>;
// EnumParams<"/:foo/:bar@number"> --> "foo" | "bar"
type EnumParams<Route extends string> = EnumParamTypes<
SplitRoute<Route>
> extends infer T
? T extends T
? keyof T
: never
: never;
// GetTypeFromObjectUnion<{ foo: string } | { bar: number }, "bar"> --> number
type GetTypeFromObjectUnion<T, K extends string> = T extends T
? K extends keyof T
? T[K]
: never
: never;
// EnumParamTypes<[":foo", ":bar@number"]> --> { foo: string } | { bar: number }
type EnumParamTypes<Params extends string[]> = {
[I in keyof Params]: Params[I] extends `:${infer Name}@${infer Type}`
? Record<Name, TypeNameToPrimitive<Type>>
: Params[I] extends `:${infer Name}`
? Record<Name, string>
: never;
}[number];
type TypeNameToPrimitive<T extends string> = T extends "string"
? string
: T extends "number"
? number
: never;
type SplitRoute<R extends string> = R extends `/${infer Head}/${infer Rest}`
? [Head, ...SplitRoute<`/${Rest}`>]
: R extends `/${infer Root}`
? [Root]
: [];
Issue Analytics
- State:
- Created a year ago
- Comments:10 (2 by maintainers)
Top GitHub Comments
Okay, I’ll take on that. I suppose I can have some time this weekend.
@yudai-nkt @usualoma It was an interesting discussion, Thank you!