@aws-sdk/lib-transfer-manager 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -2
- package/dist-cjs/index.js +2 -0
- package/dist-cjs/submodules/transfer-manager/index.js +3166 -0
- package/dist-cjs/submodules/worker/index.js +579 -0
- package/dist-es/index.js +1 -0
- package/dist-es/submodules/transfer-manager/S3TransferManager.js +2229 -0
- package/dist-es/submodules/transfer-manager/chunker.js +115 -0
- package/dist-es/submodules/transfer-manager/directory-transfer-utils.js +102 -0
- package/dist-es/submodules/transfer-manager/event-listener-types.js +1 -0
- package/dist-es/submodules/transfer-manager/file-manager.js +70 -0
- package/dist-es/submodules/transfer-manager/index.browser.js +11 -0
- package/dist-es/submodules/transfer-manager/index.js +11 -0
- package/dist-es/submodules/transfer-manager/join-streams.browser.js +101 -0
- package/dist-es/submodules/transfer-manager/join-streams.js +105 -0
- package/dist-es/submodules/transfer-manager/log-level.js +30 -0
- package/dist-es/submodules/transfer-manager/ordered-part-queue.js +57 -0
- package/dist-es/submodules/transfer-manager/types.js +1 -0
- package/dist-es/submodules/transfer-manager/worker-http-handler.browser.js +25 -0
- package/dist-es/submodules/transfer-manager/worker-http-handler.js +453 -0
- package/dist-es/submodules/worker/http-request-worker.js +578 -0
- package/dist-es/submodules/worker/index.js +1 -0
- package/dist-types/index.d.ts +2 -0
- package/dist-types/submodules/transfer-manager/S3TransferManager.d.ts +60 -0
- package/dist-types/submodules/transfer-manager/chunker.d.ts +7 -0
- package/dist-types/submodules/transfer-manager/directory-transfer-utils.d.ts +90 -0
- package/dist-types/submodules/transfer-manager/event-listener-types.d.ts +54 -0
- package/dist-types/submodules/transfer-manager/file-manager.d.ts +51 -0
- package/dist-types/submodules/transfer-manager/index.browser.d.ts +10 -0
- package/dist-types/submodules/transfer-manager/index.d.ts +10 -0
- package/dist-types/submodules/transfer-manager/join-streams.browser.d.ts +21 -0
- package/dist-types/submodules/transfer-manager/join-streams.d.ts +26 -0
- package/dist-types/submodules/transfer-manager/log-level.d.ts +13 -0
- package/dist-types/submodules/transfer-manager/ordered-part-queue.d.ts +73 -0
- package/dist-types/submodules/transfer-manager/types.d.ts +473 -0
- package/dist-types/submodules/transfer-manager/worker-http-handler.browser.d.ts +20 -0
- package/dist-types/submodules/transfer-manager/worker-http-handler.d.ts +410 -0
- package/dist-types/submodules/worker/http-request-worker.d.ts +1 -0
- package/dist-types/submodules/worker/index.d.ts +1 -0
- package/dist-types/ts3.4/index.d.ts +2 -0
- package/dist-types/ts3.4/submodules/transfer-manager/S3TransferManager.d.ts +38 -0
- package/dist-types/ts3.4/submodules/transfer-manager/chunker.d.ts +10 -0
- package/dist-types/ts3.4/submodules/transfer-manager/directory-transfer-utils.d.ts +23 -0
- package/dist-types/ts3.4/submodules/transfer-manager/event-listener-types.d.ts +12 -0
- package/dist-types/ts3.4/submodules/transfer-manager/file-manager.d.ts +9 -0
- package/dist-types/ts3.4/submodules/transfer-manager/index.browser.d.ts +3 -0
- package/dist-types/ts3.4/submodules/transfer-manager/index.d.ts +3 -0
- package/dist-types/ts3.4/submodules/transfer-manager/join-streams.browser.d.ts +13 -0
- package/dist-types/ts3.4/submodules/transfer-manager/join-streams.d.ts +13 -0
- package/dist-types/ts3.4/submodules/transfer-manager/log-level.d.ts +13 -0
- package/dist-types/ts3.4/submodules/transfer-manager/ordered-part-queue.d.ts +18 -0
- package/dist-types/ts3.4/submodules/transfer-manager/types.d.ts +184 -0
- package/dist-types/ts3.4/submodules/transfer-manager/worker-http-handler.browser.d.ts +17 -0
- package/dist-types/ts3.4/submodules/transfer-manager/worker-http-handler.d.ts +241 -0
- package/dist-types/ts3.4/submodules/worker/http-request-worker.d.ts +1 -0
- package/dist-types/ts3.4/submodules/worker/index.d.ts +1 -0
- package/package.json +108 -4
- package/transfer-manager.d.ts +5 -0
- package/transfer-manager.js +5 -0
- package/worker.d.ts +5 -0
- package/worker.js +5 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
import { lstatSync, ReadStream } from "node:fs";
|
|
3
|
+
export const byteLength = (input) => {
|
|
4
|
+
if (input == null) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
if (typeof input === "string") {
|
|
8
|
+
return Buffer.byteLength(input);
|
|
9
|
+
}
|
|
10
|
+
if (typeof input.byteLength === "number") {
|
|
11
|
+
return input.byteLength;
|
|
12
|
+
}
|
|
13
|
+
else if (typeof input.length === "number") {
|
|
14
|
+
return input.length;
|
|
15
|
+
}
|
|
16
|
+
else if (typeof input.size === "number") {
|
|
17
|
+
return input.size;
|
|
18
|
+
}
|
|
19
|
+
else if (input instanceof ReadStream) {
|
|
20
|
+
const { start: rangeStart, end: rangeEnd } = input;
|
|
21
|
+
let size;
|
|
22
|
+
try {
|
|
23
|
+
size = lstatSync(input.path).size;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const start = typeof rangeStart === "number" ? rangeStart : 0;
|
|
29
|
+
const endExclusive = Number.isFinite(rangeEnd) ? Math.min(rangeEnd + 1, size) : size;
|
|
30
|
+
return Math.max(0, endExclusive - start);
|
|
31
|
+
}
|
|
32
|
+
else if (typeof input.start === "number" && typeof input.end === "number") {
|
|
33
|
+
return input.end + 1 - input.start;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
};
|
|
37
|
+
async function* getChunkStream(data, partSize, getNextData) {
|
|
38
|
+
let partNumber = 1;
|
|
39
|
+
const currentBuffer = { chunks: [], length: 0 };
|
|
40
|
+
for await (const datum of getNextData(data)) {
|
|
41
|
+
currentBuffer.chunks.push(datum);
|
|
42
|
+
currentBuffer.length += datum.byteLength;
|
|
43
|
+
while (currentBuffer.length > partSize) {
|
|
44
|
+
const dataChunk = currentBuffer.chunks.length > 1 ? Buffer.concat(currentBuffer.chunks) : currentBuffer.chunks[0];
|
|
45
|
+
yield { partNumber, data: dataChunk.subarray(0, partSize) };
|
|
46
|
+
currentBuffer.chunks = [dataChunk.subarray(partSize)];
|
|
47
|
+
currentBuffer.length = currentBuffer.chunks[0].byteLength;
|
|
48
|
+
partNumber += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
yield {
|
|
52
|
+
partNumber,
|
|
53
|
+
data: currentBuffer.chunks.length !== 1 ? Buffer.concat(currentBuffer.chunks) : currentBuffer.chunks[0],
|
|
54
|
+
lastPart: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function* getChunkUint8Array(data, partSize) {
|
|
58
|
+
let partNumber = 1;
|
|
59
|
+
let startByte = 0;
|
|
60
|
+
let endByte = partSize;
|
|
61
|
+
while (endByte < data.byteLength) {
|
|
62
|
+
yield { partNumber, data: data.subarray(startByte, endByte) };
|
|
63
|
+
partNumber += 1;
|
|
64
|
+
startByte = endByte;
|
|
65
|
+
endByte = startByte + partSize;
|
|
66
|
+
}
|
|
67
|
+
yield { partNumber, data: data.subarray(startByte), lastPart: true };
|
|
68
|
+
}
|
|
69
|
+
async function* getDataReadable(data) {
|
|
70
|
+
for await (const chunk of data) {
|
|
71
|
+
if (Buffer.isBuffer(chunk) || chunk instanceof Uint8Array) {
|
|
72
|
+
yield chunk;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
yield Buffer.from(chunk);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function* getDataReadableStream(data) {
|
|
80
|
+
const reader = data.getReader();
|
|
81
|
+
try {
|
|
82
|
+
while (true) {
|
|
83
|
+
const { done, value } = await reader.read();
|
|
84
|
+
if (done)
|
|
85
|
+
return;
|
|
86
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
87
|
+
yield value;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
yield Buffer.from(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
reader.releaseLock();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export const getChunk = (data, partSize) => {
|
|
99
|
+
if (data instanceof Uint8Array) {
|
|
100
|
+
return getChunkUint8Array(data, partSize);
|
|
101
|
+
}
|
|
102
|
+
if (data instanceof String || typeof data === "string") {
|
|
103
|
+
return getChunkUint8Array(Buffer.from(data), partSize);
|
|
104
|
+
}
|
|
105
|
+
if (data instanceof ReadableStream) {
|
|
106
|
+
return getChunkStream(data, partSize, getDataReadableStream);
|
|
107
|
+
}
|
|
108
|
+
if (typeof data.stream === "function") {
|
|
109
|
+
return getChunkStream(data.stream(), partSize, getDataReadableStream);
|
|
110
|
+
}
|
|
111
|
+
if (Symbol.asyncIterator in data || typeof data[Symbol.asyncIterator] === "function") {
|
|
112
|
+
return getChunkStream(data, partSize, getDataReadable);
|
|
113
|
+
}
|
|
114
|
+
throw new Error("Body Data is unsupported format, expected data to be one of: string | Uint8Array | Buffer | Readable | ReadableStream | Blob;.");
|
|
115
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, stat, realpath } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
export class Semaphore {
|
|
5
|
+
limit;
|
|
6
|
+
inFlightRequests = 0;
|
|
7
|
+
waiters = [];
|
|
8
|
+
constructor(limit) {
|
|
9
|
+
this.limit = limit;
|
|
10
|
+
}
|
|
11
|
+
async acquire(count = 1) {
|
|
12
|
+
if (this.inFlightRequests < this.limit) {
|
|
13
|
+
this.inFlightRequests += count;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
17
|
+
this.inFlightRequests += count;
|
|
18
|
+
}
|
|
19
|
+
release(count = 1) {
|
|
20
|
+
this.inFlightRequests -= count;
|
|
21
|
+
while (this.waiters.length > 0 && this.inFlightRequests < this.limit) {
|
|
22
|
+
const next = this.waiters.shift();
|
|
23
|
+
if (next) {
|
|
24
|
+
next();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function validateDirectory(dirPath) {
|
|
30
|
+
const absolutePath = resolve(dirPath);
|
|
31
|
+
let stats;
|
|
32
|
+
try {
|
|
33
|
+
stats = await stat(absolutePath);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`Cannot access directory at: ${absolutePath}, ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
if (!stats.isDirectory()) {
|
|
39
|
+
throw new Error(`Path is not a directory: ${absolutePath}`);
|
|
40
|
+
}
|
|
41
|
+
return absolutePath;
|
|
42
|
+
}
|
|
43
|
+
export async function handleFailure(policy, context, abortController) {
|
|
44
|
+
if (policy === "terminate") {
|
|
45
|
+
abortController.abort();
|
|
46
|
+
return "terminate";
|
|
47
|
+
}
|
|
48
|
+
if (policy === "continue") {
|
|
49
|
+
return "continue";
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const result = await policy(context);
|
|
53
|
+
if (result === "terminate") {
|
|
54
|
+
abortController.abort();
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
abortController.abort();
|
|
60
|
+
return "terminate";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function createDestinationDirectory(dirPath) {
|
|
64
|
+
const absolutePath = resolve(dirPath);
|
|
65
|
+
if (existsSync(absolutePath)) {
|
|
66
|
+
return validateDirectory(absolutePath);
|
|
67
|
+
}
|
|
68
|
+
await mkdir(absolutePath, { recursive: true });
|
|
69
|
+
return absolutePath;
|
|
70
|
+
}
|
|
71
|
+
export function isFolderObject(object) {
|
|
72
|
+
return (object.Key?.endsWith("/") ?? false) && (object.Size ?? 0) === 0;
|
|
73
|
+
}
|
|
74
|
+
export function deriveLocalPath(destination, key) {
|
|
75
|
+
const normalizedKey = sep === "/" ? key : key.split("/").join(sep);
|
|
76
|
+
if (isAbsolute(normalizedKey)) {
|
|
77
|
+
throw new Error(`Object key resolves to an absolute path and would escape the destination directory: ${key}`);
|
|
78
|
+
}
|
|
79
|
+
const fullPath = resolve(destination, normalizedKey);
|
|
80
|
+
const destinationWithSep = destination.endsWith(sep) ? destination : destination + sep;
|
|
81
|
+
if (fullPath !== destination && !fullPath.startsWith(destinationWithSep)) {
|
|
82
|
+
throw new Error(`Object key "${key}" resolves to a path outside the destination directory. Path traversal is not allowed.`);
|
|
83
|
+
}
|
|
84
|
+
return fullPath;
|
|
85
|
+
}
|
|
86
|
+
export async function createLocalParentDirectories(destination, localPath) {
|
|
87
|
+
const canonicalDestination = await realpath(destination);
|
|
88
|
+
const relativeParent = relative(destination, dirname(localPath));
|
|
89
|
+
let currentDirectory = destination;
|
|
90
|
+
for (const segment of relativeParent.split(sep)) {
|
|
91
|
+
if (!segment || segment === ".") {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
currentDirectory = join(currentDirectory, segment);
|
|
95
|
+
await mkdir(currentDirectory, { recursive: true });
|
|
96
|
+
const canonicalDirectory = await realpath(currentDirectory);
|
|
97
|
+
const canonicalRelative = relative(canonicalDestination, canonicalDirectory);
|
|
98
|
+
if (canonicalRelative === ".." || canonicalRelative.startsWith(`..${sep}`) || isAbsolute(canonicalRelative)) {
|
|
99
|
+
throw new Error("Symbolic links outside the destination are not allowed.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import { open, rename, unlink } from "node:fs/promises";
|
|
4
|
+
export class FileManager {
|
|
5
|
+
cleanupHandlers = new Map();
|
|
6
|
+
async createTempFile(destination, totalSize) {
|
|
7
|
+
const tempFilePath = `${destination}.s3tmp.${this.generateUniqueId()}`;
|
|
8
|
+
const fd = await open(tempFilePath, "wx");
|
|
9
|
+
try {
|
|
10
|
+
await fd.truncate(totalSize);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await fd.close();
|
|
14
|
+
}
|
|
15
|
+
return tempFilePath;
|
|
16
|
+
}
|
|
17
|
+
async atomicRename(tempFilePath, destination) {
|
|
18
|
+
try {
|
|
19
|
+
await rename(tempFilePath, destination);
|
|
20
|
+
}
|
|
21
|
+
catch (renameError) {
|
|
22
|
+
if (renameError.code === "EEXIST" || renameError.code === "EPERM") {
|
|
23
|
+
try {
|
|
24
|
+
await unlink(destination);
|
|
25
|
+
await rename(tempFilePath, destination);
|
|
26
|
+
}
|
|
27
|
+
catch (fallbackError) {
|
|
28
|
+
await this.deleteTempFile(tempFilePath);
|
|
29
|
+
throw new Error(`Failed to rename temp file to destination: ${fallbackError.message || fallbackError.code}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await this.deleteTempFile(tempFilePath);
|
|
34
|
+
throw new Error(`Failed to rename temp file to destination: ${renameError.message || renameError.code}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async deleteTempFile(tempFilePath) {
|
|
39
|
+
this.unregisterCleanupHandler(tempFilePath);
|
|
40
|
+
try {
|
|
41
|
+
await unlink(tempFilePath);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code !== "ENOENT") {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
registerCleanupHandler(tempFilePath) {
|
|
50
|
+
const exitHandler = () => {
|
|
51
|
+
try {
|
|
52
|
+
unlinkSync(tempFilePath);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
process.on("exit", exitHandler);
|
|
58
|
+
this.cleanupHandlers.set(tempFilePath, { exitHandler });
|
|
59
|
+
}
|
|
60
|
+
unregisterCleanupHandler(tempFilePath) {
|
|
61
|
+
const handlers = this.cleanupHandlers.get(tempFilePath);
|
|
62
|
+
if (handlers) {
|
|
63
|
+
process.removeListener("exit", handlers.exitHandler);
|
|
64
|
+
this.cleanupHandlers.delete(tempFilePath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
generateUniqueId() {
|
|
68
|
+
return randomBytes(4).toString("hex");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { destroyStreams, joinStreams } from "./join-streams.browser";
|
|
2
|
+
import { bindS3TransferManager } from "./S3TransferManager";
|
|
3
|
+
import { createEmptyReadable, defaultWorkerCount, WorkerHttpHandler } from "./worker-http-handler.browser";
|
|
4
|
+
export class S3TransferManager extends bindS3TransferManager({
|
|
5
|
+
joinStreams,
|
|
6
|
+
destroyStreams,
|
|
7
|
+
WorkerHttpHandler,
|
|
8
|
+
createEmptyReadable,
|
|
9
|
+
defaultWorkerCount,
|
|
10
|
+
}) {
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { destroyStreams, joinStreams } from "./join-streams";
|
|
2
|
+
import { bindS3TransferManager } from "./S3TransferManager";
|
|
3
|
+
import { createEmptyReadable, defaultWorkerCount, WorkerHttpHandler } from "./worker-http-handler";
|
|
4
|
+
export class S3TransferManager extends bindS3TransferManager({
|
|
5
|
+
joinStreams,
|
|
6
|
+
destroyStreams,
|
|
7
|
+
WorkerHttpHandler,
|
|
8
|
+
createEmptyReadable,
|
|
9
|
+
defaultWorkerCount,
|
|
10
|
+
}) {
|
|
11
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isReadableStream, sdkStreamMixin } from "@smithy/core/serde";
|
|
2
|
+
export async function joinStreams(streams, eventListeners) {
|
|
3
|
+
const firstStream = await streams[0];
|
|
4
|
+
if (isReadableStream(firstStream) || firstStream instanceof Blob) {
|
|
5
|
+
const newReadableStream = new ReadableStream({
|
|
6
|
+
async start(controller) {
|
|
7
|
+
for await (const chunk of iterateStreams(streams, eventListeners)) {
|
|
8
|
+
controller.enqueue(chunk);
|
|
9
|
+
}
|
|
10
|
+
controller.close();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return sdkStreamMixin(newReadableStream);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
throw new Error("Unsupported Stream Type");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function* iterateStreams(promises, eventListeners) {
|
|
20
|
+
let bytesTransferred = 0;
|
|
21
|
+
let index = 0;
|
|
22
|
+
for (const streamPromise of promises) {
|
|
23
|
+
let stream;
|
|
24
|
+
try {
|
|
25
|
+
stream = await streamPromise;
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
await destroyStreams(promises);
|
|
29
|
+
eventListeners?.onFailure?.(e, index);
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
if (isReadableStream(stream)) {
|
|
33
|
+
const reader = stream.getReader();
|
|
34
|
+
try {
|
|
35
|
+
while (true) {
|
|
36
|
+
const { done, value } = await reader.read();
|
|
37
|
+
if (done) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
yield value;
|
|
41
|
+
bytesTransferred += value.byteLength;
|
|
42
|
+
eventListeners?.onBytes?.(bytesTransferred, index);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
await destroyStreams(promises);
|
|
48
|
+
eventListeners?.onFailure?.(e, index);
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
reader.releaseLock();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (stream instanceof Blob) {
|
|
56
|
+
const blobStream = stream.stream();
|
|
57
|
+
const reader = blobStream.getReader();
|
|
58
|
+
try {
|
|
59
|
+
while (true) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (done) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
yield value;
|
|
65
|
+
bytesTransferred += value.byteLength;
|
|
66
|
+
eventListeners?.onBytes?.(bytesTransferred, index);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
await destroyStreams(promises);
|
|
71
|
+
eventListeners?.onFailure?.(e, index);
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
reader.releaseLock();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
await destroyStreams(promises);
|
|
80
|
+
const failure = new Error(`unhandled stream type ${stream?.constructor?.name}`);
|
|
81
|
+
eventListeners?.onFailure?.(failure, index);
|
|
82
|
+
throw failure;
|
|
83
|
+
}
|
|
84
|
+
eventListeners?.onStreamConsumed?.(index);
|
|
85
|
+
index++;
|
|
86
|
+
}
|
|
87
|
+
eventListeners?.onCompletion?.(bytesTransferred, index - 1);
|
|
88
|
+
}
|
|
89
|
+
export async function destroyStreams(promises) {
|
|
90
|
+
await Promise.all(promises.map(async (streamPromise) => {
|
|
91
|
+
if (!streamPromise)
|
|
92
|
+
return;
|
|
93
|
+
return streamPromise
|
|
94
|
+
.then((stream) => {
|
|
95
|
+
if (isReadableStream(stream)) {
|
|
96
|
+
return stream.cancel();
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.catch((e) => { });
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isReadableStream, sdkStreamMixin } from "@smithy/core/serde";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
export async function joinStreams(streams, eventListeners) {
|
|
4
|
+
const firstStream = await streams[0];
|
|
5
|
+
if (isReadableStream(firstStream)) {
|
|
6
|
+
const newReadableStream = new ReadableStream({
|
|
7
|
+
async start(controller) {
|
|
8
|
+
for await (const chunk of iterateStreams(streams, eventListeners)) {
|
|
9
|
+
controller.enqueue(chunk);
|
|
10
|
+
}
|
|
11
|
+
controller.close();
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
return sdkStreamMixin(newReadableStream);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
streams.forEach((stream) => stream?.catch(() => { }));
|
|
18
|
+
return sdkStreamMixin(Readable.from(iterateStreams(streams, eventListeners)));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function* iterateStreams(promises, eventListeners) {
|
|
22
|
+
for (const p of promises) {
|
|
23
|
+
p?.catch(() => { });
|
|
24
|
+
}
|
|
25
|
+
let bytesTransferred = 0;
|
|
26
|
+
let index = 0;
|
|
27
|
+
for (const streamPromise of promises) {
|
|
28
|
+
if (!streamPromise) {
|
|
29
|
+
throw new Error("Stream promise is undefined - lazy prefetch may not have completed");
|
|
30
|
+
}
|
|
31
|
+
let stream;
|
|
32
|
+
try {
|
|
33
|
+
stream = await streamPromise;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
await destroyStreams(promises);
|
|
37
|
+
eventListeners?.onFailure?.(e, index);
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
if (isReadableStream(stream)) {
|
|
41
|
+
const reader = stream.getReader();
|
|
42
|
+
try {
|
|
43
|
+
while (true) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
yield value;
|
|
49
|
+
bytesTransferred += value.byteLength;
|
|
50
|
+
eventListeners?.onBytes?.(bytesTransferred, index);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
await destroyStreams(promises);
|
|
55
|
+
eventListeners?.onFailure?.(e, index);
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (stream instanceof Readable) {
|
|
63
|
+
try {
|
|
64
|
+
for await (const chunk of stream) {
|
|
65
|
+
yield chunk;
|
|
66
|
+
const chunkSize = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
|
|
67
|
+
bytesTransferred += chunkSize;
|
|
68
|
+
eventListeners?.onBytes?.(bytesTransferred, index);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
await destroyStreams(promises);
|
|
73
|
+
eventListeners?.onFailure?.(e, index);
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await destroyStreams(promises);
|
|
79
|
+
const failure = new Error(`unhandled stream type ${stream?.constructor?.name}`);
|
|
80
|
+
eventListeners?.onFailure?.(failure, index);
|
|
81
|
+
throw failure;
|
|
82
|
+
}
|
|
83
|
+
eventListeners?.onStreamConsumed?.(index);
|
|
84
|
+
index++;
|
|
85
|
+
}
|
|
86
|
+
eventListeners?.onCompletion?.(bytesTransferred, index - 1);
|
|
87
|
+
}
|
|
88
|
+
export async function destroyStreams(promises) {
|
|
89
|
+
await Promise.all(promises.map(async (streamPromise) => {
|
|
90
|
+
if (!streamPromise)
|
|
91
|
+
return;
|
|
92
|
+
return streamPromise
|
|
93
|
+
.then((stream) => {
|
|
94
|
+
if (stream instanceof Readable) {
|
|
95
|
+
stream.on("error", () => { });
|
|
96
|
+
stream.destroy();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
else if (isReadableStream(stream)) {
|
|
100
|
+
return stream.cancel();
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.catch((e) => { });
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class LogLevel {
|
|
2
|
+
minimum;
|
|
3
|
+
logger;
|
|
4
|
+
static LEVELS = ["trace", "debug", "info", "warn", "error"];
|
|
5
|
+
level = 5;
|
|
6
|
+
constructor(minimum, logger = console) {
|
|
7
|
+
this.minimum = minimum;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
const index = LogLevel.LEVELS.indexOf(minimum);
|
|
10
|
+
if (index === -1) {
|
|
11
|
+
throw new Error(`Log level must be one of ${LogLevel.LEVELS.join(", ")}`);
|
|
12
|
+
}
|
|
13
|
+
this.level = index;
|
|
14
|
+
}
|
|
15
|
+
trace(...args) {
|
|
16
|
+
this.level <= 0 && this.logger.trace?.(...args);
|
|
17
|
+
}
|
|
18
|
+
debug(...args) {
|
|
19
|
+
this.level <= 1 && this.logger.debug?.(...args);
|
|
20
|
+
}
|
|
21
|
+
info(...args) {
|
|
22
|
+
this.level <= 2 && this.logger.info?.(...args);
|
|
23
|
+
}
|
|
24
|
+
warn(...args) {
|
|
25
|
+
this.level <= 3 && this.logger.warn?.(...args);
|
|
26
|
+
}
|
|
27
|
+
error(...args) {
|
|
28
|
+
this.level <= 4 && this.logger.error?.(...args);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class OrderedPartQueue {
|
|
2
|
+
pending = new Map();
|
|
3
|
+
nextExpected = 0;
|
|
4
|
+
totalParts;
|
|
5
|
+
waitingConsumer = null;
|
|
6
|
+
error = undefined;
|
|
7
|
+
constructor(totalParts) {
|
|
8
|
+
this.totalParts = totalParts;
|
|
9
|
+
}
|
|
10
|
+
enqueue(rangeIndex, buffer, byteLength) {
|
|
11
|
+
if (this.error)
|
|
12
|
+
return;
|
|
13
|
+
if (rangeIndex === this.nextExpected && this.waitingConsumer) {
|
|
14
|
+
const consumer = this.waitingConsumer;
|
|
15
|
+
this.waitingConsumer = null;
|
|
16
|
+
this.nextExpected++;
|
|
17
|
+
consumer({ buffer, byteLength });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.pending.set(rangeIndex, { buffer, byteLength });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async dequeue() {
|
|
24
|
+
if (this.error) {
|
|
25
|
+
throw this.error;
|
|
26
|
+
}
|
|
27
|
+
if (this.nextExpected >= this.totalParts) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const ready = this.pending.get(this.nextExpected);
|
|
31
|
+
if (ready) {
|
|
32
|
+
this.pending.delete(this.nextExpected);
|
|
33
|
+
this.nextExpected++;
|
|
34
|
+
return ready;
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
this.waitingConsumer = resolve;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
setError(error) {
|
|
41
|
+
this.error = error;
|
|
42
|
+
if (this.waitingConsumer) {
|
|
43
|
+
const consumer = this.waitingConsumer;
|
|
44
|
+
this.waitingConsumer = null;
|
|
45
|
+
consumer(null);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
hasError() {
|
|
49
|
+
return this.error !== undefined;
|
|
50
|
+
}
|
|
51
|
+
getError() {
|
|
52
|
+
return this.error;
|
|
53
|
+
}
|
|
54
|
+
get pendingCount() {
|
|
55
|
+
return this.pending.size;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function defaultWorkerCount() {
|
|
2
|
+
return 1;
|
|
3
|
+
}
|
|
4
|
+
export function createEmptyReadable() {
|
|
5
|
+
throw new Error("WorkerHttpHandler is not supported in browser environments.");
|
|
6
|
+
}
|
|
7
|
+
export class WorkerHttpHandler {
|
|
8
|
+
constructor() {
|
|
9
|
+
throw new Error("WorkerHttpHandler is not supported in browser environments.");
|
|
10
|
+
}
|
|
11
|
+
async handle() {
|
|
12
|
+
throw new Error("WorkerHttpHandler is not supported in browser environments.");
|
|
13
|
+
}
|
|
14
|
+
updateHttpClientConfig() { }
|
|
15
|
+
httpHandlerConfigs() {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
getDownloadResult() {
|
|
19
|
+
throw new Error("WorkerHttpHandler is not supported in browser environments.");
|
|
20
|
+
}
|
|
21
|
+
getStreamDownloadResult() {
|
|
22
|
+
throw new Error("WorkerHttpHandler is not supported in browser environments.");
|
|
23
|
+
}
|
|
24
|
+
destroy() { }
|
|
25
|
+
}
|