Skip to content

fix: make emitted .d.ts self-contained for non-DOM environments#88

Open
YevheniiKotyrlo wants to merge 1 commit intobetter-auth:mainfrom
YevheniiKotyrlo:fix/dom-type-declarations
Open

fix: make emitted .d.ts self-contained for non-DOM environments#88
YevheniiKotyrlo wants to merge 1 commit intobetter-auth:mainfrom
YevheniiKotyrlo:fix/dom-type-declarations

Conversation

@YevheniiKotyrlo
Copy link
Copy Markdown

Problem

The built .d.ts references Fetch API types (Response, Headers, RequestInit, AbortSignal, URL, Blob, File, etc.) as bare identifiers from lib.dom.d.ts. Consumers whose tsconfig does not include "DOM" in lib — a standard configuration for backend projects using lib: ["ESNext"] — get 88 TS2304 errors when skipLibCheck: false.

This has been reported before in the better-auth ecosystem: better-auth#1550 mentions the Timer type leak from this package as one of the issues breaking skipLibCheck: false consumers.

Reproduction

mkdir repro && cd repro
npm init -y
npm add @better-fetch/fetch@1.1.21 typescript@5

tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "skipLibCheck": false,
    "moduleResolution": "bundler",
    "module": "ESNext",
    "lib": ["ESNext"]
  }
}

test.ts:

import type { BetterFetchOption } from '@better-fetch/fetch';
export type { BetterFetchOption };
npx tsc --noEmit
# 88 errors: Cannot find name 'Response', 'Headers', 'RequestInit', ...

Fix

Use tsup's native dts.banner option to prepend module-scoped Fetch API type stubs to the built .d.ts output. Since .d.ts files with export statements are ES modules, the prepended declarations are file-scoped — they shadow DOM globals when DOM is present (no conflicts) and provide the missing types when DOM is absent. No global namespace pollution.

1 file changed (tsup.config.ts), no new files, no build script changes.

Types declared

Minimal stubs covering every Fetch API type referenced in the built output:

  • String literal unions: RequestCache, RequestCredentials, RequestMode, RequestPriority, RequestRedirect, ReferrerPolicy
  • Utility types: Timer, HeadersInit
  • Interfaces: ReadableStream, Headers, AbortSignal, AbortController, Blob, File, URL, Response, RequestInit
  • Class declarations: declare var Blob, declare var File (needed for typeof usage in output option)
  • Namespace: declare namespace globalThis { interface Request } (needed for FetchEsque type)

Why this approach

The package uses Fetch API types both as type references (Response, Headers in type positions) and as runtime globals (new AbortController(), new Headers()). The runtime usage is correct — all modern JS runtimes provide the Fetch API. The problem is only in the type declarations.

A source-level ambient globals.d.ts would pollute the global namespace and potentially conflict with consumers who have lib: ["DOM"]. Module-scoped stubs in the DTS output avoid this entirely.

Verification

Verified on v1.1.21 (current npm release):

Gate Before After
tsc --noEmit (upstream) 0 errors 0 errors
pnpm build (tsup) ESM, CJS, DTS pass ESM, CJS, DTS pass (DTS ~3 KB larger)
vitest run 23 fail / 46 pass 23 fail / 46 pass (identical, pre-existing AbortSignal issue)
Consumer lib: ["ESNext"] 88 errors 0 errors
Consumer lib: ["ESNext", "DOM"] 0 errors 0 errors

Note on CI

CI will fail due to a pre-existing build breakage on main — undefined headers variable in utils.ts (introduced in #65, tracked in #85, fix in #81). This PR does not touch utils.ts.

The built .d.ts references Fetch API types (Response, Headers,
RequestInit, AbortSignal, URL, Blob, File, etc.) as bare identifiers
from lib.dom.d.ts. Consumers whose tsconfig does not include "DOM" in
lib get 88 TS2304 "Cannot find name" errors with skipLibCheck: false.

Use tsup's dts.banner option to prepend module-scoped Fetch API type
stubs to the declaration output. Since the .d.ts files are ES modules
(they contain export statements), the declarations are file-scoped:
they shadow DOM globals when DOM is present and provide the missing
types when it's absent. No global namespace pollution.
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