When adding configuration to middleware, helpers, or client functions, favor a single extensible options object over growing lists of positional parameters or narrow convenience flags. Concretely:
type VerifyOptions = { alg?: SignatureAlgorithm; issuer?: string | RegExp; audience?: string | string[] }
export const verify = async (
token: string,
publicKey: SignatureKey,
algOrOptions?: SignatureAlgorithm | VerifyOptions
): Promise<JWTPayload> => {
const options = typeof algOrOptions === 'string' ? { alg: algOrOptions } : algOrOptions ?? {}
// ...
}
contentTypesAsBinary: (contentType: string) => boolean instead of contentTypesAsBinary: string[]. This lets users compose with sensible defaults instead of being locked into a static list.Context (or Request/Response) into user-supplied callbacks whenever useful, so consumers aren’t limited to the data the option itself exposes (e.g. onError?: (remote, c: Context) => Response, generator?: (c: Context) => string, generateParams?: (c: Context) => SSGParams). For hooks that need both request and response, pass a single object ({ request, response }) rather than separate positional args, which also keeps the signature stable if more fields are added later.prefix: 'host' | 'secure' rather than hostPrefix?: boolean; securePrefix?: boolean.vary?: string | string[].onAuthSuccess: (c, username) => c.set('username', username), don’t add a redundant usernameContextKey option.Applying these guidelines keeps public APIs small, composable, and resilient to future growth, while still giving consumers the flexibility to handle edge cases themselves.