Skip to content

Keep @aws-sdk out of the static graph behind files-sdk/r2 - #109

Merged
haydenbleasel merged 1 commit into
mainfrom
fix/r2-lazy-aws-sdk
Jul 25, 2026
Merged

Keep @aws-sdk out of the static graph behind files-sdk/r2#109
haydenbleasel merged 1 commit into
mainfrom
fix/r2-lazy-aws-sdk

Conversation

@haydenbleasel

@haydenbleasel haydenbleasel commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Fixes #105.

Problem

Building a Worker that uses files-sdk/r2 in binding (or fetch) mode failed under rolldown-vite with one MISSING_EXPORT per @aws-sdk/client-s3 named import when the optional @aws-sdk/* peers weren't installed. The aws-sdk HTTP engine is lazy at runtime (await import("../s3/index.js")), but consumer bundlers resolve dynamically-reached chunks at build time too — and the s3 entry's static named imports then hard-error against Vite's optional-peer-dep placeholder, which is module.exports = {} plus a throw (no named exports).

Fix

No static @aws-sdk/* value import remains anywhere in the module graph reachable from files-sdk/r2:

  • src/s3/core.ts (moved from s3/index.ts, so the diff is mostly the rename): the engine is now parameterized over an S3Sdk bundle of the three SDK modules, importing only their types.
  • src/s3/index.ts: thin static wiring — same named imports as before (tree-shakeable), same sync s3() factory and raw client. No behavior change for files-sdk/s3 consumers or the s3-compatible wrappers.
  • src/r2/index.ts: lazyS3 now dynamically imports ../s3/core.js plus the three @aws-sdk/* modules. Dynamic specifiers only ever resolve to the placeholder as a chunk that never executes on the binding/fetch paths, so bundling succeeds.

Tests

  • New regression guard in build-output.test.ts: walks the dist r2 graph the way a bundler does (through dynamic imports) and asserts optional peers appear only as dynamic specifiers. The walk flags the pre-fix layout and the s3 entry (which legitimately imports the SDK statically), so it demonstrably bites.
  • Full suite: 3150 pass / 0 fail, per-file coverage gates hold (s3/core.ts 98.41/98.80).
  • Verified the dist lazy path end-to-end: r2({...}).url() presigns offline through the new Promise.all load, raw reflects the loaded S3Client, and the entire lazily-reachable chunk graph contains no static @aws-sdk import.

Summary by CodeRabbit

  • New Features

    • Added S3-compatible storage support, including uploads, downloads, deletion, listings, signed URLs, resumable uploads, metadata, and server-side copying.
    • Added multipart upload support with progress tracking and automatic handling for large files.
    • Added clearer error mapping for common S3 provider responses.
  • Bug Fixes

    • Improved R2 bundling compatibility by loading optional AWS components only when needed, preventing build-time errors in Workers applications.

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
files-sdk Ready Ready Preview, Comment Jul 25, 2026 6:33pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 68c9eb4

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

This PR includes changesets to release 1 package
Name Type
files-sdk 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

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The S3 adapter implementation moves into an injectable core. R2 dynamically loads the core and AWS SDK modules on first use, while S3 retains static wiring. Build-output tests now traverse dynamic imports to verify optional AWS peers remain absent from the R2 bundle.

Changes

R2 lazy AWS SDK loading

Layer / File(s) Summary
S3 adapter core
packages/files-sdk/src/s3/core.ts
Defines the injected AWS SDK contract and implements S3 storage, multipart, resumable upload, signing, URL, and error-mapping behavior.
Static S3 wiring and R2 lazy construction
packages/files-sdk/src/s3/index.ts, packages/files-sdk/src/r2/index.ts
Keeps static AWS SDK wiring in the S3 entry point while R2 dynamically imports the core and required AWS SDK modules before constructing the adapter.
Dynamic dependency graph validation
packages/files-sdk/test/build-output.test.ts, .changeset/r2-lazy-aws-sdk.md
Adds dynamic-import traversal to optional-peer checks and verifies the R2 bundle contains no offending AWS SDK externals; records a patch release.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant R2
  participant S3Core
  participant AWSSDK
  R2->>AWSSDK: Dynamically import S3 and presigning modules
  R2->>S3Core: Call createS3Adapter with loaded modules
  S3Core->>AWSSDK: Construct client and execute S3 operations
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: keeping @aws-sdk out of R2's static import graph.
Description check ✅ Passed The description covers the problem, fix, tests, and related issue, with only some template sections left minimal.
Linked Issues check ✅ Passed The changes address #105 by removing static AWS SDK imports from R2's reachable graph and adding a regression test.
Out of Scope Changes check ✅ Passed The refactor, lazy-loading changes, and test additions all support the R2 build fix and stay within scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/r2-lazy-aws-sdk

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

@haydenbleasel
haydenbleasel merged commit c04e0d0 into main Jul 25, 2026
18 of 19 checks passed
@haydenbleasel
haydenbleasel deleted the fix/r2-lazy-aws-sdk branch July 25, 2026 18:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/files-sdk/src/s3/core.ts (1)

136-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve the underlying import error as cause.

A bare catch turns any module-initialization failure inside @aws-sdk/lib-storage into a misleading "install the optional peer" message. Keeping the original error makes the rare non-ERR_MODULE_NOT_FOUND case debuggable.

♻️ Proposed change
 const loadLibStorage = async () => {
   try {
     return await import("`@aws-sdk/lib-storage`");
-  } catch {
+  } catch (error) {
     throw new FilesError(
       "Provider",
-      "Multipart and progress uploads on S3 require the optional peer dependency '`@aws-sdk/lib-storage`'. Install it to use the `multipart` or `onProgress` options."
+      "Multipart and progress uploads on S3 require the optional peer dependency '`@aws-sdk/lib-storage`'. Install it to use the `multipart` or `onProgress` options.",
+      error
     );
   }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/files-sdk/src/s3/core.ts` around lines 136 - 145, Update
loadLibStorage to capture the import exception and pass it as the FilesError
cause, while retaining the existing user-facing message. Preserve the original
error for all module-loading failures, including optional-dependency and
initialization errors.
packages/files-sdk/test/build-output.test.ts (1)

137-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard r2/index.js in ensureBuilt(). The build emits dist/r2/index.js, but ensureBuilt() only gates on cliBundle and loaderBundle, so a stale dist/ can still reach readFileSync with an ENOENT here. Add r2 to the precondition or check the file explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/files-sdk/test/build-output.test.ts` around lines 137 - 147, Update
ensureBuilt() to also verify that the generated r2/index.js bundle exists before
tests read it, either by including the r2 artifact in its existing build
precondition or by explicitly checking that file. Preserve the existing
cliBundle and loaderBundle checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/files-sdk/src/s3/core.ts`:
- Around line 136-145: Update loadLibStorage to capture the import exception and
pass it as the FilesError cause, while retaining the existing user-facing
message. Preserve the original error for all module-loading failures, including
optional-dependency and initialization errors.

In `@packages/files-sdk/test/build-output.test.ts`:
- Around line 137-147: Update ensureBuilt() to also verify that the generated
r2/index.js bundle exists before tests read it, either by including the r2
artifact in its existing build precondition or by explicitly checking that file.
Preserve the existing cliBundle and loaderBundle checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 908b8fa7-f2f0-4976-b43c-ee72c0ff8332

📥 Commits

Reviewing files that changed from the base of the PR and between d296f26 and 68c9eb4.

📒 Files selected for processing (5)
  • .changeset/r2-lazy-aws-sdk.md
  • packages/files-sdk/src/r2/index.ts
  • packages/files-sdk/src/s3/core.ts
  • packages/files-sdk/src/s3/index.ts
  • packages/files-sdk/test/build-output.test.ts

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.

R2 binding mode fails to build under rolldown-vite: MISSING_EXPORT on optional @aws-sdk/* peers

1 participant