Skip to content

fix(express): ship the Express.Request type augmentation - #213

Merged
Bccorb merged 1 commit into
mainfrom
fix/express-request-types
Oct 13, 2026
Merged

Bccorb merged 1 commit into
mainfrom
fix/express-request-types

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Bug

@seamless-auth/express@0.20.0 does not ship its Express.Request augmentation. In a TypeScript consumer, req.user set by requireAuth does not typecheck unless the app declares the augmentation itself.

Root cause: the augmentation lived in packages/express/src/types/express.d.ts, an ambient declaration file. tsc does not copy .d.ts sources into its output, and nothing in the entry referenced it, so it never reached dist. In-repo it appeared to work only because it was in the src program (and skipLibCheck hid that its SeamlessAuthUser reference did not resolve). The typeRoots entry in tsconfig.json pointed at a path that does not exist.

@seamless-auth/fastify@0.11.1 ships its FastifyRequest augmentation (in dist/options.d.ts) and it works under moduleResolution: "bundler", but it is lost under "NodeNext": the entry reaches it only through the extensionless export type ... from "./options", which NodeNext does not resolve.

Reproduction

Published tarballs (npm pack @seamless-auth/express@0.20.0 @seamless-auth/fastify@0.11.1) installed into a scratch consumer with strict tsc --noEmit:

import { requireAuth } from "@seamless-auth/express";
app.get("/me", requireAuth(opts), (req, res) => res.json(req.user));
  • bundler: express fails with TS2339: Property 'user' does not exist on type 'Request<...>'; fastify passes.
  • NodeNext: express fails the same way; fastify fails on request.user and request.cookiePayload.

Fix

  • Express: replace the ambient file with a module, src/types/express.ts, that uses declare global { namespace Express { interface Request { ... } } } plus export {}, and import it from src/index.ts. tsc now emits dist/types/express.d.ts and dist/index.d.ts starts with import "./types/express.js", so importing the package applies it. The augmentation also gains cookiePayload, which createEnsureCookiesMiddleware sets, matching what Fastify already declares. user is typed as core's SeamlessAuthUser, which is what requireAuth assigns.
  • Fastify: move the declare module "fastify" block out of options.ts into src/types/fastify.ts, imported from the entry the same way.
  • Both entry imports use a .js extension so they resolve under NodeNext as well as bundler. The runtime bundle is unchanged (the module is type-only and tsup drops it).
  • Remove the dead typeRoots setting from the express tsconfig.json.

Regression tests

packages/{express,fastify}/tests/consumerTypes.test.js run tsc --noEmit --strict against tests/types/consumer.ts, which imports the package by name. It resolves through the package's own exports to the built dist, as an installed consumer would. Each runs under bundler and NodeNext resolution and asserts no diagnostics. Before the fix, express fails both cases and fastify fails NodeNext.

Verification

  • pnpm build: passes.
  • pnpm test: core 408, express 213, fastify 169, nextjs 166, all passing.
  • tsc --noEmit -p tsconfig.json in each package: passes.
  • pnpm check:types-current: passes.
  • Pack and consume: pnpm pack of the fixed express and fastify, installed into the same scratch consumer. tsc --noEmit passes under both bundler and NodeNext.

Out of scope, noted

Under NodeNext, every extensionless relative import in the emitted .d.ts files of all three adapters fails to resolve, so the exported function types collapse to any. That is hidden by skipLibCheck and is a separate fix. This PR only makes sure the request augmentation still loads there.

No API contract change for seamless-auth-api or the SDKs.

The augmentation that types req.user lived in src/types/express.d.ts, an
ambient file tsc never copies into dist, so the published package left
req.user untyped and consumers had to declare it themselves.

Move it into a module (src/types/express.ts) imported from the entry, and
add req.cookiePayload, which createEnsureCookiesMiddleware sets. The
FastifyRequest augmentation moves the same way so it also loads under
NodeNext, where the entry's extensionless relative imports do not resolve.

Add consumer type tests that typecheck a fixture against the built dist
under bundler and NodeNext resolution.
@Bccorb
Bccorb merged commit f0d50f7 into main Oct 13, 2026
4 checks passed
@Bccorb
Bccorb deleted the fix/express-request-types branch October 15, 2026 03:12
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.

1 participant