Skip to content

fix(types): Fix TS 4.x legacy types with skipLibCheck off; internal(ci): fail-open esmodule relevance, faster rest legacy types - #4122

Merged
ntucker merged 5 commits into
masterfrom
claude/project-thread-bw2hx0
Oct 4, 2026
Merged

ntucker merged 5 commits into
masterfrom
claude/project-thread-bw2hx0

Conversation

@ntucker

@ntucker ntucker commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Nathaniel · project thread

Follow-ups from #4112, plus #4138 and #4140 (both merged into this branch).

Motivation

TypeScript 4.x users with skipLibCheck off got errors from @data-client/endpoint, @data-client/normalizr and @data-client/rest declarations (from the changesets):

import { Entity } from '@data-client/endpoint';
import { normalize } from '@data-client/normalizr';

// Before (TypeScript 4.7, skipLibCheck: false):
//   error TS2304: Cannot find name 'NoInfer'.
//   error TS1383: Only named exports may use 'export type'.
// After: no errors
import { Endpoint, Entity, schema } from '@data-client/endpoint';

class User extends Entity {
  id = '';
  type = 'users';
}
const getUser = new Endpoint(
  (id: string) => fetch(`/users/${id}`).then(res => res.json()),
  { schema: User },
);
const feed = new schema.Union({ users: User }, 'type');

// Before (TypeScript 4.0, skipLibCheck: false):
//   error TS2456: Type alias 'RemoveArray' circularly references itself.
//   error TS2322: Type 'typeof User' is not assignable to type 'EntityInterface<any>'.
// Before (TypeScript 4.2):
//   error TS2344: Type 'TBase' does not satisfy the constraint 'new (...args: any) => any'.
// After: no errors

CI: the esmodule relevance flag in setup used an allowlist, so a new input path would silently skip the esmodule jobs. rest's legacy types build paid several yarn and copyfiles boots inside setup.

Solution

  • Legacy types overlays (fix(types): Apply newer legacy type overlays to older TS outputs #4138): scripts/build-legacy-types.sh downlevels first, then applies every newer version's src-*-types, then the version's own. endpoint/normalizr never actually inherited newer overlays before (the copies ran before downlevel-dts overwrote them), so e.g. endpoint ts4.2 shipped the TS 5.4 NoInfer builtin. normalizr re-exports memo types by name instead of export type *.
  • TS 4.0–4.5 declaration errors (fix(types): Fix endpoint/rest legacy type errors with skipLibCheck off #4140): new TS 4.0 overlays, the script rewrites abstract new → new for outputs below 4.2 (replacing drifted hand-copied Entity overlays), ConstructorInstance<> for TS 4.2, and a getPage narrowing fix for rest. esmodule-types now also type-checks examples/todo-app/tsconfig.typetest-libcheck.json with skipLibCheck: false.
  • rest legacy types use the shared script (direct downlevel-dts, d.ts-only copies, versions in parallel): 8.6s to 4.5s locally.
  • Esmodule denylist: the shared DOCS_ONLY paths (also the tests denylist) plus .vscode/, plans/, root __tests__/, eslint.config.mjs, jest.config.js, examples/*.md, and the examples the esmodule jobs never build. Unknown paths run the jobs (fail open).
  • Patch changesets, v0.19 blog bullets, .cursor/rules/ci-config.mdc updated.

Open questions

Not done, with reasons:

  • Trimming the workspace attach (dropping .yarn/cache from it): typecheck finishes ~24–30s after setup including spin-up, attach and tsc, so attach costs well under that. 11 of 16 downstream jobs run yarn up/yarn add and would need the cache via restore_cache, so the saving on the other 5 is a few seconds.
  • Sharding unit_tests-latest: at most ~10s to gain (it finishes ~11s after the next-slowest job), while a second shard pays its own spin-up and attach and needs merged coverage for codecov.
  • TS 3.x (ts3.4/, untested in CI) still reports pre-existing SnapshotInterface.d.ts rest-element errors (noted in fix(types): Fix endpoint/rest legacy type errors with skipLibCheck off #4140).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EEcQdob4nGVuMq91oUHC2V


Note

Medium Risk
Changes published .d.ts overlays and legacy build ordering across endpoint, normalizr, and rest; CI relevance logic is inverted but intentionally fail-open.

Overview
Fixes published declaration errors for consumers on TypeScript 4.0–4.7 with skipLibCheck: false: NoInfer / export type * issues in normalizr, and Entity / Endpoint / Union / RestEndpoint breakage on 4.0–4.5 (circular RemoveArray, abstract constructors, pagination narrowing).

Legacy types pipeline: build-legacy-types.sh now downlevels lib first, then layers src-*-types overlays (fixing overlays being wiped before), rewrites abstract new → new for outputs below 4.2, and @data-client/rest uses the shared script. TS 4.0 gets a non-recursive tupleTypes overlay; source adds ConstructorInstance<>, shared tupleTypes, and looser Union args; hand-maintained TS 4.0 Entity copies are removed.

CI: Esmodule job skipping switches from an allowlist to a denylist (unknown paths still run). The esmodule-types matrix adds typetest-libcheck with skipLibCheck: false (plus bundler resolution on latest TS) so legacy typesVersions outputs are type-checked in CI. Docs/changesets/blog note the fixes.

Reviewed by Cursor Bugbot for commit 479badb. Bugbot is set up for automated code reviews on this repo. Configure here.

@ntucker ntucker self-assigned this Oct 3, 2026
@changeset-bot

changeset-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 479badb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@data-client/endpoint Patch
@data-client/rest Patch
@data-client/graphql Patch
@data-client/normalizr Patch
@data-client/react Patch
@data-client/vue Patch
example-benchmark Patch
example-benchmark-react Patch
normalizr-github-example Patch
normalizr-redux-example Patch
normalizr-relationships Patch
test-bundlesize Patch
coinbase-lite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs-site Ignored Ignored Preview Oct 4, 2026 3:15pm UTC

Request Review

@ntucker
ntucker marked this pull request as ready for review October 3, 2026 23:45
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 103 kB

ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/App.js 1.46 kB
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcClient.js 10.9 kB
examples/test-bundlesize/dist/rdcEndpoint.js 8.07 kB
examples/test-bundlesize/dist/rdcNextjs.js 12.3 kB
examples/test-bundlesize/dist/rdcPipeableStream.js 9.63 kB
examples/test-bundlesize/dist/react.js 59.7 kB
examples/test-bundlesize/dist/webpack-runtime.js 784 B

compressed-size-action

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.88%. Comparing base (84b6766) to head (479badb).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4122   +/-   ##
=======================================
  Coverage   97.88%   97.88%           
=======================================
  Files         158      158           
  Lines        3079     3079           
  Branches      616      616           
=======================================
  Hits         3014     3014           
  Misses         18       18           
  Partials       47       47           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ntucker ntucker changed the title internal(ci): Fail open on esmodule relevance and speed up rest legacy types fix(types): Fix legacy TS 4.x types; internal(ci): fail open on esmodule relevance, faster rest legacy types Oct 4, 2026

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark React

Details
Benchmark suite Current: 479badb Previous: 8b3bb89 Ratio
data-client: getlist-100 204.08 ops/s (± 4.7%) 175.44 ops/s (± 5.1%) 0.86
data-client: getlist-500 63.69 ops/s (± 4.5%) 52.22 ops/s (± 3.4%) 0.82
data-client: update-entity 500 ops/s (± 9.9%) 416.67 ops/s (± 5.6%) 0.83
data-client: update-user 434.78 ops/s (± 6.6%) 370.37 ops/s (± 7.6%) 0.85
data-client: getlist-500-sorted 63.1 ops/s (± 10.0%) 55.26 ops/s (± 6.2%) 0.88
data-client: update-entity-sorted 476.19 ops/s (± 10.2%) 322.58 ops/s (± 6.0%) 0.68
data-client: update-entity-multi-view 488.1 ops/s (± 8.8%) 339.08 ops/s (± 5.7%) 0.69
data-client: list-detail-switch-10 20.2 ops/s (± 9.0%) 12.58 ops/s (± 8.4%) 0.62
data-client: update-user-10000 113 ops/s (± 13.8%) 79.68 ops/s (± 10.4%) 0.71
data-client: invalidate-and-resolve 55.1 ops/s (± 5.3%) 46.73 ops/s (± 6.7%) 0.85
data-client: unshift-item 294.12 ops/s (± 6.3%) 250 ops/s (± 3.6%) 0.85
data-client: delete-item 384.62 ops/s (± 5.2%) 333.33 ops/s (± 4.6%) 0.87
data-client: move-item 241 ops/s (± 7.3%) 192.31 ops/s (± 8.7%) 0.80

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 479badb Previous: 8b3bb89 Ratio
normalizeLong 426 ops/sec (±4.12%) 464 ops/sec (±3.11%) 1.09
normalizeLong Values 395 ops/sec (±0.38%) 413 ops/sec (±0.27%) 1.05
normalizeLong Scalar 360 ops/sec (±3.32%) 393 ops/sec (±2.46%) 1.09
normalizeLong Scalar update 902 ops/sec (±0.17%) 842 ops/sec (±0.46%) 0.93
denormalizeLong 236 ops/sec (±5.62%) 273 ops/sec (±6.00%) 1.16
denormalizeLong Values 223 ops/sec (±4.74%) 245 ops/sec (±5.48%) 1.10
denormalizeLong donotcache 1014 ops/sec (±0.81%) 1171 ops/sec (±0.73%) 1.15
denormalizeLong Values donotcache 741 ops/sec (±0.23%) 789 ops/sec (±0.42%) 1.06
denormalizeLong Scalar donotcache 1036 ops/sec (±0.33%) 1159 ops/sec (±0.23%) 1.12
denormalizeShort donotcache 500x 1402 ops/sec (±0.12%) 1486 ops/sec (±0.27%) 1.06
denormalizeShort 500x 647 ops/sec (±6.71%) 670 ops/sec (±6.59%) 1.04
denormalizeShort 500x withCache 6631 ops/sec (±0.13%) 6479 ops/sec (±1.00%) 0.98
queryShort 500x withCache 3112 ops/sec (±0.13%) 3566 ops/sec (±1.05%) 1.15
buildQueryKey All 57065 ops/sec (±1.05%) 61510 ops/sec (±0.58%) 1.08
query All withCache 6354 ops/sec (±2.79%) 6976 ops/sec (±4.81%) 1.10
denormalizeLong with mixin Entity 214 ops/sec (±7.04%) 250 ops/sec (±7.59%) 1.17
denormalizeLong withCache 7501 ops/sec (±0.20%) 7085 ops/sec (±0.57%) 0.94
denormalizeLong withCache (Scalar churn) 7408 ops/sec (±0.61%) 7140 ops/sec (±0.23%) 0.96
denormalizeLong Values withCache 5170 ops/sec (±1.32%) 5623 ops/sec (±1.54%) 1.09
denormalizeLong Scalar withCache 7976 ops/sec (±0.41%) 7069 ops/sec (±0.31%) 0.89
denormalizeLong Scalar update withCache 4085 ops/sec (±0.18%) 3063 ops/sec (±0.06%) 0.75
denormalizeLong All withCache 6521 ops/sec (±0.16%) 6083 ops/sec (±0.19%) 0.93
denormalizeLong Query-sorted withCache 6582 ops/sec (±2.13%) 7136 ops/sec (±5.02%) 1.08
denormalizeLongAndShort withEntityCacheOnly 1823 ops/sec (±0.25%) 1694 ops/sec (±0.46%) 0.93
denormalize bidirectional 50 4648 ops/sec (±11.02%) 5227 ops/sec (±11.81%) 1.12
denormalize bidirectional 50 donotcache 42376 ops/sec (±1.62%) 48901 ops/sec (±0.45%) 1.15
getResponse 4440 ops/sec (±3.64%) 4760 ops/sec (±4.78%) 1.07
getResponse (null) 9315280 ops/sec (±0.83%) 11986909 ops/sec (±0.76%) 1.29
getResponse (clear cache) 196 ops/sec (±9.80%) 248 ops/sec (±8.12%) 1.27
getSmallResponse 3367 ops/sec (±1.47%) 4011 ops/sec (±1.29%) 1.19
getSmallInferredResponse 2837 ops/sec (±0.14%) 3196 ops/sec (±0.96%) 1.13
getResponse Collection 4491 ops/sec (±3.16%) 5257 ops/sec (±1.21%) 1.17
get Collection 2804 ops/sec (±0.50%) 2372 ops/sec (±0.29%) 0.85
get Query-sorted 4956 ops/sec (±1.73%) 5299 ops/sec (±2.05%) 1.07
setLong 441 ops/sec (±0.29%) 470 ops/sec (±0.18%) 1.07
setLongWithMerge 250 ops/sec (±0.20%) 267 ops/sec (±0.81%) 1.07
setLongWithSimpleMerge 271 ops/sec (±0.20%) 285 ops/sec (±0.20%) 1.05
setSmallResponse 500x 914 ops/sec (±1.19%) 992 ops/sec (±1.35%) 1.09
setMany 50x one-per-row 145 ops/sec (±0.79%) 196 ops/sec (±1.26%) 1.35
setMany 50 batch 3557 ops/sec (±2.76%) 4162 ops/sec (±0.72%) 1.17
setMany 500x one-per-row 14.96 ops/sec (±0.82%) 20.98 ops/sec (±2.00%) 1.40
setMany 500 batch 1390 ops/sec (±3.27%) 1408 ops/sec (±0.20%) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@ntucker ntucker changed the title fix(types): Fix legacy TS 4.x types; internal(ci): fail open on esmodule relevance, faster rest legacy types fix(types): Fix TS 4.x legacy types with skipLibCheck off; internal(ci): fail-open esmodule relevance, faster rest legacy types Oct 4, 2026

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark Spread

Details
Benchmark suite Current: 479badb Previous: 38dbb52 Ratio
setOneEntity in 10k entity store 156 ops/sec (±0.77%) 153 ops/sec (±0.97%) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

claude and others added 4 commits October 4, 2026 15:05
…y types

The esmodule relevance flag now uses a denylist of provably irrelevant
paths, so new inputs run the esmodule jobs by default. rest's legacy
types build uses scripts/build-legacy-types.sh (direct downlevel-dts,
d.ts-only copies) with --newer-overlays-last to keep its overlay order;
ts4.0/ts4.1 output is byte-identical (8.6s -> 4.5s locally).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEcQdob4nGVuMq91oUHC2V
… paths

Downlevel always runs first; --inherit-newer-overlays (rest) then applies
earlier versions' custom types. Without the flag those copies were always
overwritten by downlevel-dts, so endpoint/normalizr output is unchanged
(ts*/ byte-identical). The esmodule and tests relevance checks now share
one DOCS_ONLY path list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEcQdob4nGVuMq91oUHC2V
* fix(types): Apply newer legacy type overlays to older TS outputs

endpoint and normalizr legacy outputs (ts4.2, ts4.0, ts3.4) now inherit
every newer version's src-*-types overlay, as rest already did. This
replaces the TS 5.4 NoInfer builtin with the NI<T> = T fallback for
TS < 4.8 consumers.

normalizr re-exports memo types by name instead of `export type *`
(TS 5.0 syntax that downlevel-dts leaves as is).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpCgcZt8G8R5KARZ7z5CKE

* docs(blog): Note legacy TypeScript types fix

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpCgcZt8G8R5KARZ7z5CKE

---------

Co-authored-by: Claude <noreply@anthropic.com>
#4140)

* fix(types): Apply newer legacy type overlays to older TS outputs

endpoint and normalizr legacy outputs (ts4.2, ts4.0, ts3.4) now inherit
every newer version's src-*-types overlay, as rest already did. This
replaces the TS 5.4 NoInfer builtin with the NI<T> = T fallback for
TS < 4.8 consumers.

normalizr re-exports memo types by name instead of `export type *`
(TS 5.0 syntax that downlevel-dts leaves as is).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpCgcZt8G8R5KARZ7z5CKE

* docs(blog): Note legacy TypeScript types fix

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpCgcZt8G8R5KARZ7z5CKE

* fix(types): Fix endpoint/rest legacy type errors with skipLibCheck off

- TS 4.0: unroll RemoveArray and approximate PartialArray (recursive
  conditional types need 4.1) in new src-4.0-types overlays
- TS <4.2: rewrite `abstract new` to `new` in the downleveled output and
  drop the stale src-4.0-types Entity/EntityTypes copies
- TS 4.2: ConstructorInstance<> replaces InstanceType<> on abstract TBase
- TS 4.2-4.4: UnionInstance Args is unconstrained
- TS 4.0-4.5: rest getPage narrows paginationField with Extract<>
- CI: esmodule-types also typechecks a skipLibCheck: false consumer

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMEHZGboXdMFRkF4D2ktki

* docs(blog): Link #4140 in v0.19 notes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMEHZGboXdMFRkF4D2ktki

* refactor(types): Isolate recursive tuple types so the 4.0 overlay is one small file

- RemoveArray and PartialArray move to tupleTypes.ts; the src-4.0-types
  overlay replaces only that module instead of copying endpointTypes/utility
- Legacy build only rewrites files that contain `abstract new (`
- libcheck typetest skips TypeScript's own lib checks
- Split the legacy types bullet in ci-config.mdc

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMEHZGboXdMFRkF4D2ktki

* internal: Resolve leftover merge conflict in ci-config rule

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMEHZGboXdMFRkF4D2ktki

* internal: Resolve leftover merge conflict in build-legacy-types.sh

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMEHZGboXdMFRkF4D2ktki

---------

Co-authored-by: Claude <noreply@anthropic.com>
@ntucker
ntucker force-pushed the claude/project-thread-bw2hx0 branch from 350c124 to 182344b Compare October 4, 2026 15:06
version_lt compares major.minor in bash instead of relying on sort -V
(a missing -V silently skipped the abstract new rewrite), and the
abstract new grep no longer fails the build under pipefail when nothing
matches. esmodule-types-latest also runs the skipLibCheck: false
typetest so lib/ is checked on the newest compiler.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEcQdob4nGVuMq91oUHC2V
@ntucker
ntucker merged commit b4b502d into master Oct 4, 2026
30 checks passed
@ntucker
ntucker deleted the claude/project-thread-bw2hx0 branch October 4, 2026 15:20
@github-actions github-actions Bot mentioned this pull request Oct 4, 2026
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.

2 participants