Skip to content

@ts-check in .mjs files causes unsuppressable type errors for TypeScript consumers #366

@shellicar-eagers

Description

@shellicar-eagers

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:

  1. 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.

  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions