Skip to content

Commit 6351485

Browse files
authored
@tus/s3-store: add minPartSize option (#703)
1 parent 3b5718b commit 6351485

File tree

3 files changed

+52
-8
lines changed

3 files changed

+52
-8
lines changed

.changeset/ten-buttons-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tus/s3-store": minor
3+
---
4+
5+
Add `minPartSize` option. This can be used alongside `partSize` to guarantee that all non-trailing parts are _exactly_ the same size, which is required for Cloudflare R2.

packages/s3-store/README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [Extensions](#extensions)
1414
- [Examples](#examples)
1515
- [Example: using `credentials` to fetch credentials inside a AWS container](#example-using-credentials-to-fetch-credentials-inside-a-aws-container)
16+
- [Example: use with Cloudflare R2](#example-use-with-cloudflare-r2)
1617
- [Types](#types)
1718
- [Compatibility](#compatibility)
1819
- [Contribute](#contribute)
@@ -61,10 +62,20 @@ The bucket name.
6162

6263
#### `options.partSize`
6364

64-
The preferred part size for parts send to S3. Can not be lower than 5MiB or more than
65+
The **preferred** part size for parts send to S3. Can not be lower than 5MiB or more than
6566
5GiB. The server calculates the optimal part size, which takes this size into account, but
6667
may increase it to not exceed the S3 10K parts limit.
6768

69+
#### `options.minPartSize`
70+
71+
The minimal part size for parts.
72+
Can be used to ensure that all non-trailing parts are exactly the same size
73+
by setting `partSize` and `minPartSize` to the same value.
74+
Can not be lower than 5MiB or more than 5GiB.
75+
76+
The server calculates the optimal part size, which takes this size into account, but
77+
may increase it to not exceed the S3 10K parts limit.
78+
6879
#### `options.s3ClientConfig`
6980

7081
Options to pass to the AWS S3 SDK. Checkout the
@@ -182,7 +193,7 @@ docs for the supported values of
182193
```js
183194
const aws = require('aws-sdk')
184195
const {Server} = require('@tus/server')
185-
const {FileStore} = require('@tus/s3-store')
196+
const {S3Store} = require('@tus/s3-store')
186197

187198
const s3Store = new S3Store({
188199
partSize: 8 * 1024 * 1024,
@@ -199,6 +210,22 @@ const server = new Server({path: '/files', datastore: s3Store})
199210
// ...
200211
```
201212

213+
### Example: use with Cloudflare R2
214+
215+
`@tus/s3-store` can be used with all S3-compatible storage solutions, including Cloudflare R2.
216+
However R2 requires that all non-trailing parts are _exactly_ the same size.
217+
This can be achieved by setting `partSize` and `minPartSize` to the same value.
218+
219+
```ts
220+
// ...
221+
222+
const s3Store = new S3Store({
223+
partSize: 8 * 1024 * 1024,
224+
minPartSize: 8 * 1024 * 1024,
225+
// ...
226+
})
227+
```
228+
202229
## Types
203230

204231
This package is fully typed with TypeScript.

packages/s3-store/src/index.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ import path from 'node:path'
2525
const log = debug('tus-node-server:stores:s3store')
2626

2727
export type Options = {
28-
// The preferred part size for parts send to S3. Can not be lower than 5MiB or more than 5GiB.
29-
// The server calculates the optimal part size, which takes this size into account,
30-
// but may increase it to not exceed the S3 10K parts limit.
28+
/**
29+
* The preferred part size for parts send to S3. Can not be lower than 5MiB or more than 5GiB.
30+
* The server calculates the optimal part size, which takes this size into account,
31+
* but may increase it to not exceed the S3 10K parts limit.
32+
*/
3133
partSize?: number
34+
/**
35+
* The minimal part size for parts.
36+
* Can be used to ensure that all non-trailing parts are exactly the same size.
37+
* Can not be lower than 5MiB or more than 5GiB.
38+
*/
39+
minPartSize?: number
3240
useTags?: boolean
3341
maxConcurrentPartUploads?: number
3442
cache?: KvStore<MetadataValue>
@@ -90,12 +98,12 @@ export class S3Store extends DataStore {
9098
protected useTags = true
9199
protected partUploadSemaphore: Semaphore
92100
public maxMultipartParts = 10_000 as const
93-
public minPartSize = 5_242_880 as const // 5MiB
101+
public minPartSize = 5_242_880 // 5MiB
94102
public maxUploadSize = 5_497_558_138_880 as const // 5TiB
95103

96104
constructor(options: Options) {
97105
super()
98-
const {partSize, s3ClientConfig} = options
106+
const {partSize, minPartSize, s3ClientConfig} = options
99107
const {bucket, ...restS3ClientConfig} = s3ClientConfig
100108
this.extensions = [
101109
'creation',
@@ -106,6 +114,9 @@ export class S3Store extends DataStore {
106114
]
107115
this.bucket = bucket
108116
this.preferredPartSize = partSize || 8 * 1024 * 1024
117+
if (minPartSize) {
118+
this.minPartSize = minPartSize
119+
}
109120
this.expirationPeriodInMilliseconds = options.expirationPeriodInMilliseconds ?? 0
110121
this.useTags = options.useTags ?? true
111122
this.cache = options.cache ?? new MemoryKvStore<MetadataValue>()
@@ -509,7 +520,8 @@ export class S3Store extends DataStore {
509520
optimalPartSize = Math.ceil(size / this.maxMultipartParts)
510521
}
511522

512-
return optimalPartSize
523+
// Always ensure the part size is at least minPartSize
524+
return Math.max(optimalPartSize, this.minPartSize)
513525
}
514526

515527
/**

0 commit comments

Comments
 (0)