├── .dockerignore ├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── consumers.ts ├── controllers │ ├── index.ts │ └── message.controller.ts ├── helpers │ └── graceful-shutdown.ts ├── libs │ └── create-response.ts ├── producers.ts ├── queues │ ├── index.ts │ └── message-broker.queue.ts ├── routes │ └── index.ts ├── server.ts ├── tests │ └── integrations │ │ └── message.test.ts └── workers │ ├── index.ts │ └── message-broker.worker.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URL=redis 2 | REDIS_PORT=6379 3 | PORT=3000 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: ['standard'], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | ecmaVersion: 'latest', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint', 'import'], 14 | rules: { 15 | semi: 0, 16 | indent: ['error', 4] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | jspm_packages/ 4 | 5 | # TypeScript v1 declaration files 6 | 7 | # Optional npm cache directory 8 | .npm 9 | 10 | # Optional eslint cache 11 | .eslintcache 12 | 13 | # Optional REPL history 14 | .node_repl_history 15 | 16 | # build folder 17 | dist 18 | 19 | # environment variables file 20 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "jsxBracketSameLine": false, 6 | "tabWidth": 4, 7 | "printWidth": 120 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | FROM node:16.13 3 | 4 | WORKDIR '/var/www/app' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Reggie Escobar 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Message Broker 2 | 3 | A simple message broker example using BullMQ and Redis (worker / queue). Producers publish messages to the queue. Consumers manually get one unread message from the queue through a worker, in order as FIFO (one at a time). 4 | 5 | ## Setup 6 | 1. Install [Docker](https://www.docker.com/) 7 | 2. Build and start services: 8 | ```bash 9 | $ docker-compose up --build 10 | ``` 11 | 12 | ## Running Producers & Consumers 13 | Create node image: 14 | ```bash 15 | $ docker build --tag node-docker . 16 | ``` 17 | Run Producers: 18 | ```bash 19 | docker run -it -v $(pwd)/.:/var/www/app --network bridge node-docker npm i && npm run start:producers 20 | ``` 21 | 22 | Run consumers 23 | ```bash 24 | docker run -it -v $(pwd)/.:/var/www/app --network bridge node-docker npm i && npm run start:consumers 25 | ``` 26 | 27 | ## API Doc 28 | 29 | **Publish Message** 30 | ```bash 31 | curl -XPOST -H "Content-type: application/json" -d '{ 32 | "payload": { "message": "Hello World" } 33 | }' 'http://localhost:3000/v1/message' 34 | ``` 35 | 36 | **Read One Unread Message At A Time In Order** 37 | ```bash 38 | curl -XGET 'http://localhost:3000/v1/message' 39 | ``` 40 | 41 | ## Running Integration Tests 42 | 1. Spin up a redis instance 43 | ```bash 44 | $ docker run --name redis-test -p 6380:6379 -d redis 45 | ``` 46 | 2. Change the .env file to reflect the redis host and port eg. 47 | ```bash 48 | REDIS_URL=127.0.0.1 49 | REDIS_PORT=6380 50 | PORT=4000 51 | ``` 52 | 3. Create node-docker **if it doesn't** exist 53 | ```bash 54 | $ docker build --tag node-docker . 55 | ``` 56 | 4. Run test: 57 | ```bash 58 | docker run -it -v $(pwd)/.:/var/www/app --network bridge node-docker npm i && npm run test 59 | ``` 60 | 61 | ## Explanation 62 | Two common patterns that did not fit all the requirements but are typically used in real-world cases: 63 | 1. **Queue / Worker** - mostly used for decoupling. Worker could have acted as a consumer that processes jobs automatically but the requirements asked for a read messages api endpoint which wouldn't fit this use-case. This pattern satisfies "Bonus: Implement at-most-once delivery. This means that when a consumer reads a message, the system will guarantee that no other consumers read the message. Think about reliability especially in this case." 64 | 65 | 2. **Pub/Sub** - Also used for decoupling and may other use-cases. Uses a queue but messages typically are sent to one or more clients that are subscribed to a channel or topic and are not sent in any order. It did not satisfy the "This means that when a consumer reads a message, the system will guarantee that no other consumers read the message. Think about reliability especially in this case" (a difficult problem to solve in pub/sub). Pub/Sub typically guarantees that each subscribed client / consumer should receive the message at most once. 66 | 67 | BullMQ already handles the "At-most-once delivery". Once a job is selected, it status changes to active and so no other consumer can get / read that job. 68 | 69 | ## Dependencies 70 | 1. "axios": "^0.26.1" - to make http requests 71 | 72 | 2. "bullmq": "^1.78.2" - to use the queue and worker 73 | 74 | 3. "dotenv": "^16.0.0" - to load environment variables from .env file 75 | 76 | 4. "express": "^4.17.3" - a popular web framework that I've mostly used before 77 | 78 | 5. "helmet": "^5.0.2" - a set of security middleware that I mostly used before 79 | 80 | 6. "nodemon": "^2.0.15" - monitoring tool for development 81 | 82 | 7. "ts-node": "^10.7.0" - transpile ts to js and runs the file. Used to run consumers.ts and producers.ts 83 | 84 | 8. "txtgen": "^3.0.1" - a simple npm package to randomly generate sentences to send a message from the producers. 85 | 86 | 9. "typescript": "^4.6.3" - provide typings and type checking to javascript 87 | 88 | 10. "uuid": "^8.3.2" - to generate random ids for jobs 89 | 90 | 11. jest - a testing framework with a set of assertion methods. 91 | 92 | 12. supertest - to make requests to a server for integration testing 93 | 94 | 13. prettier / eslint - for linting -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | api: 5 | build: ./ 6 | volumes: 7 | - ./:/var/www/app 8 | ports: 9 | - 3000:3000 10 | command: sh -c 'npm i && npm run start:api' 11 | networks: 12 | - backend 13 | redis: 14 | image: redis 15 | container_name: message-broker 16 | expose: 17 | - 6379 18 | networks: 19 | - backend 20 | networks: 21 | backend: -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | roots: [''], 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.ts?$': 'ts-jest' 8 | }, 9 | setupFilesAfterEnv: ['jest-extended'], 10 | modulePathIgnorePatterns: ['/dist/'], 11 | globals: { 12 | 'ts-jest': { 13 | diagnostics: false 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "message-broker", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start:api": "nodemon src/server.ts", 8 | "start:producers": "ts-node src/producers.ts", 9 | "start:consumers": "ts-node src/consumers.ts", 10 | "build": "npm run lint && tsc --build", 11 | "lint": "eslint . --ext .js,.ts --ignore-pattern node_modules/", 12 | "test": "jest" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "axios": "^0.26.1", 19 | "bullmq": "^1.78.2", 20 | "dotenv": "^16.0.0", 21 | "express": "^4.17.3", 22 | "helmet": "^5.0.2", 23 | "nodemon": "^2.0.15", 24 | "ts-node": "^10.7.0", 25 | "txtgen": "^3.0.1", 26 | "typescript": "^4.6.3", 27 | "uuid": "^8.3.2" 28 | }, 29 | "devDependencies": { 30 | "@types/axios": "^0.14.0", 31 | "@types/body-parser": "^1.19.2", 32 | "@types/express": "^4.17.13", 33 | "@types/jest": "^27.4.1", 34 | "@types/supertest": "^2.0.12", 35 | "@types/uuid": "^8.3.4", 36 | "@typescript-eslint/eslint-plugin": "^5.18.0", 37 | "@typescript-eslint/parser": "^5.18.0", 38 | "eslint": "^7.32.0", 39 | "eslint-config-standard": "^16.0.3", 40 | "eslint-plugin-import": "^2.26.0", 41 | "eslint-plugin-node": "^11.1.0", 42 | "eslint-plugin-promise": "^5.2.0", 43 | "jest": "^27.5.1", 44 | "jest-extended": "^2.0.0", 45 | "prettier": "^2.6.2", 46 | "supertest": "^6.2.2", 47 | "ts-jest": "^27.1.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/consumers.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const createConsumer = (id: number, intervalInMilliSeconds: number): void => { 4 | setInterval(async () => { 5 | const res = await axios.get('http://127.0.0.1:3000/v1/message'); 6 | console.log(`Consumer ${id} Request Response: `, { 7 | response: JSON.stringify(res.data) 8 | }); 9 | }, intervalInMilliSeconds); 10 | }; 11 | 12 | // create 5 consumers with varying request intervals 13 | for (let i = 0; i < 5; i++) { 14 | const interval = Math.floor(Math.random() * (20000 - 3000 + 1) + 3000); 15 | createConsumer(i + 1, interval); 16 | } 17 | -------------------------------------------------------------------------------- /src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import * as messageController from './message.controller'; 2 | 3 | // export controllers 4 | export { messageController }; 5 | -------------------------------------------------------------------------------- /src/controllers/message.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { createResponse } from '../libs/create-response'; 3 | import { v4 as uuid4 } from 'uuid'; 4 | import { Job } from 'bullmq'; 5 | import { messageBrokerQueue } from '../queues'; 6 | import { messageBrokerWorker } from '../workers'; 7 | 8 | export const sendMessage = (req: Request, res: Response): Response> => { 9 | try { 10 | const data = req.body; 11 | const id = uuid4(); 12 | 13 | // make sure the client submitted a payload attribute 14 | if (!data?.payload) { 15 | const response = createResponse(400, {}, { message: 'Missing payload attribute.' }); 16 | return res.status(400).json(response); 17 | } 18 | 19 | // asynchronously add payload to queue 20 | messageBrokerQueue.add(id, data.payload); 21 | 22 | const payload = { id }; 23 | const response = createResponse(200, payload, {}); 24 | return res.status(200).json(response); 25 | } catch (error) { 26 | const errorResult = createResponse(500, {}, { message: 'Something went wrong.' }); 27 | return res.status(500).json(errorResult); 28 | } 29 | }; 30 | 31 | export const readMessage = async (req: Request, res: Response): Promise>> => { 32 | try { 33 | const token = uuid4(); 34 | const job = (await messageBrokerWorker.getNextJob(token)) as Job; 35 | let data; 36 | if (job?.data) { 37 | data = job.data; 38 | data.id = job.id; 39 | } 40 | 41 | const response = createResponse(200, data, {}); 42 | return res.status(200).json(response); 43 | } catch (error) { 44 | const errorResult = createResponse(500, {}, { message: 'Something went wrong.' }); 45 | return res.status(500).json(errorResult); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/helpers/graceful-shutdown.ts: -------------------------------------------------------------------------------- 1 | import { Server } from 'http'; 2 | import { Queue, Worker } from 'bullmq'; 3 | 4 | export const gracefulShutdown = async (server: Server, queue: Queue, worker: Worker) => { 5 | try { 6 | await worker.close(); 7 | await queue.pause(); 8 | server.close(() => { 9 | console.log('-----Closed Server!-----'); 10 | }); 11 | } catch (err) { 12 | console.log('Unable to shut down sever because: ', err); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/libs/create-response.ts: -------------------------------------------------------------------------------- 1 | export type ResponseModel = { 2 | data: any; 3 | meta: { 4 | version: string; 5 | }; 6 | response: { 7 | code: number; 8 | status: string; 9 | errors: any; 10 | }; 11 | }; 12 | 13 | /** 14 | * Get meta data for creating response model 15 | */ 16 | const getMetaData = () => { 17 | const meta = { 18 | version: '1.0.0' 19 | }; 20 | 21 | return meta; 22 | }; 23 | 24 | /** 25 | * Create response model with user friendly designed 26 | * 27 | * @param responseCode 28 | * @param data 29 | * @param errors 30 | */ 31 | export const createResponse = (responseCode: number, data: any, errors: any) => { 32 | const responseModel: ResponseModel = { 33 | data, 34 | meta: getMetaData(), 35 | response: { 36 | code: responseCode, 37 | errors, 38 | status: responseCode < 400 ? 'OK' : 'ERROR' 39 | } 40 | }; 41 | 42 | return responseModel; 43 | }; 44 | -------------------------------------------------------------------------------- /src/producers.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { sentence } from 'txtgen'; 3 | 4 | const createProducer = (id: number, intervalInMilliSeconds: number): void => { 5 | setInterval(async () => { 6 | const payload = { producerId: id, message: sentence() }; 7 | const res = await axios.post('http://127.0.0.1:3000/v1/message', { 8 | payload 9 | }); 10 | console.log(`Producer ${id}: `, { 11 | payload, 12 | response: JSON.stringify(res.data) 13 | }); 14 | }, intervalInMilliSeconds); 15 | }; 16 | 17 | // create 2 producers with varying request intervals 18 | for (let i = 0; i < 2; i++) { 19 | const interval = Math.floor(Math.random() * (20000 - 3000 + 1) + 3000); 20 | createProducer(i + 1, interval); 21 | } 22 | -------------------------------------------------------------------------------- /src/queues/index.ts: -------------------------------------------------------------------------------- 1 | import { messageBrokerQueue } from './message-broker.queue'; 2 | 3 | // export message broker queue 4 | export { messageBrokerQueue }; 5 | -------------------------------------------------------------------------------- /src/queues/message-broker.queue.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from 'bullmq'; 2 | 3 | export const queueName = 'message-broker'; 4 | 5 | export const messageBrokerQueue = new Queue(queueName, { 6 | connection: { 7 | host: process.env.REDIS_URL, 8 | port: parseInt(process.env.REDIS_PORT || '10') 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { messageController } from '../controllers'; 3 | 4 | const router = express.Router(); 5 | 6 | // routes 7 | router.get('/health-check', (_req, res) => res.status(200).json({ message: 'OK' })); 8 | 9 | // message api routes 10 | router.post('/message', messageController.sendMessage); 11 | router.get('/message', messageController.readMessage); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | // load .env file 2 | import 'dotenv/config'; 3 | import express, { Application } from 'express'; 4 | import bodyParser from 'body-parser'; 5 | import helmet from 'helmet'; 6 | import { messageBrokerQueue } from './queues'; 7 | import { messageBrokerWorker } from './workers'; 8 | import { Server } from 'http'; 9 | import { gracefulShutdown } from './helpers/graceful-shutdown'; 10 | 11 | require('dotenv').config(); 12 | 13 | export const app: Application = express(); 14 | 15 | // middleware 16 | app.use(bodyParser.json({ limit: '2mb' })); 17 | app.use(bodyParser.urlencoded({ extended: true })); 18 | 19 | // security 20 | app.use(helmet()); 21 | 22 | // set entry route for api 23 | app.use('/v1', require('./routes')); 24 | 25 | export let httpServer: Server; 26 | const PORT = process.env.PORT || 3000; 27 | 28 | // start the queue before starting the server. 29 | messageBrokerQueue.resume().then(() => { 30 | // start live server 31 | httpServer = app.listen(PORT, () => { 32 | console.log('MessageBroker API is running at port %d in %s mode', PORT, app.get('env')); 33 | console.log('Press CTRL-C to stop\n'); 34 | }); 35 | }); 36 | 37 | // handle services shutdown gracefully 38 | process.on('SIGTERM', () => gracefulShutdown(httpServer, messageBrokerQueue, messageBrokerWorker)); 39 | process.on('SIGINT', () => gracefulShutdown(httpServer, messageBrokerQueue, messageBrokerWorker)); 40 | -------------------------------------------------------------------------------- /src/tests/integrations/message.test.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { messageBrokerQueue } from '../../queues'; 3 | import { messageBrokerWorker } from '../../workers'; 4 | import request from 'supertest'; 5 | 6 | import { app, httpServer } from '../../server'; 7 | 8 | // disable all console methods except for debug 9 | for (const method of ['log', 'info', 'warn', 'error']) { 10 | jest.spyOn(global.console, method).mockImplementation(() => {}); 11 | } 12 | 13 | describe('Message', () => { 14 | beforeEach(async () => { 15 | // remove everything from the queue 16 | await messageBrokerQueue.obliterate({ force: true }); 17 | }); 18 | 19 | afterAll(async () => { 20 | await messageBrokerWorker.close(); 21 | await messageBrokerQueue.close(); 22 | httpServer.close(); 23 | }); 24 | 25 | it('HAPPY: should publish a message successfully', async () => { 26 | const message = 'Hello World'; 27 | const payload = { message }; 28 | const { body } = await request(app).post('/v1/message').send({ payload }).expect(200); 29 | 30 | expect(body.data).toHaveProperty('id'); 31 | }); 32 | 33 | it('SAD: should return bad request if payload property not included in request', async () => { 34 | const { body } = await request(app).post('/v1/message').send({}).expect(400); 35 | 36 | expect(body.response.errors).toStrictEqual({ 37 | message: 'Missing payload attribute.' 38 | }); 39 | }); 40 | 41 | it('HAPPY: should get an unread message from the broker', async () => { 42 | const message = 'Hey you!'; 43 | await request(app).post('/v1/message').send({ payload: { message } }).expect(200); 44 | 45 | const { body } = await request(app).get('/v1/message').expect(200); 46 | expect(body.data).toHaveProperty('id'); 47 | expect(body.data).toHaveProperty('message'); 48 | 49 | expect(body.data.message).toBe(message); 50 | }); 51 | 52 | it('HAPPY: should return an empty object when there is no unread message', async () => { 53 | const { body } = await request(app).get('/v1/message').expect(200); 54 | 55 | expect(body.data).toBeUndefined(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/workers/index.ts: -------------------------------------------------------------------------------- 1 | import messageBrokerWorker from './message-broker.worker'; 2 | 3 | // export workers 4 | export { messageBrokerWorker }; 5 | -------------------------------------------------------------------------------- /src/workers/message-broker.worker.ts: -------------------------------------------------------------------------------- 1 | import { Worker } from 'bullmq'; 2 | import { queueName } from '../queues/message-broker.queue'; 3 | 4 | // no specified job handler because API request will only get the next job and handle it. 5 | const messageBrokerWorker = new Worker(queueName, undefined, { 6 | autorun: false, 7 | connection: { 8 | host: process.env.REDIS_URL, 9 | port: parseInt(process.env.REDIS_PORT || '10') 10 | } 11 | }); 12 | 13 | export default messageBrokerWorker; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 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": ["es2021", "dom"], /* 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 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "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. */ 50 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | }, 101 | "exclude": ["node_modules", "dist"] 102 | } 103 | --------------------------------------------------------------------------------