Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f70735f
feat(lib-storage): add S3 Transfer Manager
lukachad Jun 11, 2025
49fd801
feat(lib-storage): concurrent requests
kuhe Jul 21, 2025
1938251
feat(lib-storage): spec updates
kuhe Jul 21, 2025
c4bf56a
Merge pull request #4 from lukachad/revised-sep-requirements
kuhe Jul 21, 2025
879000e
feat: addressed pr comments
lukachad Jul 21, 2025
94c1e1b
feat: addresses change requests for range and part validation
lukachad Jul 22, 2025
a77718f
Merge pull request #3 from lukachad/concurrency
lukachad Jul 22, 2025
deb53b5
feat: validate part and range test cases
lukachad Jul 22, 2025
16264c0
feat: creating tests for join-streams, made browser specific
lukachad Jul 22, 2025
e558dea
feat: changed join-streams path in browser.spec
lukachad Jul 23, 2025
c726213
feat(lib-storage): readme for S3TransferManager
lukachad Jul 24, 2025
3d26103
feat(lib-storage): more expect statements in multipartdownload tests …
lukachad Jul 24, 2025
c2d6863
feat(lib-storage): added try catch and dispatches for transferFailedE…
lukachad Jul 25, 2025
832e6d7
feat: fixed unhandled promise rejection issue, added unit and e2e tes…
lukachad Jul 28, 2025
3655319
feat: part and range handles 0 byte objects, added helper function fo…
lukachad Jul 28, 2025
2559e6a
feat: custom range download tests and fixes
lukachad Jul 28, 2025
53c730f
feat: fixing unhandled promise error
lukachad Jul 29, 2025
53ef260
feat: cr revisions
lukachad Jul 29, 2025
cf4964f
chore: debug
kuhe Jul 29, 2025
d44f0cc
feat: promise.all temp fix for promise unhandled rejection issue
lukachad Jul 29, 2025
164fcee
chore: update join-streams browser to be consistent with join-streams…
lukachad Jul 30, 2025
f367640
chore: code annotations S3TM functions
lukachad Jul 30, 2025
4450b16
chore: readme updates for s3transfermanager
lukachad Jul 30, 2025
927c394
chore: removed dependencies and unused internal event handler
lukachad Jul 30, 2025
41ee715
chore: import cleanup and removed dependencies
lukachad Jul 30, 2025
21dffe4
Merge pull request #5 from lukachad/testing
lukachad Jul 30, 2025
49022b9
Merge branch 'transfer-manager' into debugging
lukachad Jul 30, 2025
987d3ad
Merge pull request #6 from lukachad/debugging
lukachad Jul 30, 2025
82cc0b5
chore: reademe updates
lukachad Jul 30, 2025
21fdc36
Merge branch 'transfer-manager' into add-documentation
lukachad Jul 30, 2025
8daad36
chore: cr changes for readme and added download() examples
lukachad Jul 30, 2025
060045f
chore: deleted redundant example code file
lukachad Jul 30, 2025
4fc0d2f
chore: cr nits
lukachad Jul 30, 2025
bb26df3
Merge pull request #7 from lukachad/add-documentation
lukachad Jul 30, 2025
63040cc
chore: fixed yarn lockfile
lukachad Jul 31, 2025
1bd3255
chore: deleted unused file
lukachad Jul 31, 2025
2a68516
chore: improved async iterable error handling with minimal performanc…
lukachad Aug 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(lib-storage): add S3 Transfer Manager
feat(lib-storage): added doc comments for interfaces and types

feat(lib-storage): address PR review feedback for transfer manager types

feat(lib-storage): addressed minor review feedback

feat(lib-storage): added example code file

feat(lib-storage): changed "handler" to "listener" and changed type file names

feat(lib-storage): created TransferManager class and constructor with defaults

feat(lib-storage): beginning implementation for download(), created TM index file

feat(lib-storage): range multipart download

feat(lib-storage): download() improvements post pair-programming

feat(lib-storage): transfermanager download() iteration

feat(lib-storage): transfermanager interation post pair programming

feat(lib-storage): joinstream iteration, web stream not fully functional

feat(lib-storage): both cases of range download handled

feat(lib-storage): range download working

chore: acquire lock on streams

feat(lib-storage): bug fixes and test env setup

feat(lib-storage): implemented dispatchEvent(), need to add dispatches for complete and fail

feat(lib-storage): requests array, eventListeners revision, needs more testing

feat(lib-storage): addEventListener & removeEventListener implemented, needs support for options

feat(lib-storage): added support for adding event listeners at request level

feat(lib-storage): added ETag verification for subsequent GetObjectRequests

feat(lib-storage): totalSize changes, and added validateExpectedRanges()

feat(lib-storage): addEventListener once parameter, type fixes

feat(lib-storage): s3TM SEP test cases and added TODOs

feat(lib-storage): validateExpectedRanges fixes and unit tests

feat(lib-storage): added check in validateExpectedRanges if final part doesnt download total object

feat(lib-storage): s3TM constructor and add event listener tests

feat(lib-storage): addEventListener tests and error checking adjustment and dispatchEvent tests

feat(lib-storage): added test cases
  • Loading branch information
lukachad authored and kuhe committed Jul 21, 2025
commit f70735f63095f59caefe05baa8640927bde9aac2
1 change: 1 addition & 0 deletions lib/lib-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
},
"browser": {
"./dist-es/runtimeConfig": "./dist-es/runtimeConfig.browser",
"./dist-es/s3-transfer-manager/join-streams": "./dist-es/s3-transfer-manager/join-streams.browser",
"fs": false,
"stream": "stream-browserify"
},
Expand Down
1 change: 1 addition & 0 deletions lib/lib-storage/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./Upload";
export * from "./s3-transfer-manager/index";
export * from "./types";
3 changes: 2 additions & 1 deletion lib/lib-storage/src/lib-storage.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { afterAll, beforeAll, describe, expect, test as it } from "vitest";

import { getIntegTestResources } from "../../../tests/e2e/get-integ-test-resources";

describe("@aws-sdk/lib-storage", () => {
// todo(s3-transfer-manager): unskip
describe.skip("@aws-sdk/lib-storage", () => {
describe.each([undefined, "WHEN_REQUIRED", "WHEN_SUPPORTED"])(
"requestChecksumCalculation: %s",
(requestChecksumCalculation) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { S3 } from "@aws-sdk/client-s3";
import { beforeAll, describe, expect, test as it } from "vitest";

import { getIntegTestResources } from "../../../../tests/e2e/get-integ-test-resources";
import { Upload } from "../Upload";
import { S3TransferManager } from "./S3TransferManager";
import type { IS3TransferManager, S3TransferManagerConfig } from "./types";

describe(S3TransferManager.name, () => {
const chunk = "01234567";

function data(bytes: number) {
let buffer = "";
while (buffer.length < bytes) {
buffer += chunk;
}
return buffer.slice(0, bytes);
}

function check(str = "") {
while (str.length > 0) {
expect(str.slice(0, 8)).toEqual(chunk);
str = str.slice(8);
}
}

let client: S3;
let tmPart: S3TransferManager;
let tmRange: S3TransferManager;
let Bucket: string;
let region: string;

beforeAll(async () => {
// const integTestResourcesEnv = await getIntegTestResources();
// Object.assign(process.env, integTestResourcesEnv);

// region = process?.env?.AWS_SMOKE_TEST_REGION as string;
// Bucket = process?.env?.AWS_SMOKE_TEST_BUCKET as string;
void getIntegTestResources;

region = "us-west-2";
Bucket = "lukachad-us-west-2";

client = new S3({
region,
});
tmPart = new S3TransferManager({
s3ClientInstance: client,
multipartDownloadType: "PART",
});
tmRange = new S3TransferManager({
s3ClientInstance: client,
multipartDownloadType: "RANGE",
});
}, 120_000);

describe.skip("multi part download", () => {
const modes = ["PART", "RANGE"] as S3TransferManagerConfig["multipartDownloadType"][];
const sizes = [6, 11] as number[];

for (const mode of modes) {
for (const size of sizes) {
it(`should download an object of size ${size} with mode ${mode}`, async () => {
const Body = data(size * 1024 * 1024);
const Key = `${mode}-size`;

if (mode === "PART") {
await new Upload({
client,
params: {
Bucket,
Key,
Body,
},
}).done();
} else {
await client.putObject({
Bucket,
Key,
Body,
});
}

const tm: S3TransferManager = mode === "PART" ? tmPart : tmRange;

let bytesTransferred = 0;

const download = await tm.download(
{
Bucket,
Key,
},
{
eventListeners: {
transferInitiated: [({ request, snapshot }) => {}],
bytesTransferred: [
({ request, snapshot }) => {
bytesTransferred = snapshot.transferredBytes;
},
],
transferComplete: [({ request, snapshot, response }) => {}],
},
}
);
const serialized = await download.Body?.transformToString();
check(serialized);

expect(bytesTransferred).toEqual(Body.length);
}, 60_000);
}
}
});

describe("(SEP) download single object tests", () => {
async function sepTests(
objectType: "single" | "multipart",
multipartType: "PART" | "RANGE",
range: string | undefined,
partNumber: 2 | undefined
) {
const Body = data(12 * 1024 * 1024);
const Key = `${objectType}${multipartType}${range}${partNumber}`;
const DEFAULT_PART_SIZE = 8 * 1024 * 1024;

if (multipartType === "PART") {
await new Upload({
client,
partSize: DEFAULT_PART_SIZE,
params: {
Bucket,
Key,
Body,
},
}).done();
} else {
await client.putObject({
Bucket,
Key,
Body,
});
}

const tm: S3TransferManager = multipartType === "PART" ? tmPart : tmRange;

const download = await tm.download({
Bucket,
Key,
Range: range,
PartNumber: partNumber,
});
const serialized = await download.Body?.transformToString();
check(serialized);
if (partNumber) {
expect(serialized?.length).toEqual(DEFAULT_PART_SIZE);
} else {
expect(serialized?.length).toEqual(Body.length);
}
}

it("single object: multipartDownloadType = PART, range = 0-12MB, partNumber = null", async () => {
await sepTests("single", "PART", `bytes=0-${12 * 1024 * 1024}`, undefined);
}, 60_000);
it("multipart object: multipartDownloadType = RANGE, range = 0-12MB, partNumber = null", async () => {
await sepTests("multipart", "RANGE", `bytes=0-${12 * 1024 * 1024}`, undefined);
}, 60_000);
it("single object: multipartDownloadType = PART, range = null, partNumber = 2", async () => {
await sepTests("single", "PART", undefined, 2);
}, 60_000);
it("single object: multipartDownloadType = RANGE, range = null, partNumber = 2", async () => {
await sepTests("single", "RANGE", undefined, 2);
}, 60_000);
it("single object: multipartDownloadType = PART, range = null, partNumber = null", async () => {
await sepTests("single", "PART", undefined, undefined);
}, 60_000);
it("single object: multipartDownloadType = RANGE, range = null, partNumber = null", async () => {
await sepTests("single", "RANGE", undefined, undefined);
}, 60_000);
});
});
Loading