Skip to content

enhance(rest): Type process() params in RestEndpoint.extend() - #4183

Open
ntucker wants to merge 3 commits into
masterfrom
claude/extend-process-params-ef2q83
Open

ntucker wants to merge 3 commits into
masterfrom
claude/extend-process-params-ef2q83

Conversation

@ntucker

@ntucker ntucker commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Nathaniel · project thread

Follow-up from the review on #4173.

Motivation

process(value, params) passed to RestEndpoint.extend() or resource().extend() got params typed as any, so reading a param the endpoint doesn't have compiled and returned undefined at runtime.

const getUser = new RestEndpoint({ path: '/users/:id' });

const getUserById = getUser.extend({
  path: '/users/by-id/:userId',
  process(value, params) {
    params.userId; // string | number
    params.id; // TypeScript error: 'id' is not a param of '/users/by-id/:userId'
    return value;
  },
});

Endpoints whose params are optional pass params as possibly undefined (params?.page). On TypeScript 4.1–5.x, an .extend() that sets path together with process(value, params) no longer fails with "implicitly has an 'any' type" under strict.

Solution

  • RestEndpointExtendOptions gets process?(value, ...args: ProcessArgs<Parameters<OptionsToFunction<O, E, F>>>), so params follow the resulting endpoint (a path in the same call wins). ProcessArgs merges call-shape unions like [params] | [] or [params, body] | [body] position-wise into optional elements, so process(value, params, body) keeps compiling for endpoints callable several ways.
  • extend() options (RestEndpoint and both resource().extend(key, …) overloads, including the hand-written TS 4.1 declarations) are constrained by a new ExtendableRestGenerics (PartialRestGenerics without process). With the old constraint, TS ≤ 5.3 saw two process signatures and gave up on contextual typing (implicit any).

Zero loss: with every @ts-expect-error neutralized, diagnostics are identical before/after on TS 6 (API), TS 7 (tsc), and TS 4.0/4.1/4.3/4.8/5.3 (example typetests + libcheck + the new test), except the new expected errors; the TS7006 implicit-any errors on master for path+process disappear. TS 4.0 keeps params: any (its simplified declarations), with no new errors.

Type-check cost (instantiations, TS 6 / TS 7 check time, vs master):

scenario master this PR
extendProcess (600 extends with process, new) 602,881 · 3.01s / 1.12s 697,858 · 3.53s / 1.26s
paths (#4173 stress test) 341,361 · 2.09s / 0.72s 345,562 · 2.20s / 0.75s
resources 134,294 134,294
typical 12,995 13,038

The added cost is TypeScript now actually checking process bodies against real param types; endpoints without process pay about 1%.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bv3zm61qvR8ytLLib2KVq3


Generated by Claude Code


Note

Medium Risk
Compile-time-only change that can break builds on upgrade when existing process() callbacks use wrong or unchecked params; runtime fetch behavior is unchanged.

Overview
@data-client/rest now types the arguments passed to process() inside .extend() (including resource().extend('get', …)), derived from the resulting endpoint’s path, searchParams, and body—including a path overridden in the same .extend() call. Wrong param names become compile-time errors instead of silent undefined at runtime; optional call shapes use params?.… where needed.

The typing work adds ProcessArgs to merge optional fetch argument tuples, splits ExtendableRestGenerics (no process) from PartialRestGenerics so TypeScript ≤5.3 can contextually type process without implicit any, and wires the same constraints through TS 4.1 declaration shims. No runtime behavior changes.

Docs (RestEndpoint process section), a changeset, v0.19 blog notes, and extendProcess.test.ts type regressions document and lock in the behavior. Upgrading may surface new TypeScript errors in existing process() implementations that were previously unchecked.

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

process(value, params) passed to RestEndpoint.extend() or resource().extend()
typed params as any. Type them from the extended endpoint's path,
searchParams and body, including a path set in the same call. Endpoints
callable several ways (optional params or body) merge their argument
lists position-wise, so every way the endpoint is called type-checks.

extend() options are now constrained by ExtendableRestGenerics
(PartialRestGenerics without process), so TypeScript 4.1-5.x contextually
type process() instead of reporting implicit any.

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

vercel Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
docs-site Ready Ready Preview Oct 5, 2026 9:26am UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e43d854

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

This PR includes changesets to release 4 packages
Name Type
@data-client/rest Patch
example-benchmark-react 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

@ntucker ntucker self-assigned this Oct 5, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bv3zm61qvR8ytLLib2KVq3
@ntucker

ntucker commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): LGTM on 62e3ae70. Splitting ExtendableRestGenerics out so extend() has only one contextual process signature is the smallest fix for the TS ≤5.3 implicit-any, and ProcessArgs matches runtime: RestEndpoint.js calls this.process(res, ...args) with the raw call args, so params being P | B for the ep(body) / ep(params, body) shape is accurate, not a lie. The ~1% cost for endpoints without process is fine.

FOLLOW_UP (after merge, not a change to this PR):

  • There are now two shapes for the same args. The instance member process(value, ...args: Parameters<F>) (RestEndpointTypes.ts ~L156) still uses the raw union, while extend() uses ProcessArgs. A subclass overriding process and an extend({ process }) see different param types for the same endpoint. Consider moving the instance member to ProcessArgs too (or documenting why not) so they don't drift.
  • The changeset is patch, but code that read a param the endpoint doesn't have inside process() now fails type-check. Worth one line in the changeset and the v0.19 blog entry saying it can surface new type errors, so upgraders aren't surprised.

@github-actions

github-actions Bot commented Oct 5, 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.64 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 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.08%. Comparing base (74a964e) to head (e43d854).
⚠️ Report is 17 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4183      +/-   ##
==========================================
+ Coverage   98.06%   98.08%   +0.02%     
==========================================
  Files         163      165       +2     
  Lines        3098     3139      +41     
  Branches      617      625       +8     
==========================================
+ Hits         3038     3079      +41     
  Misses         18       18              
  Partials       42       42              

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

…s scope

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

ntucker commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

Re the Staff follow-ups, both handled in e43d854:

  • Instance process() vs ProcessArgs: I'm keeping them different on purpose and documented why on ProcessArgs. A subclass override isn't contextually typed by the base member, so the instance signature only matters to callers of ep.process(...). For callers, the raw Parameters<F> union ([params, body] | [body]) is the accurate, stricter signature. ProcessArgs is looser (every position optional) and exists only because TypeScript can't infer callback parameters from a union of tuples.
  • New type errors on upgrade: added a line to the changeset and the v0.19 blog entry saying existing process() methods can now report errors, and that each one marks a read that could be undefined or throw at runtime.

The /simplify altitude review also noted that the constructor (new RestEndpoint({ process })) and resource().extend({ get: { process } }) still type params as any. That's queued as its own follow-up (it overlaps the resource extend typing work), and the changeset now names only the forms this PR covers.


Generated by Claude Code

@ntucker
ntucker marked this pull request as ready for review October 5, 2026 09:26

This branch was successfully deployed

1 active deployment
Preview — e43d8549 Deployed Oct 5, 2026 by vercel[bot]
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