@empiricalrun/r2-uploader 0.3.0 → 0.3.2
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/CHANGELOG.md +12 -0
- package/dist/delete.d.ts +3 -0
- package/dist/delete.d.ts.map +1 -0
- package/dist/delete.js +28 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -16
- package/dist/types.d.ts +14 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/upload/buffer.d.ts +3 -0
- package/dist/upload/buffer.d.ts.map +1 -0
- package/dist/upload/buffer.js +87 -0
- package/dist/{upload.d.ts → upload/index.d.ts} +2 -4
- package/dist/upload/index.d.ts.map +1 -0
- package/dist/upload/index.js +77 -0
- package/dist/upload/stream.d.ts +3 -0
- package/dist/upload/stream.d.ts.map +1 -0
- package/dist/upload/stream.js +89 -0
- package/package.json +1 -1
- package/test-assets/trace.zip +0 -0
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -171
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @empiricalrun/r2-uploader
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f317919: feat: use multipart upload with file streams
|
|
8
|
+
|
|
9
|
+
## 0.3.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fccb68a: chore: remove uploader logs for cleaner ci runs
|
|
14
|
+
|
|
3
15
|
## 0.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/delete.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../src/delete.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,mEAqBvE"}
|
package/dist/delete.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteFile = void 0;
|
|
4
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
5
|
+
async function deleteFile(fileKey, config) {
|
|
6
|
+
const s3Client = new client_s3_1.S3Client({
|
|
7
|
+
region: "auto",
|
|
8
|
+
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
9
|
+
credentials: {
|
|
10
|
+
accessKeyId: config.accessKeyId,
|
|
11
|
+
secretAccessKey: config.secretAccessKey,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
const params = {
|
|
15
|
+
Bucket: config.bucket,
|
|
16
|
+
Key: fileKey,
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
const command = new client_s3_1.DeleteObjectCommand(params);
|
|
20
|
+
const response = await s3Client.send(command);
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Error deleting file from R2: ${error}`);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.deleteFile = deleteFile;
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
exports.uploadInMemoryFiles = exports.uploadDirectory = exports.fetchFiles = void 0;
|
|
4
|
+
var fetch_1 = require("./fetch");
|
|
5
|
+
Object.defineProperty(exports, "fetchFiles", { enumerable: true, get: function () { return fetch_1.fetchFiles; } });
|
|
6
|
+
var upload_1 = require("./upload");
|
|
7
|
+
Object.defineProperty(exports, "uploadDirectory", { enumerable: true, get: function () { return upload_1.uploadDirectory; } });
|
|
8
|
+
Object.defineProperty(exports, "uploadInMemoryFiles", { enumerable: true, get: function () { return upload_1.uploadInMemoryFiles; } });
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,15 @@ interface R2BaseConfig {
|
|
|
5
5
|
secretAccessKey: string;
|
|
6
6
|
bucket: string;
|
|
7
7
|
}
|
|
8
|
-
export interface
|
|
8
|
+
export interface R2UploadStreamConfig extends R2BaseConfig {
|
|
9
|
+
destinationDir: string;
|
|
10
|
+
files: {
|
|
11
|
+
fullPath: string;
|
|
12
|
+
fileName: string;
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
export interface R2UploadBufferConfig extends R2BaseConfig {
|
|
9
17
|
destinationDir: string;
|
|
10
18
|
files: {
|
|
11
19
|
buffer: Buffer;
|
|
@@ -16,5 +24,10 @@ export interface R2UploadConfig extends R2BaseConfig {
|
|
|
16
24
|
export interface R2FetchConfig extends R2BaseConfig {
|
|
17
25
|
prefix: string;
|
|
18
26
|
}
|
|
27
|
+
export interface R2DeleteConfig extends R2BaseConfig {
|
|
28
|
+
}
|
|
29
|
+
export interface FileMap {
|
|
30
|
+
[file: string]: string;
|
|
31
|
+
}
|
|
19
32
|
export {};
|
|
20
33
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACpE;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClE;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;CAAG;AAEvD,MAAM,WAAW,OAAO;IACtB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/upload/buffer.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEzD,eAAO,MAAM,iBAAiB,WAAkB,oBAAoB,qBA6FnE,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadFileBuffers = void 0;
|
|
7
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
8
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
9
|
+
const async_retry_1 = __importDefault(require("async-retry"));
|
|
10
|
+
const md5_1 = __importDefault(require("md5"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const uploadFileBuffers = async (config) => {
|
|
13
|
+
const map = new Map();
|
|
14
|
+
const urls = {};
|
|
15
|
+
const S3 = new client_s3_1.S3Client({
|
|
16
|
+
region: "auto",
|
|
17
|
+
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
18
|
+
credentials: {
|
|
19
|
+
accessKeyId: config.accessKeyId,
|
|
20
|
+
secretAccessKey: config.secretAccessKey,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
await Promise.all(config.files.map(async (file) => {
|
|
24
|
+
let fileKey = path_1.default.join(config.destinationDir, file.fileName);
|
|
25
|
+
if (fileKey.includes(".gitkeep")) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const mimeType = file.mimeType || "application/octet-stream";
|
|
29
|
+
const uploadParams = {
|
|
30
|
+
Bucket: config.bucket,
|
|
31
|
+
Key: fileKey,
|
|
32
|
+
Body: file.buffer,
|
|
33
|
+
ContentLength: file.buffer.length,
|
|
34
|
+
ContentType: mimeType ?? "application/octet-stream",
|
|
35
|
+
};
|
|
36
|
+
const cmd = new client_s3_1.PutObjectCommand(uploadParams);
|
|
37
|
+
const digest = (0, md5_1.default)(file.buffer);
|
|
38
|
+
cmd.middlewareStack.add((next) => async (args) => {
|
|
39
|
+
args.request.headers["if-none-match"] = `"${digest}"`;
|
|
40
|
+
return await next(args);
|
|
41
|
+
}, {
|
|
42
|
+
step: "build",
|
|
43
|
+
name: "addETag",
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
await (0, async_retry_1.default)(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await S3.send(cmd);
|
|
49
|
+
map.set(file.fileName, data);
|
|
50
|
+
const fileUrl = await (0, s3_request_presigner_1.getSignedUrl)(S3, cmd);
|
|
51
|
+
urls[file.fileName] = fileUrl;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const error = err;
|
|
55
|
+
if (error["$metadata"]) {
|
|
56
|
+
// throw only those errors that are not 412 Precondition Failed
|
|
57
|
+
// 412 errors are errors while accessing the asset, which is post upload and can be ignored
|
|
58
|
+
if (error.$metadata.httpStatusCode !== 412) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, {
|
|
64
|
+
retries: 5,
|
|
65
|
+
factor: 3,
|
|
66
|
+
minTimeout: 1000,
|
|
67
|
+
maxTimeout: 60000,
|
|
68
|
+
randomize: true,
|
|
69
|
+
onRetry: (err, i) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
const error = err;
|
|
72
|
+
console.log("Upload retry attempt:", i, ":", file.fileName);
|
|
73
|
+
console.log("Response status:", error.$response?.statusCode);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const error = err;
|
|
80
|
+
console.log(`R2 Error - ${file.fileName} \nError: ${error}`);
|
|
81
|
+
console.log("Upload response", error.$response, "with status", error.$metadata.httpStatusCode);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}));
|
|
85
|
+
return urls;
|
|
86
|
+
};
|
|
87
|
+
exports.uploadFileBuffers = uploadFileBuffers;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
[file: string]: string;
|
|
4
|
-
}
|
|
2
|
+
import { FileMap } from "../types";
|
|
5
3
|
export declare function uploadDirectory({ sourceDir, fileList, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }: {
|
|
6
4
|
sourceDir: string;
|
|
7
5
|
fileList?: string[];
|
|
@@ -23,4 +21,4 @@ export declare function uploadInMemoryFiles({ files, destinationDir, uploadBucke
|
|
|
23
21
|
accessKeyId?: string;
|
|
24
22
|
secretAccessKey?: string;
|
|
25
23
|
}): Promise<FileMap>;
|
|
26
|
-
//# sourceMappingURL=
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/upload/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAA8C,MAAM,UAAU,CAAC;AAqB/E,wBAAsB,eAAe,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBnB;AAED,wBAAsB,mBAAmB,CAAC,EACxC,KAAK,EACL,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,GAChB,EAAE;IACD,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAUnB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.uploadInMemoryFiles = exports.uploadDirectory = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
const buffer_1 = require("./buffer");
|
|
29
|
+
const stream_1 = require("./stream");
|
|
30
|
+
const getFileList = (dir) => {
|
|
31
|
+
let files = [];
|
|
32
|
+
const items = fs.readdirSync(dir, {
|
|
33
|
+
withFileTypes: true,
|
|
34
|
+
});
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
const isDir = item.isDirectory();
|
|
37
|
+
const absolutePath = `${dir}/${item.name}`;
|
|
38
|
+
if (isDir) {
|
|
39
|
+
files = [...files, ...getFileList(absolutePath)];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
files.push(absolutePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
};
|
|
47
|
+
async function uploadDirectory({ sourceDir, fileList, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }) {
|
|
48
|
+
const filePaths = fileList || getFileList(sourceDir);
|
|
49
|
+
const files = filePaths.map((filePath) => ({
|
|
50
|
+
fullPath: filePath,
|
|
51
|
+
fileName: filePath.replace(sourceDir, ""), // relative path
|
|
52
|
+
mimeType: undefined,
|
|
53
|
+
}));
|
|
54
|
+
const config = {
|
|
55
|
+
accountId: accountId || process.env.R2_ACCOUNT_ID,
|
|
56
|
+
accessKeyId: accessKeyId || process.env.R2_ACCESS_KEY_ID,
|
|
57
|
+
secretAccessKey: secretAccessKey || process.env.R2_SECRET_ACCESS_KEY,
|
|
58
|
+
bucket: uploadBucket,
|
|
59
|
+
destinationDir,
|
|
60
|
+
files,
|
|
61
|
+
};
|
|
62
|
+
const uploadedFiles = await (0, stream_1.uploadFileStreams)(config);
|
|
63
|
+
return uploadedFiles;
|
|
64
|
+
}
|
|
65
|
+
exports.uploadDirectory = uploadDirectory;
|
|
66
|
+
async function uploadInMemoryFiles({ files, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }) {
|
|
67
|
+
const config = {
|
|
68
|
+
accountId: accountId || process.env.R2_ACCOUNT_ID,
|
|
69
|
+
accessKeyId: accessKeyId || process.env.R2_ACCESS_KEY_ID,
|
|
70
|
+
secretAccessKey: secretAccessKey || process.env.R2_SECRET_ACCESS_KEY,
|
|
71
|
+
bucket: uploadBucket,
|
|
72
|
+
destinationDir,
|
|
73
|
+
files,
|
|
74
|
+
};
|
|
75
|
+
return await (0, buffer_1.uploadFileBuffers)(config);
|
|
76
|
+
}
|
|
77
|
+
exports.uploadInMemoryFiles = uploadInMemoryFiles;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/upload/stream.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEzD,eAAO,MAAM,iBAAiB,WAAkB,oBAAoB,qBAkGnE,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadFileStreams = void 0;
|
|
7
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
8
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
9
|
+
const async_retry_1 = __importDefault(require("async-retry"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const uploadFileStreams = async (config) => {
|
|
13
|
+
const urls = {};
|
|
14
|
+
const S3 = new client_s3_1.S3Client({
|
|
15
|
+
region: "auto",
|
|
16
|
+
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
17
|
+
credentials: {
|
|
18
|
+
accessKeyId: config.accessKeyId,
|
|
19
|
+
secretAccessKey: config.secretAccessKey,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
await Promise.all(config.files.map(async (file) => {
|
|
23
|
+
let fileKey = path_1.default.join(config.destinationDir, file.fileName);
|
|
24
|
+
if (fileKey.includes(".gitkeep")) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const mimeType = file.mimeType || "application/octet-stream";
|
|
28
|
+
const createMultipartUploadParams = {
|
|
29
|
+
Bucket: config.bucket,
|
|
30
|
+
Key: fileKey,
|
|
31
|
+
ContentType: mimeType,
|
|
32
|
+
};
|
|
33
|
+
const createMultipartUploadCommand = new client_s3_1.CreateMultipartUploadCommand(createMultipartUploadParams);
|
|
34
|
+
const { UploadId } = await S3.send(createMultipartUploadCommand);
|
|
35
|
+
const partSize = 5 * 1024 * 1024; // 5MB
|
|
36
|
+
const fileStream = fs_1.default.createReadStream(file.fullPath, {
|
|
37
|
+
highWaterMark: partSize,
|
|
38
|
+
});
|
|
39
|
+
let partNumber = 1;
|
|
40
|
+
const parts = [];
|
|
41
|
+
for await (const chunk of fileStream) {
|
|
42
|
+
const uploadPartParams = {
|
|
43
|
+
Bucket: config.bucket,
|
|
44
|
+
Key: fileKey,
|
|
45
|
+
PartNumber: partNumber,
|
|
46
|
+
UploadId,
|
|
47
|
+
Body: chunk,
|
|
48
|
+
};
|
|
49
|
+
const uploadPartCommand = new client_s3_1.UploadPartCommand(uploadPartParams);
|
|
50
|
+
try {
|
|
51
|
+
await (0, async_retry_1.default)(async () => {
|
|
52
|
+
const { ETag } = await S3.send(uploadPartCommand);
|
|
53
|
+
parts.push({ ETag: ETag, PartNumber: partNumber });
|
|
54
|
+
partNumber++;
|
|
55
|
+
}, {
|
|
56
|
+
retries: 5,
|
|
57
|
+
factor: 3,
|
|
58
|
+
minTimeout: 1000,
|
|
59
|
+
maxTimeout: 60000,
|
|
60
|
+
randomize: true,
|
|
61
|
+
onRetry: (err, i) => {
|
|
62
|
+
console.log("Upload part retry attempt:", i, ":", file.fileName);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.log(`R2 Error - ${file.fileName} \nError: ${err}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const completeMultipartUploadParams = {
|
|
71
|
+
Bucket: config.bucket,
|
|
72
|
+
Key: fileKey,
|
|
73
|
+
UploadId,
|
|
74
|
+
MultipartUpload: {
|
|
75
|
+
Parts: parts,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const completeMultipartUploadCommand = new client_s3_1.CompleteMultipartUploadCommand(completeMultipartUploadParams);
|
|
79
|
+
await S3.send(completeMultipartUploadCommand);
|
|
80
|
+
const getObjectCommand = new client_s3_1.GetObjectCommand({
|
|
81
|
+
Bucket: config.bucket,
|
|
82
|
+
Key: fileKey,
|
|
83
|
+
});
|
|
84
|
+
const fileUrl = await (0, s3_request_presigner_1.getSignedUrl)(S3, getObjectCommand);
|
|
85
|
+
urls[file.fileName] = fileUrl;
|
|
86
|
+
}));
|
|
87
|
+
return urls;
|
|
88
|
+
};
|
|
89
|
+
exports.uploadFileStreams = uploadFileStreams;
|
package/package.json
CHANGED
|
Binary file
|
package/dist/upload.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":";AAgBA,MAAM,WAAW,OAAO;IACtB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAuHD,wBAAsB,eAAe,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CA6BnB;AAED,wBAAsB,mBAAmB,CAAC,EACxC,KAAK,EACL,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,GAChB,EAAE;IACD,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAUnB"}
|
package/dist/upload.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.uploadInMemoryFiles = exports.uploadDirectory = void 0;
|
|
30
|
-
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
31
|
-
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
32
|
-
const async_retry_1 = __importDefault(require("async-retry"));
|
|
33
|
-
const fs = __importStar(require("fs"));
|
|
34
|
-
const md5_1 = __importDefault(require("md5"));
|
|
35
|
-
const mime_1 = __importDefault(require("mime"));
|
|
36
|
-
const path_1 = __importDefault(require("path"));
|
|
37
|
-
const getFileList = (dir) => {
|
|
38
|
-
let files = [];
|
|
39
|
-
const items = fs.readdirSync(dir, {
|
|
40
|
-
withFileTypes: true,
|
|
41
|
-
});
|
|
42
|
-
for (const item of items) {
|
|
43
|
-
const isDir = item.isDirectory();
|
|
44
|
-
const absolutePath = `${dir}/${item.name}`;
|
|
45
|
-
if (isDir) {
|
|
46
|
-
files = [...files, ...getFileList(absolutePath)];
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
files.push(absolutePath);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return files;
|
|
53
|
-
};
|
|
54
|
-
const run = async (config) => {
|
|
55
|
-
const map = new Map();
|
|
56
|
-
const urls = {};
|
|
57
|
-
const S3 = new client_s3_1.S3Client({
|
|
58
|
-
region: "auto",
|
|
59
|
-
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
60
|
-
credentials: {
|
|
61
|
-
accessKeyId: config.accessKeyId,
|
|
62
|
-
secretAccessKey: config.secretAccessKey,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
await Promise.all(config.files.map(async (file) => {
|
|
66
|
-
let fileKey = path_1.default.join(config.destinationDir, file.fileName);
|
|
67
|
-
if (fileKey.includes(".gitkeep")) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const mimeType = file.mimeType || "application/octet-stream";
|
|
71
|
-
const uploadParams = {
|
|
72
|
-
Bucket: config.bucket,
|
|
73
|
-
Key: fileKey,
|
|
74
|
-
Body: file.buffer,
|
|
75
|
-
ContentLength: file.buffer.length,
|
|
76
|
-
ContentType: mimeType ?? "application/octet-stream",
|
|
77
|
-
};
|
|
78
|
-
const cmd = new client_s3_1.PutObjectCommand(uploadParams);
|
|
79
|
-
const digest = (0, md5_1.default)(file.buffer);
|
|
80
|
-
cmd.middlewareStack.add((next) => async (args) => {
|
|
81
|
-
args.request.headers["if-none-match"] = `"${digest}"`;
|
|
82
|
-
return await next(args);
|
|
83
|
-
}, {
|
|
84
|
-
step: "build",
|
|
85
|
-
name: "addETag",
|
|
86
|
-
});
|
|
87
|
-
try {
|
|
88
|
-
await (0, async_retry_1.default)(async () => {
|
|
89
|
-
try {
|
|
90
|
-
console.log("\nStarting file upload for:", file.fileName, "\n");
|
|
91
|
-
const data = await S3.send(cmd);
|
|
92
|
-
map.set(file.fileName, data);
|
|
93
|
-
const fileUrl = await (0, s3_request_presigner_1.getSignedUrl)(S3, cmd);
|
|
94
|
-
urls[file.fileName] = fileUrl;
|
|
95
|
-
console.log("\nFinished file upload for:", file.fileName, "\n");
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
console.log("\nError uploading file: ", file.fileName, err, "\n");
|
|
99
|
-
const error = err;
|
|
100
|
-
if (error["$metadata"]) {
|
|
101
|
-
// throw only those errors that are not 412 Precondition Failed
|
|
102
|
-
// 412 errors are errors while accessing the asset, which is post upload and can be ignored
|
|
103
|
-
if (error.$metadata.httpStatusCode !== 412) {
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}, {
|
|
109
|
-
retries: 5,
|
|
110
|
-
factor: 3,
|
|
111
|
-
minTimeout: 1000,
|
|
112
|
-
maxTimeout: 60000,
|
|
113
|
-
randomize: true,
|
|
114
|
-
onRetry: (err, i) => {
|
|
115
|
-
if (err) {
|
|
116
|
-
const error = err;
|
|
117
|
-
console.log("Upload retry attempt:", i, ":", file.fileName);
|
|
118
|
-
console.log("Response status:", error.$response?.statusCode);
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
const error = err;
|
|
125
|
-
console.log(`R2 Error - ${file.fileName} \nError: ${error}`);
|
|
126
|
-
console.log("Upload response", error.$response, "with status", error.$metadata.httpStatusCode);
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}));
|
|
130
|
-
return urls;
|
|
131
|
-
};
|
|
132
|
-
async function uploadDirectory({ sourceDir, fileList, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }) {
|
|
133
|
-
const filePaths = fileList || getFileList(sourceDir);
|
|
134
|
-
const files = filePaths
|
|
135
|
-
.map((filePath) => ({
|
|
136
|
-
path: filePath,
|
|
137
|
-
fileName: filePath.replace(sourceDir, ""),
|
|
138
|
-
mimeType: undefined,
|
|
139
|
-
}))
|
|
140
|
-
.map((file) => {
|
|
141
|
-
const mimeType = mime_1.default.getType(file.path) || "application/octet-stream";
|
|
142
|
-
return {
|
|
143
|
-
buffer: fs.readFileSync(file.path),
|
|
144
|
-
fileName: file.fileName,
|
|
145
|
-
mimeType,
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
let config = {
|
|
149
|
-
accountId: accountId || process.env.R2_ACCOUNT_ID,
|
|
150
|
-
accessKeyId: accessKeyId || process.env.R2_ACCESS_KEY_ID,
|
|
151
|
-
secretAccessKey: secretAccessKey || process.env.R2_SECRET_ACCESS_KEY,
|
|
152
|
-
bucket: uploadBucket,
|
|
153
|
-
destinationDir,
|
|
154
|
-
files,
|
|
155
|
-
};
|
|
156
|
-
const uploadedFiles = await run(config);
|
|
157
|
-
return uploadedFiles;
|
|
158
|
-
}
|
|
159
|
-
exports.uploadDirectory = uploadDirectory;
|
|
160
|
-
async function uploadInMemoryFiles({ files, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }) {
|
|
161
|
-
const config = {
|
|
162
|
-
accountId: accountId || process.env.R2_ACCOUNT_ID,
|
|
163
|
-
accessKeyId: accessKeyId || process.env.R2_ACCESS_KEY_ID,
|
|
164
|
-
secretAccessKey: secretAccessKey || process.env.R2_SECRET_ACCESS_KEY,
|
|
165
|
-
bucket: uploadBucket,
|
|
166
|
-
destinationDir,
|
|
167
|
-
files,
|
|
168
|
-
};
|
|
169
|
-
return await run(config);
|
|
170
|
-
}
|
|
171
|
-
exports.uploadInMemoryFiles = uploadInMemoryFiles;
|