[Question/Bug] Is it a "fromThrowable" bug with jwt.verify?
Lp-Francois opened this issue ยท 3 comments
Hello ๐
I was playing around with fromThrowable
to wrap functions from libraries when I realised the return type didn't seem correct with the jsonwebtoken
library and verify()
. Here is an example:
Install: npm install jsonwebtoken neverthrow
// index.ts
import jwt from 'jsonwebtoken';
import { Result, fromThrowable } from 'neverthrow';
// overloads (to simulate same behaviour as jwt.verify)
function dummyFn(a: number): number;
function dummyFn(a?: number): number | undefined;
function dummyFn(a: number, b?: string): number | string {
if (Date.now() % 2 === 0) return undefined;
if (b) {
return b;
}
if (a > 5) throw new Error('a is too big');
return a;
}
// โก๏ธ Example with JSON.parse
const safeParse = fromThrowable(JSON.parse);
const result1 = safeParse('{'); // result1 type Result<any, unknown> โ
// โก๏ธ Example with jwt.verify
const safeJWTVerify = fromThrowable(jwt.verify);
const result2 = safeJWTVerify('jwtToken', 'jwtSecret'); // result2 type Result<void, unknown> โ
// โก๏ธ Example with dummyFn
const safeDummyFn = fromThrowable(dummyFn);
const result3 = safeDummyFn(7); // result3 type Result<number, unknown> โ
JSON.parse is like the example on the README. The function returns any
, so result2
returns Result<any, unknown>
, and it is correct.
When trying with jwt.verify
, it returns type jwt.Jwt (+6 overloads)
. After using safeJWTVerify, it currently returns Result<void, unknown>
instead of Result<jwt.Jwt, unknown>
. Is it a bug?
I created a dummyFn
function to see if it would cause the same error when it has overloads, like jwt.verify does, but it returns correctly type Result<number, unknown>
. Any idea what could be the issue?
2 things come to mind:
-
you're calling
fromThrowable
directly, but the documentation states thatfromThrowable
should be called like it's a static method onResult
. See https://github.com/supermacro/neverthrow#resultfromthrowable-static-class-method for the explanation. -
Looking at the content of the
@types/jsonwebtoken
typings,jwt.verify
does have 7 implementations, so chances are when you use your wrapper, it's defaulting to one of the overloads that's expecting a callback instead of returning a value, which is why you're ending up withResult<void, unknown>
. You can see the overloads with no return value here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f3b3be99b304f5049338884c70d49a41d39730c2/types/jsonwebtoken/index.d.ts#L232
So in order to narrow the type for the Result, you need to specify the signature of the overload that contains the return type you're after. Something like this would work:
import jwt, { VerifyOptions } from 'jsonwebtoken';
import { Result } from 'neverthrow';
// Returns Result<string | jwt.JwtPayload | jwt.Jwt, unknown>
const safeVerify = (token: string, secret: string, options?: VerifyOptions) => {
return Result.fromThrowable(() => jwt.verify(token, secret, options));
}
// Returns Result<jwt.Jwt, unknown>
const safeVerifyComplete = (token: string, secret: string, options: VerifyOptions & { complete: false }) => {
return Result.fromThrowable(() => jwt.verify(token, secret, options));
}
// Returns Result<string | jwt.JwtPayload, unknown>
const safeVerifyIncomplete = (token: string, secret: string, options: VerifyOptions & { complete: true }) => {
return Result.fromThrowable(() => jwt.verify(token, secret, options));
}
Addendum: the reason you're not getting Result<jwt.JWT, unknown>
in your example is that when your wrapper is called:
const result2 = safeJWTVerify('jwtToken', 'jwtSecret');
it's only called with 2 arguments, and the overload that returns a jwt.JWT
type must have the options defined, with the complete
property set to true. See https://github.com/DefinitelyTyped/DefinitelyTyped/blob/f3b3be99b304f5049338884c70d49a41d39730c2/types/jsonwebtoken/index.d.ts#L219 for the signature.
All said, this isn't a bug with neverthrow, your wrapper is just too broad for TS to correctly infer the correct return type.
@jsec Thanks so much! Super clear explanations :)
I will narrow it down then, to fix the issue which is not an issue ๐
Thanks a lot ๐ and have a nice week