Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tus/server

Package Overview
Dependencies
Maintainers
3
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tus/server - npm Package Compare versions

Comparing version 1.2.0 to 1.3.0

dist/kvstores/FileKvStore.d.ts

4

dist/constants.d.ts

@@ -16,2 +16,6 @@ export declare const REQUEST_METHODS: readonly ["POST", "HEAD", "PATCH", "OPTIONS", "DELETE"];

};
readonly INVALID_TERMINATION: {
readonly status_code: 400;
readonly body: "Cannot terminate an already completed upload";
};
readonly ERR_LOCK_TIMEOUT: {

@@ -18,0 +22,0 @@ readonly status_code: 500;

@@ -39,2 +39,6 @@ "use strict";

},
INVALID_TERMINATION: {
status_code: 400,
body: 'Cannot terminate an already completed upload',
},
ERR_LOCK_TIMEOUT: {

@@ -41,0 +45,0 @@ status_code: 500,

8

dist/handlers/BaseHandler.js

@@ -34,4 +34,2 @@ "use strict";

generateUrl(req, id) {
// @ts-expect-error req.baseUrl does exist
const baseUrl = req.baseUrl ?? '';
const path = this.options.path === '/' ? '' : this.options.path;

@@ -44,4 +42,2 @@ if (this.options.generateUrl) {

host,
// @ts-expect-error we can pass undefined
baseUrl: req.baseUrl,
path: path,

@@ -53,6 +49,6 @@ id,

if (this.options.relativeLocation) {
return `${baseUrl}${path}/${id}`;
return `${path}/${id}`;
}
const { proto, host } = this.extractHostAndProto(req);
return `${proto}://${host}${baseUrl}${path}/${id}`;
return `${proto}://${host}${path}/${id}`;
}

@@ -59,0 +55,0 @@ getFileIdFromRequest(req) {

@@ -17,2 +17,8 @@ "use strict";

try {
if (this.options.disableTerminationForFinishedUploads) {
const upload = await this.store.getUpload(id);
if (upload.offset === upload.size) {
throw constants_1.ERRORS.INVALID_TERMINATION;
}
}
await this.store.remove(id);

@@ -19,0 +25,0 @@ }

@@ -40,9 +40,18 @@ "use strict";

}
let metadata;
if ('upload-metadata' in req.headers) {
try {
metadata = models_1.Metadata.parse(upload_metadata);
}
catch {
throw constants_1.ERRORS.INVALID_METADATA;
}
}
let id;
try {
id = this.options.namingFunction(req);
id = this.options.namingFunction(req, metadata);
}
catch (error) {
log('create: check your `namingFunction`. Error', error);
throw constants_1.ERRORS.FILE_WRITE_ERROR;
throw error;
}

@@ -55,11 +64,2 @@ const maxFileSize = await this.getConfiguredMaxSize(req, id);

}
let metadata;
if ('upload-metadata' in req.headers) {
try {
metadata = models_1.Metadata.parse(upload_metadata);
}
catch {
throw constants_1.ERRORS.INVALID_METADATA;
}
}
if (this.options.onIncomingRequest) {

@@ -66,0 +66,0 @@ await this.options.onIncomingRequest(req, res, id);

@@ -6,1 +6,2 @@ export { Server } from './server';

export * from './constants';
export * from './kvstores';

@@ -24,1 +24,2 @@ "use strict";

__exportStar(require("./constants"), exports);
__exportStar(require("./kvstores"), exports);

@@ -37,3 +37,2 @@ /// <reference types="node" />

host: string;
baseUrl: string;
path: string;

@@ -54,3 +53,3 @@ id: string;

*/
namingFunction?: (req: http.IncomingMessage) => string;
namingFunction?: (req: http.IncomingMessage, metadata?: Record<string, string | null>) => string;
/**

@@ -62,2 +61,6 @@ * The Lock interface defines methods for implementing a locking mechanism.

/**
* Disallow termination for finished uploads.
*/
disableTerminationForFinishedUploads?: boolean;
/**
* `onUploadCreate` will be invoked before a new upload is created.

@@ -64,0 +67,0 @@ * If the function returns the (modified) response, the upload will be created.

{
"$schema": "https://json.schemastore.org/package.json",
"name": "@tus/server",
"version": "1.2.0",
"version": "1.3.0",
"description": "Tus resumable upload protocol in Node.js",

@@ -27,18 +27,21 @@ "main": "dist/index.js",

"devDependencies": {
"@types/debug": "^4.1.8",
"@types/mocha": "^10.0.1",
"@types/node": "^20.10.4",
"@types/sinon": "^10.0.16",
"@types/supertest": "^2.0.12",
"eslint": "^8.48.0",
"@types/debug": "^4.1.12",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.5",
"@types/sinon": "^10.0.20",
"@types/supertest": "^2.0.16",
"eslint": "^8.56.0",
"eslint-config-custom": "0.0.0",
"mocha": "^10.2.0",
"node-mocks-http": "^1.13.0",
"node-mocks-http": "^1.14.1",
"should": "^13.2.3",
"sinon": "^15.2.0",
"supertest": "^6.3.3",
"ts-node": "^10.9.1",
"supertest": "^6.3.4",
"ts-node": "^10.9.2",
"tsconfig": "*",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
},
"optionalDependencies": {
"@redis/client": "^1.5.13"
},
"engines": {

@@ -45,0 +48,0 @@ "node": ">=16"

@@ -20,2 +20,3 @@ # `@tus/server`

- [Example: validate metadata when an upload is created](#example-validate-metadata-when-an-upload-is-created)
- [Example: store files in custom nested directories](#example-store-files-in-custom-nested-directories)
- [Types](#types)

@@ -53,4 +54,4 @@ - [Compatibility](#compatibility)

This package exports `Server` and all [`constants`][], [`types`][], and [`models`][]. There is no default export.
You should only need the `Server` and `EVENTS` exports.
This package exports `Server` and all [`constants`][], [`types`][], [`models`][], and [`kvstores`][]. There is no default export.
You should only need the `Server`, `EVENTS`, and KV store exports.

@@ -83,14 +84,43 @@ ### `new Server(options)`

#### `options.generateUrl`
Control how the upload url is generated (`(req, { proto, host, baseUrl, path, id }) => string)`)
Control how the upload URL is generated (`(req, { proto, host, path, id }) => string)`)
This only changes the upload URL (`Location` header).
If you also want to change the file name in storage use `namingFunction`.
Returning `prefix-1234` in `namingFunction` means the `id` argument in `generateUrl` is `prefix-1234`.
`@tus/server` expects everything in the path after the last `/` to be the upload id.
If you change that you have to use `getFileIdFromRequest` as well.
A common use case of this function and `getFileIdFromRequest` is to base65 encode a complex id into the URL.
> [!TIP]
> Checkout the example how to [store files in custom nested directories](#example-store-files-in-custom-nested-directories).
#### `options.getFileIdFromRequest`
Control how the Upload-ID is extracted from the request (`(req) => string | void`)
By default, it expects everything in the path after the last `/` to be the upload id.
> [!TIP]
> Checkout the example how to [store files in custom nested directories](#example-store-files-in-custom-nested-directories).
#### `options.namingFunction`
Control how you want to name files (`(req) => string`)
Control how you want to name files (`(req, metadata) => string`)
In `@tus/server`, the upload ID in the URL is the same as the file name.
This means using a custom `namingFunction` will return a different `Location` header for uploading
and result in a different file name in storage.
It is important to make these unique to prevent data loss. Only use it if you need to.
Default uses `crypto.randomBytes(16).toString('hex')`.
> [!TIP]
> Checkout the example how to [store files in custom nested directories](#example-store-files-in-custom-nested-directories).
#### `disableTerminationForFinishedUploads`
Disallow the [termination extension](https://tus.io/protocols/resumable-upload#termination) for finished uploads. (`boolean`)
#### `options.onUploadCreate`

@@ -205,2 +235,53 @@

### Key-Value Stores
All stores (as in the `datastore` option) save two files,
the uploaded file and an info file with metadata, usually adjacent to each other.
In `@tus/file-store` the `FileKvStore` is used to persist upload info but the KV stores
can also be used as a cache in other stores, such as `@tus/s3-store`.
#### `MemoryKvStore`
```ts
import {MemoryKvStore} from '@tus/server'
import S3Store, {type MetadataValue} from '@tus/s3-store'
new S3Store({
// ...
cache: new MemoryKvStore<MetadataValue>(),
})
```
#### `FileKvStore`
```ts
import {FileKvStore} from '@tus/server'
import S3Store, {type MetadataValue} from '@tus/s3-store'
const path = './uploads'
new S3Store({
// ...
cache: new FileKvStore<MetadataValue>(path),
})
```
#### `RedisKvStore`
```ts
import {RedisKvStore} from '@tus/server'
import S3Store, {type MetadataValue} from '@tus/s3-store'
import {createClient} from '@redis/client'
const client = await createClient().connect()
const path = './uploads'
const prefix = 'foo' // prefix for the key (foo${id})
new S3Store({
// ...
cache: new RedisKvStore<MetadataValue>(client, prefix),
})
```
## Examples

@@ -361,3 +442,3 @@

```js
const { Server } = require("@tus/server");
const {Server} = require('@tus/server')
// ...

@@ -368,6 +449,6 @@

async onIncomingRequest(req, res) {
const token = req.headers.authorization;
const token = req.headers.authorization
if (!token) {
throw { status_code: 401, body: 'Unauthorized' }
throw {status_code: 401, body: 'Unauthorized'}
}

@@ -379,11 +460,47 @@

} catch (error) {
throw { status_code: 401, body: 'Invalid token' }
throw {status_code: 401, body: 'Invalid token'}
}
if (req.user.role !== 'admin') {
throw { status_code: 403, body: 'Access denied' }
throw {status_code: 403, body: 'Access denied'}
}
},
});
})
```
### Example: store files in custom nested directories
You can use `namingFunction` to change the name of the stored file.
If you’re only adding a prefix or suffix without a slash (`/`),
you don’t need to implement `generateUrl` and `getFileIdFromRequest`.
Adding a slash means you create a new directory, for which you need
to implement all three functions as we need encode the id with base64 into the URL.
```js
const path = '/files'
const server = new Server({
path,
datastore: new FileStore({directory: './test/output'}),
namingFunction(req) {
const id = crypto.randomBytes(16).toString('hex')
const folder = getFolderForUser(req) // your custom logic
return `users/${folder}/${id}`
},
generateUrl(req, {proto, host, path, id}) {
id = Buffer.from(id, 'utf-8').toString('base64url')
return `${proto}://${host}${path}/${id}`
},
getFileIdFromRequest(req) {
const reExtractFileID = /([^/]+)\/?$/
const match = reExtractFileID.exec(req.url as string)
if (!match || path.includes(match[1])) {
return
}
return Buffer.from(match[1], 'base64url').toString('utf-8')
},
})
```

@@ -413,2 +530,3 @@

[`models`]: https://github.com/tus/tus-node-server/blob/main/packages/server/src/models/index.ts
[`kvstores`]: https://github.com/tus/tus-node-server/blob/main/packages/server/src/kvstores/index.ts
[expiration]: https://tus.io/protocols/resumable-upload.html#expiration
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc