From c81a368dd3006b049f3ee1dd88ba05224bf67cdd Mon Sep 17 00:00:00 2001 From: Alexander Dmitrjuk Date: Sat, 2 Jan 2021 14:36:35 +0700 Subject: [PATCH] First commit --- .github/workflows/tests.yml | 24 +++++ .gitignore | 3 + .mocharc.json | 7 ++ .npmignore | 8 ++ LICENCE | 21 ++++ README.md | 52 ++++++++++ docker-compose.yml | 14 +++ index.js | 195 ++++++++++++++++++++++++++++++++++++ index.ts | 110 ++++++++++++++++++++ package.json | 23 +++++ test/iterator.ts | 72 +++++++++++++ tsconfig.json | 72 +++++++++++++ 12 files changed, 601 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .mocharc.json create mode 100644 .npmignore create mode 100644 LICENCE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 index.js create mode 100644 index.ts create mode 100644 package.json create mode 100644 test/iterator.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..0f9b7c3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: + - '*' + - '!stable/**' + +jobs: + build: + timeout-minutes: 7 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: '15' + - name: Install npm dependencies + run: npm ci + - name: Run docker-compose + run: docker-compose up -d + - name: Run tests + run: docker-compose exec -T node npm run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27cb1ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +package-lock.json +.idea +node_modules \ No newline at end of file diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000..c74002f --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extension": ["ts"], + "exit": true, + "recursive": true, + "require": ["ts-node/register"], + "inspect": "0.0.0.0:9231" +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..529c87a --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +test +tsconfig.json +.mocharc.json +LICENCE +node_modules +.gitignore +docker-compose.yml +package-lock.json \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..45e835b --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Alexander Dmitryuk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..16eee97 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Redis functions balancer +[![NPM](https://nodei.co/coden/redis-functions-balancer.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/coden/redis-functions-balancer) + +Balance executable NodeJs function with redis. + +For example, if you have several functions (A, B, C) doing the same things (http requests, long-running code), and you want to execute it evenly. + +Working in clusters (PM2, NodeJs Cluster). + +Uses [Redis][0] list with rank and [Javascript iterators][1]. + +Ready to use with TypeScript and JavaScript. + +## Installation +``` +npm install @coden/redis-functions-balancer --save-prod +``` + +## Usage +```typescript +import CallableBalancer from "@coden/redis-functions-balancer"; +const redis = require("redis"); +const redisClient = redis.createClient(6379, 'redis'); + +// Your functions here +// ... // +const A = () => {}; +const B = () => {}; +const C = () => {}; +// ... // +let balancer = new CallableBalancer([A, B, C], redisClient); +// or reuse balancer variable with another functions +balancer.setMethods([A, B]); +// ... // +// Get async iterator {done, value} +while ( (foo = await balancer.getAsyncIterator().next()) && !foo.done) { + // Your function A|B|C will be here evenly + let method = foo.value; + + try { + // Executing on your way ( + foo.value(); + } catch (e) { + // something happen badly and you want to postpone executes of the function next 10 runs + balancer.increaseMethodRank(method, 10); + } +} + +``` + +[0]: https://www.npmjs.com/package/redis +[1]: https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bbb7168 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.5" +services: + redis: + image: redis:alpine + depends_on: + - node + node: + image: node:latest + volumes: + - .:/app + working_dir: /app + entrypoint: "sleep 100" + ports: + - 9231:9231 \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..64c29d3 --- /dev/null +++ b/index.js @@ -0,0 +1,195 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } +var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +}; +var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var util_1 = require("util"); +var CallableBalancer = /** @class */ (function () { + /** + * + * @param methods not empty array of functions + * @param redisClient + */ + function CallableBalancer(methods, redisClient) { + this._STORE_PREFIX = 'balancer'; + this.INC_VALUE = 1; + this._redisClient = redisClient; + this._methods = methods; + this._storeKey = this.makeStoreKey(methods); + // Initialize Redis functions as async await + this._functions = { + delAsync: util_1.promisify(redisClient.DEL).bind(this._redisClient), + zAddAsync: util_1.promisify(redisClient.ZADD).bind(this._redisClient), + zRangeAsync: util_1.promisify(redisClient.zrange).bind(this._redisClient), + zIncRbyAsync: util_1.promisify(redisClient.zincrby).bind(this._redisClient), + }; + } + CallableBalancer.prototype.setMethods = function (methods) { + this._methods = methods; + this._storeKey = this.makeStoreKey(methods); + }; + CallableBalancer.prototype.increaseMethodRank = function (method, incValue) { + if (incValue === void 0) { incValue = this.INC_VALUE; } + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this._functions.zIncRbyAsync(this._storeKey, incValue, method.name)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + CallableBalancer.prototype.getAsyncIterator = function () { + return __asyncGenerator(this, arguments, function getAsyncIterator_1() { + var storedMethodNames, _i, storedMethodNames_1, methodName, _a, _b, method; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: return [4 /*yield*/, __await(this.getRange())]; + case 1: + storedMethodNames = _c.sent(); + _i = 0, storedMethodNames_1 = storedMethodNames; + _c.label = 2; + case 2: + if (!(_i < storedMethodNames_1.length)) return [3 /*break*/, 9]; + methodName = storedMethodNames_1[_i]; + _a = 0, _b = this._methods; + _c.label = 3; + case 3: + if (!(_a < _b.length)) return [3 /*break*/, 8]; + method = _b[_a]; + if (!(method.name === methodName)) return [3 /*break*/, 7]; + return [4 /*yield*/, __await(this.increaseMethodRank(method, this.INC_VALUE))]; + case 4: + _c.sent(); + return [4 /*yield*/, __await(method)]; + case 5: return [4 /*yield*/, _c.sent()]; + case 6: + _c.sent(); + _c.label = 7; + case 7: + _a++; + return [3 /*break*/, 3]; + case 8: + _i++; + return [3 /*break*/, 2]; + case 9: return [2 /*return*/]; + } + }); + }); + }; + /** + * Clear store + */ + CallableBalancer.prototype.resetStore = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this._functions.delAsync(this._storeKey)]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + CallableBalancer.prototype.getStoreKey = function () { + return this._storeKey; + }; + /** + * Return redis key to store list of methods with ranks + * @param methods + * @protected + */ + CallableBalancer.prototype.makeStoreKey = function (methods) { + var storeKeyArray = [this._STORE_PREFIX]; + methods.forEach(function (method) { + storeKeyArray.push(method.name); + }); + return storeKeyArray.join('.'); + }; + /** + * Returns an Array stored in Redis in Rank order + * @private + */ + CallableBalancer.prototype.getRange = function () { + return __awaiter(this, void 0, void 0, function () { + var storedMethodNames, args_1, result_1; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, this._functions.zRangeAsync(this._storeKey, 0, -1)]; + case 1: + storedMethodNames = _b.sent(); + if (!(storedMethodNames.length !== this._methods.length)) return [3 /*break*/, 3]; + args_1 = [], result_1 = []; + this._methods.forEach(function (method) { + // Default rank is 1 + args_1.push("1", method.name); + result_1.push(method.name); + }); + return [4 /*yield*/, (_a = this._functions).zAddAsync.apply(_a, __spreadArrays([this._storeKey, 'NX'], args_1))]; + case 2: + _b.sent(); + return [2 /*return*/, result_1]; + case 3: return [2 /*return*/, storedMethodNames]; + } + }); + }); + }; + return CallableBalancer; +}()); +exports.default = CallableBalancer; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..4546eaa --- /dev/null +++ b/index.ts @@ -0,0 +1,110 @@ +import {promisify} from "util"; +import {RedisClient} from "redis"; + +type RedisFunctions = { + delAsync: (key: string) => Promise, + zAddAsync: (key: string, mode: string, ...args: string[]) => Promise, + zRangeAsync: (key: string, begin: number, end: number) => Promise, + zIncRbyAsync: (key: string, incValue: number, element: string) => Promise; +}; + +export default class CallableBalancer { + private _storeKey: string; + private _methods: Array; + private readonly _STORE_PREFIX = 'balancer'; + private readonly _redisClient: RedisClient; + private readonly INC_VALUE = 1; + + private readonly _functions: RedisFunctions; + + /** + * + * @param methods not empty array of functions + * @param redisClient + */ + constructor(methods: Array, redisClient: RedisClient) { + this._redisClient = redisClient; + this._methods = methods; + this._storeKey = this.makeStoreKey(methods); + + // Initialize Redis functions as async await + this._functions = { + delAsync: promisify(redisClient.DEL).bind(this._redisClient), + zAddAsync: promisify(redisClient.ZADD).bind(this._redisClient), + zRangeAsync: promisify(redisClient.zrange).bind(this._redisClient), + zIncRbyAsync: promisify(redisClient.zincrby).bind(this._redisClient), + }; + } + + public setMethods(methods: Array) { + this._methods = methods; + this._storeKey = this.makeStoreKey(methods); + } + + public async increaseMethodRank(method: Function, incValue: number = this.INC_VALUE) { + await this._functions.zIncRbyAsync(this._storeKey, incValue, method.name); + } + + public async* getAsyncIterator(): AsyncIterableIterator { + let storedMethodNames = await this.getRange(); + + // Redis store defined + for (let methodName of storedMethodNames) { + for (let method of this._methods) { + if (method.name === methodName) { + await this.increaseMethodRank(method, this.INC_VALUE); + yield method; + } + } + } + } + + /** + * Clear store + */ + public async resetStore(): Promise { + await this._functions.delAsync(this._storeKey); + } + + public getStoreKey(): string { + return this._storeKey; + } + + /** + * Return redis key to store list of methods with ranks + * @param methods + * @protected + */ + protected makeStoreKey(methods: Array): string { + let storeKeyArray: Array = [this._STORE_PREFIX]; + methods.forEach((method: Function) => { + storeKeyArray.push(method.name); + }); + + return storeKeyArray.join('.'); + } + + /** + * Returns an Array stored in Redis in Rank order + * @private + */ + protected async getRange(): Promise> { + let storedMethodNames = await this._functions.zRangeAsync(this._storeKey, 0, -1) as Array; + // If Redis store is not initialized yield in default order + if (storedMethodNames.length !== this._methods.length) { + let args: Array = [], + result: Array = []; + + this._methods.forEach(method => { + // Default rank is 1 + args.push("1", method.name); + result.push(method.name); + }); + await this._functions.zAddAsync(this._storeKey, 'NX', ...args); + + return result; + } + + return storedMethodNames; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..efa219f --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "redis-function-balancer", + "version": "1.0.0", + "description": "Balance NodeJs functions with Redis", + "main": "index.js", + "scripts": { + "test": "./node_modules/mocha/bin/mocha" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "redis": "^3.0.2", + "ts-node": "^9.1.1", + "typescript": "^4.1.3" + }, + "devDependencies": { + "@types/mocha": "^8.2.0", + "@types/node": "^14.14.17", + "@types/redis": "^2.8.28", + "mocha": "^8.2.1" + } +} diff --git a/test/iterator.ts b/test/iterator.ts new file mode 100644 index 0000000..af2758e --- /dev/null +++ b/test/iterator.ts @@ -0,0 +1,72 @@ +import CallableBalancer from "../index"; +import {beforeEach} from "mocha"; +import {promisify} from "util"; +import assert = require("assert"); + +const redis = require("redis"); +const redisClient = redis.createClient(6379, 'redis'); + +const A = () => new Promise(() => console.log('A')); +const B = () => new Promise(() => console.log('B')); +const C = () => new Promise(() => console.log('C')); + +let methods = [A, B, C]; +let balancer: CallableBalancer; +let zrangeAsync = promisify(redisClient.zrange).bind(redisClient); +describe('Test Callable Balancer', async function () { + beforeEach(async () => { + balancer = new CallableBalancer(methods, redisClient) + await balancer.resetStore(); + }); + + it('check store key generated', async () => { + assert.strictEqual('balancer.A.B.C', balancer.getStoreKey()); + balancer.setMethods([C, B, A]); + assert.strictEqual('balancer.C.B.A', balancer.getStoreKey()); + }); + + it('check iterator first run in default order', async () => { + let iterator = balancer.getAsyncIterator(); + let foo; + let callId = 0; + while ((foo = await iterator.next()) && !foo.done) { + assert.strictEqual(methods[callId], foo.value); + callId++; + } + }); + + it('check redis state with iterator', async () => { + let key = balancer.getStoreKey(); + let result = await zrangeAsync(key, 0, -1); + assert.strictEqual(0, result.length); + let iterator = balancer.getAsyncIterator(); + result = await zrangeAsync(key, 0, -1); + assert.strictEqual(0, result.length); + + let data = await iterator.next(); + result = await zrangeAsync(key, 0, -1); + assert.deepStrictEqual(['B', 'C', 'A'], result); + assert.strictEqual(A, data.value); + + data = await iterator.next(); + result = await zrangeAsync(key, 0, -1); + assert.deepStrictEqual(['C', 'A', 'B'], result); + assert.strictEqual(B, data.value); + + data = await iterator.next(); + result = await zrangeAsync(key, 0, -1); + assert.deepStrictEqual(['A', 'B', 'C'], result); + assert.strictEqual(C, data.value); + }); + + it('test 2 iterators', async () => { + let iterator = await balancer.getAsyncIterator(), + data; + await iterator.next(); + await balancer.increaseMethodRank(B, 2); + + iterator = await balancer.getAsyncIterator(); + data = await iterator.next(); + assert.strictEqual(C, data.value); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fb52e12 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,72 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": [ + "node_modules", + "test" + ] +}