├── .prettierignore
├── .gitignore
├── .github
└── FUNDING.yml
├── __tests__
├── nodejs-512.png
└── implementation.spec.ts
├── index.d.ts
├── index.js
├── dist
├── types
│ ├── get-filename.d.ts
│ ├── get-filename.d.ts.map
│ ├── get-sharp-options.d.ts
│ ├── transformer.d.ts
│ ├── get-sharp-options.d.ts.map
│ ├── transformer.d.ts.map
│ ├── main.d.ts.map
│ ├── main.d.ts
│ └── types.d.ts
├── get-filename.js
├── get-filename.js.map
├── get-sharp-options.js.map
├── get-sharp-options.js
├── transformer.js.map
├── transformer.js
├── main.js.map
└── main.js
├── .travis.yml
├── src
├── get-filename.ts
├── get-sharp-options.ts
├── transformer.ts
├── types.d.ts
└── main.ts
├── wallaby.js
├── prettier.config.js
├── scripts
└── copy.js
├── tsconfig.json
├── .editorconfig
├── tslint.json
├── CHANGELOG.md
├── LICENSE
├── package.json
└── README.md
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn-error.log
3 | coverage
4 | .env
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: ikhsaan
2 | custom: ["https://www.paypal.com/paypalme/abdulikhsan"]
3 |
--------------------------------------------------------------------------------
/__tests__/nodejs-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikhsanalatsary/multer-sharp-s3/HEAD/__tests__/nodejs-512.png
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import s3Storage from './dist/types/main'
2 |
3 | export = s3Storage
4 | export as namespace s3Storage;
5 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const s3Storage = require('./dist/main').default
4 |
5 | module.exports = s3Storage
6 |
--------------------------------------------------------------------------------
/dist/types/get-filename.d.ts:
--------------------------------------------------------------------------------
1 | declare function getFileName(req: any, file: any, cb: any): void;
2 | export default getFileName;
3 | //# sourceMappingURL=get-filename.d.ts.map
--------------------------------------------------------------------------------
/dist/types/get-filename.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"get-filename.d.ts","sourceRoot":"src/","sources":["get-filename.ts"],"names":[],"mappings":"AAEA,iBAAS,WAAW,CAAC,GAAG,KAAA,EAAE,IAAI,KAAA,EAAE,EAAE,KAAA,QAIjC;AAED,eAAe,WAAW,CAAC"}
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | jobs:
3 | include:
4 | - os: linux
5 | dist: bionic
6 | node_js: "12"
7 | install:
8 | - npm install
9 | script:
10 | - npm test
11 | after_success:
12 | - npm run report-coverage
13 |
--------------------------------------------------------------------------------
/dist/types/get-sharp-options.d.ts:
--------------------------------------------------------------------------------
1 | import { S3StorageOptions, SharpOptions } from './types';
2 | declare function getSharpOptions(options: S3StorageOptions): SharpOptions;
3 | export default getSharpOptions;
4 | //# sourceMappingURL=get-sharp-options.d.ts.map
--------------------------------------------------------------------------------
/src/get-filename.ts:
--------------------------------------------------------------------------------
1 | import * as crypto from 'crypto';
2 |
3 | function getFileName(req, file, cb) {
4 | crypto.pseudoRandomBytes(16, function (err, raw) {
5 | cb(err, err ? undefined : raw.toString('hex'));
6 | });
7 | };
8 |
9 | export default getFileName;
10 |
--------------------------------------------------------------------------------
/dist/types/transformer.d.ts:
--------------------------------------------------------------------------------
1 | import * as sharp from 'sharp';
2 | import { ResizeOption, SharpOptions } from './types';
3 | export default transformer;
4 | declare function transformer(options: SharpOptions, size: ResizeOption): sharp.Sharp;
5 | //# sourceMappingURL=transformer.d.ts.map
--------------------------------------------------------------------------------
/dist/types/get-sharp-options.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"get-sharp-options.d.ts","sourceRoot":"src/","sources":["get-sharp-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAExD,iBAAS,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAoChE;AAED,eAAe,eAAe,CAAA"}
--------------------------------------------------------------------------------
/wallaby.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | return {
3 | files: ['tsconfig.json', 'src/**/*.ts'],
4 |
5 | tests: ['__tests__/*.ts'],
6 |
7 | env: {
8 | type: 'node',
9 | runner: 'node',
10 | },
11 |
12 | testFramework: 'jest',
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('./types').PrettierConfig}
3 | */
4 | const config = {
5 | singleQuote: true,
6 | arrowParens: 'always',
7 | semi: false,
8 | bracketSpacing: true,
9 | trailingComma: 'es5',
10 | printWidth: 80,
11 | }
12 |
13 | module.exports = config
14 |
--------------------------------------------------------------------------------
/dist/types/transformer.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"transformer.d.ts","sourceRoot":"src/","sources":["transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEpD,eAAe,WAAW,CAAA;AAQ1B,iBAAS,WAAW,CAClB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,YAAY,GACjB,KAAK,CAAC,KAAK,CAQb"}
--------------------------------------------------------------------------------
/scripts/copy.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const { copyFileSync } = require('fs')
3 | const { resolve } = require('path')
4 | ;(function main() {
5 | const rootDir = resolve(__dirname, '..')
6 | const srcDir = resolve(rootDir, 'src')
7 | const distDir = resolve(rootDir, 'dist')
8 | copyFileSync(
9 | resolve(srcDir, 'types.d.ts'),
10 | resolve(distDir, 'types/types.d.ts')
11 | )
12 | })()
13 |
--------------------------------------------------------------------------------
/dist/get-filename.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const crypto = require("crypto");
4 | function getFileName(req, file, cb) {
5 | crypto.pseudoRandomBytes(16, function (err, raw) {
6 | cb(err, err ? undefined : raw.toString('hex'));
7 | });
8 | }
9 | ;
10 | exports.default = getFileName;
11 | //# sourceMappingURL=get-filename.js.map
--------------------------------------------------------------------------------
/dist/get-filename.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"get-filename.js","sourceRoot":"src/","sources":["get-filename.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AAEjC,SAAS,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;IAChC,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,UAAU,GAAG,EAAE,GAAG;QAC7C,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC;AAAA,CAAC;AAEF,kBAAe,WAAW,CAAC"}
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceRoot": "src",
7 | "outDir": "dist",
8 | "declaration": true,
9 | "declarationMap": true,
10 | "declarationDir": "dist/types",
11 | "watch": true,
12 | "types": [
13 | "node",
14 | "jest",
15 | "core-js"
16 | ],
17 | "sourceMap": true,
18 | "lib": [
19 | "es2015",
20 | "es2017"
21 | ]
22 | },
23 | "compileOnSave": true,
24 | "exclude": [
25 | "**/*.spec.ts",
26 | "examples/*"
27 | ],
28 | "include": [
29 | "src/*.ts",
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 | max_line_length = 80
14 |
15 | # We recommend you to keep these unchanged
16 | end_of_line = lf
17 | charset = utf-8
18 | trim_trailing_whitespace = true
19 | insert_final_newline = true
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
24 | [*.{js,ts}]
25 | quote_type = single
26 | curly_bracket_next_line = false
27 | spaces_around_brackets = inside
28 | indent_brace_style = BSD KNF
29 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:recommended"
4 | ],
5 | "jsRules": {},
6 | "rules": {
7 | "semicolon": [
8 | false
9 | ],
10 | "quotemark": [
11 | false
12 | ],
13 | "trailing-comma": [
14 | false
15 | ],
16 | "no-console": [
17 | false
18 | ],
19 | "no-shadowed-variable": false,
20 | "only-arrow-functions": false,
21 | "interface-name" : [true, "never-prefix"],
22 | "object-literal-sort-keys": false,
23 | "ordered-imports": false,
24 | "max-line-length": [true, 500],
25 | "no-unused-variable": true
26 | },
27 | "rulesDirectory": []
28 | }
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [0.2.5](https://github.com/ikhsanalatsary/multer-sharp-s3/compare/v0.2.4...v0.2.5) (2022-02-16)
6 |
7 | ### [0.2.4](https://github.com/ikhsanalatsary/multer-sharp-s3/compare/v0.2.3...v0.2.4) (2022-02-15)
8 |
9 |
10 | ### Bug Fixes
11 |
12 | * Key with custom directory ([6d693fc](https://github.com/ikhsanalatsary/multer-sharp-s3/commit/6d693fc97853025ff87762a2d2aa27300b0d71b0))
13 |
14 | ### [0.2.3](https://github.com/ikhsanalatsary/multer-sharp-s3/compare/v0.2.2...v0.2.3) (2022-02-15)
15 |
16 | ### [0.2.2](https://github.com/ikhsanalatsary/multer-sharp-s3/compare/v0.2.1...v0.2.2) (2022-02-15)
17 |
18 |
19 | ### Bug Fixes
20 |
21 | * package.json to reduce vulnerabilities ([ef21012](https://github.com/ikhsanalatsary/multer-sharp-s3/commit/ef21012efecff36857376cdf342d22077e8dc724))
22 |
23 | ### [0.2.1](https://github.com/ikhsanalatsary/multer-sharp-s3/compare/v0.2.0...v0.2.1) (2020-05-26)
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Abdul Fattah Ikhsan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dist/get-sharp-options.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"get-sharp-options.js","sourceRoot":"src/","sources":["get-sharp-options.ts"],"names":[],"mappings":";;AAEA,SAAS,eAAe,CAAC,OAAyB;IAChD,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,WAAW,EAAE,OAAO,CAAC,WAAW;KACjC,CAAA;AACH,CAAC;AAED,kBAAe,eAAe,CAAA"}
--------------------------------------------------------------------------------
/dist/types/main.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"main.d.ts","sourceRoot":"src/","sources":["main.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAG5B,OAAO,UAAU,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAExD,oBAAY,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,KAAK,CAAA;CAC5C,CAAA;AACD,oBAAY,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,GACrC,OAAO,GACP,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;AACpC,oBAAY,IAAI,GAAG,OAAO,CACxB,OAAO,CAAC,MAAM,CAAC,IAAI,GACjB,aAAa,CAAC,QAAQ,GACtB,EAAE,CAAC,KAAK,CAAC,gBAAgB,GACzB,KAAK,CAAC,UAAU,CACnB,CAAA;AACD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,gBAAgB,CAAA;IACtB,SAAS,EAAE,YAAY,CAAA;CACxB;AACD,qBAAa,SAAU,YAAW,aAAa;IAC7C,SAAS,CAAC,MAAM,CAAC,cAAc;;;;;MAK9B;gBAEW,OAAO,EAAE,gBAAgB;IAsB9B,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI;IA2C7E,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IAIvE,OAAO,CAAC,cAAc;IA0JtB,OAAO,CAAC,eAAe;CA6BxB;AAED,iBAAS,SAAS,CAAC,OAAO,EAAE,gBAAgB,aAE3C;AAED,eAAe,SAAS,CAAA"}
--------------------------------------------------------------------------------
/src/get-sharp-options.ts:
--------------------------------------------------------------------------------
1 | import { S3StorageOptions, SharpOptions } from './types'
2 |
3 | function getSharpOptions(options: S3StorageOptions): SharpOptions {
4 | return {
5 | resize: options.resize,
6 | composite: options.composite,
7 | modulate: options.modulate,
8 | toFormat: options.toFormat,
9 | extract: options.extract,
10 | trim: options.trim,
11 | flatten: options.flatten,
12 | extend: options.extend,
13 | negate: options.negate,
14 | rotate: options.rotate,
15 | flip: options.flip,
16 | flop: options.flop,
17 | blur: options.blur,
18 | sharpen: options.sharpen,
19 | gamma: options.gamma,
20 | grayscale: options.grayscale,
21 | greyscale: options.greyscale,
22 | normalize: options.normalize,
23 | normalise: options.normalise,
24 | convolve: options.convolve,
25 | threshold: options.threshold,
26 | toColourspace: options.toColourspace,
27 | toColorspace: options.toColorspace,
28 | withMetadata: options.withMetadata,
29 | linear: options.linear,
30 | median: options.median,
31 | tint: options.tint,
32 | removeAlpha: options.removeAlpha,
33 | bandbool: options.bandbool,
34 | boolean: options.boolean,
35 | joinChannel: options.joinChannel,
36 | extractChannel: options.extractChannel,
37 | ensureAlpha: options.ensureAlpha,
38 | }
39 | }
40 |
41 | export default getSharpOptions
42 |
--------------------------------------------------------------------------------
/dist/types/main.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import * as sharp from 'sharp';
3 | import { ManagedUpload } from 'aws-sdk/lib/s3/managed_upload';
4 | import { StorageEngine } from 'multer';
5 | import { Request } from 'express';
6 | import { S3 } from 'aws-sdk';
7 | import defaultKey from './get-filename';
8 | import { S3StorageOptions, SharpOptions } from './types';
9 | export declare type EStream = {
10 | stream: NodeJS.ReadableStream & sharp.Sharp;
11 | };
12 | export declare type EFile = Express.Multer.File & EStream & Partial;
13 | export declare type Info = Partial;
14 | export interface S3Storage {
15 | opts: S3StorageOptions;
16 | sharpOpts: SharpOptions;
17 | }
18 | export declare class S3Storage implements StorageEngine {
19 | protected static defaultOptions: {
20 | ACL: string;
21 | Bucket: string;
22 | Key: typeof defaultKey;
23 | multiple: boolean;
24 | };
25 | constructor(options: S3StorageOptions);
26 | _handleFile(req: Request, file: EFile, cb: (error?: any, info?: Info) => void): void;
27 | _removeFile(req: Request, file: Info, cb: (error: Error) => void): void;
28 | private _uploadProcess;
29 | private _uploadNonImage;
30 | }
31 | declare function s3Storage(options: S3StorageOptions): S3Storage;
32 | export default s3Storage;
33 | //# sourceMappingURL=main.d.ts.map
--------------------------------------------------------------------------------
/dist/get-sharp-options.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | function getSharpOptions(options) {
4 | return {
5 | resize: options.resize,
6 | composite: options.composite,
7 | modulate: options.modulate,
8 | toFormat: options.toFormat,
9 | extract: options.extract,
10 | trim: options.trim,
11 | flatten: options.flatten,
12 | extend: options.extend,
13 | negate: options.negate,
14 | rotate: options.rotate,
15 | flip: options.flip,
16 | flop: options.flop,
17 | blur: options.blur,
18 | sharpen: options.sharpen,
19 | gamma: options.gamma,
20 | grayscale: options.grayscale,
21 | greyscale: options.greyscale,
22 | normalize: options.normalize,
23 | normalise: options.normalise,
24 | convolve: options.convolve,
25 | threshold: options.threshold,
26 | toColourspace: options.toColourspace,
27 | toColorspace: options.toColorspace,
28 | withMetadata: options.withMetadata,
29 | linear: options.linear,
30 | median: options.median,
31 | tint: options.tint,
32 | removeAlpha: options.removeAlpha,
33 | bandbool: options.bandbool,
34 | boolean: options.boolean,
35 | joinChannel: options.joinChannel,
36 | extractChannel: options.extractChannel,
37 | ensureAlpha: options.ensureAlpha,
38 | };
39 | }
40 | exports.default = getSharpOptions;
41 | //# sourceMappingURL=get-sharp-options.js.map
--------------------------------------------------------------------------------
/dist/transformer.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"transformer.js","sourceRoot":"src/","sources":["transformer.ts"],"names":[],"mappings":";;AAAA,+BAA8B;AAG9B,kBAAe,WAAW,CAAA;AAC1B,IAAI,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAChC,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,MAAM,EAAE,MAAM,CAAC;CACjB,CAAC,CAAA;AAEF,SAAS,WAAW,CAClB,OAAqB,EACrB,IAAkB;IAElB,IAAI,WAAW,GAAG,KAAK,EAAE,CAAA;IACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;QAClD,IAAI,KAAK,EAAE;YACT,WAAW,GAAG,kBAAkB,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;SAChE;KACF;IACD,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,EAAE,CAC5D,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACpD,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;AAClE,MAAM,cAAc,GAAG,CAAC,KAAU,EAAE,EAAE;IACpC,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;QAClB,OAAO,KAAK,CAAC,IAAI,CAAA;KAClB;IACD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AACD,MAAM,0BAA0B,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,EAAE;IAC7D,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAChC,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;YAC9B,IAAI,KAAK,EAAE;gBACT,OAAO,SAAS,CAAA;aACjB;SACF;KACF;IACD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AACD,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,IAAkB,EAAE,WAAwB,EAAE,EAAE;IACnG,IAAI,GAAG,KAAK,QAAQ,EAAE;QACpB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACxB,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;SACxE;KACF;SAAM,IAAI,GAAG,KAAK,UAAU,EAAE;QAC7B,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;KACzE;SAAM,IAAI,GAAG,KAAK,QAAQ,EAAE;QAC3B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;YAC9B,WAAW,GAAG,WAAW,CAAC,MAAM,EAAE,CAAA;SACnC;aAAM;YACL,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAA;SAC3C;KACF;SAAM,IAAI,GAAG,KAAK,aAAa,EAAE;QAChC,WAAW,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;KACnE;SAAM;QACL,MAAM,UAAU,GAAG,0BAA0B,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACzD,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAA;KAC3C;IACD,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA"}
--------------------------------------------------------------------------------
/src/transformer.ts:
--------------------------------------------------------------------------------
1 | import * as sharp from 'sharp'
2 | import { ResizeOption, SharpOptions } from './types'
3 |
4 | export default transformer
5 | let dynamicParamMethods = new Map([
6 | ['gamma', 'gamma'],
7 | ['median', 'median'],
8 | ['rotate', 'rotate'],
9 | ['trim', 'trim'],
10 | ])
11 |
12 | function transformer(
13 | options: SharpOptions,
14 | size: ResizeOption
15 | ): sharp.Sharp {
16 | let imageStream = sharp()
17 | for (const [key, value] of Object.entries(options)) {
18 | if (value) {
19 | imageStream = resolveImageStream(key, value, size, imageStream)
20 | }
21 | }
22 | return imageStream
23 | }
24 |
25 | const objectHasOwnProperty = (source: Object, prop: string) =>
26 | Object.prototype.hasOwnProperty.call(source, prop)
27 | const hasProp = (value: Object) =>
28 | typeof value === 'object' && objectHasOwnProperty(value, 'type')
29 | const validateFormat = (value: any) => {
30 | if (hasProp(value)) {
31 | return value.type
32 | }
33 | return value
34 | }
35 | const validateValueForRelatedKey = (key: string, value: any) => {
36 | if (dynamicParamMethods.has(key)) {
37 | if (typeof value === 'boolean') {
38 | if (value) {
39 | return undefined
40 | }
41 | }
42 | }
43 | return value
44 | }
45 | const resolveImageStream = (key: string, value: any, size: ResizeOption, imageStream: sharp.Sharp) => {
46 | if (key === 'resize') {
47 | if (!Array.isArray(size)) {
48 | imageStream = imageStream.resize(size.width, size.height, size.options)
49 | }
50 | } else if (key === 'toFormat') {
51 | imageStream = imageStream.toFormat(validateFormat(value), value.options)
52 | } else if (key === 'linear') {
53 | if (typeof value === 'boolean') {
54 | imageStream = imageStream.linear()
55 | } else {
56 | imageStream = imageStream.linear(...value)
57 | }
58 | } else if (key === 'joinChannel') {
59 | imageStream = imageStream.joinChannel(value.images, value.options)
60 | } else {
61 | const validValue = validateValueForRelatedKey(key, value)
62 | imageStream = imageStream[key](validValue)
63 | }
64 | return imageStream
65 | }
66 |
--------------------------------------------------------------------------------
/dist/transformer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const sharp = require("sharp");
4 | exports.default = transformer;
5 | let dynamicParamMethods = new Map([
6 | ['gamma', 'gamma'],
7 | ['median', 'median'],
8 | ['rotate', 'rotate'],
9 | ['trim', 'trim'],
10 | ]);
11 | function transformer(options, size) {
12 | let imageStream = sharp();
13 | for (const [key, value] of Object.entries(options)) {
14 | if (value) {
15 | imageStream = resolveImageStream(key, value, size, imageStream);
16 | }
17 | }
18 | return imageStream;
19 | }
20 | const objectHasOwnProperty = (source, prop) => Object.prototype.hasOwnProperty.call(source, prop);
21 | const hasProp = (value) => typeof value === 'object' && objectHasOwnProperty(value, 'type');
22 | const validateFormat = (value) => {
23 | if (hasProp(value)) {
24 | return value.type;
25 | }
26 | return value;
27 | };
28 | const validateValueForRelatedKey = (key, value) => {
29 | if (dynamicParamMethods.has(key)) {
30 | if (typeof value === 'boolean') {
31 | if (value) {
32 | return undefined;
33 | }
34 | }
35 | }
36 | return value;
37 | };
38 | const resolveImageStream = (key, value, size, imageStream) => {
39 | if (key === 'resize') {
40 | if (!Array.isArray(size)) {
41 | imageStream = imageStream.resize(size.width, size.height, size.options);
42 | }
43 | }
44 | else if (key === 'toFormat') {
45 | imageStream = imageStream.toFormat(validateFormat(value), value.options);
46 | }
47 | else if (key === 'linear') {
48 | if (typeof value === 'boolean') {
49 | imageStream = imageStream.linear();
50 | }
51 | else {
52 | imageStream = imageStream.linear(...value);
53 | }
54 | }
55 | else if (key === 'joinChannel') {
56 | imageStream = imageStream.joinChannel(value.images, value.options);
57 | }
58 | else {
59 | const validValue = validateValueForRelatedKey(key, value);
60 | imageStream = imageStream[key](validValue);
61 | }
62 | return imageStream;
63 | };
64 | //# sourceMappingURL=transformer.js.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multer-sharp-s3",
3 | "version": "0.2.5",
4 | "description": "A plugin multer to transform image and upload to AWS S3",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "tslint -c tslint.json 'src/**/*.ts' '__tests__/**/*.ts'",
8 | "pretest": "npm run lint",
9 | "test": "jest --detectOpenHandles --runInBand --ci --coverage",
10 | "prebuild": "shx rm -rf dist",
11 | "build": "tsc -w false",
12 | "postbuild": "node scripts/copy.js",
13 | "format": "prettier --config prettier.config.js \"**/*.{ts,tsx,js,jsx,md}\"",
14 | "report-coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/codecov",
15 | "release": "npm run build && git add dist/ && standard-version -a",
16 | "postrelease": "npm run release:github && npm run release:npm",
17 | "release:github": "git push --no-verify --follow-tags origin master",
18 | "release:npm": "npm publish",
19 | "dry": "npm run build && standard-version -a --dry-run"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/ikhsanalatsary/multer-sharp-s3.git"
24 | },
25 | "author": "Abdul Fattah Ikhsan (http://ikhsannetwork.com/)",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/ikhsanalatsary/multer-sharp-s3/issues"
29 | },
30 | "homepage": "https://github.com/ikhsanalatsary/multer-sharp-s3#readme",
31 | "files": [
32 | "LICENSE",
33 | "README.md",
34 | "dist/",
35 | "index.js",
36 | "index.d.ts",
37 | "yarn.lock"
38 | ],
39 | "keywords": [
40 | "multer",
41 | "sharp",
42 | "image",
43 | "resize",
44 | "imageprocessing",
45 | "aws",
46 | "aws-sdk",
47 | "aws-s3",
48 | "s3",
49 | "storage engine",
50 | "storage",
51 | "form",
52 | "post",
53 | "multipart",
54 | "form-data",
55 | "formdata",
56 | "express",
57 | "middleware"
58 | ],
59 | "devDependencies": {
60 | "@types/core-js": "^2.5.0",
61 | "@types/express": "^4.16.1",
62 | "@types/jest": "^24.0.12",
63 | "@types/mime-types": "^2.1.0",
64 | "@types/multer": "^1.4.3",
65 | "@types/sharp": "^0.29.5",
66 | "@types/supertest": "^2.0.7",
67 | "aws-sdk": "^2.445.0",
68 | "codecov": "^3.7.0",
69 | "express": "^4.16.4",
70 | "jest": "^24.0.12",
71 | "multer": "^1.4.1",
72 | "prettier": "^1.17.0",
73 | "shx": "0.3.4",
74 | "standard-version": "^8.0.0",
75 | "supertest": "^4.0.2",
76 | "ts-jest": "^24.0.2",
77 | "ts-lint": "^4.5.1",
78 | "typescript": "3.4.5"
79 | },
80 | "dependencies": {
81 | "mime-types": "2.1.24",
82 | "rxjs": "6.5.1",
83 | "sharp": "^0.30.0"
84 | },
85 | "jest": {
86 | "moduleFileExtensions": [
87 | "ts",
88 | "tsx",
89 | "js"
90 | ],
91 | "transform": {
92 | "\\.(ts|tsx)$": "/node_modules/ts-jest/preprocessor.js"
93 | },
94 | "testRegex": "/__tests__/.*\\.(ts|tsx|js)$",
95 | "verbose": true,
96 | "notify": false,
97 | "bail": false
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ResizeOptions,
3 | // RGBA,
4 | Region,
5 | ExtendOptions,
6 | ThresholdOptions,
7 | AvailableFormatInfo,
8 | OutputOptions,
9 | JpegOptions,
10 | PngOptions,
11 | WriteableMetadata,
12 | Kernel,
13 | Sharp,
14 | OverlayOptions,
15 | Color,
16 | FlattenOptions,
17 | Raw,
18 | SharpOptions as SharpOptionsCore
19 | } from 'sharp'
20 | import { S3 } from 'aws-sdk'
21 |
22 | export declare interface Size {
23 | width?: number
24 | height?: number
25 | options?: ResizeOptions
26 | }
27 |
28 | export declare interface Sharpen {
29 | sigma?: number
30 | flat?: number
31 | jagged?: number
32 | }
33 |
34 | export declare interface Bool {
35 | operand: string | Buffer
36 | operator: string
37 | options?: { raw: Raw }
38 | }
39 |
40 | export declare interface JoinChannel {
41 | images: string | Buffer | ArrayLike
42 | options?: SharpOptionsCore
43 | }
44 |
45 | export declare interface Modulate {
46 | brightness?: number
47 | saturation?: number
48 | hue?: number
49 | }
50 |
51 | export declare interface Threshold {
52 | threshold?: number
53 | options?: ThresholdOptions
54 | }
55 |
56 | export declare interface Format {
57 | type: string | AvailableFormatInfo
58 | options?: OutputOptions | JpegOptions | PngOptions
59 | }
60 |
61 | export declare interface ExtendSize extends Size {
62 | suffix: string,
63 | directory?: string,
64 | Body?: NodeJS.ReadableStream & Sharp
65 | }
66 |
67 | export declare type SharpOption = T
68 |
69 | export declare type ResizeOption =
70 | | SharpOption
71 | | Array>
72 |
73 | export declare type MaybeA = T | undefined | null
74 |
75 | export declare interface SharpOptions {
76 | _resize?: any
77 | resize?: ResizeOption
78 | // MARK: deprecated since sharp v0.22.0
79 | // crop?: SharpOption
80 | // background?: SharpOption
81 | // embed?: boolean
82 | // max?: boolean
83 | // min?: boolean
84 | // withoutEnlargement?: boolean
85 | // ignoreAspectRatio?: boolean
86 | modulate?: SharpOption
87 | composite?: SharpOption
88 | extract?: SharpOption
89 | trim?: SharpOption
90 | flatten?: SharpOption
91 | extend?: SharpOption
92 | negate?: SharpOption
93 | rotate?: SharpOption
94 | flip?: SharpOption
95 | flop?: SharpOption
96 | blur?: SharpOption
97 | sharpen?: SharpOption
98 | gamma?: SharpOption
99 | grayscale?: SharpOption
100 | greyscale?: SharpOption
101 | normalize?: SharpOption
102 | normalise?: SharpOption
103 | withMetadata?: SharpOption
104 | convolve?: SharpOption
105 | threshold?: SharpOption
106 | toColourspace?: SharpOption
107 | toColorspace?: SharpOption
108 | toFormat?: SharpOption
109 | linear?: SharpOption, MaybeA]>
110 | median?: SharpOption
111 | tint?: SharpOption
112 | removeAlpha?: SharpOption
113 | bandbool?: SharpOption
114 | boolean?: SharpOption
115 | joinChannel?: SharpOption
116 | extractChannel?: SharpOption
117 | ensureAlpha?: SharpOption
118 | }
119 |
120 | export declare interface CloudStorageOptions
121 | extends Partial {
122 | Key?: any
123 | multiple?: boolean
124 | s3: S3
125 | }
126 |
127 | export declare type S3StorageOptions = CloudStorageOptions & SharpOptions
128 |
--------------------------------------------------------------------------------
/dist/types/types.d.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ResizeOptions,
3 | // RGBA,
4 | Region,
5 | ExtendOptions,
6 | ThresholdOptions,
7 | AvailableFormatInfo,
8 | OutputOptions,
9 | JpegOptions,
10 | PngOptions,
11 | WriteableMetadata,
12 | Kernel,
13 | Sharp,
14 | OverlayOptions,
15 | Color,
16 | FlattenOptions,
17 | Raw,
18 | SharpOptions as SharpOptionsCore
19 | } from 'sharp'
20 | import { S3 } from 'aws-sdk'
21 |
22 | export declare interface Size {
23 | width?: number
24 | height?: number
25 | options?: ResizeOptions
26 | }
27 |
28 | export declare interface Sharpen {
29 | sigma?: number
30 | flat?: number
31 | jagged?: number
32 | }
33 |
34 | export declare interface Bool {
35 | operand: string | Buffer
36 | operator: string
37 | options?: { raw: Raw }
38 | }
39 |
40 | export declare interface JoinChannel {
41 | images: string | Buffer | ArrayLike
42 | options?: SharpOptionsCore
43 | }
44 |
45 | export declare interface Modulate {
46 | brightness?: number
47 | saturation?: number
48 | hue?: number
49 | }
50 |
51 | export declare interface Threshold {
52 | threshold?: number
53 | options?: ThresholdOptions
54 | }
55 |
56 | export declare interface Format {
57 | type: string | AvailableFormatInfo
58 | options?: OutputOptions | JpegOptions | PngOptions
59 | }
60 |
61 | export declare interface ExtendSize extends Size {
62 | suffix: string,
63 | directory?: string,
64 | Body?: NodeJS.ReadableStream & Sharp
65 | }
66 |
67 | export declare type SharpOption = T
68 |
69 | export declare type ResizeOption =
70 | | SharpOption
71 | | Array>
72 |
73 | export declare type MaybeA = T | undefined | null
74 |
75 | export declare interface SharpOptions {
76 | _resize?: any
77 | resize?: ResizeOption
78 | // MARK: deprecated since sharp v0.22.0
79 | // crop?: SharpOption
80 | // background?: SharpOption
81 | // embed?: boolean
82 | // max?: boolean
83 | // min?: boolean
84 | // withoutEnlargement?: boolean
85 | // ignoreAspectRatio?: boolean
86 | modulate?: SharpOption
87 | composite?: SharpOption
88 | extract?: SharpOption
89 | trim?: SharpOption
90 | flatten?: SharpOption
91 | extend?: SharpOption
92 | negate?: SharpOption
93 | rotate?: SharpOption
94 | flip?: SharpOption
95 | flop?: SharpOption
96 | blur?: SharpOption
97 | sharpen?: SharpOption
98 | gamma?: SharpOption
99 | grayscale?: SharpOption
100 | greyscale?: SharpOption
101 | normalize?: SharpOption
102 | normalise?: SharpOption
103 | withMetadata?: SharpOption
104 | convolve?: SharpOption
105 | threshold?: SharpOption
106 | toColourspace?: SharpOption
107 | toColorspace?: SharpOption
108 | toFormat?: SharpOption
109 | linear?: SharpOption, MaybeA]>
110 | median?: SharpOption
111 | tint?: SharpOption
112 | removeAlpha?: SharpOption
113 | bandbool?: SharpOption
114 | boolean?: SharpOption
115 | joinChannel?: SharpOption
116 | extractChannel?: SharpOption
117 | ensureAlpha?: SharpOption
118 | }
119 |
120 | export declare interface CloudStorageOptions
121 | extends Partial {
122 | Key?: any
123 | multiple?: boolean
124 | s3: S3
125 | }
126 |
127 | export declare type S3StorageOptions = CloudStorageOptions & SharpOptions
128 |
--------------------------------------------------------------------------------
/dist/main.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"main.js","sourceRoot":"src/","sources":["main.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __rest = (this && this.__rest) || function (s, e) {
3 | var t = {};
4 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5 | t[p] = s[p];
6 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
7 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
8 | t[p[i]] = s[p[i]];
9 | return t;
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const rxjs_1 = require("rxjs");
13 | const operators_1 = require("rxjs/operators");
14 | const sharp = require("sharp");
15 | const mime_types_1 = require("mime-types");
16 | const get_sharp_options_1 = require("./get-sharp-options");
17 | const transformer_1 = require("./transformer");
18 | const get_filename_1 = require("./get-filename");
19 | class S3Storage {
20 | constructor(options) {
21 | if (!options.s3) {
22 | throw new Error('You have to specify s3 for AWS S3 to work.');
23 | }
24 | this.opts = Object.assign({}, S3Storage.defaultOptions, options);
25 | this.sharpOpts = get_sharp_options_1.default(options);
26 | if (!this.opts.Bucket) {
27 | throw new Error('You have to specify Bucket for AWS S3 to work.');
28 | }
29 | if (typeof this.opts.Key !== 'string') {
30 | if (typeof this.opts.Key !== 'function') {
31 | throw new TypeError(`Key must be a "string" or "function" or "undefined" but got ${typeof this
32 | .opts.Key}`);
33 | }
34 | }
35 | }
36 | _handleFile(req, file, cb) {
37 | const { opts, sharpOpts } = this;
38 | const { mimetype, stream } = file;
39 | const params = {
40 | Bucket: opts.Bucket,
41 | ACL: opts.ACL,
42 | CacheControl: opts.CacheControl,
43 | ContentType: opts.ContentType,
44 | Metadata: opts.Metadata,
45 | StorageClass: opts.StorageClass,
46 | ServerSideEncryption: opts.ServerSideEncryption,
47 | SSEKMSKeyId: opts.SSEKMSKeyId,
48 | Body: stream,
49 | Key: opts.Key,
50 | };
51 | if (typeof opts._resize === 'function') {
52 | this.opts.resize = opts._resize(req);
53 | }
54 | if (typeof opts.Key === 'function') {
55 | opts.Key(req, file, (fileErr, Key) => {
56 | if (fileErr) {
57 | cb(fileErr);
58 | return;
59 | }
60 | params.Key = Key;
61 | if (mimetype.includes('image')) {
62 | this._uploadProcess(params, file, cb);
63 | }
64 | else {
65 | this._uploadNonImage(params, file, cb);
66 | }
67 | });
68 | }
69 | else {
70 | if (mimetype.includes('image')) {
71 | this._uploadProcess(params, file, cb);
72 | }
73 | else {
74 | this._uploadNonImage(params, file, cb);
75 | }
76 | }
77 | }
78 | _removeFile(req, file, cb) {
79 | this.opts.s3.deleteObject({ Bucket: file.Bucket, Key: file.Key }, cb);
80 | }
81 | _uploadProcess(params, file, cb) {
82 | const { opts, sharpOpts } = this;
83 | let { stream, mimetype } = file;
84 | const { ACL, ContentDisposition, ContentType: optsContentType, StorageClass, ServerSideEncryption, Metadata, } = opts;
85 | if (opts.multiple && Array.isArray(opts.resize) && opts.resize.length > 0) {
86 | const sizes = rxjs_1.from(opts.resize);
87 | sizes
88 | .pipe(operators_1.map((size) => {
89 | const resizerStream = transformer_1.default(sharpOpts, size);
90 | if (size.suffix === 'original') {
91 | size.Body = stream.pipe(sharp());
92 | }
93 | else {
94 | size.Body = stream.pipe(resizerStream);
95 | }
96 | return size;
97 | }), operators_1.mergeMap((size) => {
98 | const meta = { stream: size.Body };
99 | const getMetaFromSharp = meta.stream.toBuffer({
100 | resolveWithObject: true,
101 | });
102 | return rxjs_1.from(getMetaFromSharp.then((result) => {
103 | return Object.assign({}, size, result.info, { ContentType: result.info.format, currentSize: result.info.size });
104 | }));
105 | }), operators_1.mergeMap((size) => {
106 | const { Body, ContentType } = size;
107 | const keyDot = params.Key.split('.');
108 | let key = `${params.Key}-${size.suffix}`;
109 | if (keyDot.length > 1) {
110 | keyDot.pop();
111 | key = `${keyDot.join('.')}-${size.suffix}.${params.Key.split('.')[keyDot.length]}`;
112 | }
113 | let newParams = Object.assign({}, params, { Body,
114 | ContentType, Key: size.directory ? `${size.directory}/${key}` : key });
115 | const upload = opts.s3.upload(newParams);
116 | let currentSize = { [size.suffix]: 0 };
117 | upload.on('httpUploadProgress', function (ev) {
118 | if (ev.total) {
119 | currentSize[size.suffix] = ev.total;
120 | }
121 | });
122 | const upload$ = rxjs_1.from(upload.promise().then((result) => {
123 | // tslint:disable-next-line
124 | const { Body } = size, rest = __rest(size, ["Body"]);
125 | return Object.assign({}, result, rest, { currentSize: size.currentSize || currentSize[size.suffix] });
126 | }));
127 | return upload$;
128 | }), operators_1.toArray())
129 | .subscribe((res) => {
130 | const mapArrayToObject = res.reduce((acc, curr) => {
131 | // tslint:disable-next-line
132 | const { suffix, ContentType, size, format, channels, options, currentSize } = curr, rest = __rest(curr, ["suffix", "ContentType", "size", "format", "channels", "options", "currentSize"]);
133 | acc[curr.suffix] = Object.assign({ ACL,
134 | ContentDisposition,
135 | StorageClass,
136 | ServerSideEncryption,
137 | Metadata }, rest, { size: currentSize, ContentType: optsContentType || ContentType });
138 | mimetype = mime_types_1.lookup(ContentType) || `image/${ContentType}`;
139 | return acc;
140 | }, {});
141 | mapArrayToObject.mimetype = mimetype;
142 | cb(null, JSON.parse(JSON.stringify(mapArrayToObject)));
143 | }, cb);
144 | }
145 | else {
146 | let currentSize = 0;
147 | const resizerStream = transformer_1.default(sharpOpts, sharpOpts.resize);
148 | let newParams = Object.assign({}, params, { Body: stream.pipe(resizerStream) });
149 | const meta = { stream: newParams.Body };
150 | const meta$ = rxjs_1.from(meta.stream.toBuffer({
151 | resolveWithObject: true,
152 | }));
153 | meta$
154 | .pipe(operators_1.map((metadata) => {
155 | newParams.ContentType = opts.ContentType || metadata.info.format;
156 | return metadata;
157 | }), operators_1.mergeMap((metadata) => {
158 | const upload = opts.s3.upload(newParams);
159 | upload.on('httpUploadProgress', function (ev) {
160 | if (ev.total) {
161 | currentSize = ev.total;
162 | }
163 | });
164 | const upload$ = rxjs_1.from(upload.promise().then((res) => {
165 | return Object.assign({}, res, metadata.info);
166 | }));
167 | return upload$;
168 | }))
169 | .subscribe((result) => {
170 | // tslint:disable-next-line
171 | const { size, format, channels } = result, rest = __rest(result, ["size", "format", "channels"]);
172 | const endRes = Object.assign({ ACL,
173 | ContentDisposition,
174 | StorageClass,
175 | ServerSideEncryption,
176 | Metadata }, rest, { size: currentSize || size, ContentType: opts.ContentType || format, mimetype: mime_types_1.lookup(result.format) || `image/${result.format}` });
177 | cb(null, JSON.parse(JSON.stringify(endRes)));
178 | }, cb);
179 | }
180 | }
181 | _uploadNonImage(params, file, cb) {
182 | const { opts } = this;
183 | const { mimetype } = file;
184 | let currentSize = 0;
185 | params.ContentType = params.ContentType || mimetype;
186 | const upload = opts.s3.upload(params);
187 | upload.on('httpUploadProgress', function (ev) {
188 | if (ev.total) {
189 | currentSize = ev.total;
190 | }
191 | });
192 | upload.promise().then((result) => {
193 | const endRes = Object.assign({ size: currentSize, ACL: opts.ACL, ContentType: opts.ContentType || mimetype, ContentDisposition: opts.ContentDisposition, StorageClass: opts.StorageClass, ServerSideEncryption: opts.ServerSideEncryption, Metadata: opts.Metadata }, result);
194 | cb(null, JSON.parse(JSON.stringify(endRes)));
195 | }, cb);
196 | }
197 | }
198 | S3Storage.defaultOptions = {
199 | ACL: process.env.AWS_ACL || 'public-read',
200 | Bucket: process.env.AWS_BUCKET || null,
201 | Key: get_filename_1.default,
202 | multiple: false,
203 | };
204 | exports.S3Storage = S3Storage;
205 | function s3Storage(options) {
206 | return new S3Storage(options);
207 | }
208 | exports.default = s3Storage;
209 | //# sourceMappingURL=main.js.map
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { from } from 'rxjs'
2 | import { map, mergeMap, toArray } from 'rxjs/operators'
3 | import * as sharp from 'sharp'
4 | import { lookup } from 'mime-types'
5 | import { ManagedUpload } from 'aws-sdk/lib/s3/managed_upload'
6 | import { StorageEngine } from 'multer'
7 | import { Request } from 'express'
8 | import { S3 } from 'aws-sdk'
9 | import getSharpOptions from './get-sharp-options'
10 | import transformer from './transformer'
11 | import defaultKey from './get-filename'
12 | import { S3StorageOptions, SharpOptions } from './types'
13 |
14 | export type EStream = {
15 | stream: NodeJS.ReadableStream & sharp.Sharp
16 | }
17 | export type EFile = Express.Multer.File &
18 | EStream &
19 | Partial
20 | export type Info = Partial<
21 | Express.Multer.File &
22 | ManagedUpload.SendData &
23 | S3.Types.PutObjectRequest &
24 | sharp.OutputInfo
25 | >
26 | export interface S3Storage {
27 | opts: S3StorageOptions
28 | sharpOpts: SharpOptions
29 | }
30 | export class S3Storage implements StorageEngine {
31 | protected static defaultOptions = {
32 | ACL: process.env.AWS_ACL || 'public-read',
33 | Bucket: process.env.AWS_BUCKET || null,
34 | Key: defaultKey,
35 | multiple: false,
36 | }
37 |
38 | constructor(options: S3StorageOptions) {
39 | if (!options.s3) {
40 | throw new Error('You have to specify s3 for AWS S3 to work.')
41 | }
42 |
43 | this.opts = { ...S3Storage.defaultOptions, ...options }
44 | this.sharpOpts = getSharpOptions(options)
45 |
46 | if (!this.opts.Bucket) {
47 | throw new Error('You have to specify Bucket for AWS S3 to work.')
48 | }
49 |
50 | if (typeof this.opts.Key !== 'string') {
51 | if (typeof this.opts.Key !== 'function') {
52 | throw new TypeError(
53 | `Key must be a "string" or "function" or "undefined" but got ${typeof this
54 | .opts.Key}`
55 | )
56 | }
57 | }
58 | }
59 |
60 | public _handleFile(req: Request, file: EFile, cb: (error?: any, info?: Info) => void) {
61 | const { opts, sharpOpts } = this
62 | const { mimetype, stream } = file
63 | const params = {
64 | Bucket: opts.Bucket,
65 | ACL: opts.ACL,
66 | CacheControl: opts.CacheControl,
67 | ContentType: opts.ContentType,
68 | Metadata: opts.Metadata,
69 | StorageClass: opts.StorageClass,
70 | ServerSideEncryption: opts.ServerSideEncryption,
71 | SSEKMSKeyId: opts.SSEKMSKeyId,
72 | Body: stream,
73 | Key: opts.Key,
74 | }
75 |
76 | if (typeof opts._resize === 'function') {
77 | this.opts.resize = opts._resize(req);
78 | }
79 |
80 | if (typeof opts.Key === 'function') {
81 | opts.Key(req, file, (fileErr, Key) => {
82 | if (fileErr) {
83 | cb(fileErr)
84 | return
85 | }
86 | params.Key = Key
87 |
88 | if (mimetype.includes('image')) {
89 | this._uploadProcess(params, file, cb)
90 | } else {
91 | this._uploadNonImage(params, file, cb)
92 | }
93 | })
94 | } else {
95 | if (mimetype.includes('image')) {
96 | this._uploadProcess(params, file, cb)
97 | } else {
98 | this._uploadNonImage(params, file, cb)
99 | }
100 | }
101 | }
102 |
103 | public _removeFile(req: Request, file: Info, cb: (error: Error) => void) {
104 | this.opts.s3.deleteObject({ Bucket: file.Bucket, Key: file.Key }, cb)
105 | }
106 |
107 | private _uploadProcess(
108 | params: S3.Types.PutObjectRequest,
109 | file: EFile,
110 | cb: (error?: any, info?: Info) => void
111 | ) {
112 | const { opts, sharpOpts } = this
113 | let { stream, mimetype } = file
114 | const {
115 | ACL,
116 | ContentDisposition,
117 | ContentType: optsContentType,
118 | StorageClass,
119 | ServerSideEncryption,
120 | Metadata,
121 | } = opts
122 | if (opts.multiple && Array.isArray(opts.resize) && opts.resize.length > 0) {
123 | const sizes = from(opts.resize)
124 | sizes
125 | .pipe(
126 | map((size) => {
127 | const resizerStream = transformer(sharpOpts, size)
128 | if (size.suffix === 'original') {
129 | size.Body = stream.pipe(sharp())
130 | } else {
131 | size.Body = stream.pipe(resizerStream)
132 | }
133 | return size
134 | }),
135 | mergeMap((size) => {
136 | const meta = { stream: size.Body }
137 | const getMetaFromSharp = meta.stream.toBuffer({
138 | resolveWithObject: true,
139 | })
140 | return from(
141 | getMetaFromSharp.then((result) => {
142 | return {
143 | ...size,
144 | ...result.info,
145 | ContentType: result.info.format,
146 | currentSize: result.info.size,
147 | }
148 | })
149 | )
150 | }),
151 | mergeMap((size) => {
152 | const { Body, ContentType } = size
153 | const keyDot = params.Key.split('.')
154 | let key = `${params.Key}-${size.suffix}`
155 | if (keyDot.length > 1) {
156 | keyDot.pop()
157 | key = `${keyDot.join('.')}-${size.suffix}.${params.Key.split('.')[keyDot.length]}`
158 | }
159 |
160 | let newParams = {
161 | ...params,
162 | Body,
163 | ContentType,
164 | Key: size.directory ? `${size.directory}/${key}` : key,
165 | }
166 |
167 | const upload = opts.s3.upload(newParams)
168 | let currentSize = { [size.suffix]: 0 }
169 | upload.on('httpUploadProgress', function(ev) {
170 | if (ev.total) {
171 | currentSize[size.suffix] = ev.total
172 | }
173 | })
174 | const upload$ = from(
175 | upload.promise().then((result) => {
176 | // tslint:disable-next-line
177 | const { Body, ...rest } = size
178 | return {
179 | ...result,
180 | ...rest,
181 | currentSize: size.currentSize || currentSize[size.suffix],
182 | }
183 | })
184 | )
185 | return upload$
186 | }),
187 | toArray()
188 | )
189 | .subscribe((res) => {
190 | const mapArrayToObject: { [k: string]: any } = res.reduce(
191 | (acc, curr) => {
192 | // tslint:disable-next-line
193 | const { suffix, ContentType, size, format, channels, options, currentSize, ...rest } = curr
194 | acc[curr.suffix] = {
195 | ACL,
196 | ContentDisposition,
197 | StorageClass,
198 | ServerSideEncryption,
199 | Metadata,
200 | ...rest,
201 | size: currentSize,
202 | ContentType: optsContentType || ContentType
203 | }
204 | mimetype = lookup(ContentType) || `image/${ContentType}`
205 | return acc
206 | }, {})
207 |
208 | mapArrayToObject.mimetype = mimetype
209 | cb(null, JSON.parse(JSON.stringify(mapArrayToObject)))
210 | }, cb)
211 | } else {
212 | let currentSize = 0
213 | const resizerStream = transformer(sharpOpts, sharpOpts.resize)
214 | let newParams = { ...params, Body: stream.pipe(resizerStream) }
215 | const meta = { stream: newParams.Body }
216 | const meta$ = from(
217 | meta.stream.toBuffer({
218 | resolveWithObject: true,
219 | })
220 | )
221 | meta$
222 | .pipe(
223 | map((metadata) => {
224 | newParams.ContentType = opts.ContentType || metadata.info.format
225 | return metadata
226 | }),
227 | mergeMap((metadata) => {
228 | const upload = opts.s3.upload(newParams)
229 | upload.on('httpUploadProgress', function(ev) {
230 | if (ev.total) {
231 | currentSize = ev.total
232 | }
233 | })
234 | const upload$ = from(
235 | upload.promise().then((res) => {
236 | return { ...res, ...metadata.info }
237 | })
238 | )
239 | return upload$
240 | })
241 | )
242 | .subscribe((result) => {
243 | // tslint:disable-next-line
244 | const { size, format, channels, ...rest } = result
245 | const endRes = {
246 | ACL,
247 | ContentDisposition,
248 | StorageClass,
249 | ServerSideEncryption,
250 | Metadata,
251 | ...rest,
252 | size: currentSize || size,
253 | ContentType: opts.ContentType || format,
254 | mimetype: lookup(result.format) || `image/${result.format}`
255 | }
256 | cb(null, JSON.parse(JSON.stringify(endRes)))
257 | }, cb)
258 | }
259 | }
260 |
261 | private _uploadNonImage(
262 | params: S3.Types.PutObjectRequest,
263 | file: EFile,
264 | cb: (error?: any, info?: Info) => void
265 | ) {
266 | const { opts } = this
267 | const { mimetype } = file
268 | let currentSize = 0
269 | params.ContentType = params.ContentType || mimetype
270 | const upload = opts.s3.upload(params)
271 | upload.on('httpUploadProgress', function(ev) {
272 | if (ev.total) {
273 | currentSize = ev.total
274 | }
275 | })
276 | upload.promise().then((result) => {
277 | const endRes = {
278 | size: currentSize,
279 | ACL: opts.ACL,
280 | ContentType: opts.ContentType || mimetype,
281 | ContentDisposition: opts.ContentDisposition,
282 | StorageClass: opts.StorageClass,
283 | ServerSideEncryption: opts.ServerSideEncryption,
284 | Metadata: opts.Metadata,
285 | ...result,
286 | }
287 | cb(null, JSON.parse(JSON.stringify(endRes)))
288 | }, cb)
289 | }
290 | }
291 |
292 | function s3Storage(options: S3StorageOptions) {
293 | return new S3Storage(options)
294 | }
295 |
296 | export default s3Storage
297 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # multer-sharp-s3
2 |
3 | [](https://travis-ci.org/ikhsanalatsary/multer-sharp-s3)
4 | [](https://codecov.io/gh/ikhsanalatsary/multer-sharp?branch=master) [](https://david-dm.org/ikhsanalatsary/multer-sharp-s3) [](https://david-dm.org/ikhsanalatsary/multer-sharp-s3?type=dev)
5 | [](http://npm.im/multer-sharp-s3)
6 | [](https://greenkeeper.io/)
7 |
8 | [](https://www.digitalocean.com/?refcode=dec9802955b6&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)
9 |
10 | ***
11 |
12 | Multer Sharp S3 is streaming multer storage engine permit to transform / resize the image and upload to AWS S3.
13 |
14 | This project is mostly an integration piece for existing code samples from Multer's [storage engine documentation](https://github.com/expressjs/multer/blob/master/StorageEngine.md). With add-ons include [AWS S3](https://github.com/aws/aws-sdk-js) and [sharp](https://github.com/lovell/sharp)
15 |
16 | # Minimum Requirement:
17 |
18 | Node v12.13.0, npm v6+
19 |
20 | # Installation
21 |
22 | npm:
23 |
24 | npm install --save aws-sdk multer-sharp-s3
25 |
26 | yarn:
27 |
28 | yarn add aws-sdk multer-sharp-s3
29 |
30 | # Tests
31 | Change aws configuration in your local.
32 |
33 | ```
34 | yarn test
35 | ```
36 |
37 | # Importing
38 | ### NodeJS
39 |
40 | ```javascript
41 | const s3Storage = require('multer-sharp-s3');
42 |
43 | const storage = s3Storage(options);
44 | ```
45 |
46 | ### TypeScript
47 |
48 | ```typescript
49 | import * as s3Storage from 'multer-sharp-s3';
50 |
51 | const storage = s3Storage(options);
52 | ```
53 |
54 | # Usage
55 |
56 | ```javascript
57 | const express = require('express');
58 | const multer = require('multer');
59 | const s3Storage = require('multer-sharp-s3');
60 | const aws = require('aws-sdk');
61 |
62 | aws.config.update({
63 | secretAccessKey: config.uploads.aws.secretAccessKey, // Not working key, Your SECRET ACCESS KEY from AWS should go here, never share it!!!
64 | accessKeyId: config.uploads.aws.accessKeyId, // Not working key, Your ACCESS KEY ID from AWS should go here, never share it!!!
65 | region: config.uploads.aws.region, // region of your bucket
66 | })
67 |
68 | const s3 = new aws.S3()
69 | const app = express();
70 |
71 | // without resize image
72 | const storage = s3Storage({
73 | s3,
74 | Bucket: config.uploads.aws.Bucket,
75 | Key: `${config.uploads.aws.Bucket}/test/${Date.now()}-myImage`,
76 | ACL: config.uploads.aws.ACL,
77 | })
78 | const upload = multer({ storage: storage })
79 |
80 | app.post('/upload', upload.single('myPic'), (req, res) => {
81 | console.log(req.file); // Print upload details
82 | res.send('Successfully uploaded!');
83 | });
84 |
85 | // or
86 |
87 | // single resize without Key
88 | const storage2 = gcsSharp({
89 | s3,
90 | Bucket: config.uploads.aws.Bucket,
91 | ACL: config.uploads.aws.ACL,
92 | resize: {
93 | width: 400,
94 | height: 400
95 | },
96 | max: true
97 | });
98 | const upload2 = multer({ storage: storage2 });
99 |
100 | app.post('/uploadandresize', upload2.single('myPic'), (req, res, next) => {
101 | console.log(req.file); // Print upload details
102 | res.send('Successfully uploaded!');
103 | });
104 |
105 | /* If you need generate image with specific size
106 | * simply to adding `multiple: true` property and
107 | * resize must be an `array` and must be include `suffix` property
108 | * and suffix has a special value that is 'original'
109 | * it will no transform image, just upload the image as is
110 | * example below with `Key` as callback function
111 | */
112 | const storage = s3Storage({
113 | Key: (req, file, cb) => {
114 | crypto.pseudoRandomBytes(16, (err, raw) => {
115 | cb(err, err ? undefined : raw.toString('hex'))
116 | })
117 | },
118 | s3,
119 | Bucket: config.uploads.aws.Bucket,
120 | multiple: true,
121 | resize: [
122 | { suffix: 'xlg', width: 1200, height: 1200 },
123 | { suffix: 'lg', width: 800, height: 800 },
124 | { suffix: 'md', width: 500, height: 500 },
125 | { suffix: 'sm', width: 300, height: 300 },
126 | { suffix: 'xs', width: 100 },
127 | { suffix: 'original' }
128 | ],
129 | });
130 | const upload = multer({ storage });
131 |
132 | app.post('/uploadmultiplesize', upload.single('myPic'), (req, res, next) => {
133 | console.log(req.file); // print output
134 | res.send('Successfully uploaded!');
135 | });
136 |
137 | /*
138 | * If the directory property exists,
139 | * the suffix property is ignored and
140 | * inserted separated by Bucket's directory.
141 | */
142 | const storage3 = s3Storage({
143 | Key: (req, file, cb) => {
144 | crypto.pseudoRandomBytes(16, (err, raw) => {
145 | cb(err, err ? undefined : raw.toString('hex'))
146 | })
147 | },
148 | s3,
149 | Bucket: config.uploads.aws.Bucket,
150 | multiple: true,
151 | resize: [
152 | { suffix: 'lg', directory: 'large', width: 800, height: 800 }, // insert BUCKET/large/filename
153 | { suffix: 'md', directory: 'medium', width: 500, height: 500 }, // insert BUCKET/medium/filename
154 | { suffix: 'sm', directory: 'small', width: 300, height: 300 }, // insert BUCKET/small/filename
155 | ],
156 | });
157 | const upload3 = multer({ storage3 });
158 |
159 | app.post('/uploadmultiplesize', upload3.single('myPic'), (req, res, next) => {
160 | console.log(req.file); // print output
161 | res.send('Successfully uploaded!');
162 | });
163 |
164 | // also can upload any file (non image type)
165 | const storage = s3Storage({
166 | s3,
167 | Bucket: config.uploads.aws.Bucket,
168 | Key: `${config.uploads.aws.Bucket}/test/${Date.now()}-myFile`,
169 | ACL: config.uploads.aws.ACL,
170 | // resize or any sharp options will ignore when uploading non image file type
171 | resize: {
172 | width: 400,
173 | height: 400,
174 | },
175 | })
176 | const upload = multer({ storage })
177 |
178 | app.post('/uploadfile', upload.single('myFile'), (req, res, next) => {
179 | console.log(req.file); // print output
180 | res.send('Successfully uploaded!');
181 | });
182 |
183 | ```
184 |
185 | for more example you can see [here](https://github.com/ikhsanalatsary/multer-sharp-s3/blob/master/__tests__/implementation.spec.ts)
186 |
187 |
188 | #### Multer-Sharp-S3 options
189 | multer sharp s3 is inherit from s3 upload property [putObjectRequest](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property).
190 | Below are special / custom options from this package
191 |
192 | | option | default | value | role |
193 | | ------ | ------- | ----- | ---- |
194 | | S3 | no | `object` | instance from AWS.S3 class. it mus be specify |
195 | | Key | randomString | `string` or `function` | your s3 Key |
196 | | Bucket | no | `string` | Required your bucket name on AWS S3 to upload. Environment variable - AWS_BUCKET |
197 | | ACL | 'public-read' | `string` | Required acl credentials file for AWS S3 |
198 | | multiple | false | `boolean` | for multiple resize to work |
199 | | resize | no | `object` or `Array