Skip to content

Commit bb26df3

Browse files
authored
Merge pull request #7 from lukachad/add-documentation
Code Annotation and README
2 parents 987d3ad + 4fc0d2f commit bb26df3

File tree

8 files changed

+697
-305
lines changed

8 files changed

+697
-305
lines changed

lib/lib-storage/src/s3-transfer-manager/README.md

Lines changed: 509 additions & 85 deletions
Large diffs are not rendered by default.

lib/lib-storage/src/s3-transfer-manager/S3TransferManager.e2e.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ describe(S3TransferManager.name, () => {
254254
}
255255
});
256256

257-
describe("(SEP) download single object tests", () => {
258-
async function sepTests(
257+
describe("Required compliance download single object tests", () => {
258+
async function complianceTests(
259259
objectType: "single" | "multipart",
260260
multipartType: "PART" | "RANGE",
261261
range: string | undefined,
@@ -301,16 +301,16 @@ describe(S3TransferManager.name, () => {
301301
}
302302

303303
it("single object: multipartDownloadType = PART, range = 0-12MB, partNumber = null", async () => {
304-
await sepTests("single", "PART", `bytes=0-${12 * 1024 * 1024}`, undefined);
304+
await complianceTests("single", "PART", `bytes=0-${12 * 1024 * 1024}`, undefined);
305305
}, 60_000);
306306
it("multipart object: multipartDownloadType = RANGE, range = 0-12MB, partNumber = null", async () => {
307-
await sepTests("multipart", "RANGE", `bytes=0-${12 * 1024 * 1024}`, undefined);
307+
await complianceTests("multipart", "RANGE", `bytes=0-${12 * 1024 * 1024}`, undefined);
308308
}, 60_000);
309309
it("single object: multipartDownloadType = PART, range = null, partNumber = null", async () => {
310-
await sepTests("single", "PART", undefined, undefined);
310+
await complianceTests("single", "PART", undefined, undefined);
311311
}, 60_000);
312312
it("single object: multipartDownloadType = RANGE, range = null, partNumber = null", async () => {
313-
await sepTests("single", "RANGE", undefined, undefined);
313+
await complianceTests("single", "RANGE", undefined, undefined);
314314
}, 60_000);
315315
});
316316
});

lib/lib-storage/src/s3-transfer-manager/S3TransferManager.ts

Lines changed: 152 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,16 @@ import type {
2323
} from "./types";
2424

2525
/**
26-
* Describe what this is
27-
* TODO: Switch all @public to @alpha
28-
* TODO: tag internal for itneral functions
26+
* Client for efficient transfer of objects to and from Amazon S3.
27+
* Provides methods to optimize uploading and downloading individual objects
28+
* as well as entire directories, with support for multipart operations,
29+
* concurrency control, and request cancellation.
30+
* Implements an eventTarget-based progress tracking system with methods to register,
31+
* dispatch, and remove listeners for transfer lifecycle events.
32+
*
2933
* @alpha
3034
*/
35+
3136
export class S3TransferManager implements IS3TransferManager {
3237
private static MIN_PART_SIZE = 5 * 1024 * 1024; // 5MB
3338
private static DEFAULT_PART_SIZE = 8 * 1024 * 1024; // 8MB
@@ -69,6 +74,16 @@ export class S3TransferManager implements IS3TransferManager {
6974
this.validateConfig();
7075
}
7176

77+
/**
78+
* Registers a callback function to be executed when a specific transfer event occurs.
79+
* Supports monitoring the full lifecycle of transfers.
80+
*
81+
* @param type - The type of event to listen for.
82+
* @param callback - Function to execute when the specified event occurs.
83+
* @param options - Optional configuration for event listener behavior.
84+
*
85+
* @alpha
86+
*/
7287
public addEventListener(
7388
type: "transferInitiated",
7489
callback: EventListener<TransferEvent>,
@@ -130,13 +145,13 @@ export class S3TransferManager implements IS3TransferManager {
130145
}
131146

132147
/**
133-
* todo: what does the return boolean mean?
148+
* Dispatches an event to the registered event listeners.
149+
* Triggers callbacks registered via addEventListener with matching event types.
134150
*
135-
* it returns false if the event is cancellable, and at least oneo the handlers which received event called
136-
* Event.preventDefault(). Otherwise true.
137-
* The use cases of preventDefault() does not apply to transfermanager but we should still keep the boolean
138-
* and continue to return true to stay consistent with EventTarget.
151+
* @param event - The event object to dispatch.
152+
* @returns whether the event ran to completion
139153
*
154+
* @alpha
140155
*/
141156
public dispatchEvent(event: Event & TransferEvent): boolean;
142157
public dispatchEvent(event: Event & TransferCompleteEvent): boolean;
@@ -157,6 +172,16 @@ export class S3TransferManager implements IS3TransferManager {
157172
return true;
158173
}
159174

175+
/**
176+
* Removes a previously registered event listener from the specified event type.
177+
* Stops the callback from being invoked when the event occurs.
178+
*
179+
* @param type - The type of event to stop listening for.
180+
* @param callback - The function that was previously registered.
181+
* @param options - Optional configuration for the event listener.
182+
*
183+
* @alpha
184+
*/
160185
public removeEventListener(
161186
type: "transferInitiated",
162187
callback: EventListener<TransferEvent>,
@@ -209,10 +234,32 @@ export class S3TransferManager implements IS3TransferManager {
209234
}
210235
}
211236

237+
/**
238+
* Uploads objects to S3 with automatic multipart upload handling.
239+
* Automatically chooses between single object upload or multipart upload based on content length threshold.
240+
*
241+
* @param request - PutObjectCommandInput and CreateMultipartUploadCommandInput parameters for single or multipart uploads.
242+
* @param transferOptions - Optional abort signal and event listeners for transfer lifecycle monitoring.
243+
*
244+
* @returns S3 PutObject or CompleteMultipartUpload response with transfer event dispatching.
245+
*
246+
* @alpha
247+
*/
212248
public upload(request: UploadRequest, transferOptions?: TransferOptions): Promise<UploadResponse> {
213249
throw new Error("Method not implemented.");
214250
}
215251

252+
/**
253+
* Downloads single objects from S3 with automatic multipart handling.
254+
* Automatically chooses between PART or RANGE download strategies and joins streams into a single response.
255+
*
256+
* @param request - GetObjectCommandInput parameters. PartNumber is not supported - use GetObjectCommand directly for specific parts.
257+
* @param transferOptions - Optional abort signal and event listeners for transfer lifecycle monitoring.
258+
*
259+
* @returns S3 GetObject response with joined Body stream and transfer event dispatching.
260+
*
261+
* @alpha
262+
*/
216263
public async download(request: DownloadRequest, transferOptions?: TransferOptions): Promise<DownloadResponse> {
217264
const partNumber = request.PartNumber;
218265
if (typeof partNumber === "number") {
@@ -248,12 +295,6 @@ export class S3TransferManager implements IS3TransferManager {
248295
}
249296
};
250297

251-
// TODO:
252-
// after completing SEP requirements:
253-
// - acquire lock on webstreams in the same
254-
// - synchronous frame as they are opened or else
255-
// - the connection might be closed too early.
256-
257298
const response = {
258299
...metadata,
259300
Body: await joinStreams(streams, {
@@ -299,6 +340,16 @@ export class S3TransferManager implements IS3TransferManager {
299340
return response;
300341
}
301342

343+
/**
344+
* Uploads all files in a directory recursively to an S3 bucket.
345+
* Automatically maps local file paths to S3 object keys using prefix and delimiter configuration.
346+
*
347+
* @param options - Configuration including bucket, source directory, filtering, failure handling, and transfer settings.
348+
*
349+
* @returns the number of objects that have been uploaded and the number of objects that have failed.
350+
*
351+
* @alpha
352+
*/
302353
public uploadAll(options: {
303354
bucket: string;
304355
source: string;
@@ -314,6 +365,16 @@ export class S3TransferManager implements IS3TransferManager {
314365
throw new Error("Method not implemented.");
315366
}
316367

368+
/**
369+
* Downloads all objects in a bucket to a local directory.
370+
* Uses ListObjectsV2 to retrieve objects and automatically maps S3 object keys to local file paths.
371+
*
372+
* @param options - Configuration including bucket, destination directory, filtering, failure handling, and transfer settings.
373+
*
374+
* @returns The number of objects that have been downloaded and the number of objects that have failed.
375+
*
376+
* @alpha
377+
*/
317378
public downloadAll(options: {
318379
bucket: string;
319380
destination: string;
@@ -328,6 +389,11 @@ export class S3TransferManager implements IS3TransferManager {
328389
throw new Error("Method not implemented.");
329390
}
330391

392+
/**
393+
* Downloads object using part-based strategy with concurrent part requests.
394+
*
395+
* @internal
396+
*/
331397
protected async downloadByPart(
332398
request: DownloadRequest,
333399
transferOptions: TransferOptions,
@@ -432,6 +498,11 @@ export class S3TransferManager implements IS3TransferManager {
432498
};
433499
}
434500

501+
/**
502+
* Downloads object using range-based strategy with concurrent range requests.
503+
*
504+
* @internal
505+
*/
435506
protected async downloadByRange(
436507
request: DownloadRequest,
437508
transferOptions: TransferOptions,
@@ -564,6 +635,11 @@ export class S3TransferManager implements IS3TransferManager {
564635
};
565636
}
566637

638+
/**
639+
* Adds all event listeners from provided collection to the transfer manager.
640+
*
641+
* @internal
642+
*/
567643
private addEventListeners(eventListeners?: TransferEventListeners): void {
568644
for (const listeners of this.iterateListeners(eventListeners)) {
569645
for (const listener of listeners) {
@@ -572,6 +648,11 @@ export class S3TransferManager implements IS3TransferManager {
572648
}
573649
}
574650

651+
/**
652+
* Removes event listeners from provided collection from the transfer manager.
653+
*
654+
* @internal
655+
*/
575656
private removeEventListeners(eventListeners?: TransferEventListeners): void {
576657
for (const listeners of this.iterateListeners(eventListeners)) {
577658
for (const listener of listeners) {
@@ -580,6 +661,11 @@ export class S3TransferManager implements IS3TransferManager {
580661
}
581662
}
582663

664+
/**
665+
* Copies all response properties except Body to the container object.
666+
*
667+
* @internal
668+
*/
583669
private assignMetadata(container: any, response: any) {
584670
for (const key in response) {
585671
if (key === "Body") {
@@ -589,13 +675,23 @@ export class S3TransferManager implements IS3TransferManager {
589675
}
590676
}
591677

678+
/**
679+
* Updates response ContentLength and ContentRange based on total object size.
680+
*
681+
* @internal
682+
*/
592683
private updateResponseLengthAndRange(response: DownloadResponse, totalSize: number | undefined): void {
593684
if (totalSize !== undefined) {
594685
response.ContentLength = totalSize;
595686
response.ContentRange = `bytes 0-${totalSize - 1}/${totalSize}`;
596687
}
597688
}
598689

690+
/**
691+
* Clears checksum values for composite multipart downloads.
692+
*
693+
* @internal
694+
*/
599695
private updateChecksumValues(initialPart: DownloadResponse, metadata: Omit<DownloadResponse, "Body">) {
600696
if (initialPart.ChecksumType === "COMPOSITE") {
601697
metadata.ChecksumCRC32 = undefined;
@@ -605,6 +701,11 @@ export class S3TransferManager implements IS3TransferManager {
605701
}
606702
}
607703

704+
/**
705+
* Processes response metadata by updating length, copying properties, and handling checksums.
706+
*
707+
* @internal
708+
*/
608709
private processResponseMetadata(
609710
response: DownloadResponse,
610711
metadata: Omit<DownloadResponse, "Body">,
@@ -615,18 +716,33 @@ export class S3TransferManager implements IS3TransferManager {
615716
this.updateChecksumValues(response, metadata);
616717
}
617718

719+
/**
720+
* Throws AbortError if transfer has been aborted via signal.
721+
*
722+
* @internal
723+
*/
618724
private checkAborted(transferOptions?: TransferOptions): void {
619725
if (transferOptions?.abortSignal?.aborted) {
620726
throw Object.assign(new Error("Download aborted."), { name: "AbortError" });
621727
}
622728
}
623729

730+
/**
731+
* Validates if configuration parameters meets minimum requirements.
732+
*
733+
* @internal
734+
*/
624735
private validateConfig(): void {
625736
if (this.targetPartSizeBytes < S3TransferManager.MIN_PART_SIZE) {
626737
throw new Error(`targetPartSizeBytes must be at least ${S3TransferManager.MIN_PART_SIZE} bytes`);
627738
}
628739
}
629740

741+
/**
742+
* Dispatches transferInitiated event with initial progress snapshot.
743+
*
744+
* @internal
745+
*/
630746
private dispatchTransferInitiatedEvent(request: DownloadRequest | UploadRequest, totalSize?: number): boolean {
631747
this.dispatchEvent(
632748
Object.assign(new Event("transferInitiated"), {
@@ -640,6 +756,11 @@ export class S3TransferManager implements IS3TransferManager {
640756
return true;
641757
}
642758

759+
/**
760+
* Dispatches transferFailed event with error details and progress snapshot.
761+
*
762+
* @internal
763+
*/
643764
private dispatchTransferFailedEvent(
644765
request: DownloadRequest | UploadRequest,
645766
totalSize?: number,
@@ -658,6 +779,11 @@ export class S3TransferManager implements IS3TransferManager {
658779
return true;
659780
}
660781

782+
/**
783+
* Generator that yields event listeners from the provided collection for iteration.
784+
*
785+
* @internal
786+
*/
661787
private *iterateListeners(eventListeners: TransferEventListeners = {}) {
662788
for (const key in eventListeners) {
663789
const eventType = key as keyof TransferEventListeners;
@@ -675,6 +801,11 @@ export class S3TransferManager implements IS3TransferManager {
675801
}
676802
}
677803

804+
/**
805+
* Validates part download ContentRange matches expected part boundaries.
806+
*
807+
* @internal
808+
*/
678809
private validatePartDownload(contentRange: string | undefined, partNumber: number, partSize: number) {
679810
if (!contentRange) {
680811
throw new Error(`Missing ContentRange for part ${partNumber}.`);
@@ -699,6 +830,11 @@ export class S3TransferManager implements IS3TransferManager {
699830
}
700831
}
701832

833+
/**
834+
* Validates range download ContentRange matches requested byte range.
835+
*
836+
* @internal
837+
*/
702838
private validateRangeDownload(requestRange: string, responseRange: string | undefined) {
703839
if (!responseRange) {
704840
throw new Error(`Missing ContentRange for range ${requestRange}.`);
@@ -732,8 +868,9 @@ export class S3TransferManager implements IS3TransferManager {
732868
throw new Error(`Expected range to end at ${expectedEnd} but got ${end}`);
733869
}
734870
}
871+
735872
/**
736-
*
873+
* Internal event handler for download lifecycle hooks.
737874
*
738875
* @internal
739876
*/

0 commit comments

Comments
 (0)