Skip to content

fix(types): type fastify.jwt as namespace map when namespaces are declared - #422

Merged
Eomm merged 2 commits into
fastify:mainfrom
xianjianlf2:fix/namespace-jwt-decorator-type-395
Jul 26, 2026
Merged

fix(types): type fastify.jwt as namespace map when namespaces are declared#422
Eomm merged 2 commits into
fastify:mainfrom
xianjianlf2:fix/namespace-jwt-decorator-type-395

Conversation

@xianjianlf2

Copy link
Copy Markdown
Contributor

Fixes #395 (also addresses the fastify.jwt half of #321).

Problem

When registering the plugin with the namespace option, fastify.jwt becomes a { [namespace]: JWT } map at runtime (index.js), but the type declaration hardcodes FastifyInstance.jwt as JWT. Because TypeScript declaration merging cannot override a conflicting property type, users have no way to correct this on their side — app.jwt.myNamespace.sign(...) always fails to typecheck even though it works at runtime.

Fix

Types-only, no runtime change. fastify.jwt is now typed with a new exported JwtDecorator conditional type. It defaults to JWT, so existing non-namespace users are completely unaffected. Namespace users declare their registered namespaces through the existing FastifyJWT declaration-merging interface (the same mechanism already used for payload/user):

declare module '@fastify/jwt' {
  interface FastifyJWT {
    namespaces: 'auth' | 'admin'
  }
}
// app.jwt.auth.sign(...) / app.jwt.admin.verify(...) now typecheck

The conditional uses [Namespaces] extends [string] to avoid distributing over the union (Record<'auth', JWT> | Record<'admin', JWT> would be wrong).

Note on #397: the registration-scoped types rework will touch types/index.d.ts too, but it is blocked on unmerged fastify core work and explicitly keeps namespaced decorators on declaration-merging helpers, so this interim non-breaking fix is complementary rather than competing.

Tests

New types/namespace.tst.ts asserts the map shape (Record<'auth' | 'admin', JWT>), per-namespace JWT methods, and negatives: the single-decorator members (sign/verify/decode) and undeclared namespaces are not present. tstyche checks test files in isolation, so the augmentation does not affect the existing default-mode assertions in types/index.tst.ts.

README: documented the pattern in the Namespace → TypeScript section.

Checklist

  • run npm run test — lint clean, 176/176 unit tests (100% coverage), 35/35 type assertions
  • tests are included
  • documentation is changed or added
  • commit message and code follows the Developer's Certificate of Origin and the Code of Conduct

xianjianlf2 and others added 2 commits July 14, 2026 17:30
…lared

When the plugin is registered with the `namespace` option, `fastify.jwt`
is a namespace -> JWT map at runtime, but the type declaration hardcoded
it as `JWT`. Since declaration merging cannot override a conflicting
property type, users had no way to fix it on their side.

Type the decorator with a new `JwtDecorator` conditional type: it stays
`JWT` by default and resolves to `Record<Namespaces, JWT>` when users
declare their registered namespaces via the existing `FastifyJWT`
declaration-merging interface:

    declare module '@fastify/jwt' {
      interface FastifyJWT {
        namespaces: 'auth' | 'admin'
      }
    }

Non-namespace users are unaffected. Adds tstyche coverage for the
namespace map shape and documents the pattern in the README.

Closes fastify#395
@Eomm
Eomm merged commit 66291d8 into fastify:main Jul 26, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(types): fastify.jwt always typed as JWT even with namespace mode

3 participants