Skip to content

Commit

Permalink
@tus/s3-store: add minPartSize option (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
Murderlon authored Feb 3, 2025
1 parent 3b5718b commit 6351485
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-buttons-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tus/s3-store": minor
---

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.
31 changes: 29 additions & 2 deletions packages/s3-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Extensions](#extensions)
- [Examples](#examples)
- [Example: using `credentials` to fetch credentials inside a AWS container](#example-using-credentials-to-fetch-credentials-inside-a-aws-container)
- [Example: use with Cloudflare R2](#example-use-with-cloudflare-r2)
- [Types](#types)
- [Compatibility](#compatibility)
- [Contribute](#contribute)
Expand Down Expand Up @@ -61,10 +62,20 @@ The bucket name.

#### `options.partSize`

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

#### `options.minPartSize`

The minimal part size for parts.
Can be used to ensure that all non-trailing parts are exactly the same size
by setting `partSize` and `minPartSize` to the same value.
Can not be lower than 5MiB or more than 5GiB.

The server calculates the optimal part size, which takes this size into account, but
may increase it to not exceed the S3 10K parts limit.

#### `options.s3ClientConfig`

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

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

### Example: use with Cloudflare R2

`@tus/s3-store` can be used with all S3-compatible storage solutions, including Cloudflare R2.
However R2 requires that all non-trailing parts are _exactly_ the same size.
This can be achieved by setting `partSize` and `minPartSize` to the same value.

```ts
// ...

const s3Store = new S3Store({
partSize: 8 * 1024 * 1024,
minPartSize: 8 * 1024 * 1024,
// ...
})
```

## Types

This package is fully typed with TypeScript.
Expand Down
24 changes: 18 additions & 6 deletions packages/s3-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ import path from 'node:path'
const log = debug('tus-node-server:stores:s3store')

export type Options = {
// The preferred part size for parts send to S3. Can not be lower than 5MiB or more than 5GiB.
// The server calculates the optimal part size, which takes this size into account,
// but may increase it to not exceed the S3 10K parts limit.
/**
* The preferred part size for parts send to S3. Can not be lower than 5MiB or more than 5GiB.
* The server calculates the optimal part size, which takes this size into account,
* but may increase it to not exceed the S3 10K parts limit.
*/
partSize?: number
/**
* The minimal part size for parts.
* Can be used to ensure that all non-trailing parts are exactly the same size.
* Can not be lower than 5MiB or more than 5GiB.
*/
minPartSize?: number
useTags?: boolean
maxConcurrentPartUploads?: number
cache?: KvStore<MetadataValue>
Expand Down Expand Up @@ -90,12 +98,12 @@ export class S3Store extends DataStore {
protected useTags = true
protected partUploadSemaphore: Semaphore
public maxMultipartParts = 10_000 as const
public minPartSize = 5_242_880 as const // 5MiB
public minPartSize = 5_242_880 // 5MiB
public maxUploadSize = 5_497_558_138_880 as const // 5TiB

constructor(options: Options) {
super()
const {partSize, s3ClientConfig} = options
const {partSize, minPartSize, s3ClientConfig} = options
const {bucket, ...restS3ClientConfig} = s3ClientConfig
this.extensions = [
'creation',
Expand All @@ -106,6 +114,9 @@ export class S3Store extends DataStore {
]
this.bucket = bucket
this.preferredPartSize = partSize || 8 * 1024 * 1024
if (minPartSize) {
this.minPartSize = minPartSize
}
this.expirationPeriodInMilliseconds = options.expirationPeriodInMilliseconds ?? 0
this.useTags = options.useTags ?? true
this.cache = options.cache ?? new MemoryKvStore<MetadataValue>()
Expand Down Expand Up @@ -509,7 +520,8 @@ export class S3Store extends DataStore {
optimalPartSize = Math.ceil(size / this.maxMultipartParts)
}

return optimalPartSize
// Always ensure the part size is at least minPartSize
return Math.max(optimalPartSize, this.minPartSize)
}

/**
Expand Down

0 comments on commit 6351485

Please sign in to comment.