├── tests └── multer.test.js ├── .vscode └── settings.json ├── commitlint.config.js ├── bun.lockb ├── .husky └── commit-msg ├── .prettierrc ├── .npmignore ├── .eslintrc.js ├── .versionrc ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── LICENSE ├── package.json ├── .gitignore ├── CHANGELOG.md ├── README.md ├── index.ts └── tsconfig.json /tests/multer.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | test.todo('Do tests') 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khaosdoctor/multer-firebase-storage/HEAD/bun.lockb -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "" 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | bun.lockb 3 | .github/ 4 | .husky/ 5 | .prettierrc 6 | .versionrc 7 | CHANGELOG.md 8 | .eslintrc.js 9 | commitlint.config.js 10 | tests/ 11 | *.ts 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true 5 | }, 6 | extends: ['eslint:recommended', 'prettier', 'plugin:@typescript-eslint/recommended'], 7 | overrides: [], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | ecmaVersion: 'latest', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: {} 15 | } 16 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type": "feat", "section": ":cactus: Features"}, 4 | {"type": "fix", "section": ":bug: Bug Fixes"}, 5 | {"type": "chore", "section": ":octocat: Repository"}, 6 | {"type": "docs", "hidden": true}, 7 | {"type": "style", "hidden": true}, 8 | {"type": "refactor", "section": ":butterfly: Improvements"}, 9 | {"type": "perf", "section": ":butterfly: Improvements"}, 10 | {"type": "test", "hidden": true} 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-20.04 9 | if: "!contains(toJSON(github.event.commits.*.message), 'ci:')" 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 14.x, 16.x] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install Dependencies 20 | run: npm ci 21 | - name: Test 22 | run: npm test 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lucas Santos 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 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: 5 | - created 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-20.04 10 | if: "! contains(toJSON(github.event.commits.*.message), 'ci:')" 11 | strategy: 12 | matrix: 13 | node-version: [18.x, 14.x, 16.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install Dependencies 21 | run: npm ci 22 | - name: Test 23 | run: npm test 24 | 25 | publish-npm: 26 | runs-on: ubuntu-latest 27 | needs: test 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions/setup-node@v1 31 | with: 32 | node-version: 16 33 | registry-url: https://registry.npmjs.org/ 34 | - name: Install Dependencies 35 | run: npm ci 36 | - name: Build package 37 | run: npm run build 38 | - name: Publish Package 39 | uses: JS-DevTools/npm-publish@v1 40 | with: 41 | token: ${{ secrets.NPM_TOKEN }} 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multer-firebase-storage", 3 | "version": "4.1.2", 4 | "description": "Multer Storage Engine for Firebase Storage", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "prepare": "husky install", 8 | "release": "standard-version", 9 | "build": "tsc", 10 | "test": "npm run build && ava" 11 | }, 12 | "engines": { 13 | "node": ">=12.22" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/khaosdoctor/multer-firebase-storage.git" 18 | }, 19 | "keywords": [], 20 | "author": "Lucas Santos (https://lsantos.dev/)", 21 | "license": "GPL-3.0", 22 | "bugs": { 23 | "url": "https://github.com/khaosdoctor/multer-firebase-storage/issues" 24 | }, 25 | "standard-version": { 26 | "posttag": "git push --follow-tags origin main" 27 | }, 28 | "homepage": "https://github.com/khaosdoctor/multer-firebase-storage#readme", 29 | "devDependencies": { 30 | "@commitlint/cli": "^17.6.1", 31 | "@commitlint/config-conventional": "^16.2.1", 32 | "@types/multer": "^1.4.7", 33 | "@types/node": "^18.11.4", 34 | "@typescript-eslint/eslint-plugin": "^5.40.1", 35 | "@typescript-eslint/parser": "^5.40.1", 36 | "ava": "^4.1.0", 37 | "eslint": "^8.26.0", 38 | "eslint-config-prettier": "^8.5.0", 39 | "husky": "^7.0.4", 40 | "prettier": "^2.7.1", 41 | "standard-version": "^9.3.2", 42 | "typescript": "^4.8.4" 43 | }, 44 | "dependencies": { 45 | "firebase-admin": "^11.4.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /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 | ### [4.1.2](https://github.com/khaosdoctor/multer-firebase-storage/compare/v4.1.1...v4.1.2) (2022-10-24) 6 | 7 | ### [4.1.1](https://github.com/khaosdoctor/multer-firebase-storage/compare/v4.1.0...v4.1.1) (2022-10-24) 8 | 9 | 10 | ### :bug: Bug Fixes 11 | 12 | * wrong command in deploy ([58ad9a7](https://github.com/khaosdoctor/multer-firebase-storage/commit/58ad9a75229d32a4bb7cbe64e3cb20788d054186)) 13 | 14 | ## [4.1.0](https://github.com/khaosdoctor/multer-firebase-storage/compare/v4.0.0...v4.1.0) (2022-10-24) 15 | 16 | 17 | ### :cactus: Features 18 | 19 | * add ts and eslint ([21a45b8](https://github.com/khaosdoctor/multer-firebase-storage/commit/21a45b846dc015fbf2b235be0b1ff81a1ef88891)) 20 | 21 | 22 | ### :bug: Bug Fixes 23 | 24 | * remove extraneous deps ([5f70113](https://github.com/khaosdoctor/multer-firebase-storage/commit/5f7011344be84028f960b664497e50dc5ed0cdce)) 25 | * update lockfile ([6d95900](https://github.com/khaosdoctor/multer-firebase-storage/commit/6d9590071aeeccde7c9c4a0c7081e048e8aae03a)) 26 | * use npmignore to remove useless files ([e7e203f](https://github.com/khaosdoctor/multer-firebase-storage/commit/e7e203f7daf8d602eeb5503c9ecf573aea707eb2)) 27 | 28 | 29 | ### :octocat: Repository 30 | 31 | * **release:** 3.1.0 ([2c9c87c](https://github.com/khaosdoctor/multer-firebase-storage/commit/2c9c87cbb0c283203d1fda33849f230e72b462ce)) 32 | 33 | ## [4.0.0](https://github.com/khaosdoctor/multer-firebase-storage/compare/v3.0.3...v4.0.0) (2022-10-24) 34 | 35 | 36 | ### :octocat: Repository 37 | 38 | * **deps:** bump jose from 2.0.5 to 2.0.6 ([#3](https://github.com/khaosdoctor/multer-firebase-storage/issues/3)) ([3824668](https://github.com/khaosdoctor/multer-firebase-storage/commit/38246685faeff61818a9e22b7daa87364965c3cd)) 39 | * **release:** update changelog ([eb300ef](https://github.com/khaosdoctor/multer-firebase-storage/commit/eb300ef7fae8b6d5fcb290a333ee91b7d3b45c5b)) 40 | 41 | 42 | ### :cactus: Features 43 | 44 | * add bun lockdb ([05fcbf2](https://github.com/khaosdoctor/multer-firebase-storage/commit/05fcbf215ca2acf2cce013bc726182a8e06a6aec)) 45 | * add ts and eslint ([379950f](https://github.com/khaosdoctor/multer-firebase-storage/commit/379950fb15a1fb3dc7eecd70c59b514bed1dd8a9)) 46 | * convert to typescript ([9daec23](https://github.com/khaosdoctor/multer-firebase-storage/commit/9daec2385c87ffd255de46b21004571800d6b16d)) 47 | 48 | 49 | ### :bug: Bug Fixes 50 | 51 | * add build script to release ([84353e1](https://github.com/khaosdoctor/multer-firebase-storage/commit/84353e1fc87df45134f4c4098dfed2724ca43efe)) 52 | * remove extraneous deps ([b92e138](https://github.com/khaosdoctor/multer-firebase-storage/commit/b92e13849da0d4c76e79b3194eaf68707099d0b4)) 53 | * update lockfile ([68ee4b2](https://github.com/khaosdoctor/multer-firebase-storage/commit/68ee4b29dba64478b7cf8652972060883384246c)) 54 | * use npmignore to remove useless files ([4c66987](https://github.com/khaosdoctor/multer-firebase-storage/commit/4c66987a7c6bbf3398e3d70926b54817432e2900)) 55 | * use only return types from fbadmin lib ([c76e36e](https://github.com/khaosdoctor/multer-firebase-storage/commit/c76e36ec5273edb9f8314dc7009b2c945fa52040)) 56 | 57 | ### [3.0.3](https://github.com/khaosdoctor/multer-firebase-storage/compare/v3.0.2...v3.0.3) (2022-04-08) 58 | 59 | ## Breaking Change 60 | 61 | - Remove support for Node 10.x since the upstream dependencies are not compatible with it. 62 | 63 | 64 | ### :octocat: Repository 65 | 66 | * remove node 10 from tests ([d34684f](https://github.com/khaosdoctor/multer-firebase-storage/commit/d34684f8b911e17954d978e5f815b26148145be9)) 67 | 68 | ### [3.0.2](https://github.com/khaosdoctor/multer-firebase-storage/compare/v3.0.1...v3.0.2) (2022-04-08) 69 | 70 | ### [3.0.1](https://github.com/khaosdoctor/multer-firebase-storage/compare/v3.0.0...v3.0.1) (2022-04-08) 71 | 72 | ### :octocat: Repository 73 | 74 | * Update dependencies to fix security vulnerabilities 75 | 76 | ## [2.0.0](https://github.com/khaosdoctor/multer-firebase-storage/compare/v1.0.4...v2.0.0) (2021-08-27) 77 | 78 | 79 | ### :cactus: Features 80 | 81 | * add hooks ([4a3e511](https://github.com/khaosdoctor/multer-firebase-storage/commit/4a3e511acaae2d3fa1a3f5bc5f88f284e6520486)) 82 | 83 | ### [1.0.4](https://github.com/khaosdoctor/multer-firebase-storage/compare/v1.0.3...v1.0.4) (2021-08-26) 84 | 85 | 86 | ### :bug: Bug Fixes 87 | 88 | * **types:** fix appName type ([3dc8d0a](https://github.com/khaosdoctor/multer-firebase-storage/commit/3dc8d0a72f5035b8989f4d4de5e931f30371046c)) 89 | 90 | ### [1.0.3](https://github.com/khaosdoctor/multer-firebase-storage/compare/v1.0.2...v1.0.3) (2021-08-26) 91 | 92 | 93 | ### :butterfly: Improvements 94 | 95 | * **actions:** not running on any tags ([8f41554](https://github.com/khaosdoctor/multer-firebase-storage/commit/8f4155490d13ec38b69417e2f439c1e6f781c395)) 96 | 97 | ### [1.0.2](https://github.com/khaosdoctor/multer-firebase-storage/compare/v1.0.1...v1.0.2) (2021-08-26) 98 | 99 | 100 | ### :butterfly: Improvements 101 | 102 | * **actions:** change action trigger ([4f0a287](https://github.com/khaosdoctor/multer-firebase-storage/commit/4f0a2873d571f747791591fb469eeade6c89ac4b)) 103 | 104 | ### [1.0.1](https://github.com/khaosdoctor/multer-firebase-storage/compare/v1.0.0...v1.0.1) (2021-08-26) 105 | 106 | 107 | ### :butterfly: Improvements 108 | 109 | * **actions:** fix release regex ([b814b5a](https://github.com/khaosdoctor/multer-firebase-storage/commit/b814b5aee627e155ecbbb9915b4bb88005f91ad3)) 110 | 111 | ## [1.0.0](https://github.com/khaosdoctor/multer-firebase-storage/compare/v0.1.1-beta.0...v1.0.0) (2021-08-26) 112 | 113 | 114 | ### ⚠ BREAKING CHANGES 115 | 116 | * rename parameter to directoryPath 117 | 118 | ### :cactus: Features 119 | 120 | * add firebaseClient option ([c0ee57c](https://github.com/khaosdoctor/multer-firebase-storage/commit/c0ee57ce5d0d6af75773ec4102417726c00bacad)) 121 | * add unique option ([b34eef0](https://github.com/khaosdoctor/multer-firebase-storage/commit/b34eef0071711850f074f3174c070eafa08fe43c)) 122 | 123 | 124 | ### :butterfly: Improvements 125 | 126 | * **actions:** fix release action ([d41d084](https://github.com/khaosdoctor/multer-firebase-storage/commit/d41d08482d1370d80a6823e805690afeeb23ce7b)) 127 | * **actions:** rename workflow ([62f89cc](https://github.com/khaosdoctor/multer-firebase-storage/commit/62f89cca502f7799a5d90e58b757655e7d22982f)) 128 | * rename parameter to directoryPath ([6a00b7b](https://github.com/khaosdoctor/multer-firebase-storage/commit/6a00b7b4e6e653660e717ddb480de795becca2ac)) 129 | 130 | ### [0.1.1-beta.0](https://github.com/khaosdoctor/multer-firebase-storage/compare/v0.1.0...v0.1.1-beta.0) (2021-08-26) 131 | 132 | 133 | ### :cactus: Features 134 | 135 | * add mime map ([2f5bf57](https://github.com/khaosdoctor/multer-firebase-storage/commit/2f5bf57c900de334bc130c1009cd54dec310a6f5)) 136 | * add name prefix and suffix ([21b8f7f](https://github.com/khaosdoctor/multer-firebase-storage/commit/21b8f7f22c98c5a3d3c47ff04d5616971acd20d9)) 137 | 138 | 139 | ### :bug: Bug Fixes 140 | 141 | * fix property bugs ([881518d](https://github.com/khaosdoctor/multer-firebase-storage/commit/881518db3c7e6eda8e2c1e7049d940658b5975ce)) 142 | * wrong parameter name ([9eb1889](https://github.com/khaosdoctor/multer-firebase-storage/commit/9eb188968c4df7e551a4f984e5653815490e8c99)) 143 | 144 | 145 | ### :butterfly: Improvements 146 | 147 | * ignore unused params ([1899d93](https://github.com/khaosdoctor/multer-firebase-storage/commit/1899d937e1abe7611af8e5261081e63d8353784a)) 148 | 149 | ## 0.1.0 (2021-08-26) 150 | 151 | 152 | ### ⚠ BREAKING CHANGES 153 | 154 | * initial implementation 155 | 156 | ### :cactus: Features 157 | 158 | * initial implementation ([dd94eed](https://github.com/khaosdoctor/multer-firebase-storage/commit/dd94eed809c6dae1f6769703bef365b6479e8ce9)) 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multer-firebase-storage 2 | 3 | > Multer Storage Engine for Firebase 4 | 5 | ## Installation 6 | 7 | `npm install multer-firebase-storage` 8 | 9 | ## Usage 10 | 11 | Using Express: 12 | 13 | ```javascript 14 | const Express = require('express') 15 | const Multer = require('multer') 16 | const FirebaseStorage = require('multer-firebase-storage') 17 | const app = new Express() 18 | 19 | const multer = Multer({ 20 | storage: FirebaseStorage({ 21 | bucketName: 'your-default-bucket', 22 | credentials: { 23 | clientEmail: 'your-firebase-client-email', 24 | privateKey: 'your private key', 25 | projectId: 'your-project-id' 26 | } 27 | }) 28 | }) 29 | 30 | app.post('/file', multer.single('file'), (req, res) => { 31 | res.status(201).json(req.file) 32 | }) 33 | 34 | app.listen(3000, () => { 35 | console.log('Example app listening on port 3000!') 36 | }) 37 | ``` 38 | 39 | ## Tweaks and options 40 | 41 | Firebase Storage supports the following setup options: 42 | 43 | ```typescript 44 | { 45 | bucketName: string; 46 | credentials: string | { projectId: string, privateKey: string, clientEmail: string } 47 | directoryPath?: string 48 | mimeMap?: { 49 | [fileName: string]: string 50 | } 51 | appName?: string 52 | namePrefix?: string 53 | nameSuffix?: string 54 | unique?: boolean 55 | public?: boolean 56 | hooks: { 57 | [hookName: string]: function 58 | } 59 | } 60 | ``` 61 | 62 | ## Required options 63 | 64 | - `bucketName`: The name of the bucket to upload to. 65 | - `credentials`: The credentials to use for authentication. It can be a refresh token string or the Firebase credentials object (just like the firebase admin SDK requests). 66 | - Credentials can be provided by reading the Firebase Service Account JSON file and passing the contents __as an object__ 67 | - Credentials can be a set of the following properties: `projectId`, `privateKey`, `clientEmail` which can be obtained by the Firebase console. 68 | - __Note:__ The `privateKey` field needs to be in the same format as in the JSON file. 69 | 70 | ### Optional options 71 | 72 | - `directoryPath`: Will be prepended to the file name to include the file in a subdirectory. 73 | - For example: if the file name is `image.jpg` and the directory path is `images`, the resulting file name will be `images/image.jpg`. There's no need to add a trailing slash. 74 | - `appName`: Firebase allows only a single instance of its admin SDK to be executed per app. If you need more than one, specify the name of the app you want to use. Remember it __needs to be unique in the application__ 75 | - `namePrefix`: The prefix to be added to the file name. 76 | - This will append a string before the file name, but after the directory path. For example: if the file name is `image.jpg` and the prefix is `preview_`, the resulting file name will be `preview_image.jpg`. 77 | - `nameSuffix`: The suffix to be added to the file name. 78 | - This will append a string after the file name, but before the file extension. For example: if the file name is `image.jpg` and the suffix is `_final`, the resulting file name will be `image_final.jpg`. 79 | - `unique`: If set to `true`, the file name will be unique by generating a time-based hash that will be appended to the end of the file name (after `nameSuffix` and before the file extension). If set to `false`, the file name will be the same as the original file name. 80 | - For example: if the file name is `image.jpg` and the suffix is `_final` and `unique` is `true`, the resulting file name will be `image_final.jpg`. 81 | - `public`: If set to `true`, the file will be made public and the public URL will be returned. If set to `false`, the file will be private. 82 | - `hooks`: Where you can define [lifecycle hooks](#lifecycle-hooks) 83 | 84 | ## Returned data 85 | 86 | After a successful insertion, all returned data will be appended to the `req.file` object. Besides the original Multer properties, the following properties will be added: 87 | 88 | - `fileRef`: A reference to the Firebase Storage file object. You can use that to manipulate the file after the upload has been done. 89 | - Common operations to this reference are: generating signed URLs, deleting the file, etc. 90 | - The type of this property is the same as if you were using the Firebase Storage SDK directly with `firebase.storage().bucket().file(filename)` 91 | - `path`: The path of the file in the bucket. 92 | - `bucket`: The name of the bucket. 93 | - `bucketRef`: A reference to the Firebase Storage bucket object. You can use that to manipulate the bucket after the upload has been done. 94 | - The type of this property is the same as if you were using the Firebase Storage SDK directly with `firebase.storage().bucket(bucketname)` 95 | - `isPublic`: If the file is public or private. 96 | - `publicUrl`: If the file is public, the public URL will be returned. 97 | 98 | ## Using your own Firebase instance 99 | 100 | You can pass an optional parameter to the `FirebaseStorage` constructor to use your own Firebase instance. In this case, the `credentials` and `bucket` options will be ignored. 101 | 102 | ```javascript 103 | const Express = require('express') 104 | const Multer = require('multer') 105 | const fbAdmin = require('firebase-admin') 106 | const FirebaseStorage = require('multer-firebase-storage') 107 | const app = new Express() 108 | 109 | const fbInstance = fbAdmin.initializeApp({ 110 | credential: fbAdmin.credential.cert(somecredentials), 111 | storageBucket: 'some bucket' 112 | }) 113 | 114 | const multer = Multer({ 115 | storage: FirebaseStorage({}, fbInstance) 116 | }) 117 | 118 | app.post('/file', multer.single('file'), (req, res) => { 119 | res.status(201).json(req.file) 120 | }) 121 | 122 | app.listen(3000, () => { 123 | console.log('Example app listening on port 3000!') 124 | }) 125 | ``` 126 | 127 | ## Lifecycle hooks 128 | 129 | Multer-Firebase-Storage supports the following lifecycle hooks: 130 | 131 | - `beforeUpload`: This hook will be called before the file is uploaded to Firebase Storage. 132 | - `afterUpload`: This hook will be called after the file is uploaded to Firebase Storage. 133 | - `beforeDelete`: This hook will be called before the file is deleted from Firebase Storage. 134 | - `afterDelete`: This hook will be called after the file is deleted from Firebase Storage. 135 | - `beforeInit`: This hook will be called before the Firebase Storage instance is initialized. 136 | - `afterInit`: This hook will be called after the Firebase Storage instance is initialized. 137 | 138 | Each hook has a different function signature: 139 | 140 | - `beforeUpload`: `(req, file) => void` 141 | - `req` is the Express request object. `file` is the Multer file object. 142 | - `afterUpload`: `(req, file, fileRef, bucketRef) => void` 143 | - `req` is the Express request object. `file` is the Multer file object. `fileRef` and `bucketRef` are the references to the Firebase Storage objects. 144 | - `beforeDelete`: `(req, file) => void` 145 | - `req` is the Express request object. `file` is the Multer file object. 146 | - `afterDelete`: `(req, file, fileRef, bucketRef) => void` 147 | - `req` is the Express request object. `file` is the Multer file object. `fileRef` and `bucketRef` are the references to the Firebase Storage objects. 148 | - `beforeInit`: `(storageInstance) => void` 149 | - `storageInstance` is the Firebase Storage instance passed as `this`. 150 | - `afterInit`: `(storageInstance, firebaseInstance) => void` 151 | - `storageInstance` is the Firebase Storage instance passed as `this`. `firebaseInstance` is the Firebase instance passed either as the second parameter to the `FirebaseStorage` constructor or the internally constructed instance. 152 | 153 | ### Usage example 154 | 155 | ```javascript 156 | const Express = require('express') 157 | const Multer = require('multer') 158 | const FirebaseStorage = require('multer-firebase-storage') 159 | const app = new Express() 160 | 161 | const multer = Multer({ 162 | storage: FirebaseStorage({ 163 | bucketName: 'your-default-bucket', 164 | credentials: { 165 | clientEmail: 'your-firebase-client-email', 166 | privateKey: 'your private key', 167 | projectId: 'your-project-id' 168 | }, 169 | hooks: { 170 | beforeInit (instance) { 171 | console.log(`before init:`, instance) 172 | }, 173 | afterInit (instance, fb) { 174 | console.log(`after init:`, instance, fb) 175 | }, 176 | beforeUpload (req, file) { 177 | console.log(`before upload:`, req, file) 178 | }, 179 | afterUpload (req, file, fref, bref) { 180 | console.log(`after upload:`, req, file, fref, bref) 181 | }, 182 | beforeRemove (req, file) { 183 | console.log(`before remove:`, req, file) 184 | }, 185 | afterRemove (req, file, fref, bref) { 186 | console.log(`after remove:`, req, file, fref, bref) 187 | } 188 | } 189 | }) 190 | }) 191 | 192 | app.post('/file', multer.single('file'), (req, res) => { 193 | res.status(201).json(req.file) 194 | }) 195 | 196 | app.listen(3000, () => { 197 | console.log('Example app listening on port 3000!') 198 | }) 199 | ``` 200 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import fbAdmin from 'firebase-admin' 3 | import type FirebaseRequest from 'teeny-request/build/src/index' 4 | import type { Storage } from 'firebase-admin/lib/storage/storage' 5 | 6 | type Nullable = T | null 7 | type MimeMap = { [fileName: string]: string } 8 | type MulterFile = Express.Multer.File 9 | type ExpressRequest = Express.Request 10 | type FirebaseBucket = ReturnType['bucket']> 11 | type FirebaseFile = ReturnType 12 | type FirebaseRequest = Parameters['0'] 13 | 14 | /** 15 | * Subset of all firebase credentials with only the needed keys for the engine 16 | */ 17 | export type FirebaseCredentials = { 18 | projectId: string 19 | privateKey: string 20 | clientEmail: string 21 | } 22 | 23 | export enum AvailableHooks { 24 | beforeUpload = 'beforeUpload', 25 | afterUpload = 'afterUpload', 26 | beforeDelete = 'beforeDelete', 27 | afterDelete = 'afterDelete', 28 | beforeInit = 'beforeInit', 29 | afterInit = 'afterInit', 30 | } 31 | 32 | /** 33 | * Reference object from the uploaded file 34 | */ 35 | interface MulterFirebaseStorageFileReference { 36 | /** 37 | * The file reference object in firebase, this is not the file itself 38 | * You can use firebase functions on this reference like delete and others 39 | */ 40 | fileRef: FirebaseFile, 41 | /** 42 | * The file path 43 | */ 44 | path: string, 45 | /** 46 | * Bucket name 47 | */ 48 | bucket: string, 49 | /** 50 | * The reference to the bucket in firebase to be manipulated 51 | */ 52 | bucketRef: FirebaseBucket, 53 | /** 54 | * The file is public or not 55 | */ 56 | isPublic: boolean, 57 | /** 58 | * If isPublic is true, this is the url to the file otherwise is undefined 59 | */ 60 | publicUrl?: string 61 | } 62 | 63 | interface MulterFirebaseOptions { 64 | /** 65 | * The bucket to upload to. 66 | */ 67 | bucketName: string 68 | /** 69 | * Firebase credentials 70 | */ 71 | credentials: string | FirebaseCredentials 72 | /** 73 | * The destination path of the file, this will be appended to the file name 74 | */ 75 | directoryPath?: string 76 | /** 77 | * A map of file names to mime types 78 | */ 79 | mimeMap?: MimeMap 80 | /** 81 | * The name of the app. 82 | */ 83 | appName?: string 84 | /** 85 | * The prefix to prepend to the file name. 86 | */ 87 | namePrefix?: string 88 | /** 89 | * The suffix to append to the file name. 90 | */ 91 | nameSuffix?: string 92 | /** 93 | * If true, will append an unique identifier to the file name. (default: false) 94 | */ 95 | unique?: boolean 96 | /** 97 | * Whether the file should be public or not (default false) 98 | */ 99 | public?: boolean 100 | /** 101 | * Defined function hooks, these will be called during the lifecycle of the engine. 102 | */ 103 | hooks?: { [hookName: string]: Hooks } 104 | } 105 | 106 | interface Hooks { 107 | /** 108 | * Called before the file is uploaded 109 | */ 110 | beforeUpload?: (req: ExpressRequest, file: MulterFile) => void 111 | /** 112 | * Called after the file is uploaded 113 | */ 114 | afterUpload?: ( 115 | req: ExpressRequest, 116 | file: MulterFile, 117 | fileRef: FirebaseFile, 118 | bucketRef: FirebaseBucket 119 | ) => void 120 | /** 121 | * Called before the file is deleted 122 | */ 123 | beforeDelete?: (req: ExpressRequest, file: MulterFile) => void 124 | /** 125 | * Called after the file is deleted 126 | */ 127 | afterDelete?: ( 128 | req: ExpressRequest, 129 | file: MulterFile, 130 | fileRef: FirebaseFile, 131 | bucketRef: FirebaseBucket 132 | ) => void 133 | /** 134 | * Called before the Firebase client is initialized 135 | */ 136 | beforeInit?: (instance: FirebaseStorage) => void 137 | /** 138 | * Called after the Firebase client is initialized 139 | */ 140 | afterInit?: (instance: FirebaseStorage, client: fbAdmin.app.App) => void 141 | } 142 | 143 | class FirebaseStorage { 144 | #directoryPath = '' 145 | #bucket = '' 146 | #namePrefix = '' 147 | #nameSuffix = '' 148 | #firebase: Nullable = null 149 | #unique = false 150 | #appName = '' 151 | #public = false 152 | #mimeMap: MimeMap = {} 153 | #hooks: Hooks = {} 154 | 155 | #required = (message: string) => { throw new Error(message) } 156 | 157 | #callHook (hookName: HookName, ...params: Parameters[HookName]>): void { 158 | type HookFunction = (...args: Parameters[HookName]>) => ReturnType[HookName]> 159 | type HookParameters = Parameters[HookName]> 160 | type HookReturn = ReturnType[HookName]> 161 | 162 | const hookToBeCalled = this.#hooks[hookName] 163 | if (hookToBeCalled) { 164 | return (hookToBeCalled as unknown as HookFunction).call, HookParameters, HookReturn>(this, ...params) 165 | } 166 | } 167 | 168 | /** 169 | * @param {MulterFirebaseOptions} opts Configuration Options 170 | **/ 171 | constructor (opts: MulterFirebaseOptions, firebaseClient: fbAdmin.app.App | null) { 172 | this.#directoryPath = opts.directoryPath || '' 173 | this.#namePrefix = opts.namePrefix || '' 174 | this.#nameSuffix = opts.nameSuffix || '' 175 | this.#mimeMap = opts.mimeMap || {} 176 | this.#public = opts.public || false 177 | this.#unique = opts.unique || false 178 | this.#hooks = opts.hooks || {} 179 | this.#bucket = opts.bucketName || this.#required('Bucket Name Required') 180 | this.#appName = opts.appName ? opts.appName : `multer-firebase-${this.#bucket}-${Date.now().toString(16)}` 181 | this.#firebase = firebaseClient 182 | 183 | this.#callHook('beforeInit', this) 184 | 185 | if (!firebaseClient) { 186 | this.#validateCredentials(opts.credentials) 187 | 188 | this.#firebase = fbAdmin.initializeApp({ 189 | credential: fbAdmin.credential.cert(opts.credentials), 190 | storageBucket: this.#bucket 191 | }, this.#appName) 192 | } 193 | 194 | this.#callHook('afterInit', this, this.#firebase as fbAdmin.app.App) 195 | } 196 | 197 | /** 198 | * @private 199 | **/ 200 | _handleFile (req: ExpressRequest, file: MulterFile, cb: (err: Error | null, info?: MulterFirebaseStorageFileReference) => void) { 201 | this.#callHook('beforeUpload', req, file) 202 | const fileName = this.#getFileName(file) 203 | const bucketFile = this.#firebase!.storage().bucket().file(fileName) 204 | const outStream = bucketFile.createWriteStream({ 205 | metadata: { 206 | contentType: this.#getMimetype(file) 207 | } 208 | }) 209 | file.stream.pipe(outStream) 210 | 211 | outStream.on('error', (err: Error) => { 212 | cb(err) 213 | }) 214 | 215 | outStream.on('finish', () => { 216 | const returnObject: MulterFirebaseStorageFileReference = { 217 | fileRef: bucketFile, 218 | path: fileName, 219 | bucket: this.#bucket, 220 | bucketRef: this.#firebase!.storage().bucket(this.#bucket), 221 | isPublic: this.#public, 222 | } 223 | if (this.#public) { 224 | bucketFile.makePublic() 225 | returnObject.publicUrl = bucketFile.publicUrl() 226 | } 227 | this.#callHook('afterUpload', req, file, returnObject.fileRef, returnObject.bucketRef) 228 | cb(null, returnObject) 229 | }) 230 | 231 | } 232 | 233 | /** 234 | * @private 235 | **/ 236 | _removeFile (req: ExpressRequest, file: MulterFile, cb: (err: Error | null, data?: unknown) => void) { 237 | this.#callHook('beforeDelete', req, file) 238 | const fileRef = this.#firebase!.storage().bucket().file(this.#getFileName(file)) 239 | 240 | fileRef.delete({ ignoreNotFound: true }, (err: Error | null, data?: FirebaseRequest.Response) => { 241 | this.#callHook('afterDelete', req, file, fileRef, this.#firebase!.storage().bucket(this.#bucket)) 242 | cb(err, data) 243 | }) 244 | } 245 | 246 | #getMimetype (file: MulterFile) { 247 | const mime = this.#mimeMap[file.originalname.split('.')[0]] || this.#mimeMap['*'] || file.mimetype 248 | return mime 249 | } 250 | 251 | #getFileName (file: MulterFile) { 252 | return `${this.#directoryPath ? this.#directoryPath + '/' : ''}${this.#namePrefix}${file.originalname.split('.')[0]}${this.#nameSuffix}${this.#unique ? Date.now().toString(16) : ''}.${file.originalname.split('.')[1] || ''}` 253 | } 254 | 255 | #validateCredentials (credentials: string | FirebaseCredentials) { 256 | if (!credentials) return this.#required('Credentials Required') 257 | if (!['string', 'object'].includes(typeof credentials)) return this.#required('Credentials must be a string or service account object') 258 | if (typeof credentials === 'object' && 259 | !(credentials as FirebaseCredentials).projectId || !(credentials as FirebaseCredentials).privateKey || !(credentials as FirebaseCredentials).clientEmail) return this.#required('Credentials must be a string or service account object') 260 | return credentials 261 | } 262 | } 263 | 264 | /** 265 | * The firebase storage engine for multer 266 | */ 267 | export default function (opts: MulterFirebaseOptions, firebaseClient: fbAdmin.app.App | null = null): FirebaseStorage { 268 | return new FirebaseStorage(opts, firebaseClient) 269 | } 270 | module.exports = (opts: MulterFirebaseOptions, firebaseClient: Nullable = null) => new FirebaseStorage(opts, firebaseClient) 271 | 272 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 48 | "declarationMap": true /* Create sourcemaps for d.ts files. */, 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | "importsNotUsedAsValues": "remove" /* Specify emit/checking behavior for imports that are only used for types. */, 57 | "downlevelIteration": true /* Emit more compliant, but verbose and less performant JavaScript for iteration. */, 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 77 | 78 | /* Type Checking */ 79 | "strict": true /* Enable all strict type-checking options. */, 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | "skipDefaultLibCheck": true /* Skip type checking .d.ts files that are included with TypeScript. */, 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | --------------------------------------------------------------------------------