Profiles·Public

@aws-sdk/lib-storage

semver>=3.0.0 <4.0.0postconditions7functions2last verified2026-06-24coverage score100%

Postconditions: what we check

  • done · upload-done-no-try-catch
    error
    Whenupload.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.
    ThrowsS3ServiceException 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
    Sources[1][2]
  • done · upload-done-already-called
    error
    Whenupload.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.
    ThrowsError: "@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 unavailablevisibilityvisible
    Sources[3]
  • done · upload-done-exceeds-max-parts
    warning
    WhenThe 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.
    ThrowsError: "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
    Sources[3][4]
  • done · upload-done-missing-etag-cors
    error
    WhenUploadPart 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.
    ThrowsError: "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
    Sources[3][5]
  • abort · abort-does-not-clean-up-synchronously
    error
    Whenabort() 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.
    Throwsabort() 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 confirmed
    costmediumin proddelayed failureusers seelost datavisibilitysilent
    Sources[3][6]
  • abort · leave-parts-on-error-prevents-cleanup
    warning
    WhenUpload 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.
    ThrowsNo 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
    Sources[3][7][6]
  • abort · abort-before-done-is-noop
    info
    Whenabort() 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.
    Throwsabort() 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 unavailablevisibilityvisible
    Sources[3]

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
Source code

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

S3 API Reference — CreateMultipartUpload

S3 API Reference — UploadPart

S3 API Reference — CompleteMultipartUpload

S3 API Reference — AbortMultipartUpload

S3 Error Responses


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() vs send(): Unlike other AWS SDK v3 commands which use client.send(new XxxCommand()), @aws-sdk/lib-storage uses new Upload({...}) + await upload.done(). The method is done, NOT send.
  • 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 partSize option in the Upload constructor overrides this. Files smaller than partSize are uploaded as a single PutObject, not multipart.
Need a different package?
Request a profile