From a4e06ead2dc59a7eede7cadcf28f02efba6bb6a3 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Wed, 4 Mar 2026 14:32:57 +0200 Subject: [PATCH] fix: make emitted .d.ts self-contained for non-DOM environments 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. --- packages/better-fetch/tsup.config.ts | 129 ++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/packages/better-fetch/tsup.config.ts b/packages/better-fetch/tsup.config.ts index 1d6ae17..d05a302 100644 --- a/packages/better-fetch/tsup.config.ts +++ b/packages/better-fetch/tsup.config.ts @@ -1,11 +1,138 @@ import { defineConfig } from "tsup"; +/** + * Fetch API type stubs prepended to built .d.ts files. + * + * The built .d.ts references Fetch API types (Response, Headers, RequestInit, etc.) + * as bare identifiers from lib.dom.d.ts. Consumers whose tsconfig does not include + * "DOM" in lib (e.g., backend projects with lib: ["ESNext"]) get TS2304 errors. + * + * These module-scoped declarations shadow DOM globals when DOM is present and provide + * the necessary types when it's absent. Since .d.ts files with export statements are + * modules, these declarations are file-scoped and do not pollute the global namespace. + */ +const FETCH_API_STUBS = `// -- Fetch API type stubs (self-contained for non-DOM environments) -- + +// Simple string-literal union types +type RequestCache = "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload"; +type RequestCredentials = "include" | "omit" | "same-origin"; +type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin"; +type RequestPriority = "auto" | "high" | "low"; +type RequestRedirect = "error" | "follow" | "manual"; +type ReferrerPolicy = "" | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url"; + +// Timer — opaque handle compatible with all runtimes +type Timer = number | { ref(): void; unref(): void }; + +// ReadableStream — minimal stub for body types +interface ReadableStream { + readonly locked: boolean; + getReader(): any; +} + +// Fetch API interfaces — minimal subset covering usage in this package +interface Headers { + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + has(name: string): boolean; + set(name: string, value: string): void; + forEach(callbackfn: (value: string, key: string, parent: Headers) => void): void; +} +type HeadersInit = [string, string][] | Record | Headers; + +interface AbortSignal { + readonly aborted: boolean; + readonly reason: any; +} + +interface AbortController { + readonly signal: AbortSignal; + abort(reason?: any): void; +} + +interface Blob { + readonly size: number; + readonly type: string; + text(): Promise; + arrayBuffer(): Promise; +} +declare var Blob: { new(blobParts?: any[], options?: any): Blob; prototype: Blob; }; + +interface File extends Blob { + readonly name: string; + readonly lastModified: number; +} +declare var File: { new(fileBits: any[], fileName: string, options?: any): File; prototype: File; }; + +interface URL { + href: string; + readonly origin: string; + protocol: string; + host: string; + hostname: string; + port: string; + pathname: string; + search: string; + hash: string; + toString(): string; +} + +interface Response { + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly headers: Headers; + readonly body: ReadableStream | null; + readonly bodyUsed: boolean; + readonly url: string; + readonly redirected: boolean; + json(): Promise; + text(): Promise; + blob(): Promise; + arrayBuffer(): Promise; + clone(): Response; +} + +interface RequestInit { + method?: string; + headers?: HeadersInit; + body?: any; + mode?: RequestMode; + credentials?: RequestCredentials; + cache?: RequestCache; + redirect?: RequestRedirect; + referrer?: string; + referrerPolicy?: ReferrerPolicy; + integrity?: string; + keepalive?: boolean; + signal?: AbortSignal | null; + window?: null; + priority?: RequestPriority; + duplex?: string; +} + +declare namespace globalThis { + interface Request { + readonly url: string; + readonly method: string; + readonly headers: Headers; + readonly body: ReadableStream | null; + clone(): globalThis.Request; + } +} + +// -- End Fetch API type stubs -- +`; + export default defineConfig({ entry: ["./src/index.ts"], splitting: false, sourcemap: true, format: ["esm", "cjs"], - dts: true, + dts: { + banner: FETCH_API_STUBS, + }, clean: true, external: ["zod"], });