-
Notifications
You must be signed in to change notification settings - Fork 62
Description
I was using hono-openapi
package for openapi, and want to migrate to chanafa, I like chanafa's way to define endpoints with class.
But one big problem I have with chanafa is type inference on context for middleware.
Example (with regular hono code)
export interface AccountContext {
account: NonNullable<Awaited<ReturnType<typeof accountService.getAccount>>>
}
export const withAccountMiddleware = (): MiddlewareHandler<
AppContextTypes & { Variables: AccountContext }
> => {
return async (c, next) => {
const account = await accountService.getAccount(c.req.valid("query").accountId)
c.set("account", account!)
await next()
}
}
const app = new Hono()
app.get("/middleware-demo", withAccountMiddleware(), (c) => {
const account = c.get("account")
return c.json(account)
})
When withAccountMiddleware
middleware is added to a route, its context types are automatically inherited.
TypeScript LSP knows c
has account
and c.get("account")
type.
If I didn't add withAccountMiddleware()
, then c.get("account")
will show error warnings provided by typescript. So I can confidently use account directly without checking null or checking undefined.
But in Chanafa, routing and class endpoints are defined separately, middlewares are defined with routing.
The classes has no prior knowledge of the middlewares added before them.
I have to assume that account exists within a custom Context.
type AccountBindings = {
Variables: {
binanceAPI: BinanceAPI
account: NonNullable<Awaited<ReturnType<typeof accountService.getAccount>>>
}
}
type AppContextWithAccount = Context<AccountBindings>
class Endpint extends OpenAPIRoute {
async handle(c: AppContextWithAccount) {
const account = c.get("account")
...
}
}
If I forgot to add the middleware to inject account into context, there is no compile time warning. I will have to find out in runtime, with manual testing, unit testing or production environment error.
So I have to check whether account
is null or undefined.
A slightly better solution:
The best workaround I found so far is to make an abstract class route like this, and call this.getAccount()
instead of c.get("account")
. So at least in runtime I get a proper error but not error with un-informative message like trying to access field "x" on undefined
.
abstract class BinanceAccountRoute extends OpenAPIRoute {
// Type-safe getter for account - will cause compile error if context doesn't have it
protected getAccount(c: AppContextWithAccount) {
const account = c.get("account")
if (!account) {
throw new HTTPException(500, { message: "Account not available in context" })
}
return account
}
// Override the handle method signature to require AppContextWithAccount
abstract handle(c: AppContextWithAccount): Promise<any> | any
}
However this is still not ideal, the error could only be detected in runtime, not compile time.