Skip to content

build: migrate from tsc to tsdown - #188

Merged
dinwwwh merged 1 commit into
mainfrom
claude/charming-lamport-1i70iz
Nov 9, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/charming-lamport-1i70iz

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Why

Builds move from per-package tsc to tsdown with one shared root config, the same migration as middleapi/orpc#2448 in a simpler form. This repo needs neither oRPC's re-export hook for Expo tree shaking nor its runtime-module list. Output keeps tsc's shape (one .js and .d.ts per source module), so publishConfig.exports stays as it is.

Changes

  • One root tsdown.config.ts. tsdown finds it from each package directory, so every prepack is just tsdown. Entries come from each package's exports; unbundle keeps one file per module, and packages stay external (neverBundle: true). platform: 'neutral', as in the other two repos.
  • Declarations come from oxc (dts: { generator: 'oxc' }), which needs explicit types on exports. A missing one now fails the build.
  • AsyncIteratorClass[asyncDisposeSymbol]. Isolated declarations can't infer a class member keyed by asyncDisposeSymbol, whose type is conditional. So the member is declared on a merged interface, and the method is defined after the class with Object.defineProperty. It calls this.return(), as the spec's AsyncIterator.prototype[Symbol.asyncDispose] does; the old body was a copy of return() minus the return value. It is still a non-enumerable, writable, configurable prototype method.
  • Undeclared imports fail the build. deps.onlyImport lists each package's dependencies and peerDependencies, plus node: built-ins (the node and aws-lambda packages import them).
  • Bundler module resolution. The root tsconfig uses module: preserve with moduleResolution: bundler, and the .ts extension is dropped from relative imports in packages, tests and benches. dist still imports with .js extensions. verbatimModuleSyntax stays: without it pnpm check misses type-only re-exports, which then only fail at build time.
  • Cleanup and bumps. The emit-only options in tsconfig.lib.json are gone; oxlint, oxlint-tsgolint and oxfmt are bumped. CodSpeedHQ/action moves to v5, because v4's runner exits with Unsupported system on the ubuntu-26.04 image that ubuntu-latest now uses.

Verification

  • pnpm check, pnpm test (1,487 vitest + 52 bun) and all 7 package builds pass without warnings.
  • Against the published 0.15.0 tarballs:
    • Every entry point exports the same names and value types at runtime.
    • The public API is semantically unchanged, checked with the TypeScript compiler API.
    • JSDoc is preserved; the only declaration missing is the unexported anyAbortSignal.
  • The emitted declarations type-check for a nodenext consumer with skipLibCheck: false.
  • await using and const d: AsyncDisposable = iterator type-check for consumers, and the Symbol.for('Symbol.asyncDispose') fallback test passes.
  • platform: 'neutral' produces output byte-identical to 'node' for all 7 packages. An undeclared import (srvx in @standard-server/fetch) fails the build, while node: and declared imports pass.
  • Every emitted .js compiles under @react-native/babel-preset.

Behavior changes

  • dist no longer contains empty .js files for type-only modules, nor .d.ts files that no public type refers to. Nothing imports them.
  • @standard-server/core no longer ships dist/event-stream/index.*; the root entry re-exports from the modules directly. It isn't an export path.

Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 138 files, which is 38 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 439dd472-5018-4b75-80e3-404c66bd1daa
Commits

Reviewing files that changed from the base of the PR and between c079917 and ad98793.

Files ignored due to path filters: 1

  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 138

  • .github/workflows/codspeed.yaml
  • benches/__shared__/client-server.fetch.ts
  • benches/__shared__/client-server.node.ts
  • benches/__shared__/client-server.peer.ts
  • benches/e2e.bench.ts
  • package.json
  • packages/aws-lambda/package.json
  • packages/aws-lambda/src/body.test.ts
  • packages/aws-lambda/src/body.ts
  • packages/aws-lambda/src/headers.test.ts
  • packages/aws-lambda/src/headers.ts
  • packages/aws-lambda/src/index.test.ts
  • packages/aws-lambda/src/index.ts
  • packages/aws-lambda/src/request.test.ts
  • packages/aws-lambda/src/request.ts
  • packages/aws-lambda/src/response.test.ts
  • packages/aws-lambda/src/response.ts
  • packages/aws-lambda/src/types.test-d.ts
  • packages/aws-lambda/src/url.test.ts
  • packages/aws-lambda/src/url.ts
  • packages/core/package.json
  • packages/core/src/event-stream/decoder.test.ts
  • packages/core/src/event-stream/decoder.ts
  • packages/core/src/event-stream/encoder.test.ts
  • packages/core/src/event-stream/encoder.ts
  • packages/core/src/event-stream/error.test.ts
  • packages/core/src/event-stream/index.ts
  • packages/core/src/event-stream/meta.test.ts
  • packages/core/src/event-stream/meta.ts
  • packages/core/src/index.test.ts
  • packages/core/src/index.ts
  • packages/core/src/utils.test.ts
  • packages/core/src/utils.ts
  • packages/core/src/validators.test.ts
  • packages/core/src/validators.ts
  • packages/fastify/package.json
  • packages/fastify/src/index.test.ts
  • packages/fastify/src/index.ts
  • packages/fastify/src/request.test.ts
  • packages/fastify/src/request.ts
  • packages/fastify/src/response.test.ts
  • packages/fastify/src/response.ts
  • packages/fastify/src/types.test-d.ts
  • packages/fastify/tests/http2.test.ts
  • packages/fetch/package.json
  • packages/fetch/src/body.test.ts
  • packages/fetch/src/body.ts
  • packages/fetch/src/event-stream.test.ts
  • packages/fetch/src/headers.test.ts
  • packages/fetch/src/index.test.ts
  • packages/fetch/src/index.ts
  • packages/fetch/src/request.test.ts
  • packages/fetch/src/request.ts
  • packages/fetch/src/response.test.ts
  • packages/fetch/src/response.ts
  • packages/fetch/src/url.test.ts
  • packages/node/package.json
  • packages/node/src/body.test.ts
  • packages/node/src/body.ts
  • packages/node/src/event-stream.test.ts
  • packages/node/src/event-stream.ts
  • packages/node/src/index.test.ts
  • packages/node/src/index.ts
  • packages/node/src/method.test.ts
  • packages/node/src/request.test.ts
  • packages/node/src/request.ts
  • packages/node/src/response.test.ts
  • packages/node/src/response.ts
  • packages/node/src/signal.test.ts
  • packages/node/src/signal.ts
  • packages/node/src/url.test.ts
  • packages/node/src/url.ts
  • packages/node/src/utils.test.ts
  • packages/node/src/utils.ts
  • packages/peer/package.json
  • packages/peer/src/body.test.ts
  • packages/peer/src/body.ts
  • packages/peer/src/client.test.ts
  • packages/peer/src/client.ts
  • packages/peer/src/codec.test.ts
  • packages/peer/src/codec.ts
  • packages/peer/src/event-stream.test.ts
  • packages/peer/src/event-stream.ts
  • packages/peer/src/hibernation.test.ts
  • packages/peer/src/index.test.ts
  • packages/peer/src/index.ts
  • packages/peer/src/octet-stream.test.ts
  • packages/peer/src/octet-stream.ts
  • packages/peer/src/server.test.ts
  • packages/peer/src/server.ts
  • packages/peer/src/validators.test.ts
  • packages/peer/src/validators.ts
  • packages/peer/tests/peer.test.ts
  • packages/shared/package.json
  • packages/shared/src/array.test-d.ts
  • packages/shared/src/array.test.ts
  • packages/shared/src/error.test.ts
  • packages/shared/src/function.test.ts
  • packages/shared/src/id.test.ts
  • packages/shared/src/index.test.ts
  • packages/shared/src/index.ts
  • packages/shared/src/iterator.test.ts
  • packages/shared/src/iterator.ts
  • packages/shared/src/json.test-d.ts
  • packages/shared/src/json.test.ts
  • packages/shared/src/object.test.ts
  • packages/shared/src/promise.test.ts
  • packages/shared/src/proxy.test.ts
  • packages/shared/src/queue.test.ts
  • packages/shared/src/queue.ts
  • packages/shared/src/signal.test.ts
  • packages/shared/src/time.test.ts
  • packages/shared/src/time.ts
  • packages/shared/src/uri.test.ts
  • tests/bun/tests/client-server.bun-fetch.ts
  • tests/bun/tests/client-server.bun-ws.ts
  • tests/bun/tests/data-transfer.test.ts
  • tests/bun/tests/signal-and-cancel.test.ts
  • tests/client-server.expressjs.ts
  • tests/client-server.fastify.ts
  • tests/client-server.h3-node-handler.ts
  • tests/client-server.h3-web-handler.ts
  • tests/client-server.hono-fetch.ts
  • tests/client-server.inprogress-fetch.ts
  • tests/client-server.inprogress.ts
  • tests/client-server.message-port.ts
  • tests/client-server.node-fetch-server.ts
  • tests/client-server.node-http.ts
  • tests/client-server.node-http2.ts
  • tests/client-server.node-https.ts
  • tests/client-server.node-srvx.ts
  • tests/client-server.node-ws.ts
  • tests/client-server.peer.ts
  • tests/data-transfer.test.ts
  • tests/signal-and-cancel.test.ts
  • tsconfig.json
  • tsconfig.lib.json
  • tsdown.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pkg-pr-new

pkg-pr-new Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
@standard-server/aws-lambda

npm i https://pkg.pr.new/@standard-server/aws-lambda@188

@standard-server/core

npm i https://pkg.pr.new/@standard-server/core@188

@standard-server/fastify

npm i https://pkg.pr.new/@standard-server/fastify@188

@standard-server/fetch

npm i https://pkg.pr.new/@standard-server/fetch@188

@standard-server/node

npm i https://pkg.pr.new/@standard-server/node@188

@standard-server/peer

npm i https://pkg.pr.new/@standard-server/peer@188

@standard-server/shared

npm i https://pkg.pr.new/@standard-server/shared@188

commit: ad98793

@codecov

codecov Bot commented Nov 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

dinwwwh commented Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

benchmarks (CodSpeed) failed, and the cause wasn't this PR. CodSpeedHQ/action@v4 installs CodSpeed runner v4.19.1, which exits with Unsupported system before any benchmark runs. It failed the same way after one re-run (job).

ubuntu-latest now resolves to the ubuntu-26.04 image (kernel 7.0.0), which runner v4.19.1 doesn't support. middleapi/orpc#2448 hits the same failure. middleapi/openapi-spec#395 runs on the same image with CodSpeedHQ/action@v5 (runner v5.6.3) and its benchmarks pass. So I ported that fix into this PR: .github/workflows/codspeed.yaml now uses CodSpeedHQ/action@v5. It is part of the single commit (0c805fc).


Generated by Claude Code

@dinwwwh
dinwwwh force-pushed the claude/charming-lamport-1i70iz branch from 7202587 to 0c805fc Compare November 9, 2026 06:55
@codspeed

codspeed Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Hooray! CodSpeed harness just leveled up!

The base and head of this comparison were measured with different runner settings, so their benchmark values are not directly comparable.

What changed between base and head:

Re-run the base with the same settings to get a valid performance comparison.


Comparing claude/charming-lamport-1i70iz (0c805fc) with main (cfbacfc)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (c079917) during the generation of this report, so cfbacfc was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Build pipeline: every packages/*/package.json prepack now runs tsdown, driven by one root tsdown.config.ts that derives entries from each package's exports, keeps one output file per module (unbundle), externalizes dependencies (neverBundle + onlyImport), and emits declarations with the oxc generator. Builds for all seven packages succeed and the packed tarballs expose the dist-based exports.
  • Module resolution: root tsconfig.json moves to module: preserve + moduleResolution: bundler, drops rewriteRelativeImportExtensions, and relative imports across packages, tests, and benches drop their .ts extension. No stale .ts-extension or tsc references remain outside the standalone playgrounds.
  • AsyncIteratorClass: [Symbol.asyncDispose] is declared on a merged interface and installed on the prototype via Object.defineProperty after the class, delegating to return(). The descriptor matches a normal class method (writable, non-enumerable, configurable) and cleanup still reports { kind: 'cancelled' }.
  • Cleanup: emit-only options are removed from tsconfig.lib.json, and oxlint / oxlint-tsgolint / oxfmt are bumped.

Verified locally: pnpm check is clean, vitest run passes 1487 tests, all package builds succeed, and a nodenext consumer with skipLibCheck: false type-checks and runtime-imports packed shared/core/fetch/peer. The dropped declarations (core's dist/event-stream/index.*, peer's body.d.ts/octet-stream.d.ts) are not referenced by any emitted .js or .d.ts.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes (incremental)

  • .github/workflows/codspeed.yaml is the only delta since the prior review: CodSpeedHQ/action@v4 → @v5. The v5 action still accepts mode: simulation and run, and the job already grants id-token: write for OIDC, so the workflow remains valid.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Build every package with tsdown from one shared root tsdown.config.ts.
Each package's prepack runs `tsdown`, which finds the root config; entries
come from the package's exports. unbundle keeps one output file per source
module, as tsc did, and packages stay external.

- Generate declarations with oxc. AsyncIteratorClass's asyncDispose method
  is declared on a merged interface and defined after the class, because
  isolated declarations can't infer a class member keyed by a conditional
  type. It now calls return(), as the spec's AsyncIterator dispose does.
- Fail the build when dist imports a package that isn't in the package's
  dependencies or peerDependencies, plus node: built-ins (deps.onlyImport).
- Switch the root tsconfig to module preserve with bundler resolution and
  drop the .ts extension from relative imports.
- Remove the tsc-only tsconfig.lib.json options.
- Bump oxlint, oxlint-tsgolint and oxfmt.
- Bump CodSpeedHQ/action to v5. v4 installs a CodSpeed runner that exits
  with "Unsupported system" on the ubuntu-26.04 image ubuntu-latest now
  uses; openapi-spec already runs v5 there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND
@dinwwwh
dinwwwh force-pushed the claude/charming-lamport-1i70iz branch from 0c805fc to ad98793 Compare November 9, 2026 07:57

dinwwwh commented Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

benchmarks and test_node_matrix (20) are red on ad98793, and the cause isn't this PR: two downloads timed out (HTTP 504) during job setup. Each failed again on one re-run.

Check What timed out Jobs
benchmarks downloading the CodSpeed CLI 5.6.3 installer from codspeed.io (HTTP 504 - Gateway Time-out), before any benchmark runs first, re-run
test_node_matrix (20) pnpm runtime set bun 1 fetching github.com/oven-sh/bun/releases/download/bun-v1.4.4/SHASUMS256.txt (status 504), before pnpm i and the tests first, re-run

Both checks passed on the previous commit 0c805fc. Since then the only change is platform: 'neutral' in tsdown.config.ts, which affects neither download. On this commit, Node 22, 24 and 26 (which installed Bun fine), check and publish_preview pass. Nothing in this PR can fix an outage on those hosts; both checks should go green once codspeed.io and GitHub release downloads recover.


Generated by Claude Code

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • platform switched to neutral: the root tsdown.config.ts no longer forces platform: 'node'. Verified locally that all seven packages still build and that neutral output is byte-identical to the previous node output for the node, fetch, core, and peer packages, including declarations.
  • fixedExtension removed rather than lost: tsdown's default is fixedExtension = platform === 'node', so with neutral it still defaults to false and emits .js/.d.ts (confirmed against the tsdown 0.23.7 source and an actual build — no .mjs output, so publishConfig.exports stays valid).
  • node: imports explicitly allowlisted: under neutral, tsdown no longer auto-allows Node built-ins, so /^node:/ was added to deps.onlyImport. Confirmed the pattern matches node:fs / node:fs/promises through tsdown's parsePackageSpecifier + regex path, and that node/aws-lambda build cleanly.

pnpm check is clean. The /^node:/ entry applies repo-wide, so a stray node:* import in a browser-targeted package would not trip the guard — that hole predates this delta (the old node platform auto-allowed builtins everywhere too), so it is not a regression.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

dinwwwh added a commit to middleapi/orpc that referenced this pull request Nov 9, 2026
## Why

Builds move from per-package `tsc` to tsdown, driven by one shared
config at the root. Output keeps tsc's shape (one `.js` and `.d.ts` per
source module), so `publishConfig.exports` and `sideEffects` stay as
they are. Same migration as middleapi/standard-server#188 and
middleapi/openapi-spec#395, which use a simpler config.

## Changes

- **One root `tsdown.config.ts`.** tsdown finds it from each package
directory, so every `prepack` is just `tsdown`. Entries come from each
package's `exports`. `unbundle` keeps one file per module; relative
imports like `../package.json` are still bundled in
(`@orpc/opentelemetry` inlines only `name` and `version`), and packages
stay external.
- **Declarations come from oxc** (`dts: { generator: 'oxc' }`). The tsgo
generator drops the `DecoratedProcedure` import from `@orpc/effect`'s
`extensions/effect.d.ts`, because it is used only inside a `declare
module '@orpc/server'` augmentation. That is the bug reported in #2446
(comment 19474307). oxc needs explicit types on exports. Only
`@orpc/shared`'s regex constants lacked them; a missing annotation now
fails the build.
- **Re-exports stay tree-shakeable.** Rolldown writes re-exports as
`import { a } from 'x'; export { a }`, which Expo's tree shaking can't
see through (the problem behind #2362). A small `renderChunk` hook in
the config turns them back into `export { a } from 'x'`, in the import's
position so evaluation order is unchanged. tsdown and rolldown have no
option for this.
- **Undeclared imports fail the build.** `deps.onlyImport` lists each
package's `dependencies` and `peerDependencies` plus the runtime modules
(`bun`, `bun:*`, `node:*`, `cloudflare:*`). Any `.js` or `.d.ts` in
`dist` that imports something else fails the build, including
CommonJS-only packages and type-only imports.
- **Runtime modules are marked external** through rolldown's `external`
option, using the same list. `neverBundle: true` keeps Node built-ins
and npm-style names as written, but other `prefix:` modules (e.g.
`cloudflare:workers`) fall through to resolution and warn that they
can't be resolved.
- **Bundler module resolution.** The root tsconfig uses `module:
preserve` with `moduleResolution: bundler`, and the `.ts`/`.tsx`
extension is dropped from relative imports across packages, tests,
benches and the docs site. `dist` still imports with `.js` extensions.
`verbatimModuleSyntax` stays: without it `pnpm check` misses type-only
re-exports, which then only fail at build time.
- **Cleanup and bumps.** The emit-only options in `tsconfig.lib.json`
and the bun/cloudflare/nest `tsconfig.build.json` files are gone.
oxlint, oxlint-tsgolint, oxfmt and `@orpc/nest`'s `@oxc-project/runtime`
are bumped.
- **JSDoc kept under oxc.** `ORPCModule` is written as `export
@module({}) class`, since oxc drops a class's JSDoc that sits above its
decorators. `TmpFile.path` is now a documented field rather than a
documented constructor parameter.

## Verification

- `pnpm check`, `pnpm test` and `pnpm docs:validate` pass, and all 29
packages build without warnings.
- Every relative import in `dist` resolves, and every
`publishConfig.exports` and `sideEffects` path exists.
- The emitted declarations type-check for a `nodenext` consumer with
`skipLibCheck: false`.
- **#2446 reproduction**, built against `dist`: with tsgo declarations,
a missing required context and a wrong input type both compile. With oxc
both are rejected, matching the source types.
- **Declaration diff, tsgo vs oxc:** the same 312 files, 295 identical
apart from formatting. The rest differ only in kept `override`
modifiers, quoted member names, and three file-level comments in
`json-schema` that aren't attached to any declaration.
- **Re-export hook:** it rewrites 44 of 309 `.js` files and no `.d.ts`.
Loading each rewritten file in Node gives the same export names and
value types as a build without the hook.
- **`onlyImport`:** adding an undeclared `valibot` (runtime and
type-only) or `change-case` import to `@orpc/zod` fails the build with
`… is imported in index.js but is not included in deps.onlyImport
option`. A `bun:sqlite` import in `@orpc/bun` builds cleanly and stays
an import.
- **Expo SDK 58** (Android export, `EXPO_UNSTABLE_TREE_SHAKING=1`),
importing only `OpenAPILink` and the client:

| Build | Bundle | Server-only markers |
|---|---|---|
| tsc (beta.49) | 1,024,626 B | 0/12 |
| tsdown, no hook | 1,109,354 B | 12/12 |
| this PR | 1,027,558 B | 0/12 |

## Behavior changes

- `dist` no longer contains empty `.js` files for type-only modules, nor
`.d.ts` files that no public type refers to (for example
`router-hidden.d.ts`). Nothing imports them.
- Declarations no longer contain non-JSDoc comments.
- `@orpc/nest` bundles its decorator helpers under
`dist/_virtual/_@oxc-project_runtime@0.157.0/…`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND

Co-authored-by: Claude <noreply@anthropic.com>
@dinwwwh
dinwwwh merged commit bb2cd0a into main Nov 9, 2026
14 of 18 checks passed
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