Skip to content

Tighten TypeScript safety: strict flags, typed linting, public-API type tests #564

@tombeckenham

Description

@tombeckenham

Context

TanStack AI's primary value proposition is type safety, but a configuration audit shows several high-value strict flags off and no typed-linting rules active. The compiler baseline is already strong (strict: true, noUncheckedIndexedAccess: true, noUnusedLocals, noUnusedParameters, noImplicitReturns, isolatedModules, checkJs), but the codebase currently contains ~191 : any, ~25 as any, ~43 as unknown as, and 9 @ts-expect-error/@ts-ignore in non-test source — a notable surface for a library that sells type safety.

Scope (single PR)

Compiler flags (root tsconfig.json)

  • exactOptionalPropertyTypes: true — distinguishes foo?: T from foo: T | undefined. Dominant source of churn; expect cascades through ModelMessage, provider options, and tool schemas.
  • noImplicitOverride: true
  • noPropertyAccessFromIndexSignature: true
  • noFallthroughCasesInSwitch: true
  • useDefineForClassFields: true

Explicitly deferred: verbatimModuleSyntax@typescript-eslint/consistent-type-imports already enforces import discipline; flipping verbatim would force export-statement churn for low marginal value.

Typed ESLint rules (new tanstack/ai/typed block in root eslint.config.js)

Scoped to packages/typescript/**/src/**/*.{ts,tsx} (skip tests, dist, examples), using parserOptions.projectService: true.

  • @typescript-eslint/no-floating-promiseserror
  • @typescript-eslint/no-misused-promiseserror
  • @typescript-eslint/await-thenableerror
  • @typescript-eslint/switch-exhaustiveness-checkerror
  • @typescript-eslint/no-explicit-anywarn (baseline; ratchet later)
  • @typescript-eslint/no-non-null-assertionwarn
  • @typescript-eslint/consistent-type-exportserror
  • @typescript-eslint/prefer-readonlywarn

Why these specifically: streaming + agent-loop code (packages/typescript/ai/src/core/chat.ts) is async-heavy, so floating-promise + misused-promise bugs are the highest-leverage class to catch. Stream chunks, content parts, and tool states are discriminated unions where switch-exhaustiveness-check turns "added a new variant, forgot a case" into a compile error.

Suppression discipline

  • Override @typescript-eslint/ban-ts-comment to require descriptions on @ts-expect-error as well ({ 'ts-expect-error': 'allow-with-description', 'ts-ignore': false, 'ts-nocheck': true }).
  • Add CI step (Nx target or workspace script) that fails if grep -rE "@ts-(ignore|nocheck)\b" packages/typescript/*/src returns anything.

Public-API type-test coverage

Following the existing pattern in packages/typescript/ai/tests/image-per-model-type-safety.test.ts and packages/typescript/ai-openai/tests/tools-per-model-type-safety.test.ts:

  • chat-per-model-type-safety.test.ts in each of ai, ai-anthropic, ai-gemini, ai-openai (provider options × model coverage).
  • use-chat-types.test.ts in ai-solid — parity with ai-react, ai-vue, ai-svelte.
  • InferChatMessages round-trip test in ai-client/tests/, proving tool input/output types propagate to message-array types.

Fix fallout

  • Resolve every compile error surfaced by the new compiler flags.
  • Resolve every error-level lint surfaced by the new typed rules. Treat each no-floating-promises site individually rather than blanket-suppressing — these often indicate real concurrency bugs in streaming/tool-call code.

Out of scope (deliberate)

  • verbatimModuleSyntax — see above.
  • Migrating to tsd/expect-type for type-tests — the existing @ts-expect-error pattern is sufficient.
  • Sweeping removal of as unknown as casts — many are load-bearing vendor-SDK bridges; address case-by-case as no-explicit-any warnings surface them.

Verification

pnpm test:types
pnpm test:eslint
pnpm test:lib
pnpm --filter @tanstack/ai-e2e test:e2e
pnpm test:build

And:

grep -rE "@ts-(ignore|nocheck)\b" packages/typescript/*/src

should return no matches.

Risk

Large coordinated diff spanning every workspace package. exactOptionalPropertyTypes is the dominant source of compile-time changes — reviewers should focus on whether each adjusted optional-property site is semantically correct (absent vs explicit undefined). no-floating-promises fixes can subtly change concurrency ordering in stream consumers and agent loops; review each site individually.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions