Skip to content

Commit 2c18bc3

Browse files
committed
* 'main' of https://github.com/tus/tus-node-server: @tus/s3-store: add `maxMultipartParts` option (#712) [ci] release (#701) @tus/s3-store: add `minPartSize` option (#703)
2 parents 5314be6 + 7db2f17 commit 2c18bc3

File tree

11 files changed

+102
-24
lines changed

11 files changed

+102
-24
lines changed

.changeset/gold-adults-warn.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/proud-terms-swim.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/tricky-emus-fail.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 `maxMultipartParts` option. This can be used when using S3-compatible storage provider with different part number limitations.

packages/gcs-store/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @tus/gcs-store
22

3+
## 1.4.2
4+
5+
### Patch Changes
6+
7+
- 8217f5e: Correctly pass the content type from upload.metadata to GCS.
8+
39
## 1.4.1
410

511
### Patch Changes

packages/gcs-store/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tus/gcs-store",
4-
"version": "1.4.1",
4+
"version": "1.4.2",
55
"description": "Google Cloud Storage for @tus/server",
66
"main": "dist/index.js",
77
"homepage": "https://github.com/tus/tus-node-server#readme",
88
"bugs": "https://github.com/tus/tus-node-server/issues",
99
"repository": "tus/tus-node-server",
1010
"license": "MIT",
11-
"files": ["README.md", "LICENSE", "dist", "src"],
11+
"files": [
12+
"README.md",
13+
"LICENSE",
14+
"dist",
15+
"src"
16+
],
1217
"scripts": {
1318
"build": "tsc --build",
1419
"test": "mocha --timeout 30000 --exit --extension ts --require ts-node/register"

packages/s3-store/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# @tus/s3-store
22

3+
## 1.8.0
4+
5+
### Minor Changes
6+
7+
- 6351485: 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.
8+
9+
### Patch Changes
10+
11+
- c970858: Fix zero byte files only storing a .info file. Now correctly stores an empty file.
12+
313
## 1.7.0
414

515
### Minor Changes

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/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tus/s3-store",
4-
"version": "1.7.0",
4+
"version": "1.8.0",
55
"description": "AWS S3 store for @tus/server",
66
"main": "dist/index.js",
77
"homepage": "https://github.com/tus/tus-node-server#readme",

packages/s3-store/src/index.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,22 @@ 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
40+
/**
41+
* The maximum number of parts allowed in a multipart upload. Defaults to 10,000.
42+
*/
43+
maxMultipartParts?: number
3244
useTags?: boolean
3345
maxConcurrentPartUploads?: number
3446
cache?: KvStore<MetadataValue>
@@ -89,13 +101,13 @@ export class S3Store extends DataStore {
89101
protected expirationPeriodInMilliseconds = 0
90102
protected useTags = true
91103
protected partUploadSemaphore: Semaphore
92-
public maxMultipartParts = 10_000 as const
93-
public minPartSize = 5_242_880 as const // 5MiB
104+
public maxMultipartParts = 10_000
105+
public minPartSize = 5_242_880 // 5MiB
94106
public maxUploadSize = 5_497_558_138_880 as const // 5TiB
95107

96108
constructor(options: Options) {
97109
super()
98-
const {partSize, s3ClientConfig} = options
110+
const {maxMultipartParts, partSize, minPartSize, s3ClientConfig} = options
99111
const {bucket, ...restS3ClientConfig} = s3ClientConfig
100112
this.extensions = [
101113
'creation',
@@ -106,6 +118,12 @@ export class S3Store extends DataStore {
106118
]
107119
this.bucket = bucket
108120
this.preferredPartSize = partSize || 8 * 1024 * 1024
121+
if (minPartSize) {
122+
this.minPartSize = minPartSize
123+
}
124+
if (maxMultipartParts) {
125+
this.maxMultipartParts = maxMultipartParts
126+
}
109127
this.expirationPeriodInMilliseconds = options.expirationPeriodInMilliseconds ?? 0
110128
this.useTags = options.useTags ?? true
111129
this.cache = options.cache ?? new MemoryKvStore<MetadataValue>()
@@ -509,7 +527,8 @@ export class S3Store extends DataStore {
509527
optimalPartSize = Math.ceil(size / this.maxMultipartParts)
510528
}
511529

512-
return optimalPartSize
530+
// Always ensure the part size is at least minPartSize
531+
return Math.max(optimalPartSize, this.minPartSize)
513532
}
514533

515534
/**

packages/s3-store/test/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,22 @@ describe('S3DataStore', () => {
278278
}
279279
})
280280

281+
it('should use default maxMultipartParts when not specified', function () {
282+
const store = new S3Store({
283+
s3ClientConfig,
284+
})
285+
assert.equal(store.maxMultipartParts, 10000)
286+
})
287+
288+
it('should use custom maxMultipartParts when specified', function () {
289+
const customMaxParts = 5000
290+
const store = new S3Store({
291+
s3ClientConfig,
292+
maxMultipartParts: customMaxParts,
293+
})
294+
assert.equal(store.maxMultipartParts, customMaxParts)
295+
})
296+
281297
shared.shouldHaveStoreMethods()
282298
shared.shouldCreateUploads()
283299
shared.shouldRemoveUploads() // Termination extension

0 commit comments

Comments
 (0)