Skip to content

Commit

Permalink
Simplify types & improve type docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Murderlon committed Dec 12, 2023
1 parent 7873d93 commit 7ff31e4
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 33 deletions.
6 changes: 3 additions & 3 deletions packages/server/src/handlers/BaseHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import EventEmitter from 'node:events'

import type {ServerOptions, WithRequired} from '../types'
import type {ServerOptions} from '../types'
import type {DataStore, CancellationContext} from '../models'
import type http from 'node:http'
import stream from 'node:stream'
Expand All @@ -11,10 +11,10 @@ const reForwardedHost = /host="?([^";]+)/
const reForwardedProto = /proto=(https?)/

export class BaseHandler extends EventEmitter {
options: WithRequired<ServerOptions, 'locker'>
options: ServerOptions
store: DataStore

constructor(store: DataStore, options: WithRequired<ServerOptions, 'locker'>) {
constructor(store: DataStore, options: ServerOptions) {
super()
if (!store) {
throw new Error('Store must be defined')
Expand Down
5 changes: 2 additions & 3 deletions packages/server/src/handlers/PostHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ const log = debug('tus-node-server:handlers:post')

export class PostHandler extends BaseHandler {
// Overriding the `BaseHandler` type. We always set `namingFunction` in the constructor.
options!: Required<Pick<ServerOptions, 'namingFunction' | 'locker'>> &
Omit<ServerOptions, 'namingFunction' | 'locker'>
options!: WithRequired<ServerOptions, 'namingFunction'>

constructor(store: DataStore, options: WithRequired<ServerOptions, 'locker'>) {
constructor(store: DataStore, options: ServerOptions) {
if (options.namingFunction && typeof options.namingFunction !== 'function') {
throw new Error("'namingFunction' must be a function")
}
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from './constants'

import type stream from 'node:stream'
import type {ServerOptions, RouteHandler, WithRequired} from './types'
import type {ServerOptions, RouteHandler, WithOptional} from './types'
import type {DataStore, Upload, CancellationContext} from './models'
import {MemoryLocker} from './lockers'

Expand Down Expand Up @@ -77,9 +77,9 @@ const log = debug('tus-node-server')
export class Server extends EventEmitter {
datastore: DataStore
handlers: Handlers
options: WithRequired<ServerOptions, 'locker'>
options: ServerOptions

constructor(options: ServerOptions & {datastore: DataStore}) {
constructor(options: WithOptional<ServerOptions, 'locker'> & {datastore: DataStore}) {
super()

if (!options) {
Expand All @@ -99,7 +99,7 @@ export class Server extends EventEmitter {
}

const {datastore, ...rest} = options
this.options = rest as WithRequired<ServerOptions, 'locker'>
this.options = rest as ServerOptions
this.datastore = datastore
this.handlers = {
// GET handlers should be written in the implementations
Expand Down
104 changes: 82 additions & 22 deletions packages/server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,115 @@ import type http from 'node:http'

import type {Locker, Upload} from './models'

/**
* Represents the configuration options for a server.
*/
export type ServerOptions = {
// The route to accept requests.
/**
* The route to accept requests.
*/
path: string
// Return a relative URL as the `Location` header.

/**
* Return a relative URL as the `Location` header.
*/
relativeLocation?: boolean
// Allow `Forwarded`, `X-Forwarded-Proto`, and `X-Forwarded-Host` headers
// to override the `Location` header returned by the server.

/**
* Allow `Forwarded`, `X-Forwarded-Proto`, and `X-Forwarded-Host` headers
* to override the `Location` header returned by the server.
*/
respectForwardedHeaders?: boolean
// adds custom headers sent in `Access-Control-Allow-Headers`.

/**
* Additional headers sent in `Access-Control-Allow-Headers`.
*/
allowedHeaders?: string[]
// Control how the upload url is generated

/**
* Control how the upload URL is generated.
* @param req - The incoming HTTP request.
* @param options - Options for generating the URL.
*/
generateUrl?: (
req: http.IncomingMessage,
options: {proto: string; host: string; baseUrl: string; path: string; id: string}
) => string
// Control how the Upload-ID is extracted from the request

/**
* Control how the Upload-ID is extracted from the request.
* @param req - The incoming HTTP request.
*/
getFileIdFromRequest?: (req: http.IncomingMessage) => string | void
// Control how you want to name files.
// It is important to make these unique to prevent data loss. Only use it if you really need to.
// Default uses `crypto.randomBytes(16).toString('hex')`.

/**
* Control how you want to name files.
* It is important to make these unique to prevent data loss.
* Only use it if you really need to.
* Default uses `crypto.randomBytes(16).toString('hex')`.
* @param req - The incoming HTTP request.
*/
namingFunction?: (req: http.IncomingMessage) => string
// locker implementation to support distributed locks
locker?:

/**
* The Lock interface defines methods for implementing a locking mechanism.
* It is primarily used to ensure exclusive access to resources, such as uploads and their metadata.
*/
locker:
| Locker
| Promise<Locker>
| ((req: http.IncomingMessage) => Locker | Promise<Locker>)
// `onUploadCreate` will be invoked before a new upload is created.
// If the function returns the (modified) response, the upload will be created.
// If an error is thrown, the HTTP request will be aborted and the provided `body` and `status_code` (or their fallbacks)
// will be sent to the client. This can be used to implement validation of upload metadata or add headers.

/**
* `onUploadCreate` will be invoked before a new upload is created.
* If the function returns the (modified) response, the upload will be created.
* If an error is thrown, the HTTP request will be aborted, and the provided `body` and `status_code`
* (or their fallbacks) will be sent to the client. This can be used to implement validation of upload
* metadata or add headers.
* @param req - The incoming HTTP request.
* @param res - The HTTP response.
* @param upload - The Upload object.
*/
onUploadCreate?: (
req: http.IncomingMessage,
res: http.ServerResponse,
upload: Upload
) => Promise<http.ServerResponse>
// `onUploadFinish` will be invoked after an upload is completed but before a response is returned to the client.
// If the function returns the (modified) response, the upload will finish.
// If an error is thrown, the HTTP request will be aborted and the provided `body` and `status_code` (or their fallbacks)
// will be sent to the client. This can be used to implement post-processing validation.

/**
* `onUploadFinish` will be invoked after an upload is completed but before a response is returned to the client.
* If the function returns the (modified) response, the upload will finish.
* If an error is thrown, the HTTP request will be aborted, and the provided `body` and `status_code`
* (or their fallbacks) will be sent to the client. This can be used to implement post-processing validation.
* @param req - The incoming HTTP request.
* @param res - The HTTP response.
* @param upload - The Upload object.
*/
onUploadFinish?: (
req: http.IncomingMessage,
res: http.ServerResponse,
upload: Upload
) => Promise<http.ServerResponse>

/**
* `onIncomingRequest` will be invoked when an incoming request is received.
* @param req - The incoming HTTP request.
* @param res - The HTTP response.
* @param uploadId - The ID of the upload.
*/
onIncomingRequest?: (
req: http.IncomingMessage,
res: http.ServerResponse,
uploadId: string
) => Promise<void>
// `onResponseError` will be invoked when an error response is about to be sent by the server.
// you use this function to map custom errors to tus errors or for custom observability.

/**
* `onResponseError` will be invoked when an error response is about to be sent by the server.
* Use this function to map custom errors to tus errors or for custom observability.
* @param req - The incoming HTTP request.
* @param res - The HTTP response.
* @param err - The error object or response.
*/
onResponseError?: (
req: http.IncomingMessage,
res: http.ServerResponse,
Expand All @@ -65,4 +123,6 @@ export type ServerOptions = {

export type RouteHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void

export type WithOptional<T, K extends keyof T> = Omit<T, K> & {[P in K]+?: T[P]}

export type WithRequired<T, K extends keyof T> = T & {[P in K]-?: T[P]}
2 changes: 1 addition & 1 deletion packages/server/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function addPipableStreamBody<T extends httpMocks.MockRequest<unknown>>(
})

// Add the pipe method to the mockRequest
// @ts-ignore
// @ts-expect-error pipe exists
mockRequest.pipe = function (dest: stream.Writable) {
bodyStream.pipe(dest)
}
Expand Down

0 comments on commit 7ff31e4

Please sign in to comment.