@aws-sdk/lib-storage
semver
>=3.0.0 <4.0.0postconditions7functions2last verified2026-06-24coverage score100%Postconditions: what we check
- done · upload-done-no-try-catcherrorWhenupload.done() called without try-catch: S3 bucket does not exist (NoSuchBucket), insufficient permissions (AccessDenied), network failure, credentials expired, content-type mismatch, file size limits exceeded, or any S3 service error during the multipart upload process.Throws
S3ServiceException subclass (e.g., NoSuchBucket with error.name 'NoSuchBucket', AccessDenied with error.name 'AccessDenied', EntityTooLarge). For network failures: generic Error with connection/timeout message. For credentials: CredentialsProviderError.Required handlingCaller MUST wrap upload.done() in try-catch. Multipart uploads involve multiple network requests (initiate, upload parts, complete) and can fail at any stage. Unhandled rejections cause silent data loss — the file appears to be uploading but never completes. Minimum handling: try { await upload.done(); } catch (err) { console.error('Upload failed:', err); await upload.abort(); // Clean up the incomplete multipart upload throw err; } Note: Incomplete multipart uploads incur S3 storage costs. Always abort on failure.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - done · upload-done-already-callederrorWhenupload.done() is called a second time on the same Upload instance. Upload instances are single-use — calling done() twice throws immediately regardless of whether the first call succeeded or failed.Throws
Error: "@aws-sdk/lib-storage: this instance of Upload has already executed .done(). Create a new instance."Required handlingCaller MUST create a new Upload instance for each upload operation. Do NOT reuse Upload instances across retries — if done() fails, create a new Upload instance with the same parameters and call done() on it instead. WRONG — will throw on retry: const upload = new Upload({...}); try { await upload.done(); } catch (e) { await upload.done(); } // THROWS CORRECT — create new instance for retry: const makeUpload = () => new Upload({client, params}); try { await makeUpload().done(); } catch (e) { await makeUpload().done(); }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - done · upload-done-exceeds-max-partswarningWhenThe Upload's total body size divided by the configured partSize (default 5 MB) exceeds 10,000 parts. With the default partSize this caps the file at ~48.8 GB. The error is thrown DURING done() execution after parts have already been uploaded — partial cleanup runs only when leavePartsOnError is false (the default), otherwise costs accumulate.Throws
Error: "Exceeded 10000 parts in multipart upload to Bucket: <bucket> Key: <key>."Required handlingCaller MUST wrap upload.done() in try-catch (already required by upload-done-no-try-catch). For files larger than 48.8 GB, the caller MUST also explicitly tune partSize: CORRECT — uploading a 1 TB file (must use ≥100 MB parts): new Upload({ client: s3, params: { Bucket, Key, Body: stream }, partSize: 100 * 1024 * 1024, // 100 MB; 1 TB / 100 MB = 10,240 parts → still over queueSize: 4, }); // For files near or above 5 TB (S3's per-object limit), use 500 MB+ parts. The maximum object size S3 supports is 5 TB. To stay under 10,000 parts: minimum partSize = ceil(fileSize / 10000) Operationally, the safer pattern is to size partSize from a known maximum file size at construction time: const partSize = Math.max(5 * 1024 * 1024, Math.ceil(maxBytes / 10000));costmediumin prodimmediate exceptionusers seelost transactionvisibilityvisible - done · upload-done-missing-etag-corserrorWhenUploadPart succeeds (HTTP 200) but the response does not include the ETag header. This is almost always caused by the destination bucket's CORS configuration omitting ETag from ExposeHeaders — the browser strips the header before the SDK can read it. The SDK aborts the entire multipart upload mid-stream because it cannot send a CompleteMultipartUpload without the per-part ETags.Throws
Error: "Part N is missing ETag in UploadPart response. Missing Bucket CORS configuration for ETag header?"Required handlingCaller MUST wrap upload.done() in try-catch (already required by upload-done-no-try-catch). When this specific error message appears, surface a remediation hint pointing at the bucket's CORS configuration — the SDK cannot recover automatically. Remediation (bucket CORS rules MUST include ETag in ExposeHeaders): [ { "AllowedHeaders": ["*"], "AllowedMethods": ["PUT", "POST"], "AllowedOrigins": ["https://your-app.example.com"], "ExposeHeaders": ["ETag"] } ] In a backend-only deployment (no browser uploads) this error is rare but possible if a proxy or service worker is stripping response headers. Log the full error text so the CORS-vs-proxy distinction can be made from logs alone.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - abort · abort-does-not-clean-up-synchronouslyerrorWhenabort() is called on an in-progress upload without waiting for done() to resolve or reject. Developers assume abort() immediately stops the upload and cleans up parts, but it only signals the AbortController — done() must still reject with AbortError before markUploadAsAborted() runs.Throws
abort() itself throws nothing. done() (the concurrent Promise) rejects with: Error("Upload aborted.") with error.name === 'AbortError'. The AbortError is thrown from __abortTimeout via Promise.race inside done().Required handlingCaller MUST await done() after calling abort() to ensure cleanup completes. The pattern is: call abort(), then catch the AbortError from done(). CORRECT pattern: const upload = new Upload({...}); const donePromise = upload.done(); // Start upload // ... later, when cancellation is needed: await upload.abort(); try { await donePromise; // Will reject with AbortError } catch (err) { if (err.name === 'AbortError') { // Upload cancelled cleanly — parts already cleaned up (unless leavePartsOnError=true) } else { throw err; // Re-throw non-abort errors } } WRONG — fire and forget abort: upload.abort(); // No await, done() never awaited → cleanup never confirmedcostmediumin proddelayed failureusers seelost datavisibilitysilent - abort · leave-parts-on-error-prevents-cleanupwarningWhenUpload is configured with leavePartsOnError: true and the upload fails or is aborted. With this flag, markUploadAsAborted() skips the AbortMultipartUploadCommand call — uploaded parts remain in S3 indefinitely and incur ongoing storage costs.Throws
No additional throw — parts are silently left on S3. The original failure error is still thrown by done(), but the cleanup is skipped due to the flag.Required handlingWhen leavePartsOnError: true, callers MUST manually clean up orphaned parts by listing and aborting multipart uploads or configuring an S3 lifecycle rule. leavePartsOnError: true should ONLY be used when you need to inspect uploaded parts for debugging. In production, use leavePartsOnError: false (the default). To list and abort orphaned multipart uploads manually: const { UploadId } = upload; // capture before done() rejects await s3Client.send(new AbortMultipartUploadCommand({ Bucket, Key, UploadId })); Or configure S3 lifecycle rule to abort incomplete multipart uploads after N days: "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 1 } WARNING: Default S3 storage costs for incomplete multipart uploads: Each part (5 MB minimum) that is not cleaned up is billed at standard S3 rates. A failed 100 MB upload leaves up to 20 parts (5 MB each) billable until aborted.costmediumin prodsilent failureusers seelost datavisibilitysilent - abort · abort-before-done-is-noopinfoWhenabort() is called before done() has been called. Since the upload has not started yet (no multipart upload initiated, no uploadId set), abort() only signals the AbortController but there is nothing to clean up. When done() is subsequently called, it will immediately reject with AbortError — but no S3 network requests were ever made.Throws
abort() itself throws nothing. If done() is called after abort(), done() immediately rejects via Promise.race with AbortError (from __abortTimeout). No S3 AbortMultipartUploadCommand is sent because uploadId is undefined.Required handlingThis is generally safe behavior — calling abort() before done() prevents the upload from starting at all. However, callers should still catch the AbortError from done() when calling done() after abort(). Example (cancel before start): const upload = new Upload({...}); upload.abort(); // Signal: don't start try { await upload.done(); // Throws AbortError immediately } catch (err) { if (err.name !== 'AbortError') throw err; // Normal cancellation — no S3 state to clean up }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
Official documentation
- [1]docs.aws.amazon.com/AWSJavaScriptSDK/v3/latestAws Sdk Lib Storage
- [2]docs.aws.amazon.com/AmazonS3/latest/APIAPI CreateMultipartUpload
- [4]docs.aws.amazon.com/AmazonS3/latest/userguideQfacts
- [5]docs.aws.amazon.com/AmazonS3/latest/userguideManageCorsUsing
- [7]docs.aws.amazon.com/AmazonS3/latest/userguideMpuoverview
Source code
- [3]raw.githubusercontent.com/aws/aws-sdk-js-v3/mainaws/aws-sdk-js-v3 · Upload.ts
- [6]github.com/aws/aws-sdk-js-v3/treeaws/aws-sdk-js-v3 · lib-storage
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: @aws-sdk/lib-storage
All behavioral claims in contract.yaml are derived from the following sources.
Official AWS Documentation
SDK v3 lib-storage Package Reference
- URL: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-storage/
- Key claim:
Upload.done()returns a Promise that can reject with S3ServiceException subclasses or network errors. Documents the Upload class constructor and.done()method.
S3 API Reference — CreateMultipartUpload
- URL: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html
- Key claim: Multipart upload initiation can fail with NoSuchBucket, AccessDenied, or other S3 errors. Upload.done() orchestrates CreateMultipartUpload → UploadPart × N → CompleteMultipartUpload, so any stage can fail.
S3 API Reference — UploadPart
- URL: https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html
- Key claim: Individual part uploads can fail with EntityTooLarge, SlowDown (503 retryable), or network errors. lib-storage retries internally but will eventually reject if retries are exhausted.
S3 API Reference — CompleteMultipartUpload
- URL: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html
- Key claim: Completion can fail even after all parts are uploaded (e.g., if the upload was aborted externally or the upload ID expired).
S3 API Reference — AbortMultipartUpload
- URL: https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html
- Relevance: Explains why upload.abort() must be called on failure — incomplete multipart uploads remain in S3 and incur storage costs until explicitly aborted or cleaned up by lifecycle rules.
S3 Error Responses
- URL: https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
- Key claim: NoSuchBucket, AccessDenied, EntityTooLarge, NoSuchUpload, InvalidPart, InvalidPartOrder are all possible errors during multipart upload.
SDK v3 Error Handling Pattern
AWS SDK v3 errors inherit from ServiceException (package @smithy/smithy-client).
The error code is in error.name.
try {
await upload.done();
} catch (err) {
if (err instanceof Error) {
switch (err.name) {
case 'NoSuchBucket':
// Bucket does not exist — check bucket name
break;
case 'AccessDenied':
// IAM permissions missing for s3:PutObject
break;
case 'EntityTooLarge':
// File exceeds S3 size limits
break;
default:
// Network error, credentials issue, etc.
break;
}
}
await upload.abort(); // Clean up incomplete multipart upload
throw err;
}
Source: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/error-handling.html
Package Notes
done()vssend(): Unlike other AWS SDK v3 commands which useclient.send(new XxxCommand()),@aws-sdk/lib-storageusesnew Upload({...})+await upload.done(). The method isdone, NOTsend.- Multipart cost risk: Incomplete multipart uploads are billed as stored data. Always call
upload.abort()in the catch block or configure an S3 lifecycle rule to auto-abort incomplete uploads. - Progress tracking:
upload.on('httpUploadProgress', cb)can be registered before calling.done(). - Part size: Default minimum part size is 5MB. The
partSizeoption in the Upload constructor overrides this. Files smaller thanpartSizeare uploaded as a single PutObject, not multipart.
Need a different package?
Request a profile