Skip to content

Commit f70735f

Browse files
lukachadkuhe
authored andcommitted
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
1 parent 2e7d107 commit f70735f

File tree

14 files changed

+1807
-2
lines changed

14 files changed

+1807
-2
lines changed

lib/lib-storage/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
},
6060
"browser": {
6161
"./dist-es/runtimeConfig": "./dist-es/runtimeConfig.browser",
62+
"./dist-es/s3-transfer-manager/join-streams": "./dist-es/s3-transfer-manager/join-streams.browser",
6263
"fs": false,
6364
"stream": "stream-browserify"
6465
},

lib/lib-storage/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./Upload";
2+
export * from "./s3-transfer-manager/index";
23
export * from "./types";

lib/lib-storage/src/lib-storage.e2e.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { afterAll, beforeAll, describe, expect, test as it } from "vitest";
66

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

9-
describe("@aws-sdk/lib-storage", () => {
9+
// todo(s3-transfer-manager): unskip
10+
describe.skip("@aws-sdk/lib-storage", () => {
1011
describe.each([undefined, "WHEN_REQUIRED", "WHEN_SUPPORTED"])(
1112
"requestChecksumCalculation: %s",
1213
(requestChecksumCalculation) => {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { S3 } from "@aws-sdk/client-s3";
2+
import { beforeAll, describe, expect, test as it } from "vitest";
3+
4+
import { getIntegTestResources } from "../../../../tests/e2e/get-integ-test-resources";
5+
import { Upload } from "../Upload";
6+
import { S3TransferManager } from "./S3TransferManager";
7+
import type { IS3TransferManager, S3TransferManagerConfig } from "./types";
8+
9+
describe(S3TransferManager.name, () => {
10+
const chunk = "01234567";
11+
12+
function data(bytes: number) {
13+
let buffer = "";
14+
while (buffer.length < bytes) {
15+
buffer += chunk;
16+
}
17+
return buffer.slice(0, bytes);
18+
}
19+
20+
function check(str = "") {
21+
while (str.length > 0) {
22+
expect(str.slice(0, 8)).toEqual(chunk);
23+
str = str.slice(8);
24+
}
25+
}
26+
27+
let client: S3;
28+
let tmPart: S3TransferManager;
29+
let tmRange: S3TransferManager;
30+
let Bucket: string;
31+
let region: string;
32+
33+
beforeAll(async () => {
34+
// const integTestResourcesEnv = await getIntegTestResources();
35+
// Object.assign(process.env, integTestResourcesEnv);
36+
37+
// region = process?.env?.AWS_SMOKE_TEST_REGION as string;
38+
// Bucket = process?.env?.AWS_SMOKE_TEST_BUCKET as string;
39+
void getIntegTestResources;
40+
41+
region = "us-west-2";
42+
Bucket = "lukachad-us-west-2";
43+
44+
client = new S3({
45+
region,
46+
});
47+
tmPart = new S3TransferManager({
48+
s3ClientInstance: client,
49+
multipartDownloadType: "PART",
50+
});
51+
tmRange = new S3TransferManager({
52+
s3ClientInstance: client,
53+
multipartDownloadType: "RANGE",
54+
});
55+
}, 120_000);
56+
57+
describe.skip("multi part download", () => {
58+
const modes = ["PART", "RANGE"] as S3TransferManagerConfig["multipartDownloadType"][];
59+
const sizes = [6, 11] as number[];
60+
61+
for (const mode of modes) {
62+
for (const size of sizes) {
63+
it(`should download an object of size ${size} with mode ${mode}`, async () => {
64+
const Body = data(size * 1024 * 1024);
65+
const Key = `${mode}-size`;
66+
67+
if (mode === "PART") {
68+
await new Upload({
69+
client,
70+
params: {
71+
Bucket,
72+
Key,
73+
Body,
74+
},
75+
}).done();
76+
} else {
77+
await client.putObject({
78+
Bucket,
79+
Key,
80+
Body,
81+
});
82+
}
83+
84+
const tm: S3TransferManager = mode === "PART" ? tmPart : tmRange;
85+
86+
let bytesTransferred = 0;
87+
88+
const download = await tm.download(
89+
{
90+
Bucket,
91+
Key,
92+
},
93+
{
94+
eventListeners: {
95+
transferInitiated: [({ request, snapshot }) => {}],
96+
bytesTransferred: [
97+
({ request, snapshot }) => {
98+
bytesTransferred = snapshot.transferredBytes;
99+
},
100+
],
101+
transferComplete: [({ request, snapshot, response }) => {}],
102+
},
103+
}
104+
);
105+
const serialized = await download.Body?.transformToString();
106+
check(serialized);
107+
108+
expect(bytesTransferred).toEqual(Body.length);
109+
}, 60_000);
110+
}
111+
}
112+
});
113+
114+
describe("(SEP) download single object tests", () => {
115+
async function sepTests(
116+
objectType: "single" | "multipart",
117+
multipartType: "PART" | "RANGE",
118+
range: string | undefined,
119+
partNumber: 2 | undefined
120+
) {
121+
const Body = data(12 * 1024 * 1024);
122+
const Key = `${objectType}${multipartType}${range}${partNumber}`;
123+
const DEFAULT_PART_SIZE = 8 * 1024 * 1024;
124+
125+
if (multipartType === "PART") {
126+
await new Upload({
127+
client,
128+
partSize: DEFAULT_PART_SIZE,
129+
params: {
130+
Bucket,
131+
Key,
132+
Body,
133+
},
134+
}).done();
135+
} else {
136+
await client.putObject({
137+
Bucket,
138+
Key,
139+
Body,
140+
});
141+
}
142+
143+
const tm: S3TransferManager = multipartType === "PART" ? tmPart : tmRange;
144+
145+
const download = await tm.download({
146+
Bucket,
147+
Key,
148+
Range: range,
149+
PartNumber: partNumber,
150+
});
151+
const serialized = await download.Body?.transformToString();
152+
check(serialized);
153+
if (partNumber) {
154+
expect(serialized?.length).toEqual(DEFAULT_PART_SIZE);
155+
} else {
156+
expect(serialized?.length).toEqual(Body.length);
157+
}
158+
}
159+
160+
it("single object: multipartDownloadType = PART, range = 0-12MB, partNumber = null", async () => {
161+
await sepTests("single", "PART", `bytes=0-${12 * 1024 * 1024}`, undefined);
162+
}, 60_000);
163+
it("multipart object: multipartDownloadType = RANGE, range = 0-12MB, partNumber = null", async () => {
164+
await sepTests("multipart", "RANGE", `bytes=0-${12 * 1024 * 1024}`, undefined);
165+
}, 60_000);
166+
it("single object: multipartDownloadType = PART, range = null, partNumber = 2", async () => {
167+
await sepTests("single", "PART", undefined, 2);
168+
}, 60_000);
169+
it("single object: multipartDownloadType = RANGE, range = null, partNumber = 2", async () => {
170+
await sepTests("single", "RANGE", undefined, 2);
171+
}, 60_000);
172+
it("single object: multipartDownloadType = PART, range = null, partNumber = null", async () => {
173+
await sepTests("single", "PART", undefined, undefined);
174+
}, 60_000);
175+
it("single object: multipartDownloadType = RANGE, range = null, partNumber = null", async () => {
176+
await sepTests("single", "RANGE", undefined, undefined);
177+
}, 60_000);
178+
});
179+
});

0 commit comments

Comments
 (0)