Skip to content

bun-types: add an S3File signature to Bun.write() and ReadableStream to the S3 write() data - #46923

Open
robobun wants to merge 3 commits into
mainfrom
robobun/82f5e2a8/bun-types-s3-write-overloads
Open

robobun wants to merge 3 commits into
mainfrom
robobun/82f5e2a8/bun-types-s3-write-overloads

Conversation

@robobun

@robobun robobun commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Bun.write(s3file, input, options) reads S3 options and takes a Response or Request. The declarations take neither: Bun.write(client.file("k"), "x", { acl: "private" }) is error TS2769: No overload matches this call (packages/bun-types/bun.d.ts:2151). Fix every type error in test/ #44614 lists both gaps.
  • The three S3 write() methods take a ReadableStream, which their data unions lack (packages/bun-types/s3.d.ts:716).

Fix

  • One added signature: Bun.write(destination: S3File, input, options?), after the Response file signatures, so a file write tries no extra signature. Its options are S3Options without type, plus the two file options. The three data unions gain ReadableStream.
  • Correct: the runtime parses one options object for each destination (src/runtime/webcore/Blob.rs:5026). Tests in s3.test.ts run each sentence of the new JSDoc.
  • Verified: test/integration/bun-types/bun-types.test.ts (25 pass, 10 fail on main's declarations) and test/js/bun/s3/s3.test.ts. The tsc projects of lint.yml and test: 0 new diagnostics.
  • Self-reviewed: 23 concerns raised, 20 addressed. Not taken: rewriting Bun.write as three signatures (Notes).

Background

  • Bun.write has one declaration for each kind of call, and TypeScript takes the first that matches.
  • An S3File is what client.file(key) returns. s3file.write(input, options) is the same native write, typed already.
  • Considered removing S3File from the first signature, so an editor completes S3 options in { }. That needs a copy of the signature and changes no compile result.

Downsides

  • Compile fix only: Bun.write(s3file, x, { }) still completes createPath, mode, as on main.
  • bun check spends +284 instructions (+1.1%) for each Bun.write call.
  • Code that reads the old types stops compiling, for example Parameters<S3File["write"]>[0] used as the old union. This repository has none.
Notes

What the added signature takes

  • input: the input of the file signature, plus ArrayBufferView (a DataView), Response and Request. Each kind was run with options against test/packages/s3-server.
  • options: S3Options without type, and the two file options. The runtime does not apply a type of this call, so a literal { type } stays a compile error, as on main. A variable typed S3Options still fits. The runtime parses mode and createPath from the same object for each destination. For S3, mode is range-checked and not used, and createPath is not used. Both compile for an S3File on main. With them in the type, the options of the new signature extend the file options. So a function typed typeof Bun.write that declares options?: { createPath?: boolean } still compiles. TypeScript also offers no S3 key while a file write has a half-typed key. They are spelled without | undefined, like the file signatures: with it, both effects are lost under exactOptionalPropertyTypes. A type test with that flag holds the spelling.
  • Position: fourth of six. Before the Response signatures, each Bun.write(path, response) would try and reject it. Last, it would change the error text of each call that matches no signature, because TypeScript prints the error of the last one.

What the JSDoc says about the options, and the test for it

  • An option of the call replaces the same option of the S3File for that write only. Test: applies an option of the call to that write only.
  • options takes no type. The content type is the type of the file, or else the type of the extension of its key. s3file.write(x, { type }) and the type option of file() do set it. Test: does not apply a type of the call.
  • partSize, queueSize and retry of the call are applied to a stream input. For a BunFile or an S3File input, those of the destination are used. Test: applies partSize of the call to a stream input, not to a BunFile input. s3: add tagging option and route write headers through one carrier #46746 changes the BunFile case. It then changes this sentence and this test.

These tests describe main's runtime, so they pass without this PR. They fail when the runtime changes under the JSDoc. Runtime checks ran on a debug build of this branch and on canary a8fcd31.

Measurements, main 179664e against this PR

  • Overload candidates for each call site (TypeScript 6.0.2, V8 call counts of getSignatureApplicabilityError): no change for a call that compiles on main and has no any argument (6 file shapes, S3File, BunFile | S3File). any destination 6 to 7. A call that matches no signature 11 to 13. An S3File call that is TS2769 on main 11 to 4.
  • tsc 7.0.2 allocations for each call site (--extendedDiagnostics, slope 1000 to 3000 calls): -0.03 to +0.00 for each shape that compiles on main. A call that is TS2769 on main: 362.47 to 21.17.
  • TypeScript 6.0.2 checker function calls for each call site: +28 for each call that compiles on main (plain file write 1,256 to 1,284). any destination +244.
  • bun check user instructions for each call site (perf_event_open, minimum of 5 interleaved runs, slope 2000 to 4000 calls, spread 53 to 150): file write with a literal path +194 (+0.7%), with a string path +284 (+1.1%). Response-body file write +266 (+0.8%). S3File destination +260 (+1.0%). any destination +2,308 (+6.5%). An S3File call that is TS2769 on main: 2,670,471 to 81,224.
  • Bun.write call sites of the test project (2,023 sites): candidates tried 2,150 to 2,164 (+14). 2,008 sites unchanged. +1 at 8 sites with an any argument, +2 at 6 calls that match no signature, -6 at 1 site.
  • Type-check of the repository: 0 new diagnostics in the five projects of lint.yml. test project: 1 gone (test/js/bun/s3/s3-stream-stored-error-fixture.ts:79), 0 new, 0 new unused @ts-expect-error.
  • Probe of reflective forms (tsc 7.0.2): 27 rows that compile on main stop, with and without lib.dom. 156 rows newly compile. The 27 are of four kinds:
    • a function typed typeof Bun.write with each parameter annotated
    • an overload of Bun.write picked by index
    • Parameters<..> of an S3 write() read as the old union
    • a // @ts-expect-error above a call that now compiles
  • Editor (TypeScript 6.0.2 LanguageService): completions in Bun.write(s3file, "x", { }) stay createPath, mode. After one S3 key, or with a Response input, the S3 keys are listed. With a half-typed key the list is createPath, as on main, for each destination. Hover on Bun.write(s3file, "x", { acl }) shows the new signature. The error text of a call that matches no signature does not change.
  • Published package: bun.d.ts +1,739 bytes, s3.d.ts +90 bytes. No native file changes.

Not in this PR, with the reason

Self-review

The first version of this branch was wider. It also removed S3File from the first signature and added a copy of that signature for union destinations, with a pinned order. It typed the four statics and used a shared Bun.__internal data type. The review changed it as follows:

  • The statics are out: no demand on record.
  • The reordered signatures are out: they change no compile result.
  • The JSDoc claimed partSize for a BunFile input. It now states the exception, with a test.
  • __internal is not for a union that callers pass. The unions are inline, and fixture checks tie them.
  • An array of parts loses its file-backed parts. It stays off the S3 methods.
  • The fixture had no reject row for the new signature. It has four.
  • The example used acl: "public-read". It uses storageClass.

Not taken: the rewrite to three signatures, for the reason above. Also not taken: one concern asked to keep the reordered signatures for the editor. That is the second item of the list above.

After the code review of the first push:

  • The options took the whole S3Options, so a literal { type } compiled and was not applied. They now leave type out.
  • The review asked for | undefined on mode and createPath. Not taken: with exactOptionalPropertyTypes, a function typed typeof Bun.write that takes the file options then fails with TS2322. The new type test shows it.

Other open PRs on these lines

Demand. No user reported the type error. The record is the gap list of #44614, the three-argument call in the repro of #42722, and two casts in test/js/web/fetch/fetch-backpressure.test.ts, which this PR removes. The other four casts that #44614 left for S3 stay: they are for s3:// destinations.

Suites run. test/integration/bun-types/bun-types.test.ts. Under a debug build: test/js/bun/s3/s3.test.ts (310 pass, 270 skipped for credentials) and the S3 block of test/js/web/fetch/fetch-backpressure.test.ts (8 pass). tsc --noEmit -p for scripts/build, scripts, src/js, src, src/runtime/bake, test and packages/bun-types. bun fmt --check.


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/web/fetch/fetch-backpressure.test.ts

Bun.write(s3file, input, options) reads `options` as S3 options and
takes a Response or a Request as input. The declarations took neither,
so both calls were TS2769.

- Bun.write: one added signature for an S3File destination, after the
  two file signatures for a Response or a Request. A file write tries
  no extra signature. The options are S3Options plus the two file
  options, which the runtime also parses for an S3 destination.
- S3File.write and both S3Client.write: the data union gains
  ReadableStream, which the runtime takes.
- Tests in s3.test.ts run what the new JSDoc says about the options.
- Remove two casts in fetch-backpressure.test.ts that the missing
  signature made necessary, and correct a stale comment in s3.test.ts.
@robobun
robobun requested a review from alii as a code owner October 25, 2026 20:51
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 179664e and a32e8be.

Files selected for processing: 6
  • packages/bun-types/bun.d.ts
  • packages/bun-types/s3.d.ts
  • test/integration/bun-types/bun-types.test.ts
  • test/integration/bun-types/fixture/s3.ts
  • test/js/bun/s3/s3.test.ts
  • test/js/web/fetch/fetch-backpressure.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough

The S3 write declarations now accept ReadableStream inputs and provide a Bun.write overload for S3File destinations. New type and runtime tests cover supported inputs and upload option behavior.

Changes

S3 write API

Layer / File(s) Summary
S3 write signatures and type coverage
packages/bun-types/bun.d.ts, packages/bun-types/s3.d.ts, test/integration/bun-types/*
Bun.write gains an S3File overload, and S3 write methods accept ReadableStream. Type tests cover supported inputs, options, and compatibility with exactOptionalPropertyTypes.
Upload option behavior tests
test/js/bun/s3/s3.test.ts, test/js/web/fetch/fetch-backpressure.test.ts
Tests cover per-write ACL and content disposition, content type handling, and partSize behavior for stream and BunFile inputs. Fetch backpressure tests remove casts from S3 file calls.

Suggested reviewers: jarred-sumner

Priority: Normal

Merge Risk: Minimal · up to a32e8

The S3 write typings align with the inspected upload path, and no issue remains that should block merging after normal checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly identifies both main changes: the S3File overload for Bun.write() and ReadableStream support for S3 write() data.
Description check Passed The description explains the problem, fix, scope, runtime behavior, tests, verification results, and known limitations. It does not use the template headings exactly, but it provides the required info…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, fix, scope, runtime behavior, tests, verification results, and known limitations. It does not use the template headings exactly, but it provides the required information in equivalent sections.

  • 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

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:45 PM PT - Oct 25th, 2026

✅ @robobun, your commit a32e8be447fa2d702d72890df427bffe26baef4a passed in Build #132790! 🎉


🧪   To try this PR locally:

bunx bun-pr 46923

That installs a local version of the PR into your bun-46923 executable, so you can run:

bun-46923 --bun

@robobun

robobun commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

How I reproduced it, with the declarations of main (179664e):

import { S3Client } from "bun";
declare const client: S3Client;

await Bun.write(client.file("k"), "x", { acl: "private" }); // error TS2769: No overload matches this call.
await Bun.write(client.file("k"), new Response("x")); // error TS2769: No overload matches this call.
await client.file("k").write(new ReadableStream()); // error TS2345

The three calls upload at runtime against test/packages/s3-server. With this PR they compile.

test/integration/bun-types/bun-types.test.ts with the new fixture: 10 of 25 tests fail on the declarations of main, and 25 pass with this PR.

PR: #46923

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

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread packages/bun-types/bun.d.ts Outdated
Comment thread packages/bun-types/bun.d.ts Outdated
The runtime does not apply a `type` given to Bun.write(s3file, x,
options): the content type comes from the S3File. The signature now
takes `Omit<S3Options, "type">`, so a literal `{ type }` stays a
compile error there, as it is on main. S3File.write() and the `type`
option of file() do apply it, and the JSDoc points to them.

Add a type test with exactOptionalPropertyTypes for the spelling of
`mode` and `createPath` on that signature: a function typed as
Bun.write that takes the file options must still compile.

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Commit 6f9840e addresses both points from my earlier review: the options type is now Omit<S3Options, "type">, and the new exactOptionalPropertyTypes typeTest in test/integration/bun-types/bun-types.test.ts shows why mode/createPath are spelled without | undefined (each overload's options must stay assignable to a typeof Bun.write mock's { mode?; createPath? }). Beyond the inline nit, I also checked that passing a Bun.S3Options variable carrying type still compiles (no excess-property check on non-literals) -- that is the designed escape hatch and the runtime test "does not apply a type of the call" pins its behavior -- and that the new overload sits after the Response file signatures so existing Bun.write(path, response) bindings are unchanged. Since packages/bun-types/ is CODEOWNER-owned, a maintainer look at the overload shape is still worthwhile.

Extended reasoning...

The change is declarations-only: a fourth Bun.write overload for an S3File destination in packages/bun-types/bun.d.ts, ReadableStream added to the data unions of the three S3 write() methods in packages/bun-types/s3.d.ts, a type fixture, a typeTest, three runtime tests in test/js/bun/s3/s3.test.ts, and removal of two as any casts. It touches no security-sensitive surface. The second commit resolved both of my prior inline findings. Approval is withheld because *.d.ts and packages/bun-types/ are CODEOWNER-owned and the overload design (position, deliberate deviation from the | undefined convention) is an API-surface decision for a maintainer; the one remaining finding is a test nit posted inline.

Comment thread test/js/bun/s3/s3.test.ts
`expect.not.stringContaining("-")` also passes for a value that is not a
string, so the two one-part rows passed with a missing ETag. Each row
now matches the shape of its ETag.

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

Code review completed

Nothing new to post: everything this review found is already covered by existing comments on this pull request or didn't merit a separate one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant