-
-
Notifications
You must be signed in to change notification settings - Fork 873
Closed
Labels
Description
What version of Hono are you using?
4.10.5
What runtime/platform is your app running on? (with version if possible)
Bun 1.2.13
What steps can reproduce the bug?
a typing edge case was reported on discord
- below i've included an illustrative test case adapted from the existing Env types and a path type with
app.use(path, handler...)\- test only types in/src/tests.ts
TL;DR
- when a handler or middleware is called with a
pathargument, theHonoinstancethis.#pathis updated- if the next method in the chain is also called with the
pathargument, there's no issue - if the next method is called without the
pathargument, then:- the call defaults the path to the previously-set
this.#path(expected) - the schema uses the previously-set path type (expected)
- any methods called before the middleware are also typed using the not-yet-set path (unexpected)
- the call defaults the path to the previously-set
- if the next method in the chain is also called with the
- i don't totally understand why this happens, but i think has to do with the type merging that
ChangePathOfSchemadoes- in order to resolve downstream schemas, it flattens them to the
Pathkey, but i think this also overwrites the correct (type) paths for upstream schemas - linking these PRs as they seem like relevant context:
- in order to resolve downstream schemas, it flattens them to the
Test Case
- the expected endpoints are
GET /andPOST /:id - the actual endpoints are
GET /andPOST /:id - the endpoint types are
GET /:idandPOST /:id
type Env = {
Variables: {
foo: string
}
}
const app = new Hono<Env>()
.get((c) => c.text('before'))
.use('/:id', async (_c, next) => {
await next()
})
.post((c) => c.text('after'))
type Actual = ExtractSchema<typeof app>
/**
{
'/:id': {
$get: {
input: {};
output: 'before';
outputFormat: 'text';
status: ContentfulStatusCode;
}
}
} & {
'/:id': {
$post: {
input: {
param: {
id: string;
};
};
output: 'after';
outputFormat: 'text';
status: ContentfulStatusCode;
}
}
}
*/
type Expected = {
'/': {
$get: {
input: {};
output: 'before';
outputFormat: 'text';
status: ContentfulStatusCode;
}
}
} & {
'/:id': {
$post: {
input: {
param: {
id: string;
};
};
output: 'after';
outputFormat: 'text';
status: ContentfulStatusCode;
}
}
}
// error
type verify = Expect<Equal<Expected, Actual>>What is the expected behavior?
No response
What do you see instead?
No response
Additional information
ideally the types would be in sync with the endpoint behavior. that being said, the current solution covers a majority of cases, and i'm not sure there's a performant solution with better coverage. there are a few simple rules/workarounds
- avoid calling methods/
usewithout thepathargument - front-load middleware at the top of the chain
- break routes up RESTfully
Example Workaround
const idRoute = new Hono<Env>()
.use('/:id', async (_c, next) => {
await next()
})
.post('/:id', (c) => c.text('after'))
const app = new Hono<Env>()
.get('/', (c) => c.text('before'))
.route('/', idRoute)Next Steps
- i'll update the docs to describe the path defaulting behavior when methods are called without a
pathargument- i'll include a warning about this edge-case
- i'll work on a solution when i have a bit more time, but i'm wondering whether it's worth trying to resolve at all right now
bhalbach-modular