Skip to content
This repository was archived by the owner on Jan 16, 2024. It is now read-only.

Commit 1f7f5e5

Browse files
committed
feat(s3): implement multipart upload
1 parent 7ab9c38 commit 1f7f5e5

File tree

9 files changed

+3066
-9813
lines changed

9 files changed

+3066
-9813
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
dev/tmp
2+
13
# Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
24

35
### macOS ###

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ This plugin supports the following adapters:
4545

4646
However, you can create your own adapter for any third-party service you would like to use.
4747

48+
All adapters are implemented `dev` directory's [Payload Config](https://github.com/payloadcms/plugin-cloud-storage/blob/master/dev/src/payload.config.ts). See this file for examples.
49+
4850
## Plugin options
4951

5052
This plugin is configurable to work across many different Payload collections. A `*` denotes that the property is required.
@@ -84,7 +86,9 @@ const adapter = azureBlobStorageAdapter({
8486

8587
### S3 Adapter
8688

87-
To use the S3 adapter, you need to have `@aws-sdk/client-s3` installed in your project dependencies. To do so, run `yarn add @aws-sdk/client-s3`.
89+
To use the S3 adapter, some peer dependencies need to be installed:
90+
91+
`yarn add @aws-sdk/client-s3 @aws-sdk/lib-storage`.
8892

8993
From there, create the adapter, passing in all of its required properties:
9094

@@ -97,13 +101,18 @@ const adapter = s3Adapter({
97101
accessKeyId: process.env.S3_ACCESS_KEY_ID,
98102
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
99103
}
104+
// ... Other S3 configuration
100105
},
101106
bucket: process.env.S3_BUCKET,
102107
})
103108

104109
// Now you can pass this adapter to the plugin
105110
```
106111

112+
Other S3 Client configuration is documented [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/s3clientconfig.html).
113+
114+
Any upload over 50MB will automatically be uploaded using S3's multi-part upload.
115+
107116
#### Other S3-Compatible Storage
108117

109118
If you're running an S3-compatible object storage such as MinIO or Digital Ocean Spaces, you'll have to set the `endpoint` appropriately for the provider.

dev/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@google-cloud/storage": "^6.4.2",
2020
"dotenv": "^8.2.0",
2121
"express": "^4.17.1",
22-
"payload": "^1.1.3"
22+
"payload": "^1.6.6"
2323
},
2424
"devDependencies": {
2525
"@types/express": "^4.17.9",

dev/yarn.lock

Lines changed: 1960 additions & 5434 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
"prepublishOnly": "yarn build",
1414
"peerDependencies": {
1515
"@aws-sdk/client-s3": "^3.142.0",
16+
"@aws-sdk/lib-storage": "^3.267.0",
1617
"@azure/storage-blob": "^12.11.0",
1718
"@google-cloud/storage": "^6.4.1",
18-
"payload": "^1.0.27"
19+
"payload": "^1.6.6"
1920
},
2021
"peerDependenciesMeta": {
2122
"@aws-sdk/client-s3": {
2223
"optional": true
2324
},
25+
"@aws-sdk/lib-storage": {
26+
"optional": true
27+
},
2428
"@azure/storage-blob": {
2529
"optional": true
2630
},
@@ -49,7 +53,7 @@
4953
"eslint-plugin-import": "2.25.4",
5054
"eslint-plugin-prettier": "^4.0.0",
5155
"nodemon": "^2.0.6",
52-
"payload": "^1.1.3",
56+
"payload": "^1.6.6",
5357
"prettier": "^2.7.1",
5458
"ts-node": "^9.1.1",
5559
"typescript": "^4.1.3"

src/adapters/s3/handleUpload.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'path'
33
import type * as AWS from '@aws-sdk/client-s3'
44
import type { CollectionConfig } from 'payload/types'
55
import type stream from 'stream'
6+
7+
import { Upload } from '@aws-sdk/lib-storage'
68
import type { HandleUpload } from '../../types'
79

810
interface Args {
@@ -13,6 +15,8 @@ interface Args {
1315
getStorageClient: () => AWS.S3
1416
}
1517

18+
const multipartThreshold = 1024 * 1024 * 50 // 50MB
19+
1620
export const getHandleUpload = ({
1721
getStorageClient,
1822
bucket,
@@ -22,21 +26,37 @@ export const getHandleUpload = ({
2226
return async ({ data, file }) => {
2327
const fileKey = path.posix.join(prefix, file.filename)
2428

25-
let fileBufferOrStream: Buffer | stream.Readable
26-
if (file.tempFilePath) {
27-
fileBufferOrStream = fs.createReadStream(file.tempFilePath)
28-
} else {
29-
fileBufferOrStream = file.buffer
29+
const fileBufferOrStream: Buffer | stream.Readable = file.tempFilePath
30+
? fs.createReadStream(file.tempFilePath)
31+
: file.buffer
32+
33+
if (file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
34+
await getStorageClient().putObject({
35+
Bucket: bucket,
36+
Key: fileKey,
37+
Body: fileBufferOrStream,
38+
ACL: acl,
39+
ContentType: file.mimeType,
40+
})
41+
42+
return data
3043
}
3144

32-
await getStorageClient().putObject({
33-
Bucket: bucket,
34-
Key: fileKey,
35-
Body: fileBufferOrStream,
36-
ACL: acl,
37-
ContentType: file.mimeType,
45+
const parallelUploadS3 = new Upload({
46+
client: getStorageClient(),
47+
params: {
48+
Bucket: bucket,
49+
Key: fileKey,
50+
Body: fileBufferOrStream,
51+
ACL: acl,
52+
ContentType: file.mimeType,
53+
},
54+
queueSize: 4,
55+
partSize: multipartThreshold,
3856
})
3957

58+
await parallelUploadS3.done()
59+
4060
return data
4161
}
4262
}

src/adapters/s3/mock.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
exports.S3 = function () {
2-
return null
3-
}
1+
exports.S3 = () => null
42

5-
exports.HeadObjectCommand = function () {
6-
return null
7-
}
3+
exports.HeadObjectCommand = () => null
4+
exports.PutObjectCommand = () => null
5+
exports.UploadPartCommand = () => null
6+
exports.CreateMultipartUploadCommand = () => null
7+
exports.CompleteMultipartUploadCommand = () => null
8+
exports.PutObjectTaggingCommand = () => null

src/adapters/s3/webpack.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): Webpa
99
alias: {
1010
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
1111
'@aws-sdk/client-s3': path.resolve(__dirname, './mock.js'),
12+
'@aws-sdk/lib-storage': path.resolve(__dirname, './mock.js'),
1213
},
1314
},
1415
}

0 commit comments

Comments
 (0)