Skip to content

[PB-1967]: feat/upload manager#58

Merged
xabg2 merged 6 commits into
masterfrom
feature/upload-attachment-manager
Jun 3, 2026
Merged

[PB-1967]: feat/upload manager#58
xabg2 merged 6 commits into
masterfrom
feature/upload-attachment-manager

Conversation

@xabg2

@xabg2 xabg2 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Adding an upload manager to handle the attachment upload.

@xabg2 xabg2 self-assigned this Jun 3, 2026
@xabg2 xabg2 added the enhancement New feature or request label Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@xabg2, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 50 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b5222a3-efc9-4be1-93f1-16d914bf3629

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd888e and eae4f68.

📒 Files selected for processing (1)
  • src/test-utils/fixtures.ts
📝 Walkthrough

Walkthrough

Adds MailService attachment upload/download methods, defines upload manager types, implements a concurrent UploadManager with retry and cancellation, enables global HTTP retry, updates async dependencies, and adds Vitest tests covering upload flows.

Changes

Attachment Upload and Task Management

Layer / File(s) Summary
Upload manager type contracts
src/types/mail/upload-manager/index.ts
Defines UploadAttachmentCallbacks, UploadHandle, and UploadAttachmentTask types with optional RequestCanceler and status flags.
MailService attachment methods
src/services/sdk/mail/index.ts
Adds uploadAttachment(file) returning { promise, requestCanceler } and downloadAttachment(mailId, blobId, mailName, mailType) delegating metadata { name, type } to the client.
SDK global retry enablement
src/services/sdk/index.ts
Imports HttpClient and calls HttpClient.enableGlobalRetry() during module initialization.
UploadManager service with queue and retry
src/services/upload-manager/index.ts
Implements UploadManager singleton with an async queue (fixed concurrency), run, retry, and remove APIs, per-task cancellation and failed flags, and uploadWithRetries that retries transient errors and clears per-attempt cancelers.
UploadManager test suite
src/services/upload-manager/upload-manager.test.ts
Vitest tests for handle creation, success and error callback behavior, retrying transient failures up to MAX_RETRIES, non-retry on 4xx, retry semantics, and cancellation behavior including request canceler invocation.
Dependency updates
package.json
Bumps @internxt/sdk to ^1.17.4, adds/updates async to ^3.2.6, and @types/async to ^3.2.25.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • jzunigax2
  • larryrider
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'PB-1967: feat/upload manager' is directly related to the main change: introducing an UploadManager to handle attachment uploads across multiple new files.
Description check ✅ Passed The description 'Adding an upload manager to handle the attachment upload' is related to the changeset, which introduces the UploadManager class and supporting infrastructure for handling file attachments.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/upload-attachment-manager

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 and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 3, 2026

Copy link
Copy Markdown

Deploying mail-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: eae4f68
Status: ✅  Deploy successful!
Preview URL: https://1436f761.mail-web-ea0.pages.dev
Branch Preview URL: https://feature-upload-attachment-ma.mail-web-ea0.pages.dev

View logs

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 5

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

Inline comments:
In `@src/services/upload-manager/index.ts`:
- Around line 68-82: In uploadWithRetries, don't blindly retry every rejection
from MailService.instance.uploadAttachment; instead detect transient failures
(e.g., network timeouts, 5xx responses) and only loop when
isTransientError(error) is true (or error.statusCode/ error.code indicates
transient), otherwise rethrow immediately; alternatively add an idempotencyToken
property to UploadAttachmentTask and pass it into
MailService.instance.uploadAttachment(task.file, { idempotencyToken }) so the
server can safely make retries idempotent; update the retry loop in
uploadWithRetries to consult the transient check and the task.cancelled flag,
only assign/clear task.canceler around attempts, and throw the lastError only if
it was transient and max retries exhausted.
- Around line 34-54: Add JSDoc comments to the public service methods run,
retry, and remove to document their purpose and contract: for run describe that
it accepts an array of File and UploadAttachmentCallbacks, returns an array of
UploadHandle objects with id and file, and that it enqueues tasks via
enqueueTask; for retry describe that it takes an existing task id and callbacks,
cancels the current task if present and re-enqueues it; for remove describe that
it cancels and deletes the task by id. Include `@param` and `@returns` tags (and any
thrown/side-effect notes) and reference the UploadAttachmentCallbacks and
UploadHandle types in the descriptions.

In `@src/services/upload-manager/upload-manager.test.ts`:
- Line 2: The test imports the internal module using a relative path
("./index"); update the import to use the project path alias so it follows the
src/* convention—replace the './index' import for UploadManager in
upload-manager.test.ts with the '`@/services/upload-manager`' style import
(referencing the UploadManager symbol) so the test uses the alias-based module
path.
- Around line 29-31: Replace the current beforeEach that only calls
uploadAttachment.mockReset() with a call to vi.restoreAllMocks() so all
spies/mocks are restored between tests; locate the beforeEach block in this test
file (the one referencing uploadAttachment) and change it to invoke
vi.restoreAllMocks() (you may keep or add uploadAttachment.mockReset() if you
still need to clear call history, but ensure vi.restoreAllMocks() is called to
fully restore mocked implementations).
- Line 38: Rename the test titles to describe observable behavior using the
"When <situation>, then <expected outcome>" pattern and remove
implementation-specific terms like "id", "File", "onSuccess", and "canceler";
for example change "When files are pushed, then it returns a handle per file
with stable id and File" to something like "When files are pushed, then each
pushed file yields a stable handle" and similarly update the other titles that
mention implementation details (the test titled at "When files are pushed..."
and the tests around the strings at the other two occurrences) so they focus
only on what a user/system observes rather than internal variable or type names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 08eda9bf-cda1-4e8b-99e2-d650925170c3

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5ede1 and 4342577.

📒 Files selected for processing (4)
  • src/services/sdk/mail/index.ts
  • src/services/upload-manager/index.ts
  • src/services/upload-manager/upload-manager.test.ts
  • src/types/mail/upload-manager/index.ts

Comment thread src/services/upload-manager/index.ts
Comment thread src/services/upload-manager/index.ts
Comment thread src/services/upload-manager/upload-manager.test.ts
Comment thread src/services/upload-manager/upload-manager.test.ts
Comment thread src/services/upload-manager/upload-manager.test.ts Outdated
@xabg2 xabg2 requested a review from jzunigax2 June 3, 2026 11:19
Comment on lines +96 to +111
if (!isTransientError(error)) throw error;
} finally {
task.canceler = undefined;
}
}
throw lastError;
}
}

function isTransientError(error: unknown): boolean {
if (error instanceof AxiosResponseError) return error.status >= 500;
if (error instanceof AxiosUnknownError) {
return error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT' || error.code === 'ERR_NETWORK';
}
return false;
}

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.

seems like this misses 429s. those should also be retryable and even better if we honor Retry-After header if included in response

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Even better, the SDK has a method for that. Just by calling HttpClient.enableGlobalRetry, the SDK will manage the retry back off for each SDK call internally.

@xabg2 xabg2 requested a review from jzunigax2 June 3, 2026 14:37

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/upload-manager/index.ts (1)

86-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop canceled tasks before starting the next retry attempt.

A task can be canceled after a transient failure and before the next loop iteration, but this code still calls MailService.instance.uploadAttachment(task.file) again on Line 89. For uploads, that means remove()/retry() can still trigger one extra write after the user has canceled it.

Suggested fix
   private async uploadWithRetries(task: UploadAttachmentTask): Promise<UploadAttachmentResponse> {
     let lastError: unknown;
     for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
+      if (task.cancelled) throw new Error(CANCEL_REASON);
+
       const { promise, requestCanceler } = MailService.instance.uploadAttachment(task.file);
       task.canceler = requestCanceler;
       try {
         return await promise;
🤖 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 `@src/services/upload-manager/index.ts` around lines 86 - 96, The retry loop in
uploadWithRetries calls MailService.instance.uploadAttachment(task.file) even if
the task was canceled between attempts; to fix, ensure you check task.cancelled
before starting a new attempt (either at the top of the for-loop or immediately
after obtaining/assigning requestCanceler) and if canceled, cancel the pending
request (call the requestCanceler if available) and throw/return a cancellation
error (or rethrow lastError) instead of proceeding; update the logic in
uploadWithRetries (referencing MAX_RETRIES, task.canceler, task.cancelled, and
MailService.instance.uploadAttachment) so no new upload is started for a task
that has been marked cancelled.
🤖 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.

Inline comments:
In `@package.json`:
- Line 27: The dependency uses unstable deep imports from `@internxt/sdk/dist/`*
which can break on minor updates; either pin the package to the exact working
version or remove deep imports and switch to the SDK's public API: update
package.json to replace "^1.17.4" with the exact resolved version (e.g.,
"1.17.4") if you want to pin, or refactor all imports in files such as
src/services/sdk/index.ts and src/services/upload-manager/index.ts (and any
other files importing `@internxt/sdk/dist/`*) to use the SDK's documented public
entrypoints, then run install/build/tests to verify no import errors remain.

---

Outside diff comments:
In `@src/services/upload-manager/index.ts`:
- Around line 86-96: The retry loop in uploadWithRetries calls
MailService.instance.uploadAttachment(task.file) even if the task was canceled
between attempts; to fix, ensure you check task.cancelled before starting a new
attempt (either at the top of the for-loop or immediately after
obtaining/assigning requestCanceler) and if canceled, cancel the pending request
(call the requestCanceler if available) and throw/return a cancellation error
(or rethrow lastError) instead of proceeding; update the logic in
uploadWithRetries (referencing MAX_RETRIES, task.canceler, task.cancelled, and
MailService.instance.uploadAttachment) so no new upload is started for a task
that has been marked cancelled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ae85379-1b3f-4e0f-ba0b-389f20ea4991

📥 Commits

Reviewing files that changed from the base of the PR and between 4342577 and 5bd888e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/services/sdk/index.ts
  • src/services/upload-manager/index.ts
  • src/services/upload-manager/upload-manager.test.ts

Comment thread package.json
"@internxt/css-config": "^1.1.0",
"@internxt/lib": "^1.4.1",
"@internxt/sdk": "1.17.2",
"@internxt/sdk": "^1.17.4",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'SDK version in package.json:\n'
jq -r '.dependencies["`@internxt/sdk`"]' package.json

printf '\nDeep SDK imports currently used in src/:\n'
rg -n "`@internxt/sdk/dist/`" src

Repository: internxt/mail-web

Length of output: 6977


Pin @internxt/sdk (or stop relying on dist/* deep imports)

package.json sets @internxt/sdk to ^1.17.4, but src/ imports multiple internal SDK artifacts via @internxt/sdk/dist/* (e.g., src/services/sdk/index.ts and src/services/upload-manager/index.ts, plus many other files). Because dist/* paths aren’t a stable public API, an SDK refresh can break builds without any code changes.

Pin @internxt/sdk to the exact version used by these imports, or refactor to SDK public exports/entrypoints.

🤖 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 `@package.json` at line 27, The dependency uses unstable deep imports from
`@internxt/sdk/dist/`* which can break on minor updates; either pin the package to
the exact working version or remove deep imports and switch to the SDK's public
API: update package.json to replace "^1.17.4" with the exact resolved version
(e.g., "1.17.4") if you want to pin, or refactor all imports in files such as
src/services/sdk/index.ts and src/services/upload-manager/index.ts (and any
other files importing `@internxt/sdk/dist/`*) to use the SDK's documented public
entrypoints, then run install/build/tests to verify no import errors remain.

@sonarqubecloud

sonarqubecloud Bot commented Jun 3, 2026

Copy link
Copy Markdown

@xabg2 xabg2 merged commit 51e34d7 into master Jun 3, 2026
6 checks passed
@xabg2 xabg2 deleted the feature/upload-attachment-manager branch June 3, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants