Related: #362 (comment) — I've read the explanation of how JSDoc types work in the shipped .mjs files. This issue is specifically about the @ts-check directive and its impact on consumers.
Problem
apollo-upload-client v19 ships .mjs files with JSDoc type annotations and @ts-check at the top. This approach has a significant issue for TypeScript consumers: type errors inside the .mjs source files are surfaced as hard errors in downstream projects, with no way to suppress them.
With .d.ts files, skipLibCheck: true in tsconfig suppresses any internal type issues in library code. However, skipLibCheck only applies to .d.ts files — it does not skip .mjs files. Because TypeScript resolves .mjs files through the module graph, the @ts-check directive causes TypeScript to fully type-check the library's internal implementation, and any errors become the consumer's problem.
Specific error
With @apollo/client v4, UploadHttpLink.mjs line 121 produces:
TS2322: Type 'Record<string, string | undefined> | undefined' is not assignable to type 'Record<string, string> | undefined'.
This is a type mismatch between Apollo Client's own types — DefaultContext.headers is Record<string, string | undefined> but HttpConfig.headers (used by selectHttpOptionsAndBodyInternal) expects Record<string, string>. The apollo-upload-client code passes one to the other, and because of @ts-check, the error is raised in the consumer's tsc output.
Why this differs from .d.ts
JSDoc types in .mjs files with @ts-check are not interchangeable with .d.ts files from a consumer's perspective:
|
.d.ts files |
@ts-check + JSDoc in .mjs |
skipLibCheck: true |
Skips type-checking |
No effect |
| Internal type errors |
Hidden from consumers |
Surfaced as consumer errors |
| Consumer escape hatch |
skipLibCheck |
None (short of patching the package) |
The TypeScript documentation for skipLibCheck is clear: skipLibCheck applies to declaration files only. When a library uses .mjs with @ts-check instead of .d.ts, it removes the consumer's ability to work around internal type issues in the library.
Suggested fix
Either:
-
Ship .d.ts files alongside the .mjs files — this is the standard approach for npm packages consumed by TypeScript projects. It gives consumers skipLibCheck as an escape hatch and separates the public API contract from the internal implementation.
-
Remove @ts-check — the JSDoc types will still provide type information for consumers via inference, but internal type errors won't propagate.
Note: fixing the specific type error above (e.g. with @ts-ignore) would only address the current symptom. The core issue is that .mjs files with @ts-check are fully type-checked by consumers with no way to opt out — any future type mismatch introduced by upstream dependencies would cause the same problem again.
Environment
apollo-upload-client: 19.0.0
@apollo/client: 4.1.3
- TypeScript: 5.9
- tsconfig:
allowJs: true, maxNodeModuleJsDepth: 10
Related: #362 (comment) — I've read the explanation of how JSDoc types work in the shipped
.mjsfiles. This issue is specifically about the@ts-checkdirective and its impact on consumers.Problem
apollo-upload-clientv19 ships.mjsfiles with JSDoc type annotations and@ts-checkat the top. This approach has a significant issue for TypeScript consumers: type errors inside the.mjssource files are surfaced as hard errors in downstream projects, with no way to suppress them.With
.d.tsfiles,skipLibCheck: truein tsconfig suppresses any internal type issues in library code. However,skipLibCheckonly applies to.d.tsfiles — it does not skip.mjsfiles. Because TypeScript resolves.mjsfiles through the module graph, the@ts-checkdirective causes TypeScript to fully type-check the library's internal implementation, and any errors become the consumer's problem.Specific error
With
@apollo/clientv4,UploadHttpLink.mjsline 121 produces:This is a type mismatch between Apollo Client's own types —
DefaultContext.headersisRecord<string, string | undefined>butHttpConfig.headers(used byselectHttpOptionsAndBodyInternal) expectsRecord<string, string>. Theapollo-upload-clientcode passes one to the other, and because of@ts-check, the error is raised in the consumer'stscoutput.Why this differs from
.d.tsJSDoc types in
.mjsfiles with@ts-checkare not interchangeable with.d.tsfiles from a consumer's perspective:.d.tsfiles@ts-check+ JSDoc in.mjsskipLibCheck: trueskipLibCheckThe TypeScript documentation for
skipLibCheckis clear:skipLibCheckapplies to declaration files only. When a library uses.mjswith@ts-checkinstead of.d.ts, it removes the consumer's ability to work around internal type issues in the library.Suggested fix
Either:
Ship
.d.tsfiles alongside the.mjsfiles — this is the standard approach for npm packages consumed by TypeScript projects. It gives consumersskipLibCheckas an escape hatch and separates the public API contract from the internal implementation.Remove
@ts-check— the JSDoc types will still provide type information for consumers via inference, but internal type errors won't propagate.Note: fixing the specific type error above (e.g. with
@ts-ignore) would only address the current symptom. The core issue is that.mjsfiles with@ts-checkare fully type-checked by consumers with no way to opt out — any future type mismatch introduced by upstream dependencies would cause the same problem again.Environment
apollo-upload-client: 19.0.0@apollo/client: 4.1.3allowJs: true,maxNodeModuleJsDepth: 10