# hastily

### Drop-in compatible Express middleware to replicate the Fastly Image Optimization API

## Usage

The middleware works a lot like compression, a standard Express middleware which transparently compresses any text-based response body that emits from something in the middleware chain.

So `hastily` works as an add-on to an Express server or middleware that is already serving images.

    import express, { static } from 'express';
    import { imageopto } from 'hastily';

    const app = express();
    app.use('/images', imageopto(), static('/www/images'));

    app.listen(8000);

You now have an app which can serve any image from `/www/images`, and optimize it with URL parameters from the Fastly Image Optimization API.

## Behavior

### Request Filtering

Hastily is meant to be registered as a middleware within an app that may be handling non-image requests as well as image requests. By default, it will only attempt to transform responses that appear to be uncompressed images. Hastily verifies this in several steps.

1. By URL: The default `filter` function for the imageopto middleware is `hastily.hasSupportedExtension(req: Request)` It. checks a request's URL path for an extension that indicates a Sharp-supported file type. It reads the supported extensions from Sharp itself. If a file does not have an image-file extension, it will do nothing.

   Supply an alternative function as the `filter` property of imageopto options to override this behavior.

       import { imageopto, hasSupportedExtension } from 'hastily';
       // Don't require an extension. Require a certain base directory and query param.
       imageopto({
         filter: (req) => req.path.startsWith('/media/') && 'optimize' in req.query,
       });
       // Require an extension, OR an 'imageServer' path.
       hastily.imageopto({
         filter: (req) => hasSupportedExtension(req) || req.path === '/imageServer',
       });

2. By method and status: Hastily will not transform any response unless the request was a `GET` and the response code is in the `2xx` range.

3. By Cache-control: If the response has a `Cache-Control` header containing `no-transform`, Hastily will respect that.

4. By dedupe: Both Hastily and Fastly (which Hastily emulates) set telltale headers on responses after processing them. If a `fastly-io-info` header is present, OR an `X-Optimized: hastily` header is present, Hastily will assume a transform has already occurred and won't attempt another. This behavior can be overridden by `options.force`, but you shouldn't do that.

5. By content type: Hastily will try to get the `Content-Type` header of the response. If it cannot detect a content type, OR if Sharp does not support the content type, Hastily will not attempt to transform.

6. By content encoding: Hastily will not attempt to decompress gzipped responses. Images shouldn't be gzipped (or deflated, or brotli'd, or what have you) and it's a mistake to configure a server to do so by default. The available gzip algorithms are designed for text data, and they don't compress binary data hardly at all. If your server is gzipping images before Hastily handles them, fix this configuration for the highest speeds.

### Debugging

The Hastily middleware logs debugs, warnings and errors to standard error, which is what web server stacks expect. When `NODE_ENV` is `production`, only warnings will be logged, as parseable JSON lines.

When `NODE_ENV` is not `production`, Hastily respects the Node convention of path syntax in the `DEBUG` environment variable to determine log level. `DEBUG=hastily:*` will show all debug data in a pretty format. You can limit logging to subject areas by using paths like `DEBUG=hastily:request,hastily:params,hastily:splice` instead.

### Full API doc at zetlen.github.io/hastily

## TODO

- implement resize and crop mappers
- throw on unsupported
- implement enable and disable for upscaling in resize
- implement format, auto=webp, and quality params in post-manip phase
- add unit tests
- add image-diff automated testing
- implement sharpen, mapping [amt, radius, threshold] to libvips sharpen params
- implement brightness, contrast, saturation by figuring out percentage to multiplier mapping
- use image.metadata() to implement relative and context-based methods
- add header-based methods
  - montage
  - overlay

## Index

### Classes

- FastlyParams
- RequestErrors

### Interfaces

- ImageOptoOptions

### Type aliases

- Orienter

### Variables

- QUALITY
- UNSET
- cacheControlNoTransformRegExp
- imageExtensionRE
- log
- mappers
- paramsLogger
- streamableFileExtensions

### Functions

- addListeners
- chunkLength
- createLogger
- exif
- flipH
- flipHV
- flipV
- hasSupportedExtension
- imageopto
- left
- right
- shouldTransform

### Object literals

- HASTILY_HEADER
- formatters
- optoFitToSharp
- optoResizeFilterToSharp
- orienters

## Type aliases

### Orienter

Orienter: (x: Sharp) => Sharp

- Defined in src/lib/mappers/orient.ts:4

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

## Variables

### Const QUALITY

QUALITY: "bg-color" | "blur" | "brightness" | "canvas" | "crop" | "contrast" | "disable" | "dpr" | "enable" | "fit" | "height" | "orient" | "pad" | "resize-filter" | "saturation" | "sharpen" | "trim" | "width" = 'quality' as Param

- Defined in src/lib/fastly-params.ts:17

### Const UNSET

UNSET: unique symbol = Symbol('unset')

- Defined in src/lib/fastly-params.ts:18

### Const cacheControlNoTransformRegExp

cacheControlNoTransformRegExp: RegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/

- Defined in src/lib/imageopto.ts:141

### Const imageExtensionRE

imageExtensionRE: RegExp = new RegExp(`\\.(?:${[...streamableFileExtensions].join('|')})$`)

- Defined in src/lib/imageopto.ts:57

### Const log

log: BaseLogger & {} = createLogger('splice')

- Defined in src/lib/splice-response.ts:13

### Const mappers

mappers: [Param, Mapper][] = [['width', resize],['height', resize],['dpr', resize],['fit', resize],['disable', resize],['enable', resize],['resize-filter', resize],['crop', extractCrop],['canvas', resizeCanvas],['pad', extend],['orient', orient],['bg-color', bgFlatten],['blur', blur],['brightness', unsupported('brightness', 'absolute brightness adjustment')],['contrast', unsupported('contrast', 'absolute contrast adjustment')],['saturation', unsupported('saturation', 'absolute saturation adjustment')],['sharpen', unsupported('sharpen', 'unsharp mask')],['trim', unsupported('trim', 'relative trimming from all four sides')],]

- Defined in src/lib/map-options.ts:24

### Const paramsLogger

paramsLogger: BaseLogger & {} = createLogger('params')

- Defined in src/lib/fastly-params.ts:15

### Const streamableFileExtensions

streamableFileExtensions: Set<string> = new Set(Object.keys(sharp.format).filter((ext) => sharp.format[ext as keyof FormatEnum].input.stream))

- Defined in src/lib/imageopto.ts:48

## Functions

### Private addListeners

- addListeners(stream: IMutableResponse | IWorkStream, on: any, listeners: Listener[]): void

- - Defined in src/lib/splice-response.ts:209

  Add bufferred listeners to stream

  #### Parameters

  - ##### stream: IMutableResponse | IWorkStream

  - ##### on: any

  - ##### listeners: Listener[]

  #### Returns void

### chunkLength

- chunkLength(chunk: any, encoding: any): number

- - Defined in src/lib/splice-response.ts:223

  Get the length of a given chunk

  #### Parameters

  - ##### chunk: any

  - ##### encoding: any

  #### Returns number

### createLogger

- createLogger(label: string): Logger

- - Defined in src/lib/logging.ts:5

  #### Parameters

  - ##### label: string

  #### Returns Logger

### Const exif

- exif(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:5

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Const flipH

- flipH(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:8

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Const flipHV

- flipHV(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:10

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Const flipV

- flipV(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:9

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Const hasSupportedExtension

- hasSupportedExtension(req: Request<ParamsDictionary, any, any, ParsedQs>): boolean

- - Defined in src/lib/imageopto.ts:66

  Use the `sharp.format` manifest to determine if the current request's file extension matches a format that sharp can stream in to optimize.

  #### Parameters

  - ##### req: Request<ParamsDictionary, any, any, ParsedQs>

  #### Returns boolean

### imageopto

- imageopto(filterOrOpts: RequestFilter | ImageOptoOptions): Middleware

- - Defined in src/lib/imageopto.ts:74

  Returns a new imageopto middleware for use in Express `app.use()`. Won't do anything if the Express app isn't already serving images!

  #### Parameters

  - ##### filterOrOpts: RequestFilter | ImageOptoOptions

  #### Returns Middleware

### Const left

- left(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:7

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Const right

- right(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:6

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### Private shouldTransform

- shouldTransform(req: Request, res: IMutableResponse, reqLog: Logger, options: ImageOptoOptions): boolean

- - Defined in src/lib/imageopto.ts:147

  Determine if the entity should be transformed.

  #### Parameters

  - ##### req: Request

  - ##### res: IMutableResponse

  - ##### reqLog: Logger

  - ##### options: ImageOptoOptions

  #### Returns boolean

## Object literals

### Const HASTILY_HEADER

HASTILY_HEADER: object

- Defined in src/lib/imageopto.ts:43

### NAME

NAME: string = "X-Optimized"

- Defined in src/lib/imageopto.ts:44

### VALUE

VALUE: string = "hastily"

- Defined in src/lib/imageopto.ts:45

### Const formatters

formatters: object

- Defined in src/lib/map-options.ts:45

### gif

gif: (transform: Sharp, params: IFastlyParams) => Sharp | false = unsupported('format' as Param, 'GIF output unsupported by node-hastily')

- Defined in src/lib/map-options.ts:46

#### Type declaration

- - (transform: Sharp, params: IFastlyParams): Sharp | false

  - #### Parameters

    - ##### transform: Sharp

    - ##### params: IFastlyParams

    #### Returns Sharp | false

### jpg

- jpg(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:47

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### pjpg

- pjpg(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:48

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### png

- png(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:50

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### png8

- png8(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:51

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### webp

- webp(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:53

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### webpll

- webpll(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:54

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### webply

- webply(transform: Sharp, params: IFastlyParams): Sharp

- - Defined in src/lib/map-options.ts:56

  #### Parameters

  - ##### transform: Sharp

  - ##### params: IFastlyParams

  #### Returns Sharp

### Const optoFitToSharp

optoFitToSharp: object

- Defined in src/lib/mappers/resize.ts:4

### bounds

bounds: "inside" = "inside"

- Defined in src/lib/mappers/resize.ts:5

### cover

cover: "outside" = "outside"

- Defined in src/lib/mappers/resize.ts:6

### crop

crop: "cover" = "cover"

- Defined in src/lib/mappers/resize.ts:7

### Const optoResizeFilterToSharp

optoResizeFilterToSharp: object

- Defined in src/lib/mappers/resize.ts:12

### bicubic

bicubic: "cubic" = "cubic"

- Defined in src/lib/mappers/resize.ts:13

### bilinear

bilinear: "mitchell" = "mitchell"

- Defined in src/lib/mappers/resize.ts:14

### cubic

cubic: "cubic" = "cubic"

- Defined in src/lib/mappers/resize.ts:15

### lanczos

lanczos: "lanczos3" = "lanczos3"

- Defined in src/lib/mappers/resize.ts:16

### lanczos2

lanczos2: "lanczos2" = "lanczos2"

- Defined in src/lib/mappers/resize.ts:17

### lanczos3

lanczos3: "lanczos3" = "lanczos3"

- Defined in src/lib/mappers/resize.ts:18

### linear

linear: "mitchell" = "mitchell"

- Defined in src/lib/mappers/resize.ts:19

### nearest

nearest: "nearest" = "nearest"

- Defined in src/lib/mappers/resize.ts:20

### Const orienters

orienters: object

- Defined in src/lib/mappers/orient.ts:12

### 1

1: (x: Sharp) => Sharp = exif

- Defined in src/lib/mappers/orient.ts:13

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 2

2: (x: Sharp) => Sharp = flipH

- Defined in src/lib/mappers/orient.ts:14

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 3

3: (x: Sharp) => Sharp = flipHV

- Defined in src/lib/mappers/orient.ts:15

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 4

4: (x: Sharp) => Sharp = flipV

- Defined in src/lib/mappers/orient.ts:16

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 6

6: (x: Sharp) => Sharp = right

- Defined in src/lib/mappers/orient.ts:18

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 8

8: (x: Sharp) => Sharp = left

- Defined in src/lib/mappers/orient.ts:20

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### h

h: (x: Sharp) => Sharp = flipH

- Defined in src/lib/mappers/orient.ts:21

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### hv

hv: (x: Sharp) => Sharp = flipHV

- Defined in src/lib/mappers/orient.ts:22

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### l

l: (x: Sharp) => Sharp = left

- Defined in src/lib/mappers/orient.ts:23

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### r

r: (x: Sharp) => Sharp = right

- Defined in src/lib/mappers/orient.ts:24

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### v

v: (x: Sharp) => Sharp = flipV

- Defined in src/lib/mappers/orient.ts:25

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### vh

vh: (x: Sharp) => Sharp = flipHV

- Defined in src/lib/mappers/orient.ts:26

#### Type declaration

- - (x: Sharp): Sharp

  - #### Parameters

    - ##### x: Sharp

    #### Returns Sharp

### 5

- 5(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:17

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

### 7

- 7(sharp: Sharp): Sharp

- - Defined in src/lib/mappers/orient.ts:19

  #### Parameters

  - ##### sharp: Sharp

  #### Returns Sharp

Generated using TypeDoc