Skip to content

Optional deps#453

Open
KoblerS wants to merge 7 commits into
mainfrom
optional-deps
Open

Optional deps#453
KoblerS wants to merge 7 commits into
mainfrom
optional-deps

Conversation

@KoblerS
Copy link
Copy Markdown
Member

@KoblerS KoblerS commented May 26, 2026

Make Cloud Storage SDKs Optional Peer Dependencies (Breaking Change v4.0.0)

New Feature / Refactor

♻️ Cloud storage SDKs (@aws-sdk/client-s3, @aws-sdk/lib-storage, @azure/storage-blob, @google-cloud/storage) are now optional peer dependencies. Users only need to install the SDK(s) for the cloud provider they actually use. If a required SDK is missing at runtime, a clear, actionable error message is shown with the exact npm install command to fix it.

Changes

  • package.json: Moved cloud storage SDKs from devDependencies to peerDependencies and added peerDependenciesMeta marking all four SDKs as optional: true. Also added axios as a direct dependency.
  • srv/attachments/aws-s3.js: Wrapped require calls for @aws-sdk/client-s3 and @aws-sdk/lib-storage in a try/catch block to throw a helpful error if either SDK is missing.
  • srv/attachments/azure-blob-storage.js: Wrapped require for @azure/storage-blob in a try/catch to provide a clear install instruction on MODULE_NOT_FOUND.
  • srv/attachments/gcp.js: Wrapped require for @google-cloud/storage in a try/catch with an informative error message if the SDK is absent.
  • lib/mtx/server.js: Applied the same lazy-require pattern with helpful error messages for the cleanup functions targeting AWS S3, Azure Blob, and Google Cloud Storage.
  • tests/unit/optionalDeps.test.js: Added new unit tests verifying that each storage provider module throws a helpful error with the correct install command when its SDK is missing, and loads successfully when the SDK is present.
  • CHANGELOG.md: Documented this as a breaking change in the upcoming v4.0.0 release.

⚠️ Breaking Change: Projects that relied on these SDKs being bundled must now explicitly install the relevant cloud storage SDK(s).

  • 🔄 Regenerate and Update Summary
PR Bot Information

Version: 1.20.51

  • Output Template: Default Template
  • Summary Prompt: Default Prompt
  • Correlation ID: ca32d7d4-42b1-4341-9c88-0392b574989f
  • LLM: anthropic--claude-4.6-sonnet
  • Event Trigger: pull_request.opened
  • File Content Strategy: Full file content

@KoblerS KoblerS requested a review from a team as a code owner May 26, 2026 07:28
@KoblerS
Copy link
Copy Markdown
Member Author

KoblerS commented May 26, 2026

Closes #448

Copy link
Copy Markdown
Contributor

@hyperspace-insights hyperspace-insights Bot left a comment

Choose a reason for hiding this comment

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

The remaining tests (lines 41–74) have the same jest.isolateModules() return-value issue and should be fixed identically by adding return before each jest.isolateModules(...) call.


Summary: The PR correctly moves cloud storage SDKs to optional peer dependencies and adds runtime MODULE_NOT_FOUND guards with actionable error messages — the overall approach is sound. However, all seven tests in optionalDeps.test.js discard the return value of jest.isolateModules(), which means assertion failures inside those callbacks may not always be reliably propagated to Jest, making the test suite fragile. Each test should return jest.isolateModules(...) to ensure proper error propagation.

PR Bot Information

Version: 1.20.51

  • Correlation ID: ca32d7d4-42b1-4341-9c88-0392b574989f
  • Event Trigger: pull_request.opened
  • File Content Strategy: Full file content
  • LLM: anthropic--claude-4.6-sonnet

Comment on lines +8 to +16
test("aws-s3 throws helpful error when @aws-sdk/client-s3 is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/client-s3", () => {
throw makeNotFound("@aws-sdk/client-s3")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
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.

Bug: expect assertions inside jest.isolateModules() callbacks are silently swallowed on failure

jest.isolateModules() executes the callback synchronously and propagates thrown errors, but the callback's return value is discarded. If an expect(...).toThrow() assertion fails (i.e., no error is thrown), expect itself throws internally — and that will propagate, so failure detection works in that direction. However, for expect(...).not.toThrow() (lines 56, 65, 72), if the module does throw, the error is thrown inside the callback but Jest may not always reliably associate it with the enclosing test, especially when modules load side-effects or when an unhandled rejection occurs.

More critically, these tests that assert successful loading (.not.toThrow()) load real modules that have real side-effects (e.g., aws-s3.js calls cds.log(...), requires ../../lib/helper, etc.). If those side-effect requires fail, the error propagates, but the isolation is not guaranteed to be clean across tests because jest.dontMock inside jest.isolateModules doesn't reset the mock registry for modules that were previously mocked in earlier tests.

The safest pattern is to return the jest.isolateModules call from the test callback, ensuring the module registry is properly scoped:

Suggested change
test("aws-s3 throws helpful error when @aws-sdk/client-s3 is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/client-s3", () => {
throw makeNotFound("@aws-sdk/client-s3")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
test("aws-s3 throws helpful error when @aws-sdk/client-s3 is missing", () => {
return jest.isolateModules(() => {
jest.doMock("@aws-sdk/client-s3", () => {
throw makeNotFound("@aws-sdk/client-s3")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
})

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +19 to +28
test("aws-s3 throws helpful error when @aws-sdk/lib-storage is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/lib-storage", () => {
throw makeNotFound("@aws-sdk/lib-storage")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
})
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.

Bug: expect assertions inside jest.isolateModules() callbacks are silently swallowed on failure

Same issue as the first test — jest.isolateModules() return value is discarded. Return it to ensure the assertion result is propagated to Jest properly.

Suggested change
test("aws-s3 throws helpful error when @aws-sdk/lib-storage is missing", () => {
jest.isolateModules(() => {
jest.doMock("@aws-sdk/lib-storage", () => {
throw makeNotFound("@aws-sdk/lib-storage")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
})
test("aws-s3 throws helpful error when @aws-sdk/lib-storage is missing", () => {
return jest.isolateModules(() => {
jest.doMock("@aws-sdk/lib-storage", () => {
throw makeNotFound("@aws-sdk/lib-storage")
})
expect(() => require("../../srv/attachments/aws-s3")).toThrow(
"npm install @aws-sdk/client-s3 @aws-sdk/lib-storage",
)
})
})

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +30 to +39
test("azure-blob-storage throws helpful error when @azure/storage-blob is missing", () => {
jest.isolateModules(() => {
jest.doMock("@azure/storage-blob", () => {
throw makeNotFound("@azure/storage-blob")
})
expect(() => require("../../srv/attachments/azure-blob-storage")).toThrow(
"npm install @azure/storage-blob",
)
})
})
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.

Bug: jest.isolateModules() return value discarded — assertion failures may not propagate reliably

All remaining jest.isolateModules() calls discard the return value. Return it from the test callback so Jest can properly handle any thrown assertion errors.

Suggested change
test("azure-blob-storage throws helpful error when @azure/storage-blob is missing", () => {
jest.isolateModules(() => {
jest.doMock("@azure/storage-blob", () => {
throw makeNotFound("@azure/storage-blob")
})
expect(() => require("../../srv/attachments/azure-blob-storage")).toThrow(
"npm install @azure/storage-blob",
)
})
})
test("azure-blob-storage throws helpful error when @azure/storage-blob is missing", () => {
return jest.isolateModules(() => {
jest.doMock("@azure/storage-blob", () => {
throw makeNotFound("@azure/storage-blob")
})
expect(() => require("../../srv/attachments/azure-blob-storage")).toThrow(
"npm install @azure/storage-blob",
)
})
})

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

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.

1 participant