├── .gitignore ├── .env ├── README.md ├── public ├── stylesheets │ └── style.css └── index.html ├── IoC-container ├── providers │ ├── databaseProvider.js │ ├── repositoriesProvider.js │ ├── cacheProvider.js │ ├── queryHandlersProvider.js │ └── httpProvider.js ├── index.js └── Container.js ├── chain-of-responsibility-software-design-pattern.jpg ├── routes ├── index.js └── users.js ├── commons ├── redis.js └── knex.js ├── accessEnv.js ├── repositories ├── UserDataRepository.js ├── GitHubAPIRepository.js ├── StorageQueryHandlersManager.js ├── HttpServiceRepository.js └── DatabaseTableRepository.js ├── app.js ├── package.json ├── query-handlers ├── FileSystemQueryTaskHandler.js ├── PostgreSQLDatabaseQueryTaskHandler.js ├── RESTServiceQueryTaskHandler.js ├── GraphQLServiceQueryTaskHandler.js ├── StorageQueryTaskHandler.js └── RedisCacheQueryTaskHandler.js ├── caches ├── CacheWithEncryption.js ├── CacheWithBytePacking.js └── Cache.js ├── helpers.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | AWS_SDK_ACCESS_ID= 3 | AWS_SDK_ACCESS_SECRET= 4 | AWS_SDK_REGION=us-east-1 5 | AWS_SDK_SESSION_TOKEN=i-session 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # expressjs-api-gateway-coding-patterns-demo 2 | Created with CodeSandbox 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /IoC-container/providers/databaseProvider.js: -------------------------------------------------------------------------------- 1 | const knex = require("../../commons/knex"); 2 | 3 | module.exports = function (c) { 4 | c.service("Database", () => knex); 5 | }; 6 | -------------------------------------------------------------------------------- /chain-of-responsibility-software-design-pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isocroft/expressjs-api-gateway-coding-patterns-demo/HEAD/chain-of-responsibility-software-design-pattern.jpg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Express 5 | 6 | 7 | 8 | 9 |

Express

10 |

Welcome to Express

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /commons/redis.js: -------------------------------------------------------------------------------- 1 | const accessEnv = require("../accessEnv"); 2 | const { createClient } = require("redis"); 3 | 4 | module.exports = createClient({ 5 | url: `redis://${accessEnv("REDIS_USERNAME", "alice")}:${accessEnv( 6 | "REDIS_PASSWORD", 7 | "inwonderland" 8 | )}@${accessEnv("REDIS_SERVER", "custom.redis.server")}:${accessEnv( 9 | "REDIS_PORT", 10 | 6380 11 | )}` 12 | }); 13 | -------------------------------------------------------------------------------- /accessEnv.js: -------------------------------------------------------------------------------- 1 | const cache = {}; 2 | 3 | const accessEnv = (key, defaultValue) => { 4 | if (!(key in process.env)) { 5 | if (defaultValue) { 6 | return defaultValue; 7 | } 8 | throw new Error(`${key} not found in process.env!`); 9 | } 10 | 11 | if (cache[key]) return cache[key]; 12 | 13 | cache[key] = process.env[key]; 14 | 15 | return process.env[key] || defaultValue; 16 | }; 17 | 18 | module.exports = accessEnv; 19 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const container = require("../IoC-container"); 5 | 6 | /* GET users listing. */ 7 | router.get("/", function (req, res, next) { 8 | const { status } = req.query; 9 | container.UserModelRepository.fetchAll(["*"], (buidler) => { 10 | buidler.where("status", status); 11 | }); 12 | res.send("respond with a resource"); 13 | }); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /IoC-container/index.js: -------------------------------------------------------------------------------- 1 | const Container = require("./Container"); 2 | 3 | module.exports = (function () { 4 | let container = new Container(); 5 | 6 | require("./providers/cacheProvider")(container); 7 | require("./providers/databaseProvider")(container); 8 | require("./providers/httpProvider")(container); 9 | require("./providers/queryHandlersProvider")(container); 10 | require("./providers/repositoriesProvider")(container); 11 | 12 | return container; 13 | })(); 14 | -------------------------------------------------------------------------------- /commons/knex.js: -------------------------------------------------------------------------------- 1 | const accessEnv = require("../accessEnv"); 2 | 3 | module.exports = require("knex")({ 4 | client: accessEnv("DB_DRIVER", "pg"), 5 | version: accessEnv("DB_DRIVER_VERSION", "5.7"), 6 | connection: { 7 | host: accessEnv("DB_HOST", "127.0.0.1"), 8 | port: accessEnv("DB_PORT", 5432), 9 | user: accessEnv("DB_USER", ""), 10 | password: accessEnv("DB_PASSWORD", ""), 11 | database: accessEnv("DB_NAME", "app") 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /IoC-container/Container.js: -------------------------------------------------------------------------------- 1 | class Container { 2 | constructor() { 3 | this.services = {}; 4 | } 5 | 6 | service(name, cb) { 7 | Object.defineProperty(this, name, { 8 | get: () => { 9 | if (!this.services.hasOwnProperty(name)) { 10 | this.services[name] = cb(this); 11 | } 12 | 13 | return this.services[name]; 14 | }, 15 | configurable: true, 16 | enumerable: true 17 | }); 18 | 19 | return this; 20 | } 21 | } 22 | 23 | module.exports = Container; 24 | -------------------------------------------------------------------------------- /repositories/UserDataRepository.js: -------------------------------------------------------------------------------- 1 | const DatabaseTableRepository = require("./DatabaseTableRepository"); 2 | 3 | /* @HINT: This is the user data repository class. */ 4 | 5 | class UserDataRepository extends DatabaseTableRepository { 6 | constructor (queryTaskHandlers, Database) { 7 | super(queryTaskHandlers) 8 | this.Database = Database 9 | } 10 | 11 | get table() { 12 | return "users"; 13 | } 14 | 15 | get queryBuilder() { 16 | return this.Database(this.table); 17 | } 18 | 19 | async fetchAllUsers (columnsToFetch = ['*']) { 20 | return await this.fetchAll(columnsToFetch) 21 | } 22 | } 23 | 24 | module.exports = UserDataRepository; 25 | -------------------------------------------------------------------------------- /IoC-container/providers/repositoriesProvider.js: -------------------------------------------------------------------------------- 1 | const GitHubAPIRepository = require("../../repositories/GitHubAPIRepository"); 2 | const UserDataRepository = require("../../repositories/UserDataRepository"); 3 | 4 | module.exports = function (c) { 5 | c.service( 6 | "UserModelRepository", 7 | (c) => 8 | new UserDataRepository([ 9 | c.RedisCacheQueryTaskHandler, 10 | c.PostgreSQLDatabaseQueryTaskHandler 11 | ], c.Database) 12 | ); 13 | c.service( 14 | "GitHubAPIRepository", 15 | (c) => new GitHubAPIRepository([ 16 | c.RESTServiceQueryTaskHandler, 17 | /* @HINT: Redundancy, in case GitHub REST API is down, use the GraphQL API instead */ 18 | c.GraphQLServiceQueryTaskHandler 19 | ], c.LRUCache) 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /IoC-container/providers/cacheProvider.js: -------------------------------------------------------------------------------- 1 | const accessEnv = require("../../accessEnv"); 2 | const redis = require("../../commons/redis"); 3 | 4 | const QuickLRU = require('quick-lru'); 5 | 6 | const Cache = require("../../caches/Cache"); 7 | const CacheWithBytePacking = require("../../caches/CacheWithBytePacking"); 8 | const CacheWithEncryption = require("../../caches/CacheWithEncryption"); 9 | 10 | module.exports = function (c) { 11 | c.service("LRUCache", () => new QuickLRU({ maxSize: 1000, maxAge: 180000 })) 12 | c.service("RedisCache", () => redis); 13 | c.service( 14 | "Cache", 15 | (c) => new Cache(redis, accessEnv("REDIS_KEY_PREFIX", "")) 16 | ); 17 | c.service("BytePackedCache", (c) => new CacheWithBytePacking(c.Cache)); 18 | c.service( 19 | "EncryptedCache", 20 | (c) => new CacheWithEncryption(c.CacheWithBytePacking) 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | 3 | dotenv.config(); 4 | 5 | const express = require("express"); 6 | const path = require("path"); 7 | const cookieParser = require("cookie-parser"); 8 | const logger = require("morgan"); 9 | const accessEnv = require("./accessEnv"); 10 | 11 | const indexRouter = require("./routes/index"); 12 | const usersRouter = require("./routes/users"); 13 | 14 | const app = express(); 15 | 16 | app.use(logger("dev")); 17 | app.use(express.json()); 18 | app.use(express.urlencoded({ extended: false })); 19 | app.use(cookieParser()); 20 | app.use(express.static(path.join(__dirname, "public"))); 21 | 22 | app.use("/", indexRouter); 23 | app.use("/users", usersRouter); 24 | 25 | const PORT = accessEnv("PORT", 3001); 26 | 27 | const listener = app.listen(PORT, "127.0.0.1", () => { 28 | console.info(`API gateway listening on port ${listener.address().port}`); 29 | }); 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expressjs-demo-coding-patterns", 3 | "version": "1.0.0", 4 | "description": "A DEMO with Express.js showing implementation of a cache, database and graphql query handlers for a data repository using 3 coding patterns \n\n- chain-of-responsibility pattern\n- decorator pattern\n- repository pattern", 5 | "main": "app.js", 6 | "private": true, 7 | "scripts": { 8 | "start": "nodemon app.js localhost 8080" 9 | }, 10 | "dependencies": { 11 | "aws4": "1.12.0", 12 | "aws-sdk": "2.1617.0", 13 | "cookie-parser": "~1.4.4", 14 | "debug": "~2.6.9", 15 | "dotenv": "8.2.0", 16 | "express": "~4.16.1", 17 | "got": "8.3.2", 18 | "gotql": "1.0.0", 19 | "knex": "0.21.1", 20 | "morgan": "~1.9.1", 21 | "pg": "8.10.0", 22 | "quick-lru": "5.1.1", 23 | "redis": "4.6.5" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "1.18.4" 27 | }, 28 | "keywords": [] 29 | } 30 | -------------------------------------------------------------------------------- /IoC-container/providers/queryHandlersProvider.js: -------------------------------------------------------------------------------- 1 | const PostgreSQLDatabaseQueryTaskHandler = require("../../query-handlers/PostgreSQLDatabaseQueryTaskHandler"); 2 | const GraphQLServiceQueryTaskHandler = require("../../query-handlers/GraphQLServiceQueryTaskHandler"); 3 | const RESTServiceQueryTaskHandler = require("../../query-handlers/RESTServiceQueryTaskHandler"); 4 | const RedisCacheQueryTaskHandler = require("../../query-handlers/RedisCacheQueryTaskHandler"); 5 | 6 | module.exports = function (c) { 7 | c.service( 8 | "PostgreSQLDatabaseQueryTaskHandler", 9 | (c) => new PostgreSQLDatabaseQueryTaskHandler(c.Database) 10 | ); 11 | c.service( 12 | "RedisCacheQueryTaskHandler", 13 | (c) => new RedisCacheQueryTaskHandler(c.EncryptedCache) 14 | ); 15 | c.service( 16 | "GraphQLServiceQueryTaskHandler", 17 | (c) => new GraphQLServiceQueryTaskHandler(c.GraphQLClient) 18 | ); 19 | c.service( 20 | "RESTServiceQueryTaskHandler", 21 | (c) => new RESTServiceQueryTaskHandler(c.RestClient) 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /query-handlers/FileSystemQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("./StorageQueryTaskHandler"); 2 | 3 | const { isEmpty } = require("../helpers"); 4 | 5 | /* @HINT: This is a file system query task handler for any file system and files/folders. */ 6 | class FileSystemQueryTaskHandler extends StorageQueryTaskHandler { 7 | constructor(fileClient, contextTransformMap = {}) { 8 | super( 9 | "file system handler execution declined; either stalled connection or refused connection" 10 | ); 11 | this.fileClient = fileClient; 12 | this.migrateContextTransformMap = contextTransformMap; 13 | } 14 | 15 | migrateContext (builderOrRequest) { 16 | if (typeof builderOrRequest.url !== "string") { 17 | return {}; 18 | } 19 | 20 | return this.migrateContextTransformMap[`${builderOrRequest.method.toLowerCase()}|${builderOrRequest.url}`]; 21 | } 22 | 23 | async beginProcessing (fileSystemQuery) { 24 | return isEmpty(fileSystemQuery) ? this.skipHandlerProcessing() : ""; 25 | } 26 | 27 | async finalizeProcessing() { 28 | console.log("e don finish file system request ooo"); 29 | } 30 | 31 | async finalizeProcessingWithError() { 32 | console.error("e don finish file system request with error ooo"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /repositories/GitHubAPIRepository.js: -------------------------------------------------------------------------------- 1 | const HttpServiceRepository = require("./HttpServiceRepository"); 2 | 3 | /* @HINT: This is the github (third-party) api repository class. */ 4 | 5 | class GitHubAPIRepository extends HttpServiceRepository { 6 | constructor (queryTaskHandlers, httpLRUCache = null) { 7 | super(queryTaskHandlers) 8 | this.httpCache = httpLRUCache 9 | } 10 | 11 | get apiVersion () { 12 | return "2022-11-28" 13 | } 14 | 15 | baseURL(pathName = "/") { 16 | return `https://api.github.com${pathName}`; 17 | } 18 | 19 | requestConfig( 20 | pathName = "/", 21 | httpMethod = "GET", 22 | headers = {}, 23 | requestParams = {} 24 | ) { 25 | const $config = super.requestConfig( 26 | pathName, 27 | httpMethod, 28 | Object.assign(headers, { 29 | accept: 'application/json', 30 | 'content-type': 'application/json' 31 | }), 32 | requestParams 33 | ) 34 | 35 | return Object.assign( 36 | {}, 37 | { timeout: this.httpTimeout, cache: this.httpCache }, 38 | $config 39 | ); 40 | } 41 | 42 | async getAllNotifications (pollingInterval = 60) { 43 | return this.httpGetRequest({ 44 | headers: { 45 | 'x-poll-interval': String(pollingInterval), 46 | 'X-GitHub-Api-Version': this.apiVersion 47 | }, 48 | pathName: '/notifications' 49 | }) 50 | } 51 | } 52 | 53 | module.exports = GitHubAPIRepository; 54 | -------------------------------------------------------------------------------- /repositories/StorageQueryHandlersManager.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("../query-handlers/StorageQueryTaskHandler"); 2 | 3 | /* @HINT: This is a storage query manger for all data access an storage needs of a data repository. */ 4 | class StorageQueryHandlersManager { 5 | constructor(storageQueryHandlers = []) { 6 | const totalCount = storageQueryHandlers.length; 7 | 8 | if (totalCount === 0) { 9 | throw new Error("Cannot proceed: No storage query task handlers found"); 10 | } 11 | 12 | for (let count = 0; count + 1 < totalCount && totalCount > count; count++) { 13 | const previousQueryTaskHandler = storageQueryHandlers[count]; 14 | const nextQueryTaskHandler = storageQueryHandlers[count + 1]; 15 | 16 | if (previousQueryTaskHandler instanceof StorageQueryTaskHandler) { 17 | previousQueryTaskHandler.setNextHandler(nextQueryTaskHandler); 18 | } else { 19 | continue; 20 | } 21 | } 22 | 23 | const [rootTaskHandler] = storageQueryHandlers; 24 | 25 | this.rootTaskHandler = rootTaskHandler; 26 | } 27 | 28 | async execute(queryObject) { 29 | return this.rootTaskHandler.handle( 30 | queryObject 31 | ); 32 | } 33 | 34 | swapRootHandler(newRootTaskHandler) { 35 | if (!(newRootTaskHandler instanceof StorageQueryTaskHandler)) { 36 | throw new Error( 37 | "Cannot proceed: root handler not swapable as object provided isn't a handler" 38 | ); 39 | } 40 | 41 | const formerRootTaskHandler = this.rootTaskHandler; 42 | 43 | this.rootTaskHandler = newRootTaskHandler; 44 | this.rootTaskHandler.setNextHandler(formerRootTaskHandler); 45 | } 46 | } 47 | 48 | module.exports = StorageQueryHandlersManager; 49 | -------------------------------------------------------------------------------- /caches/CacheWithEncryption.js: -------------------------------------------------------------------------------- 1 | const Cache = require("./Cache"); 2 | const CacheWithBytePacking = require("./CacheWithBytePacking"); 3 | const { encryptData, decryptData } = require("../helpers"); 4 | 5 | /* @NOTE: | We can change the behavior of this class by switching its' injected cache */ 6 | 7 | /* @NOTE: By switching the injected cache with a class that has the same interface contract, we maintain simplicity */ 8 | 9 | /* @NOTE: This class implements the Decorator coding pattern for encrypted cache */ 10 | 11 | /* @HINT: This is the cache (with encryption) base/parent class. */ 12 | 13 | class CacheWithEncryption { 14 | constructor(redis) { 15 | if (!(redis instanceof CacheWithBytePacking) || !(redis instanceof Cache)) { 16 | throw new Error("Cannot construct encrypted cache instance"); 17 | } 18 | 19 | this.$_ = redis; 20 | } 21 | 22 | async get(key) { 23 | const buffer = await this.$_.get(key, { 24 | asBuffer: true, 25 | withEncoding: "base64" 26 | }); 27 | 28 | return decryptData(buffer); 29 | } 30 | 31 | async set(key, value) { 32 | await this.$_.set(key, encryptData(value), { asBuffer: true }); 33 | } 34 | 35 | async establishConnectionWithRedisServer({ timeoutInMilliSeconds }) { 36 | await this.$_.establishConnectionWithRedisServer({ timeoutInMilliSeconds }); 37 | } 38 | 39 | async destroyConnectionWithRedisServer() { 40 | await this.$_.destroyConnectionWithRedisServer(); 41 | } 42 | 43 | async hasKey(key) { 44 | return await this.$_.hasKey(key); 45 | } 46 | 47 | async allKeys(keyOrPattern) { 48 | return await this.$_.allKeys(keyOrPattern); 49 | } 50 | } 51 | 52 | module.exports = CacheWithEncryption; 53 | -------------------------------------------------------------------------------- /caches/CacheWithBytePacking.js: -------------------------------------------------------------------------------- 1 | const Cache = require("./Cache"); 2 | const { 3 | convertBinaryStringToBytesArray, 4 | convertBytesArrayToBinaryString, 5 | pack, 6 | unpack 7 | } = require("../helpers"); 8 | 9 | /* @CHECK: https://blog.shalvah.me/posts/packing-and-unpacking-bytes */ 10 | 11 | /* @NOTE: This base class implements the Decorator coding pattern for byte-packed cache */ 12 | 13 | /* @HINT: This is the cache (with byte-packing) base/parent class. */ 14 | 15 | class CacheWithBytePacking { 16 | constructor(redis) { 17 | if (!(redis instanceof Cache)) { 18 | throw new Error("Cannot construct byte-packed cache instance"); 19 | } 20 | 21 | this._$ = redis; 22 | } 23 | 24 | async get(key, { asBuffer = true, withEncoding = "utf8" }) { 25 | const buffer = await this._$.get(key, { 26 | asBuffer: asBuffer || true, 27 | withEncoding 28 | }); 29 | 30 | const bytes = []; 31 | 32 | for (let byte of buffer.values()) { 33 | bytes.push(byte); 34 | } 35 | 36 | return Buffer.from( 37 | unpack(convertBytesArrayToBinaryString(new Uint8Array(bytes))), 38 | withEncoding 39 | ); 40 | } 41 | 42 | async set(key, value, { asBuffer = true }) { 43 | if (typeof value !== "string") { 44 | return; 45 | } 46 | 47 | let modifiedValue = convertBinaryStringToBytesArray(value); 48 | 49 | await this._$.set(key, pack(modifiedValue), { 50 | asBuffer: asBuffer || true 51 | }); 52 | } 53 | 54 | async establishConnectionWithRedisServer({ timeoutInMilliSeconds }) { 55 | await this._$.establishConnectionWithRedisServer({ timeoutInMilliSeconds }); 56 | } 57 | 58 | async destroyConnectionWithRedisServer() { 59 | await this._$.destroyConnectionWithRedisServer(); 60 | } 61 | 62 | async hasKey(key) { 63 | return await this._$.hasKey(key); 64 | } 65 | 66 | async allKeys(keyOrPattern) { 67 | return await this._$.allKeys(keyOrPattern); 68 | } 69 | } 70 | 71 | module.exports = CacheWithBytePacking; 72 | -------------------------------------------------------------------------------- /caches/Cache.js: -------------------------------------------------------------------------------- 1 | const { commandOptions } = require("redis"); 2 | const { waitFor } = require("../helpers"); 3 | 4 | /* @HINT: This is the reis cache base/parent class. */ 5 | 6 | class Cache { 7 | constructor(redis, keyPrefix = "") { 8 | this.keyPrefix = keyPrefix; 9 | this.redis = redis; 10 | } 11 | 12 | async set(key, value, { asBuffer = false }) { 13 | if (typeof value !== "string") { 14 | return; 15 | } 16 | // Buffer.from("", "utf16le"); 17 | await this.redis.set( 18 | this.keyPrefix + key, 19 | asBuffer ? Buffer.from(value) : value 20 | ); 21 | } 22 | 23 | async get(key, { asBuffer = false, withEncoding = "utf8" }) { 24 | if (asBuffer) { 25 | const buffer = await this.redis.get( 26 | commandOptions({ returnBuffers: asBuffer }), 27 | this.keyPrefix + key 28 | ); 29 | 30 | if (!buffer.isEncoding(withEncoding)) { 31 | const characters = buffer.toString(withEncoding); 32 | return Buffer.from(characters, withEncoding); 33 | } 34 | 35 | return buffer; 36 | } else { 37 | return await this.redis.get(this.keyPrefix + key); 38 | } 39 | } 40 | 41 | async allKeys(keyOrPattern) { 42 | const keys = []; 43 | for await (const scannedKey of this.redis.scanIterator({ 44 | type: "string", 45 | MATCH: keyOrPattern, 46 | COUNT: 10 47 | })) { 48 | keys.push(scannedKey); 49 | } 50 | 51 | return keys; 52 | } 53 | 54 | async hasKey(key) { 55 | const keys = await this.allKeys(key); 56 | return keys.length > 0; 57 | } 58 | 59 | async establishConnectionWithRedisServer({ timeoutInMilliSeconds }) { 60 | await this.redis.connect(); 61 | await waitFor( 62 | () => this.redis.isOpen && this.redis.isReady, 63 | 100, 64 | timeoutInMilliSeconds 65 | ).orThrow(new Error("Couldn't establish connection with redis server")); 66 | } 67 | 68 | async destroyConnectionWithRedisServer() { 69 | await this.redis.disconnect(); 70 | } 71 | } 72 | 73 | module.exports = Cache; 74 | -------------------------------------------------------------------------------- /IoC-container/providers/httpProvider.js: -------------------------------------------------------------------------------- 1 | const accessEnv = require("../../accessEnv"); 2 | 3 | const gotQl = require('gotql') 4 | const got = require("got"); 5 | 6 | const url = require('url'); 7 | const AWS = require('aws-sdk'); 8 | const AWS4 = require('aws4'); 9 | 10 | const { isEmpty } = require("../../helpers"); 11 | 12 | const awsConfig = new AWS.Config({ 13 | accessKeyId: accessEnv("AWS_SDK_ACCESS_ID", 'ACC_ID'), 14 | secretAccessKey: accessEnv("AWS_SDK_ACCESS_SECRET", 'ACC_SECRET'), 15 | region: accessEnv("AWS_SDK_REGION", 'us-west-2'), 16 | credentials: new AWS.Credentials({ 17 | accessKeyId: accessEnv("AWS_SDK_ACCESS_ID", 'ACC_ID'), 18 | secretAccessKey: accessEnv("AWS_SDK_ACCESS_SECRET", 'ACC_SECRET'), 19 | sessionToken: accessEnv("AWS_SDK_SESSION_TOKEN", 'session') 20 | }) 21 | }); 22 | 23 | const awsSdkOptions = { 24 | region: awsConfig.region, 25 | headers: { 26 | accept: 'application/json', 27 | 'content-type': 'application/json' 28 | }, 29 | method: 'GET', 30 | json: true 31 | }; 32 | 33 | /* @NOTE: | We are wrapping and exposing a similar contract to the logic being wrapped for consumption */ 34 | 35 | /* @NOTE: This function implements the Adapter coding pattern for data query tasks */ 36 | 37 | const awsClient = (uri, options) => { 38 | let requestPayload = {} 39 | /* @HINT: We need to parse the URL before passing it to `got` so `aws4` can sign the request */ 40 | if (typeof uri === "string") { 41 | requestPayload = Object.assign( 42 | typeof url['parse'] === "function" 43 | ? url.parse(uri) 44 | : new url.URL(uri), 45 | awsSdkOptions, 46 | options || {} 47 | ); 48 | } else { 49 | if (uri instanceof Object) { 50 | requestPayload = uri 51 | } 52 | } 53 | 54 | if (isEmpty(requestPayload)) { 55 | throw new Error("no valid http request object"); 56 | } 57 | 58 | AWS4.sign( 59 | requestPayload, 60 | awsConfig.credentials 61 | ); 62 | 63 | return got(requestPayload); 64 | }; 65 | 66 | module.exports = function (c) { 67 | c.service("AWSHttpClient", () => awsClient); 68 | c.service("RestClient", () => got); 69 | c.service("GraphQLClient", () => gotQl) 70 | }; 71 | -------------------------------------------------------------------------------- /query-handlers/PostgreSQLDatabaseQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("./StorageQueryTaskHandler"); 2 | 3 | /* @NOTE: | Don't create premature parent classes | Keep it simple! */ 4 | 5 | /* @HINT: This is a database query task handler for a PostgreSQL database. */ 6 | /* @HINT: There is no need to create a parent class `DatabaseQueryTaskHandler` yet. */ 7 | /* @HINT: 8 | If for the meantime PostgreSQL is the database of choice now and there's no 9 | indication that this will change in the forseeable future, then, there no need 10 | to preempt the future by creating a parent base class (`DatabaseQueryTaskHandler`) 11 | and then extending it to create `PostgreSQLDatabaseQueryTaskHandler` derived class 12 | or a `MySQLDatabaseQueryTaskHandler` or `MSSQLDatabaseQueryTaskHandler` derived 13 | classes either 14 | */ 15 | 16 | class PostgreSQLDatabaseQueryTaskHandler extends StorageQueryTaskHandler { 17 | constructor(databaseClient) { 18 | super("database handler execution declined"); 19 | this.database = databaseClient; 20 | this.databaseConnection = null; 21 | } 22 | 23 | async beginProcessing(builderOrRequest) { 24 | let canProceedWithProcessing = false; 25 | 26 | /* @HINT: Check if variable `builderOrRequest` is a knex query builder instance */ 27 | if (builderOrRequest instanceof Object && typeof builderOrRequest.toSQL === "function") { 28 | canProceedWithProcessing = true; 29 | } 30 | 31 | if (!canProceedWithProcessing) { 32 | return this.skipHandlerProcessing(); 33 | } 34 | 35 | try { 36 | this.databaseConnection = await this.database.client.acquireConnection(); 37 | this.databaseConnection.query('SET timezone="UTC";'); 38 | } catch (error) { 39 | return this.skipHandlerProcessing(error); 40 | } 41 | 42 | /* @HINT: Setup a database query timeout via the knex query builder instance */ 43 | builderOrRequest.timeout(5500, { 44 | cancel: true 45 | }); 46 | 47 | /* @HINT: database query result initialization */ 48 | let queryResult = null; 49 | 50 | try { 51 | /* @HINT: Setup database transaction for database query */ 52 | await this.database.transaction(async (transaction) => { 53 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 54 | queryResult = await builderOrRequest.transacting(transaction); 55 | }); 56 | } catch (error) { 57 | return this.skipHandlerProcessing(error); 58 | } 59 | 60 | return queryResult; 61 | } 62 | 63 | async finalizeProcessing() { 64 | await this.database.client.releaseConnection(this.databaseConnection); 65 | } 66 | 67 | async finalizeProcessingWithError() { 68 | console.error("e don finish postgre sql database query with error ooo"); 69 | } 70 | } 71 | 72 | module.exports = PostgreSQLDatabaseQueryTaskHandler; 73 | -------------------------------------------------------------------------------- /repositories/HttpServiceRepository.js: -------------------------------------------------------------------------------- 1 | const StorageQueryHandlersManager = require("./StorageQueryHandlersManager"); 2 | 3 | /* @NOTE: | Implicit dependencies can often lead to tight coupling but not always! */ 4 | 5 | /* @HINT: This is a http service repository base class. */ 6 | class HttpServiceRepository { 7 | constructor(queryHandlers = []) { 8 | /* @NOTE: | Composition can also lead to tight coupling! */ 9 | /* @NOTE: Tight coupling here: will allow temporarily */ 10 | this.queryManager = new StorageQueryHandlersManager(queryHandlers); 11 | this.timeoutInMilliSeconds = 1500; 12 | } 13 | 14 | set newRootHandler(rootHandler) { 15 | this.queryManager.swapRootHandler(rootHandler); 16 | } 17 | 18 | get httpTimeout () { 19 | return this.timeoutInMilliSeconds 20 | } 21 | 22 | get apiVersion () { 23 | throw new Error("api version not set") 24 | } 25 | 26 | set httpTimeout (newHttpTimeout = 0) { 27 | if (typeof newHttpTimeout === "number" 28 | && !Number.isNaN(newHttpTimeout)) { 29 | this.timeoutInMilliSeconds = newHttpTimeout 30 | } 31 | } 32 | 33 | baseURL(pathName = "/") { 34 | throw new Error(`base URL string with ${pathName} is not available from abstract class`); 35 | } 36 | 37 | requestConfig( 38 | pathName = "/", 39 | httpMethod = "GET", 40 | headers = {}, 41 | requestParams = {} 42 | ) { 43 | return { 44 | url: this.baseURL(pathName), 45 | method: httpMethod, 46 | [httpMethod === "GET" || httpMethod === "HEAD" ? "query" : "body"]: requestParams, 47 | headers, 48 | json: headers['content-type'].includes('json'), 49 | form: headers['content-type'].includes('form-data') 50 | }; 51 | } 52 | 53 | async httpPostRequest({ headers = {}, pathName = "/", requestParams = {} }) { 54 | const httpRequest = this.requestConfig( 55 | pathName, 56 | "POST", 57 | headers, 58 | requestParams 59 | ); 60 | 61 | return this.queryManager.execute(httpRequest); 62 | } 63 | 64 | async httpGetRequest({ headers = {}, pathName = "/", requestParams = {} }) { 65 | const httpRequest = this.requestConfig( 66 | pathName, 67 | "GET", 68 | headers, 69 | requestParams 70 | ); 71 | 72 | return this.queryManager.execute(httpRequest); 73 | } 74 | 75 | async httpPutRequest({ headers = {}, pathName = "/", requestParams = {} }) { 76 | const httpRequest = this.requestConfig( 77 | pathName, 78 | "PUT", 79 | headers, 80 | requestParams 81 | ); 82 | 83 | return this.queryManager.execute(httpRequest); 84 | } 85 | 86 | async httpDeleteRequest({ headers = {}, pathName = "/", requestParams = {} }) { 87 | const httpRequest = this.requestConfig( 88 | pathName, 89 | "DELETE", 90 | headers, 91 | requestParams 92 | ); 93 | 94 | return this.queryManager.execute(httpRequest); 95 | } 96 | } 97 | 98 | module.exports = HttpServiceRepository; 99 | -------------------------------------------------------------------------------- /query-handlers/RESTServiceQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("./StorageQueryTaskHandler"); 2 | 3 | /* @HINT: This is a rest service query task handler for a REST API server. */ 4 | class RESTServiceQueryTaskHandler extends StorageQueryTaskHandler { 5 | constructor(restClient) { 6 | super( 7 | "rest service handler execution declined; either stalled connection or refused connection" 8 | ); 9 | this.httpClient = restClient; 10 | } 11 | 12 | async beginProcessing(builderOrRequest) { 13 | let canProceedWithProcessing = false; 14 | let result = null; 15 | 16 | if (builderOrRequest instanceof Object 17 | && typeof builderOrRequest.url === "string" 18 | && typeof builderOrRequest.method === "string" 19 | && [ 20 | "get", 21 | "post", 22 | "patch", 23 | "delete", 24 | "head", 25 | "put" 26 | ].includes(builderOrRequest.method.toLowerCase())) { 27 | canProceedWithProcessing = true; 28 | } 29 | 30 | if (!canProceedWithProcessing) { 31 | return this.skipHandlerProcessing(); 32 | } 33 | 34 | try { 35 | result = await this.httpClient(builderOrRequest) 36 | return result; 37 | } catch (error) { 38 | const HttpError = this.httpClient.HTTPError; 39 | const RequestError = this.httpClient.RequestError; 40 | const CancelError = this.httpClient.CancelError; 41 | 42 | if (error instanceof HttpError) { 43 | if (error.statusCode === "400" || error.statusCode === "404" || error.statusCode === "401") { 44 | return this.skipHandlerProcessingWithCustomMessage( 45 | `server "${error.url}" request encountered a service error; reason: ${error.statusMessage}` 46 | ); 47 | } 48 | return this.skipHandlerProcessingWithCustomMessage( 49 | `http request to ${error.url} failed; reason: ${error.message}` 50 | ); 51 | } else if (error instanceof RequestError) { 52 | if (error.code === 'ETIMEDOUT' 53 | || error.code === 'ECONNREFUSED' 54 | || error.statusCode === "500" 55 | || error.statusCode === "503") { 56 | /* @HINT: This REST API server is down, trigger the next handler to run */ 57 | return this.skipHandlerProcessing(); 58 | } 59 | return this.skipHandlerProcessingWithCustomMessage( 60 | `http request to ${error.url} failed; reason: ${error.message}` 61 | ); 62 | } else if (error instanceof CancelError) { 63 | return this.skipHandlerProcessingWithCustomMessage( 64 | `server "${error.url}" request was cancelled` 65 | ); 66 | } 67 | } 68 | } 69 | 70 | async finalizeProcessing(builderOrRequest) { 71 | console.log(`e don finish rest service request ooo; for ${builderOrRequest.url}`); 72 | } 73 | 74 | async finalizeProcessingWithError(builderOrRequest, error) { 75 | console.error(`e don finish rest service request with error: ${error} for ${builderOrRequest.url}`); 76 | } 77 | } 78 | 79 | module.exports = RESTServiceQueryTaskHandler; 80 | -------------------------------------------------------------------------------- /query-handlers/GraphQLServiceQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("./StorageQueryTaskHandler"); 2 | const url = require('url'); 3 | 4 | const { camelCaseify } = require("../helpers"); 5 | 6 | /* @HINT: This is a graphql service query task handler for a GraphQL API server. */ 7 | class GraphQLServiceQueryTaskHandler extends StorageQueryTaskHandler { 8 | constructor(graphQlClient, operationNameToFieldsNameMap = { '_': [] }) { 9 | super("graphql service handler execution declined"); 10 | this.httpClient = graphQlClient; 11 | 12 | this.pathname = "/graphql" 13 | this.operationNamesMap = operationNameToFieldsNameMap; 14 | } 15 | 16 | set updateGraphQlServicePathname (newPathname = "/") { 17 | this.pathname = newPathname 18 | } 19 | 20 | set updateGraphQlOperationNameToFieldsMap (newOperationNamesMap) { 21 | this.operationNamesMap = newOperationNamesMap 22 | } 23 | 24 | get graphQlServicePathname () { 25 | return this.pathname 26 | } 27 | 28 | get graphQlOperationNameToFieldsMap () { 29 | return this.operationNamesMap 30 | } 31 | 32 | async beginProcessing(builderOrRequest) { 33 | let result = null 34 | let canProceedWithProcessing = false; 35 | 36 | /* @HINT: Check if variable `builderOrRequest` is a knex query builder instance */ 37 | if (builderOrRequest instanceof Object 38 | && typeof builderOrRequest.url === "string" 39 | && builderOrRequest.url.endsWith(this.graphQlServicePathname)) { 40 | canProceedWithProcessing = true; 41 | } 42 | 43 | if (!canProceedWithProcessing) { 44 | return this.skipHandlerProcessing(); 45 | } 46 | 47 | const url = builderOrRequest.url 48 | const type = builderOrRequest.type 49 | const query = builderOrRequest[type] 50 | 51 | delete builderOrRequest.url 52 | delete builderOrRequest.type 53 | delete builderOrRequest[type] 54 | 55 | try { 56 | result = await this.graphQlClient[type](url, query, builderOrRequest) 57 | return result 58 | } catch (error) { 59 | /* @HINT: re-throw the error using `error.message` */ 60 | this.skipHandlerProcessingWithCustomMessage(error.message) 61 | } 62 | } 63 | 64 | migrateContext (builderOrRequest) { 65 | let type = "query" 66 | let operationName = "_" 67 | 68 | if (builderOrRequest['query'] instanceof Object 69 | || builderOrRequest['mutation'] instanceof Object) { 70 | return builderOrRequest; 71 | } 72 | 73 | /* @HINT: transform request object meant for a rest service to a graph ql service payload object */ 74 | if (typeof builderOrRequest.url === "string" 75 | && !builderOrRequest.url.endsWith(this.graphQlServicePathname)) { 76 | const { host, pathname } = typeof url['parse'] === "function" 77 | ? url.parse(uri) 78 | : new url.URL(uri); 79 | 80 | operationName = camelCaseify(pathname) 81 | builderOrRequest.url = `${host}${this.graphQlServicePathname}`; 82 | } 83 | 84 | if (typeof builderOrRequest.method === "string" 85 | && builderOrRequest.method !== "POST") { 86 | type = builderOrRequest.method === "GET" || builderOrRequest.method === "GET" 87 | ? "query" 88 | : "mutation" 89 | 90 | delete builderOrRequest.method; 91 | } 92 | 93 | /* @TODO: add `variable` and `args` to `operation` object the JSON-query object for proper GraphQL types */ 94 | builderOrRequest[type] = { 95 | operation: { 96 | name: operationName, 97 | fields: this.graphQlOperationToFieldsNameMap[operationName] 98 | } 99 | } 100 | 101 | builderOrRequest.useHttp2 = true 102 | builderOrRequest.type = type 103 | 104 | delete builderOrRequest.body 105 | delete builderOrRequest.query 106 | delete builderOrRequest.form 107 | delete builderOrRequest.timeout 108 | 109 | return builderOrRequest 110 | } 111 | 112 | async finalizeProcessing() { 113 | console.log("e don finish graphql service request ooo"); 114 | } 115 | 116 | async finalizeProcessingWithError() { 117 | console.error("e don finish graphql service request with error ooo"); 118 | } 119 | } 120 | 121 | module.exports = GraphQLServiceQueryTaskHandler; 122 | -------------------------------------------------------------------------------- /repositories/DatabaseTableRepository.js: -------------------------------------------------------------------------------- 1 | const StorageQueryHandlersManager = require("./StorageQueryHandlersManager"); 2 | const { isEmpty } = require("../helpers"); 3 | 4 | /* @NOTE: | Implicit dependencies can often lead to tight coupling but not always! */ 5 | 6 | /* @HINT: This is a database table repository base class. */ 7 | class DatabaseTableRepository { 8 | constructor(queryHandlers = []) { 9 | /* @NOTE: | Composition can also lead to tight coupling! */ 10 | /* @NOTE: Tight coupling (and tight cohesion) here: will allow temporarily */ 11 | /* @INFO: `StorageQueryHandlersManager` is a concrete class so we don't extend it, we compose around it */ 12 | this.queryManager = new StorageQueryHandlersManager(queryHandlers); 13 | } 14 | 15 | get table() { 16 | throw new Error("table name value is not available from abstract class"); 17 | } 18 | 19 | get queryBuidler() { 20 | throw new Error( 21 | "query builder object is not available from abstract class" 22 | ); 23 | } 24 | 25 | set newRootHandler(rootHandler) { 26 | this.queryManager.swapRootHandler(rootHandler); 27 | } 28 | 29 | whenConflict(queryBuidler, conflictColumn = undefined) { 30 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 31 | return typeof conflictColumn === "string" 32 | ? queryBuidler.onConflict(conflictColumn) 33 | : queryBuidler; 34 | } 35 | 36 | merge(queryBuidler, columnsToMerge = []) { 37 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 38 | return Array.isArray(columnsToMerge) && columnsToMerge.length > 0 39 | ? queryBuidler.merge(columnsToMerge) 40 | : queryBuidler.merge(); 41 | } 42 | 43 | addWhereClauses(queryBuidler, whereClausesCallback = () => undefined) { 44 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 45 | return typeof whereClausesCallback === "function" 46 | ? queryBuidler.where(whereClausesCallback) 47 | : queryBuidler; 48 | } 49 | 50 | async fetchAll(columnsToFetch = ["*"], whereClausesCallback = undefined) { 51 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 52 | const queryBuilder = this.queryBuilder; 53 | const { addWhereClauses } = this; 54 | 55 | if (!columnsToFetch || columnsToFetch.length === 0) { 56 | columnsToFetch = ["*"]; 57 | } 58 | 59 | return this.queryManager.execute( 60 | addWhereClauses(queryBuilder, whereClausesCallback) 61 | .select(...columnsToFetch) 62 | .clone() 63 | ); 64 | } 65 | 66 | async modify( 67 | columnsToFetch = ["*"], 68 | whereClausesCallback = undefined, 69 | rowsToUpdate = {} 70 | ) { 71 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 72 | const queryBuilder = this.queryBuilder; 73 | const { addWhereClauses } = this; 74 | 75 | if (isEmpty(rowsToUpdate)) { 76 | throw new Error("Cannot proceed: No rows to update found"); 77 | } 78 | 79 | if (!columnsToFetch || columnsToFetch.length === 0) { 80 | columnsToFetch = ["*"]; 81 | } 82 | 83 | return this.queryManager.execute( 84 | addWhereClauses(queryBuilder, whereClausesCallback) 85 | .update(rowsToUpdate, columnsToFetch) 86 | .clone() 87 | ); 88 | } 89 | 90 | async fetchFirst() { 91 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 92 | const queryBuilder = this.queryBuilder; 93 | 94 | return this.queryManager.execute(queryBuilder.first().clone()); 95 | } 96 | 97 | async fetchFirstWhere(whereClausesCallback = undefined) { 98 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 99 | const queryBuilder = this.queryBuilder; 100 | const { addWhereClauses } = this; 101 | 102 | return this.queryManager.execute( 103 | addWhereClauses(queryBuilder, whereClausesCallback).first().clone() 104 | ); 105 | } 106 | 107 | async keepWhenConflict( 108 | rowsToInsert = [], 109 | conflictColumn = "id", 110 | columnsToMerge = [] 111 | ) { 112 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 113 | const queryBuilder = this.queryBuilder; 114 | const { merge, whenConflict } = this; 115 | 116 | return this.queryManager.execute( 117 | merge( 118 | whenConflict( 119 | queryBuilder.insert( 120 | rowsToInsert.length === 1 ? rowsToInsert[0] : rowsToInsert 121 | ), 122 | conflictColumn 123 | ), 124 | columnsToMerge 125 | ).clone() 126 | ); 127 | } 128 | } 129 | 130 | module.exports = DatabaseTableRepository; 131 | -------------------------------------------------------------------------------- /query-handlers/StorageQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | /* @NOTE: | We have brought together all the common logic to this base class here! */ 2 | 3 | /* @NOTE: This base class implements the Chain-Of-Responsibility coding pattern for data query tasks */ 4 | 5 | /* @INFO: 6 | In a language like PHP or Java, this class will be an abstract class with 4 protected and abstract methods: 7 | 8 | - beginProcessing(...); 9 | - finalizeProcessing(...); 10 | - finalizeProcessingWithError(...); 11 | - alternateProcessing(...); 12 | 13 | This abstract class will also have 1 protected and final method: 14 | 15 | - skipHandlerProcessing(...); 16 | 17 | This abstract class will also have 2 public and final methods: 18 | 19 | - handle(...); 20 | - setNextHandler(...); 21 | 22 | This abstract class will have 2 protected methods 23 | 24 | - migrateQueryTask(...); 25 | - skipHandlerProcessingWithCustomMessage(...); 26 | */ 27 | 28 | /* @HINT: This is the query task handler base/parent class. */ 29 | 30 | class StorageQueryTaskHandler { 31 | constructor(skipHandlerErrorMessage = "") { 32 | /* @INFO 2 private member variables */ 33 | this.message = skipHandlerErrorMessage; 34 | this.nextHandler = null; 35 | } 36 | 37 | setNextHandler(handler) { 38 | if (handler instanceof StorageQueryTaskHandler) { 39 | this.nextHandler = handler; 40 | return; 41 | } 42 | throw new Error( 43 | "Cannot set next handler as object provided is not a hanlder" 44 | ); 45 | } 46 | 47 | async beginProcessing() { 48 | throw new Error( 49 | "Implementation needed for [protected] [abstract] method {async} `process()`. \r\n\r\n" + 50 | " > This interface is not available from abstract class." 51 | ); 52 | } 53 | 54 | skipHandlerProcessing(error) { 55 | if (this.nextHandler === null) { 56 | throw error; 57 | } 58 | throw new Error(this.message); 59 | } 60 | 61 | skipHandlerProcessingWithCustomMessage (message) { 62 | throw new Error(message); 63 | } 64 | 65 | async alternateProcessing () { 66 | throw new Error( 67 | "Implementation needed for [protected] [abstract] method {async} `finalizeProcessing()`. \r\n\r\n" + 68 | " > This interface is not available from abstract class." 69 | ); 70 | } 71 | 72 | async finalizeProcessing() { 73 | throw new Error( 74 | "Implementation needed for [protected] [abstract] method {async} `finalizeProcessing()`. \r\n\r\n" + 75 | " > This interface is not available from abstract class." 76 | ); 77 | } 78 | 79 | async finalizeProcessingWithError() { 80 | throw new Error( 81 | "Implementation needed for [protected] [abstract] method {async} `finalizeProcessingWithError()`. \r\n\r\n" + 82 | " > This interface is not available from abstract class." 83 | ); 84 | } 85 | 86 | migrateQueryTask (builderOrRequest) { 87 | return builderOrRequest; 88 | } 89 | 90 | async handle (builderOrRequest) { 91 | let result = null; 92 | let processingError = null; 93 | let noResult = true; 94 | let hasError = false; 95 | 96 | try { 97 | try { 98 | result = await this.beginProcessing( 99 | this.migrateQueryTask(builderOrRequest) 100 | ); 101 | noResult = false; 102 | return result; 103 | } catch (error) { 104 | if (!(error instanceof Error)) { 105 | throw error 106 | } 107 | 108 | if (error.message === this.message 109 | && this.nextHandler !== null) { 110 | result = await this.nextHandler.handle( 111 | builderOrRequest 112 | ); 113 | noResult = false; 114 | return result; 115 | } else { 116 | hasError = true; 117 | processingError = error; 118 | throw error; 119 | } 120 | } finally { 121 | if (!hasError) { 122 | await this.finalizeProcessing(this.migrateQueryTask(builderOrRequest), result); 123 | } else { 124 | await this.finalizeProcessingWithError(builderOrRequest, processingError); 125 | if (noResult) { 126 | /* @HINT: If there's no result and we have an error, try to get a result from an alternate process */ 127 | if (processingError.message === this.message) { 128 | result = await this.alternateProcessing( 129 | this.migrateQueryTask(builderOrRequest) 130 | ); 131 | noResult = false; 132 | return result; 133 | } 134 | } 135 | } 136 | } 137 | } catch ($error) { 138 | throw $error; 139 | } 140 | 141 | return result; 142 | } 143 | } 144 | 145 | module.exports = StorageQueryTaskHandler; 146 | -------------------------------------------------------------------------------- /query-handlers/RedisCacheQueryTaskHandler.js: -------------------------------------------------------------------------------- 1 | const StorageQueryTaskHandler = require("./StorageQueryTaskHandler"); 2 | const { murmurHash } = require("../helpers"); 3 | /* @NOTE: | The way the code for this class is written, it's totally closed for modification! */ 4 | 5 | /* @NOTE: To change the behaviour of this class, you have to swap it's input */ 6 | 7 | /* @HINT: This is a cache query task handler for the Redis cache. */ 8 | 9 | class RedisCacheQueryTaskHandler extends StorageQueryTaskHandler { 10 | constructor(cacheContainer) { 11 | super("cache handler execution declined"); 12 | cacheContainer.on("error", (error) => { 13 | throw new Error(error); 14 | }); 15 | this.cache = cacheContainer; 16 | } 17 | 18 | async canQuery (queryKey) { 19 | return this.cache.hasKey(queryKey); 20 | } 21 | 22 | async beginProcessing (builderOrRequest) { 23 | let canProceedWithProcessing = false; 24 | let isSQLDatabaseQueryTask = false; 25 | 26 | /* @HINT: Check if variable `builderOrRequest` is a knex query builder object */ 27 | if (typeof builderOrRequest.toSQL === "function") { 28 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 29 | const query = builderOrRequest.toSQL(); 30 | canProceedWithProcessing = query.sql.toLowerCase().startsWith("select") 31 | && !query.sql.toLowerCase().includes("join"); 32 | isSQLDatabaseQueryTask = true 33 | } 34 | /* @HINT: Check if the variable `builderOrRequest` is a http (graphql) request config object */ 35 | else if (typeof builderOrRequest.url === "string" 36 | && !builderOrRequest.url.endsWith("/graphql")) { 37 | const hasHeaders = builderOrRequest.headers instanceof Object; 38 | const headers = builderOrRequest.headers; 39 | const method = builderOrRequest.method.toLowerCase(); 40 | 41 | /* @HINT: This redis cache query task handler will not handle graphql query tasks */ 42 | canProceedWithProcessing = !(hasHeaders 43 | && headers['content-type'] === "application/graphql") 44 | || !(method === "post" 45 | && typeof builderOrRequest.body === "string" 46 | && headers['content-type'] === "application/json" 47 | && /([\s]*){([\s]*)"([\s]*)query([\s]*)"([\s]*)\:([\s]*)"([\s]*){/m.test( 48 | builderOrRequest.body.toLowerCase() 49 | ) 50 | ) || !(method === "get" 51 | && typeof builderOrRequest.query === "string" 52 | && builderOrRequest.query.includes("query={") 53 | ); 54 | } 55 | 56 | if (!canProceedWithProcessing) { 57 | return this.skipHandlerProcessing(); 58 | } 59 | 60 | try { 61 | await this.cache.establishConnectionWithRedisServer({ 62 | timeoutInMilliSeconds: 1200 63 | }); 64 | } catch (error) { 65 | return this.skipHandlerProcessing(error); 66 | } 67 | 68 | const queryHash = murmurHash( 69 | isSQLDatabaseQueryTask 70 | ? builderOrRequest.toSQL().sql 71 | : `${builderOrRequest.method.toLowerCase()}-${builderOrRequest.url}` 72 | ); 73 | const isCacheHit = await this.canQuery(queryHash); 74 | 75 | if (isCacheHit) { 76 | return await this.cache.get(queryHash); 77 | } 78 | 79 | return this.skipHandlerProcessing(); 80 | } 81 | 82 | async finalizeProcessing(builderOrRequest, result) { 83 | let canProceedWithProcessing = false; 84 | let isSQLDatabaseQueryTask = false; 85 | 86 | /* @HINT: Check if variable `builderOrRequest` is a knex query builder object */ 87 | if (typeof builderOrRequest.toSQL === "function") { 88 | /* @CHECK: https://github.com/knex/knex/issues/3018#issuecomment-458781094/ */ 89 | const query = builderOrRequest.toSQL(); 90 | canProceedWithProcessing = query.sql.toLowerCase().startsWith("select") 91 | && !query.sql.toLowerCase().includes("join"); 92 | isSQLDatabaseQueryTask = true 93 | } 94 | /* @HINT: Check if the variable `builderOrRequest` is a http (graphql) request config object */ 95 | else if (typeof builderOrRequest.url === "string" 96 | && !builderOrRequest.url.endsWith("/graphql")) { 97 | const hasHeaders = builderOrRequest.headers instanceof Object; 98 | const headers = builderOrRequest.headers; 99 | const method = builderOrRequest.method.toLowerCase(); 100 | 101 | /* @HINT: This redis cache query task handler will not handle graphql query tasks */ 102 | canProceedWithProcessing = !(hasHeaders 103 | && headers['content-type'] === "application/graphql") 104 | || !(method === "post" 105 | && typeof builderOrRequest.body === "string" 106 | && headers['content-type'] === "application/json" 107 | && /([\s]*){([\s]*)"([\s]*)query([\s]*)"([\s]*)\:([\s]*)"([\s]*){/m.test( 108 | builderOrRequest.body.toLowerCase() 109 | ) 110 | ) || !(method === "get" 111 | && typeof builderOrRequest.query === "string" 112 | && builderOrRequest.query.includes("query={") 113 | ); 114 | } 115 | 116 | if (canProceedWithProcessing) { 117 | const queryHash = murmurHash( 118 | isSQLDatabaseQueryTask 119 | ? builderOrRequest.toSQL().sql 120 | : `${builderOrRequest.method.toLowerCase()}-${builderOrRequest.url}` 121 | ); 122 | const isCacheMiss = !(await this.canQuery(queryHash)); 123 | 124 | if (isCacheMiss) { 125 | await this.cache.set(queryHash, result); 126 | } 127 | } 128 | 129 | await this.cache.destroyConnectionWithRedisServer(); 130 | } 131 | } 132 | 133 | module.exports = RedisCacheQueryTaskHandler; 134 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | 3 | /** 4 | * 5 | * @param {Number} durationMilliSeconds 6 | * 7 | * @returns {Promise} 8 | * 9 | * @see https://davidwalsh.name/waitfortime/ 10 | */ 11 | const sleepFor = (durationMilliSeconds = 10) => { 12 | return new Promise((resolve) => setTimeout(resolve, durationMilliSeconds)); 13 | }; 14 | 15 | /** 16 | * 17 | * @param {Function} conditionCallback 18 | * @param {Number} pollIntervalMilliSeconds 19 | * @param {Number} timeoutAfterMilliSeconds 20 | * 21 | * @returns {Promise} 22 | * 23 | * @see https://davidwalsh.name/waitfor/ 24 | */ 25 | const waitFor = async ( 26 | conditionCallback, 27 | pollIntervalMilliSeconds = 50, 28 | timeoutAfterMilliSeconds = 3000 29 | ) => { 30 | const startTimeMilliSeconds = Date.now(); 31 | const timeoutThresholdMilliseconds = 32 | startTimeMilliSeconds + 33 | (typeof timeoutAfterMilliSeconds === "number" 34 | ? timeoutAfterMilliSeconds 35 | : 0); 36 | 37 | let timedOut = false; 38 | let error = new Error("Condition not met before timeout"); 39 | 40 | while (true) { 41 | if (Date.now() >= timeoutThresholdMilliseconds) { 42 | timedOut = true; 43 | break; 44 | } 45 | 46 | const result = conditionCallback(); 47 | 48 | if (result) { 49 | return result; 50 | } 51 | 52 | await sleepFor(pollIntervalMilliSeconds); 53 | } 54 | 55 | if (timedOut) { 56 | throw error; 57 | } 58 | 59 | return { 60 | orThrow(newError) { 61 | error = newError; 62 | } 63 | }; 64 | }; 65 | 66 | /** 67 | * 68 | * @param {Uint8Array} bytes 69 | * 70 | * @return {String} 71 | * 72 | * @see https://stegriff.co.uk/upblog/bit-packing-binary-in-javascript-and-json/ 73 | */ 74 | const pack = (bytes = []) => { 75 | const characters = []; 76 | 77 | for (let count = 0, bytesLength = bytes.length; count < bytesLength; ) { 78 | characters.push(((bytes[count++] & 0xff) << 8) | (bytes[count++] & 0xff)); 79 | } 80 | 81 | String.fromCharCode(...characters); 82 | }; 83 | 84 | /** 85 | * 86 | * @param {String} utf16EncodedString 87 | * 88 | * @return {Array} 89 | * 90 | * @see https://stegriff.co.uk/upblog/bit-packing-binary-in-javascript-and-json/ 91 | */ 92 | const unpack = (utf16EncodedString) => { 93 | const bytes = []; 94 | 95 | for ( 96 | let count = 0, binaryStringLength = utf16EncodedString.length; 97 | count < binaryStringLength; 98 | count++ 99 | ) { 100 | const character = utf16EncodedString.charCodeAt(count); 101 | bytes.push(character >>> 8, character & 0xff); 102 | } 103 | return bytes; 104 | }; 105 | 106 | /** 107 | * 108 | * @param {String} text 109 | * @param {String} delimeter 110 | * 111 | * @returns {String} 112 | */ 113 | const camelCaseify = (text) => { 114 | return text.replace(/\W+(.)/g, function(_, chr) { 115 | return chr.toUpperCase(); 116 | }); 117 | }; 118 | 119 | /** 120 | * 121 | * @param {*} objectValue 122 | * 123 | * @returns {Boolean} 124 | */ 125 | const isEmpty = (objectValue) => { 126 | if (!objectValue || typeof objectValue !== "object") { 127 | return true; 128 | } 129 | 130 | for (const prop in objectValue) { 131 | if (Object.prototype.hasOwnProperty.call(objectValue, prop)) { 132 | return false; 133 | } 134 | } 135 | 136 | return JSON.stringify(objectValue) === JSON.stringify({}); 137 | }; 138 | 139 | /** 140 | * @class 141 | * @see https://medium.com/@vincentcorbee/utf-16-to-utf-8-in-javascript-18b4b11b6e1e 142 | */ 143 | 144 | class StringReader { 145 | constructor(source) { 146 | this.position = 0; 147 | this.source = source; 148 | } 149 | 150 | isInRange(position) { 151 | if (position > -1 && position < this.source.length) return true; 152 | 153 | return false; 154 | } 155 | 156 | next() { 157 | if (!this.isInRange(this.position)) return null; 158 | 159 | return this.source[this.position++].charCodeAt(0); 160 | } 161 | 162 | previous() { 163 | if (!this.isInRange(this.position)) return null; 164 | 165 | return this.source[this.position--].charCodeAt(0); 166 | } 167 | 168 | peak() { 169 | const oldPosition = this.position; 170 | 171 | const character = this.next(); 172 | 173 | this.position = oldPosition; 174 | 175 | return character; 176 | } 177 | 178 | seek(position) { 179 | if (!this.isInRange(position)) { 180 | throw RangeError(`Offset: ${position} is out of range`); 181 | } 182 | 183 | this.position = position; 184 | 185 | return this; 186 | } 187 | 188 | advance() { 189 | const position = this.position + 1; 190 | 191 | if (!this.isInRange(position)) this.position = -1; 192 | else this.position++; 193 | 194 | return this; 195 | } 196 | 197 | getPosition() { 198 | return this.position; 199 | } 200 | } 201 | 202 | /** 203 | * @callback isLeadingSurrogate 204 | * @see https://medium.com/@vincentcorbee/utf-16-to-utf-8-in-javascript-18b4b11b6e1e 205 | */ 206 | 207 | const isLeadingSurrogate = (charCode) => { 208 | return charCode >= 0xd800 && charCode <= 0xdbff; 209 | }; 210 | 211 | /** 212 | * @callback isTrailingSurrogate 213 | * @see https://medium.com/@vincentcorbee/utf-16-to-utf-8-in-javascript-18b4b11b6e1e 214 | */ 215 | const isTrailingSurrogate = (charCode) => { 216 | return charCode >= 0xdc00 && charCode <= 0xdfff; 217 | }; 218 | 219 | 220 | /** 221 | * @class StringEncoder 222 | * @see https://medium.com/@vincentcorbee/utf-16-to-utf-8-in-javascript-18b4b11b6e1e 223 | */ 224 | class StringEncoder { 225 | static UTF16SurrogatePairToCodePoint(leading, trailing) { 226 | return (leading - 0xd800) * 0x400 + (trailing - 0xdc00) + 0x10000; 227 | } 228 | 229 | static stringToUtf8(source) { 230 | const utf8codes = []; 231 | 232 | const stringReader = new StringReader(source); 233 | 234 | let charCode = null; 235 | 236 | /* eslint-disable-next-line no-cond-assign */ 237 | while ((charCode = stringReader.next()) !== null) { 238 | /* Character takes one byte in UTF-8 (US-ASCII range) */ 239 | if (charCode >= 0x0000 && charCode <= 0x007f) { 240 | utf8codes.push(charCode); 241 | 242 | /* Character takes two bytes in UTF-8 */ 243 | } else if (charCode >= 0x0080 && charCode <= 0x07ff) { 244 | let firstByte = 0xc0 | (charCode >>> 6); 245 | let secondByte = 0x80 | (charCode & 0x3f); 246 | 247 | utf8codes.push(firstByte, secondByte); 248 | } else if (isLeadingSurrogate(charCode)) { 249 | /* High surrogate */ 250 | const leading = charCode; 251 | 252 | /* Low surrogate */ 253 | const trailing = stringReader.peak(); 254 | 255 | if (trailing && isTrailingSurrogate(trailing)) { 256 | /* Surrogate pairs takes four bytes in UTF-8 */ 257 | const codePoint = StringEncoder.UTF16SurrogatePairToCodePoint( 258 | leading, 259 | trailing 260 | ); 261 | 262 | let firstByte = 0xf0 | (codePoint >>> 18); 263 | let secondByte = 0x80 | ((codePoint >>> 12) & 0x3f); 264 | let thirdByte = 0x80 | ((codePoint >>> 6) & 0x3f); 265 | let fourthByte = 0x80 | (codePoint & 0x3f); 266 | 267 | utf8codes.push(firstByte, secondByte, thirdByte, fourthByte); 268 | 269 | stringReader.advance(); 270 | } else { 271 | /* Isolated high surrogate */ 272 | } 273 | } else if (isTrailingSurrogate(charCode)) { 274 | /* Low surrogate */ 275 | /* Isolated low surrogate */ 276 | } else if (charCode >= 0x0800 && charCode <= 0xffff) { 277 | /* Character takes three bytes in UTF-8 */ 278 | let firstByte = 0xe0 | (charCode >>> 12); 279 | let secondByte = 0x80 | ((charCode >>> 6) & 0x3f); 280 | let thirdByte = 0x80 | (charCode & 0x3f); 281 | 282 | utf8codes.push(firstByte, secondByte, thirdByte); 283 | } 284 | } 285 | 286 | return new Uint8Array(utf8codes); 287 | } 288 | } 289 | 290 | /** 291 | * @class StringDecoder 292 | * @see https://medium.com/@vincentcorbee/utf-16-to-utf-8-in-javascript-18b4b11b6e1e 293 | */ 294 | 295 | class StringDecoder { 296 | static stringFromUTF16CharCode(charCodes) { 297 | return String.fromCharCode(...charCodes); 298 | } 299 | 300 | static UTF8ToString(uint8Array) { 301 | const charCodes = []; 302 | 303 | for ( 304 | let index = 0, length = uint8Array.byteLength; 305 | index < length; 306 | index++ 307 | ) { 308 | const charCode = uint8Array[index]; 309 | 310 | /* Character takes one byte */ 311 | if (charCode < 0xc0) charCodes.push(charCode); 312 | /* Character takes two bytes */ else if (charCode < 0xe0) 313 | charCodes.push(((charCode & 0x1f) << 6) | (uint8Array[++index] & 0x3f)); 314 | /* Character takes three bytes */ else if (charCode < 0xef) 315 | charCodes.push( 316 | ((charCode & 0xf) << 12) | 317 | ((uint8Array[++index] & 0x3f) << 6) | 318 | (uint8Array[++index] & 0x3f) 319 | ); 320 | /* Character takes four bytes */ else { 321 | /* Character consists of high and low surrogate pair */ 322 | const codePoint = 323 | (((charCode & 0x7) << 18) | 324 | ((uint8Array[++index] & 0x3f) << 12) | 325 | ((uint8Array[++index] & 0x3f) << 6) | 326 | (uint8Array[++index] & 0x3f)) - 327 | 0x10000; 328 | 329 | charCodes.push( 330 | (codePoint >>> 10) + 0xd800, 331 | (codePoint & 0x3ff) + 0xdc00 332 | ); 333 | } 334 | } 335 | 336 | return StringDecoder.stringFromUTF16CharCode(charCodes); 337 | } 338 | } 339 | 340 | /** 341 | * 342 | * @param {Uint8Array} bytesArray 343 | * 344 | * @returns {String} 345 | */ 346 | const convertBytesArrayToBinaryString = (bytesArray) => { 347 | const typedArrayUTF8 = bytesArray; 348 | return StringDecoder.UTF8ToString(typedArrayUTF8); 349 | }; 350 | 351 | /** 352 | * 353 | * @param {String} rawBinaryString 354 | * 355 | * @returns {Uint8Array} 356 | */ 357 | const convertBinaryStringToBytesArray = (rawBinaryString) => { 358 | const javaScriptStringUTF16 = rawBinaryString; 359 | return StringEncoder.stringToUtf8(javaScriptStringUTF16); 360 | }; 361 | 362 | const encryptionKey = crypto 363 | .createHash("sha512") 364 | .update("Shcxb573jbci9nbdk") 365 | .digest("hex") 366 | .substring(0, 32); 367 | 368 | const encryptionIV = crypto 369 | .createHash("sha512") 370 | .update("92hFAywb579MNmjh86") 371 | .digest("hex") 372 | .substring(0, 16); 373 | 374 | /** 375 | * 376 | * @param {String} plainData 377 | * @param {String} sourceEncoding 378 | * 379 | * @returns {String} 380 | */ 381 | const encryptData = (plainData, sourceEncoding = "utf8") => { 382 | const cipherIV = crypto.createCipheriv( 383 | "aes-256-ocb", 384 | encryptionKey, 385 | encryptionIV 386 | ); 387 | return Buffer.from( 388 | cipherIV.update(plainData, sourceEncoding, "hex") + cipherIV.final("hex") 389 | ).toString("base64"); 390 | }; 391 | 392 | /** 393 | * 394 | * @param {Buffer | String} encryptedData 395 | * @param {String} sourceEncoding 396 | * 397 | * @returns {String} 398 | */ 399 | const decryptData = (encryptedData, sourceEncoding = "utf8") => { 400 | const buffer = 401 | encryptedData instanceof Buffer && encryptedData.isEncoding("base64") 402 | ? encryptedData 403 | : Buffer.from(encryptedData, "base64"); 404 | const decipherIV = crypto.createDecipheriv( 405 | "aes-256-ocb", 406 | encryptionKey, 407 | encryptionIV 408 | ); 409 | return ( 410 | decipherIV.update(buffer.toString(sourceEncoding), "hex", sourceEncoding) + 411 | decipherIV.final(sourceEncoding) 412 | ); 413 | }; 414 | 415 | /* 416 | * The MurmurHash3 algorithm was created by Austin Appleby. This JavaScript port was authored 417 | * by whitequark (based on Java port by Yonik Seeley) and is placed into the public domain. 418 | * The author hereby disclaims copyright to this source code. 419 | * 420 | * This produces exactly the same hash values as the final C++ version of MurmurHash3 and 421 | * is thus suitable for producing the same hash values across platforms. 422 | * 423 | * There are two versions of this hash implementation. First interprets the string as a 424 | * sequence of bytes, ignoring most significant byte of each codepoint. The second one 425 | * interprets the string as a UTF-16 codepoint sequence, and appends each 16-bit codepoint 426 | * to the hash independently. The latter mode was not written to be compatible with 427 | * any other implementation, but it should offer better performance for JavaScript-only 428 | * applications. 429 | * 430 | * See http://github.com/whitequark/murmurhash3-js for future updates to this file. 431 | */ 432 | 433 | const MurmurHash3 = { 434 | mul32: function (m, n) { 435 | var nlo = n & 0xffff; 436 | var nhi = n - nlo; 437 | return (((nhi * m) | 0) + ((nlo * m) | 0)) | 0; 438 | }, 439 | 440 | hashBytes: function (data, len, seed) { 441 | var c1 = 0xcc9e2d51; 442 | var c2 = 0x1b873593; 443 | 444 | var h1 = seed; 445 | var roundedEnd = len & ~0x3; 446 | 447 | for (var i = 0; i < roundedEnd; i += 4) { 448 | var k1 = 449 | (data.charCodeAt(i) & 0xff) | 450 | ((data.charCodeAt(i + 1) & 0xff) << 8) | 451 | ((data.charCodeAt(i + 2) & 0xff) << 16) | 452 | ((data.charCodeAt(i + 3) & 0xff) << 24); 453 | 454 | k1 = this.mul32(k1, c1); 455 | k1 = ((k1 & 0x1ffff) << 15) | (k1 >>> 17); // ROTL32(k1,15); 456 | k1 = this.mul32(k1, c2); 457 | 458 | h1 ^= k1; 459 | h1 = ((h1 & 0x7ffff) << 13) | (h1 >>> 19); // ROTL32(h1,13); 460 | h1 = (h1 * 5 + 0xe6546b64) | 0; 461 | } 462 | 463 | k1 = 0; 464 | 465 | switch (len % 4) { 466 | case 3: 467 | k1 = (data.charCodeAt(roundedEnd + 2) & 0xff) << 16; 468 | // fallthrough 469 | case 2: 470 | k1 |= (data.charCodeAt(roundedEnd + 1) & 0xff) << 8; 471 | // fallthrough 472 | case 1: 473 | k1 |= data.charCodeAt(roundedEnd) & 0xff; 474 | k1 = this.mul32(k1, c1); 475 | k1 = ((k1 & 0x1ffff) << 15) | (k1 >>> 17); // ROTL32(k1,15); 476 | k1 = this.mul32(k1, c2); 477 | h1 ^= k1; 478 | break; 479 | default: 480 | k1 = 0; 481 | } 482 | 483 | // finalization 484 | h1 ^= len; 485 | 486 | // fmix(h1); 487 | h1 ^= h1 >>> 16; 488 | h1 = this.mul32(h1, 0x85ebca6b); 489 | h1 ^= h1 >>> 13; 490 | h1 = this.mul32(h1, 0xc2b2ae35); 491 | h1 ^= h1 >>> 16; 492 | 493 | return h1; 494 | }, 495 | 496 | hashString: function (data, len, seed) { 497 | var c1 = 0xcc9e2d51; 498 | var c2 = 0x1b873593; 499 | 500 | var h1 = seed; 501 | var roundedEnd = len & ~0x1; 502 | 503 | for (var i = 0; i < roundedEnd; i += 2) { 504 | var k1 = data.charCodeAt(i) | (data.charCodeAt(i + 1) << 16); 505 | 506 | k1 = this.mul32(k1, c1); 507 | k1 = ((k1 & 0x1ffff) << 15) | (k1 >>> 17); // ROTL32(k1,15); 508 | k1 = this.mul32(k1, c2); 509 | 510 | h1 ^= k1; 511 | h1 = ((h1 & 0x7ffff) << 13) | (h1 >>> 19); // ROTL32(h1,13); 512 | h1 = (h1 * 5 + 0xe6546b64) | 0; 513 | } 514 | 515 | if (len % 2 === 1) { 516 | k1 = data.charCodeAt(roundedEnd); 517 | k1 = this.mul32(k1, c1); 518 | k1 = ((k1 & 0x1ffff) << 15) | (k1 >>> 17); // ROTL32(k1,15); 519 | k1 = this.mul32(k1, c2); 520 | h1 ^= k1; 521 | } 522 | 523 | // finalization 524 | h1 ^= len << 1; 525 | 526 | // fmix(h1); 527 | h1 ^= h1 >>> 16; 528 | h1 = this.mul32(h1, 0x85ebca6b); 529 | h1 ^= h1 >>> 13; 530 | h1 = this.mul32(h1, 0xc2b2ae35); 531 | h1 ^= h1 >>> 16; 532 | 533 | return h1; 534 | } 535 | }; 536 | 537 | /** 538 | * 539 | * @param {String} data 540 | * 541 | * @returns {String} 542 | */ 543 | const murmurHash = (data) => { 544 | const hash = MurmurHash3.hashBytes(data, data.length, 150); 545 | 546 | return Number(Math.abs(hash) % 262144263494052048758001).toString(16); 547 | }; 548 | 549 | module.exports = { 550 | encryptData, 551 | decryptData, 552 | camelCaseify, 553 | pack, 554 | unpack, 555 | isEmpty, 556 | waitFor, 557 | sleepFor, 558 | murmurHash, 559 | convertBytesArrayToBinaryString, 560 | convertBinaryStringToBytesArray 561 | }; 562 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 9 | 10 | accepts@~1.3.5: 11 | version "1.3.7" 12 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 13 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 14 | dependencies: 15 | mime-types "~2.1.24" 16 | negotiator "0.6.2" 17 | 18 | ansi-align@^2.0.0: 19 | version "2.0.0" 20 | resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" 21 | integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= 22 | dependencies: 23 | string-width "^2.0.0" 24 | 25 | ansi-regex@^3.0.0: 26 | version "3.0.0" 27 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 28 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 29 | 30 | ansi-styles@^3.2.1: 31 | version "3.2.1" 32 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 33 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 34 | dependencies: 35 | color-convert "^1.9.0" 36 | 37 | anymatch@^2.0.0: 38 | version "2.0.0" 39 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" 40 | integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== 41 | dependencies: 42 | micromatch "^3.1.4" 43 | normalize-path "^2.1.1" 44 | 45 | arr-diff@^4.0.0: 46 | version "4.0.0" 47 | resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" 48 | integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= 49 | 50 | arr-flatten@^1.1.0: 51 | version "1.1.0" 52 | resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" 53 | integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== 54 | 55 | arr-union@^3.1.0: 56 | version "3.1.0" 57 | resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" 58 | integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= 59 | 60 | array-flatten@1.1.1: 61 | version "1.1.1" 62 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 63 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 64 | 65 | array-unique@^0.3.2: 66 | version "0.3.2" 67 | resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" 68 | integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= 69 | 70 | assign-symbols@^1.0.0: 71 | version "1.0.0" 72 | resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" 73 | integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= 74 | 75 | async-each@^1.0.1: 76 | version "1.0.3" 77 | resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" 78 | integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== 79 | 80 | atob@^2.1.1: 81 | version "2.1.2" 82 | resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" 83 | integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== 84 | 85 | balanced-match@^1.0.0: 86 | version "1.0.0" 87 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 88 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 89 | 90 | base@^0.11.1: 91 | version "0.11.2" 92 | resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" 93 | integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== 94 | dependencies: 95 | cache-base "^1.0.1" 96 | class-utils "^0.3.5" 97 | component-emitter "^1.2.1" 98 | define-property "^1.0.0" 99 | isobject "^3.0.1" 100 | mixin-deep "^1.2.0" 101 | pascalcase "^0.1.1" 102 | 103 | basic-auth@~2.0.0: 104 | version "2.0.1" 105 | resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" 106 | integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== 107 | dependencies: 108 | safe-buffer "5.1.2" 109 | 110 | binary-extensions@^1.0.0: 111 | version "1.13.1" 112 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" 113 | integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== 114 | 115 | bindings@^1.5.0: 116 | version "1.5.0" 117 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 118 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 119 | dependencies: 120 | file-uri-to-path "1.0.0" 121 | 122 | body-parser@1.18.3: 123 | version "1.18.3" 124 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" 125 | integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= 126 | dependencies: 127 | bytes "3.0.0" 128 | content-type "~1.0.4" 129 | debug "2.6.9" 130 | depd "~1.1.2" 131 | http-errors "~1.6.3" 132 | iconv-lite "0.4.23" 133 | on-finished "~2.3.0" 134 | qs "6.5.2" 135 | raw-body "2.3.3" 136 | type-is "~1.6.16" 137 | 138 | boxen@^1.2.1: 139 | version "1.3.0" 140 | resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" 141 | integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== 142 | dependencies: 143 | ansi-align "^2.0.0" 144 | camelcase "^4.0.0" 145 | chalk "^2.0.1" 146 | cli-boxes "^1.0.0" 147 | string-width "^2.0.0" 148 | term-size "^1.2.0" 149 | widest-line "^2.0.0" 150 | 151 | brace-expansion@^1.1.7: 152 | version "1.1.11" 153 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 154 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 155 | dependencies: 156 | balanced-match "^1.0.0" 157 | concat-map "0.0.1" 158 | 159 | braces@^2.3.1, braces@^2.3.2: 160 | version "2.3.2" 161 | resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" 162 | integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== 163 | dependencies: 164 | arr-flatten "^1.1.0" 165 | array-unique "^0.3.2" 166 | extend-shallow "^2.0.1" 167 | fill-range "^4.0.0" 168 | isobject "^3.0.1" 169 | repeat-element "^1.1.2" 170 | snapdragon "^0.8.1" 171 | snapdragon-node "^2.0.1" 172 | split-string "^3.0.2" 173 | to-regex "^3.0.1" 174 | 175 | bytes@3.0.0: 176 | version "3.0.0" 177 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 178 | integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= 179 | 180 | cache-base@^1.0.1: 181 | version "1.0.1" 182 | resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" 183 | integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== 184 | dependencies: 185 | collection-visit "^1.0.0" 186 | component-emitter "^1.2.1" 187 | get-value "^2.0.6" 188 | has-value "^1.0.0" 189 | isobject "^3.0.1" 190 | set-value "^2.0.0" 191 | to-object-path "^0.3.0" 192 | union-value "^1.0.0" 193 | unset-value "^1.0.0" 194 | 195 | camelcase@^4.0.0: 196 | version "4.1.0" 197 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" 198 | integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= 199 | 200 | capture-stack-trace@^1.0.0: 201 | version "1.0.1" 202 | resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" 203 | integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== 204 | 205 | chalk@^2.0.1: 206 | version "2.4.2" 207 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 208 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 209 | dependencies: 210 | ansi-styles "^3.2.1" 211 | escape-string-regexp "^1.0.5" 212 | supports-color "^5.3.0" 213 | 214 | chokidar@^2.0.2: 215 | version "2.1.8" 216 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" 217 | integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== 218 | dependencies: 219 | anymatch "^2.0.0" 220 | async-each "^1.0.1" 221 | braces "^2.3.2" 222 | glob-parent "^3.1.0" 223 | inherits "^2.0.3" 224 | is-binary-path "^1.0.0" 225 | is-glob "^4.0.0" 226 | normalize-path "^3.0.0" 227 | path-is-absolute "^1.0.0" 228 | readdirp "^2.2.1" 229 | upath "^1.1.1" 230 | optionalDependencies: 231 | fsevents "^1.2.7" 232 | 233 | ci-info@^1.5.0: 234 | version "1.6.0" 235 | resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" 236 | integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== 237 | 238 | class-utils@^0.3.5: 239 | version "0.3.6" 240 | resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" 241 | integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== 242 | dependencies: 243 | arr-union "^3.1.0" 244 | define-property "^0.2.5" 245 | isobject "^3.0.0" 246 | static-extend "^0.1.1" 247 | 248 | cli-boxes@^1.0.0: 249 | version "1.0.0" 250 | resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" 251 | integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= 252 | 253 | collection-visit@^1.0.0: 254 | version "1.0.0" 255 | resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" 256 | integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= 257 | dependencies: 258 | map-visit "^1.0.0" 259 | object-visit "^1.0.0" 260 | 261 | color-convert@^1.9.0: 262 | version "1.9.3" 263 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 264 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 265 | dependencies: 266 | color-name "1.1.3" 267 | 268 | color-name@1.1.3: 269 | version "1.1.3" 270 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 271 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 272 | 273 | component-emitter@^1.2.1: 274 | version "1.3.0" 275 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" 276 | integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== 277 | 278 | concat-map@0.0.1: 279 | version "0.0.1" 280 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 281 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 282 | 283 | configstore@^3.0.0: 284 | version "3.1.2" 285 | resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" 286 | integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== 287 | dependencies: 288 | dot-prop "^4.1.0" 289 | graceful-fs "^4.1.2" 290 | make-dir "^1.0.0" 291 | unique-string "^1.0.0" 292 | write-file-atomic "^2.0.0" 293 | xdg-basedir "^3.0.0" 294 | 295 | content-disposition@0.5.2: 296 | version "0.5.2" 297 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 298 | integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= 299 | 300 | content-type@~1.0.4: 301 | version "1.0.4" 302 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 303 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 304 | 305 | cookie-parser@~1.4.4: 306 | version "1.4.4" 307 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188" 308 | integrity sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw== 309 | dependencies: 310 | cookie "0.3.1" 311 | cookie-signature "1.0.6" 312 | 313 | cookie-signature@1.0.6: 314 | version "1.0.6" 315 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 316 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 317 | 318 | cookie@0.3.1: 319 | version "0.3.1" 320 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 321 | integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= 322 | 323 | copy-descriptor@^0.1.0: 324 | version "0.1.1" 325 | resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" 326 | integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= 327 | 328 | core-util-is@~1.0.0: 329 | version "1.0.2" 330 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 331 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 332 | 333 | create-error-class@^3.0.0: 334 | version "3.0.2" 335 | resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" 336 | integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= 337 | dependencies: 338 | capture-stack-trace "^1.0.0" 339 | 340 | cross-spawn@^5.0.1: 341 | version "5.1.0" 342 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 343 | integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= 344 | dependencies: 345 | lru-cache "^4.0.1" 346 | shebang-command "^1.2.0" 347 | which "^1.2.9" 348 | 349 | crypto-random-string@^1.0.0: 350 | version "1.0.0" 351 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" 352 | integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= 353 | 354 | debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@~2.6.9: 355 | version "2.6.9" 356 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 357 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 358 | dependencies: 359 | ms "2.0.0" 360 | 361 | debug@^3.1.0: 362 | version "3.2.6" 363 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 364 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 365 | dependencies: 366 | ms "^2.1.1" 367 | 368 | decode-uri-component@^0.2.0: 369 | version "0.2.0" 370 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" 371 | integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= 372 | 373 | deep-extend@^0.6.0: 374 | version "0.6.0" 375 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 376 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 377 | 378 | define-property@^0.2.5: 379 | version "0.2.5" 380 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" 381 | integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= 382 | dependencies: 383 | is-descriptor "^0.1.0" 384 | 385 | define-property@^1.0.0: 386 | version "1.0.0" 387 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" 388 | integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= 389 | dependencies: 390 | is-descriptor "^1.0.0" 391 | 392 | define-property@^2.0.2: 393 | version "2.0.2" 394 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" 395 | integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== 396 | dependencies: 397 | is-descriptor "^1.0.2" 398 | isobject "^3.0.1" 399 | 400 | depd@~1.1.2: 401 | version "1.1.2" 402 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 403 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 404 | 405 | destroy@~1.0.4: 406 | version "1.0.4" 407 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 408 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 409 | 410 | dot-prop@^4.1.0: 411 | version "4.2.0" 412 | resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" 413 | integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== 414 | dependencies: 415 | is-obj "^1.0.0" 416 | 417 | dotenv@8.2.0: 418 | version "8.2.0" 419 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" 420 | integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== 421 | 422 | duplexer3@^0.1.4: 423 | version "0.1.4" 424 | resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 425 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 426 | 427 | ee-first@1.1.1: 428 | version "1.1.1" 429 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 430 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 431 | 432 | encodeurl@~1.0.2: 433 | version "1.0.2" 434 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 435 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 436 | 437 | escape-html@~1.0.3: 438 | version "1.0.3" 439 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 440 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 441 | 442 | escape-string-regexp@^1.0.5: 443 | version "1.0.5" 444 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 445 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 446 | 447 | etag@~1.8.1: 448 | version "1.8.1" 449 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 450 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 451 | 452 | execa@^0.7.0: 453 | version "0.7.0" 454 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" 455 | integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= 456 | dependencies: 457 | cross-spawn "^5.0.1" 458 | get-stream "^3.0.0" 459 | is-stream "^1.1.0" 460 | npm-run-path "^2.0.0" 461 | p-finally "^1.0.0" 462 | signal-exit "^3.0.0" 463 | strip-eof "^1.0.0" 464 | 465 | expand-brackets@^2.1.4: 466 | version "2.1.4" 467 | resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" 468 | integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= 469 | dependencies: 470 | debug "^2.3.3" 471 | define-property "^0.2.5" 472 | extend-shallow "^2.0.1" 473 | posix-character-classes "^0.1.0" 474 | regex-not "^1.0.0" 475 | snapdragon "^0.8.1" 476 | to-regex "^3.0.1" 477 | 478 | express@~4.16.1: 479 | version "4.16.4" 480 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" 481 | integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== 482 | dependencies: 483 | accepts "~1.3.5" 484 | array-flatten "1.1.1" 485 | body-parser "1.18.3" 486 | content-disposition "0.5.2" 487 | content-type "~1.0.4" 488 | cookie "0.3.1" 489 | cookie-signature "1.0.6" 490 | debug "2.6.9" 491 | depd "~1.1.2" 492 | encodeurl "~1.0.2" 493 | escape-html "~1.0.3" 494 | etag "~1.8.1" 495 | finalhandler "1.1.1" 496 | fresh "0.5.2" 497 | merge-descriptors "1.0.1" 498 | methods "~1.1.2" 499 | on-finished "~2.3.0" 500 | parseurl "~1.3.2" 501 | path-to-regexp "0.1.7" 502 | proxy-addr "~2.0.4" 503 | qs "6.5.2" 504 | range-parser "~1.2.0" 505 | safe-buffer "5.1.2" 506 | send "0.16.2" 507 | serve-static "1.13.2" 508 | setprototypeof "1.1.0" 509 | statuses "~1.4.0" 510 | type-is "~1.6.16" 511 | utils-merge "1.0.1" 512 | vary "~1.1.2" 513 | 514 | extend-shallow@^2.0.1: 515 | version "2.0.1" 516 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" 517 | integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= 518 | dependencies: 519 | is-extendable "^0.1.0" 520 | 521 | extend-shallow@^3.0.0, extend-shallow@^3.0.2: 522 | version "3.0.2" 523 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" 524 | integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= 525 | dependencies: 526 | assign-symbols "^1.0.0" 527 | is-extendable "^1.0.1" 528 | 529 | extglob@^2.0.4: 530 | version "2.0.4" 531 | resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" 532 | integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== 533 | dependencies: 534 | array-unique "^0.3.2" 535 | define-property "^1.0.0" 536 | expand-brackets "^2.1.4" 537 | extend-shallow "^2.0.1" 538 | fragment-cache "^0.2.1" 539 | regex-not "^1.0.0" 540 | snapdragon "^0.8.1" 541 | to-regex "^3.0.1" 542 | 543 | file-uri-to-path@1.0.0: 544 | version "1.0.0" 545 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 546 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 547 | 548 | fill-range@^4.0.0: 549 | version "4.0.0" 550 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" 551 | integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= 552 | dependencies: 553 | extend-shallow "^2.0.1" 554 | is-number "^3.0.0" 555 | repeat-string "^1.6.1" 556 | to-regex-range "^2.1.0" 557 | 558 | finalhandler@1.1.1: 559 | version "1.1.1" 560 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 561 | integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== 562 | dependencies: 563 | debug "2.6.9" 564 | encodeurl "~1.0.2" 565 | escape-html "~1.0.3" 566 | on-finished "~2.3.0" 567 | parseurl "~1.3.2" 568 | statuses "~1.4.0" 569 | unpipe "~1.0.0" 570 | 571 | for-in@^1.0.2: 572 | version "1.0.2" 573 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" 574 | integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= 575 | 576 | forwarded@~0.1.2: 577 | version "0.1.2" 578 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 579 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 580 | 581 | fragment-cache@^0.2.1: 582 | version "0.2.1" 583 | resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" 584 | integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= 585 | dependencies: 586 | map-cache "^0.2.2" 587 | 588 | fresh@0.5.2: 589 | version "0.5.2" 590 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 591 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 592 | 593 | fsevents@^1.2.7: 594 | version "1.2.11" 595 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" 596 | integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== 597 | dependencies: 598 | bindings "^1.5.0" 599 | nan "^2.12.1" 600 | 601 | get-stream@^3.0.0: 602 | version "3.0.0" 603 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 604 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= 605 | 606 | get-value@^2.0.3, get-value@^2.0.6: 607 | version "2.0.6" 608 | resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" 609 | integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= 610 | 611 | glob-parent@^3.1.0: 612 | version "3.1.0" 613 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" 614 | integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= 615 | dependencies: 616 | is-glob "^3.1.0" 617 | path-dirname "^1.0.0" 618 | 619 | global-dirs@^0.1.0: 620 | version "0.1.1" 621 | resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" 622 | integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= 623 | dependencies: 624 | ini "^1.3.4" 625 | 626 | got@^6.7.1: 627 | version "6.7.1" 628 | resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" 629 | integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= 630 | dependencies: 631 | create-error-class "^3.0.0" 632 | duplexer3 "^0.1.4" 633 | get-stream "^3.0.0" 634 | is-redirect "^1.0.0" 635 | is-retry-allowed "^1.0.0" 636 | is-stream "^1.0.0" 637 | lowercase-keys "^1.0.0" 638 | safe-buffer "^5.0.1" 639 | timed-out "^4.0.0" 640 | unzip-response "^2.0.1" 641 | url-parse-lax "^1.0.0" 642 | 643 | graceful-fs@^4.1.11, graceful-fs@^4.1.2: 644 | version "4.2.3" 645 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 646 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 647 | 648 | has-flag@^3.0.0: 649 | version "3.0.0" 650 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 651 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 652 | 653 | has-value@^0.3.1: 654 | version "0.3.1" 655 | resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" 656 | integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= 657 | dependencies: 658 | get-value "^2.0.3" 659 | has-values "^0.1.4" 660 | isobject "^2.0.0" 661 | 662 | has-value@^1.0.0: 663 | version "1.0.0" 664 | resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" 665 | integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= 666 | dependencies: 667 | get-value "^2.0.6" 668 | has-values "^1.0.0" 669 | isobject "^3.0.0" 670 | 671 | has-values@^0.1.4: 672 | version "0.1.4" 673 | resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" 674 | integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= 675 | 676 | has-values@^1.0.0: 677 | version "1.0.0" 678 | resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" 679 | integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= 680 | dependencies: 681 | is-number "^3.0.0" 682 | kind-of "^4.0.0" 683 | 684 | http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: 685 | version "1.6.3" 686 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 687 | integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= 688 | dependencies: 689 | depd "~1.1.2" 690 | inherits "2.0.3" 691 | setprototypeof "1.1.0" 692 | statuses ">= 1.4.0 < 2" 693 | 694 | iconv-lite@0.4.23: 695 | version "0.4.23" 696 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 697 | integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== 698 | dependencies: 699 | safer-buffer ">= 2.1.2 < 3" 700 | 701 | ignore-by-default@^1.0.1: 702 | version "1.0.1" 703 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 704 | integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= 705 | 706 | import-lazy@^2.1.0: 707 | version "2.1.0" 708 | resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 709 | integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= 710 | 711 | imurmurhash@^0.1.4: 712 | version "0.1.4" 713 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 714 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 715 | 716 | inherits@2.0.3: 717 | version "2.0.3" 718 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 719 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 720 | 721 | inherits@^2.0.3, inherits@~2.0.3: 722 | version "2.0.4" 723 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 724 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 725 | 726 | ini@^1.3.4, ini@~1.3.0: 727 | version "1.3.5" 728 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 729 | integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== 730 | 731 | ipaddr.js@1.9.0: 732 | version "1.9.0" 733 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" 734 | integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== 735 | 736 | is-accessor-descriptor@^0.1.6: 737 | version "0.1.6" 738 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" 739 | integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= 740 | dependencies: 741 | kind-of "^3.0.2" 742 | 743 | is-accessor-descriptor@^1.0.0: 744 | version "1.0.0" 745 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" 746 | integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== 747 | dependencies: 748 | kind-of "^6.0.0" 749 | 750 | is-binary-path@^1.0.0: 751 | version "1.0.1" 752 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" 753 | integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= 754 | dependencies: 755 | binary-extensions "^1.0.0" 756 | 757 | is-buffer@^1.1.5: 758 | version "1.1.6" 759 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 760 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== 761 | 762 | is-ci@^1.0.10: 763 | version "1.2.1" 764 | resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" 765 | integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== 766 | dependencies: 767 | ci-info "^1.5.0" 768 | 769 | is-data-descriptor@^0.1.4: 770 | version "0.1.4" 771 | resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" 772 | integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= 773 | dependencies: 774 | kind-of "^3.0.2" 775 | 776 | is-data-descriptor@^1.0.0: 777 | version "1.0.0" 778 | resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" 779 | integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== 780 | dependencies: 781 | kind-of "^6.0.0" 782 | 783 | is-descriptor@^0.1.0: 784 | version "0.1.6" 785 | resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" 786 | integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== 787 | dependencies: 788 | is-accessor-descriptor "^0.1.6" 789 | is-data-descriptor "^0.1.4" 790 | kind-of "^5.0.0" 791 | 792 | is-descriptor@^1.0.0, is-descriptor@^1.0.2: 793 | version "1.0.2" 794 | resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" 795 | integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== 796 | dependencies: 797 | is-accessor-descriptor "^1.0.0" 798 | is-data-descriptor "^1.0.0" 799 | kind-of "^6.0.2" 800 | 801 | is-extendable@^0.1.0, is-extendable@^0.1.1: 802 | version "0.1.1" 803 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" 804 | integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= 805 | 806 | is-extendable@^1.0.1: 807 | version "1.0.1" 808 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" 809 | integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== 810 | dependencies: 811 | is-plain-object "^2.0.4" 812 | 813 | is-extglob@^2.1.0, is-extglob@^2.1.1: 814 | version "2.1.1" 815 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 816 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 817 | 818 | is-fullwidth-code-point@^2.0.0: 819 | version "2.0.0" 820 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 821 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 822 | 823 | is-glob@^3.1.0: 824 | version "3.1.0" 825 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" 826 | integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= 827 | dependencies: 828 | is-extglob "^2.1.0" 829 | 830 | is-glob@^4.0.0: 831 | version "4.0.1" 832 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 833 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 834 | dependencies: 835 | is-extglob "^2.1.1" 836 | 837 | is-installed-globally@^0.1.0: 838 | version "0.1.0" 839 | resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" 840 | integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= 841 | dependencies: 842 | global-dirs "^0.1.0" 843 | is-path-inside "^1.0.0" 844 | 845 | is-npm@^1.0.0: 846 | version "1.0.0" 847 | resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" 848 | integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= 849 | 850 | is-number@^3.0.0: 851 | version "3.0.0" 852 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" 853 | integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= 854 | dependencies: 855 | kind-of "^3.0.2" 856 | 857 | is-obj@^1.0.0: 858 | version "1.0.1" 859 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" 860 | integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= 861 | 862 | is-path-inside@^1.0.0: 863 | version "1.0.1" 864 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 865 | integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= 866 | dependencies: 867 | path-is-inside "^1.0.1" 868 | 869 | is-plain-object@^2.0.3, is-plain-object@^2.0.4: 870 | version "2.0.4" 871 | resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" 872 | integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== 873 | dependencies: 874 | isobject "^3.0.1" 875 | 876 | is-redirect@^1.0.0: 877 | version "1.0.0" 878 | resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" 879 | integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= 880 | 881 | is-retry-allowed@^1.0.0: 882 | version "1.2.0" 883 | resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" 884 | integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== 885 | 886 | is-stream@^1.0.0, is-stream@^1.1.0: 887 | version "1.1.0" 888 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 889 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 890 | 891 | is-windows@^1.0.2: 892 | version "1.0.2" 893 | resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" 894 | integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== 895 | 896 | isarray@1.0.0, isarray@~1.0.0: 897 | version "1.0.0" 898 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 899 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 900 | 901 | isexe@^2.0.0: 902 | version "2.0.0" 903 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 904 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 905 | 906 | isobject@^2.0.0: 907 | version "2.1.0" 908 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" 909 | integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= 910 | dependencies: 911 | isarray "1.0.0" 912 | 913 | isobject@^3.0.0, isobject@^3.0.1: 914 | version "3.0.1" 915 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" 916 | integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= 917 | 918 | kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: 919 | version "3.2.2" 920 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" 921 | integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= 922 | dependencies: 923 | is-buffer "^1.1.5" 924 | 925 | kind-of@^4.0.0: 926 | version "4.0.0" 927 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" 928 | integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= 929 | dependencies: 930 | is-buffer "^1.1.5" 931 | 932 | kind-of@^5.0.0: 933 | version "5.1.0" 934 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" 935 | integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== 936 | 937 | kind-of@^6.0.0, kind-of@^6.0.2: 938 | version "6.0.2" 939 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" 940 | integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== 941 | 942 | latest-version@^3.0.0: 943 | version "3.1.0" 944 | resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" 945 | integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= 946 | dependencies: 947 | package-json "^4.0.0" 948 | 949 | lowercase-keys@^1.0.0: 950 | version "1.0.1" 951 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 952 | integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== 953 | 954 | lru-cache@^4.0.1: 955 | version "4.1.5" 956 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" 957 | integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== 958 | dependencies: 959 | pseudomap "^1.0.2" 960 | yallist "^2.1.2" 961 | 962 | make-dir@^1.0.0: 963 | version "1.3.0" 964 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" 965 | integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== 966 | dependencies: 967 | pify "^3.0.0" 968 | 969 | map-cache@^0.2.2: 970 | version "0.2.2" 971 | resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" 972 | integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= 973 | 974 | map-visit@^1.0.0: 975 | version "1.0.0" 976 | resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" 977 | integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= 978 | dependencies: 979 | object-visit "^1.0.0" 980 | 981 | media-typer@0.3.0: 982 | version "0.3.0" 983 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 984 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 985 | 986 | merge-descriptors@1.0.1: 987 | version "1.0.1" 988 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 989 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 990 | 991 | methods@~1.1.2: 992 | version "1.1.2" 993 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 994 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 995 | 996 | micromatch@^3.1.10, micromatch@^3.1.4: 997 | version "3.1.10" 998 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" 999 | integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== 1000 | dependencies: 1001 | arr-diff "^4.0.0" 1002 | array-unique "^0.3.2" 1003 | braces "^2.3.1" 1004 | define-property "^2.0.2" 1005 | extend-shallow "^3.0.2" 1006 | extglob "^2.0.4" 1007 | fragment-cache "^0.2.1" 1008 | kind-of "^6.0.2" 1009 | nanomatch "^1.2.9" 1010 | object.pick "^1.3.0" 1011 | regex-not "^1.0.0" 1012 | snapdragon "^0.8.1" 1013 | to-regex "^3.0.2" 1014 | 1015 | mime-db@1.42.0: 1016 | version "1.42.0" 1017 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" 1018 | integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== 1019 | 1020 | mime-types@~2.1.24: 1021 | version "2.1.25" 1022 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437" 1023 | integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg== 1024 | dependencies: 1025 | mime-db "1.42.0" 1026 | 1027 | mime@1.4.1: 1028 | version "1.4.1" 1029 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 1030 | integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== 1031 | 1032 | minimatch@^3.0.4: 1033 | version "3.0.4" 1034 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 1035 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 1036 | dependencies: 1037 | brace-expansion "^1.1.7" 1038 | 1039 | minimist@^1.2.0: 1040 | version "1.2.0" 1041 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 1042 | integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= 1043 | 1044 | mixin-deep@^1.2.0: 1045 | version "1.3.2" 1046 | resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" 1047 | integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== 1048 | dependencies: 1049 | for-in "^1.0.2" 1050 | is-extendable "^1.0.1" 1051 | 1052 | morgan@~1.9.1: 1053 | version "1.9.1" 1054 | resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" 1055 | integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== 1056 | dependencies: 1057 | basic-auth "~2.0.0" 1058 | debug "2.6.9" 1059 | depd "~1.1.2" 1060 | on-finished "~2.3.0" 1061 | on-headers "~1.0.1" 1062 | 1063 | ms@2.0.0: 1064 | version "2.0.0" 1065 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1066 | 1067 | ms@^2.1.1: 1068 | version "2.1.2" 1069 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1070 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1071 | 1072 | nan@^2.12.1: 1073 | version "2.14.0" 1074 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" 1075 | integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== 1076 | 1077 | nanomatch@^1.2.9: 1078 | version "1.2.13" 1079 | resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" 1080 | integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== 1081 | dependencies: 1082 | arr-diff "^4.0.0" 1083 | array-unique "^0.3.2" 1084 | define-property "^2.0.2" 1085 | extend-shallow "^3.0.2" 1086 | fragment-cache "^0.2.1" 1087 | is-windows "^1.0.2" 1088 | kind-of "^6.0.2" 1089 | object.pick "^1.3.0" 1090 | regex-not "^1.0.0" 1091 | snapdragon "^0.8.1" 1092 | to-regex "^3.0.1" 1093 | 1094 | negotiator@0.6.2: 1095 | version "0.6.2" 1096 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 1097 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 1098 | 1099 | nodemon@1.18.4: 1100 | version "1.18.4" 1101 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.4.tgz#873f65fdb53220eb166180cf106b1354ac5d714d" 1102 | integrity sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg== 1103 | dependencies: 1104 | chokidar "^2.0.2" 1105 | debug "^3.1.0" 1106 | ignore-by-default "^1.0.1" 1107 | minimatch "^3.0.4" 1108 | pstree.remy "^1.1.0" 1109 | semver "^5.5.0" 1110 | supports-color "^5.2.0" 1111 | touch "^3.1.0" 1112 | undefsafe "^2.0.2" 1113 | update-notifier "^2.3.0" 1114 | 1115 | nopt@~1.0.10: 1116 | version "1.0.10" 1117 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 1118 | integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= 1119 | dependencies: 1120 | abbrev "1" 1121 | 1122 | normalize-path@^2.1.1: 1123 | version "2.1.1" 1124 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" 1125 | integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= 1126 | dependencies: 1127 | remove-trailing-separator "^1.0.1" 1128 | 1129 | normalize-path@^3.0.0: 1130 | version "3.0.0" 1131 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 1132 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 1133 | 1134 | npm-run-path@^2.0.0: 1135 | version "2.0.2" 1136 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 1137 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= 1138 | dependencies: 1139 | path-key "^2.0.0" 1140 | 1141 | object-copy@^0.1.0: 1142 | version "0.1.0" 1143 | resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" 1144 | integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= 1145 | dependencies: 1146 | copy-descriptor "^0.1.0" 1147 | define-property "^0.2.5" 1148 | kind-of "^3.0.3" 1149 | 1150 | object-visit@^1.0.0: 1151 | version "1.0.1" 1152 | resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" 1153 | integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= 1154 | dependencies: 1155 | isobject "^3.0.0" 1156 | 1157 | object.pick@^1.3.0: 1158 | version "1.3.0" 1159 | resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" 1160 | integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= 1161 | dependencies: 1162 | isobject "^3.0.1" 1163 | 1164 | on-finished@~2.3.0: 1165 | version "2.3.0" 1166 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 1167 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 1168 | dependencies: 1169 | ee-first "1.1.1" 1170 | 1171 | on-headers@~1.0.1: 1172 | version "1.0.2" 1173 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" 1174 | integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== 1175 | 1176 | p-finally@^1.0.0: 1177 | version "1.0.0" 1178 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 1179 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 1180 | 1181 | package-json@^4.0.0: 1182 | version "4.0.1" 1183 | resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" 1184 | integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= 1185 | dependencies: 1186 | got "^6.7.1" 1187 | registry-auth-token "^3.0.1" 1188 | registry-url "^3.0.3" 1189 | semver "^5.1.0" 1190 | 1191 | parseurl@~1.3.2: 1192 | version "1.3.3" 1193 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 1194 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 1195 | 1196 | pascalcase@^0.1.1: 1197 | version "0.1.1" 1198 | resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" 1199 | integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= 1200 | 1201 | path-dirname@^1.0.0: 1202 | version "1.0.2" 1203 | resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" 1204 | integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= 1205 | 1206 | path-is-absolute@^1.0.0: 1207 | version "1.0.1" 1208 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1209 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 1210 | 1211 | path-is-inside@^1.0.1: 1212 | version "1.0.2" 1213 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 1214 | integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 1215 | 1216 | path-key@^2.0.0: 1217 | version "2.0.1" 1218 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 1219 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 1220 | 1221 | path-to-regexp@0.1.7: 1222 | version "0.1.7" 1223 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 1224 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 1225 | 1226 | pify@^3.0.0: 1227 | version "3.0.0" 1228 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 1229 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 1230 | 1231 | posix-character-classes@^0.1.0: 1232 | version "0.1.1" 1233 | resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" 1234 | integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= 1235 | 1236 | prepend-http@^1.0.1: 1237 | version "1.0.4" 1238 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" 1239 | integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= 1240 | 1241 | process-nextick-args@~2.0.0: 1242 | version "2.0.1" 1243 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 1244 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 1245 | 1246 | proxy-addr@~2.0.4: 1247 | version "2.0.5" 1248 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" 1249 | integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== 1250 | dependencies: 1251 | forwarded "~0.1.2" 1252 | ipaddr.js "1.9.0" 1253 | 1254 | pseudomap@^1.0.2: 1255 | version "1.0.2" 1256 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 1257 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 1258 | 1259 | pstree.remy@^1.1.0: 1260 | version "1.1.7" 1261 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" 1262 | integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== 1263 | 1264 | qs@6.5.2: 1265 | version "6.5.2" 1266 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 1267 | integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== 1268 | 1269 | range-parser@~1.2.0: 1270 | version "1.2.1" 1271 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 1272 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 1273 | 1274 | raw-body@2.3.3: 1275 | version "2.3.3" 1276 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" 1277 | integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== 1278 | dependencies: 1279 | bytes "3.0.0" 1280 | http-errors "1.6.3" 1281 | iconv-lite "0.4.23" 1282 | unpipe "1.0.0" 1283 | 1284 | rc@^1.0.1, rc@^1.1.6: 1285 | version "1.2.8" 1286 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 1287 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 1288 | dependencies: 1289 | deep-extend "^0.6.0" 1290 | ini "~1.3.0" 1291 | minimist "^1.2.0" 1292 | strip-json-comments "~2.0.1" 1293 | 1294 | readable-stream@^2.0.2: 1295 | version "2.3.6" 1296 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 1297 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== 1298 | dependencies: 1299 | core-util-is "~1.0.0" 1300 | inherits "~2.0.3" 1301 | isarray "~1.0.0" 1302 | process-nextick-args "~2.0.0" 1303 | safe-buffer "~5.1.1" 1304 | string_decoder "~1.1.1" 1305 | util-deprecate "~1.0.1" 1306 | 1307 | readdirp@^2.2.1: 1308 | version "2.2.1" 1309 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" 1310 | integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== 1311 | dependencies: 1312 | graceful-fs "^4.1.11" 1313 | micromatch "^3.1.10" 1314 | readable-stream "^2.0.2" 1315 | 1316 | regex-not@^1.0.0, regex-not@^1.0.2: 1317 | version "1.0.2" 1318 | resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" 1319 | integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== 1320 | dependencies: 1321 | extend-shallow "^3.0.2" 1322 | safe-regex "^1.1.0" 1323 | 1324 | registry-auth-token@^3.0.1: 1325 | version "3.4.0" 1326 | resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" 1327 | integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== 1328 | dependencies: 1329 | rc "^1.1.6" 1330 | safe-buffer "^5.0.1" 1331 | 1332 | registry-url@^3.0.3: 1333 | version "3.1.0" 1334 | resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" 1335 | integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= 1336 | dependencies: 1337 | rc "^1.0.1" 1338 | 1339 | remove-trailing-separator@^1.0.1: 1340 | version "1.1.0" 1341 | resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" 1342 | integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= 1343 | 1344 | repeat-element@^1.1.2: 1345 | version "1.1.3" 1346 | resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" 1347 | integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== 1348 | 1349 | repeat-string@^1.6.1: 1350 | version "1.6.1" 1351 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 1352 | integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= 1353 | 1354 | resolve-url@^0.2.1: 1355 | version "0.2.1" 1356 | resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" 1357 | integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= 1358 | 1359 | ret@~0.1.10: 1360 | version "0.1.15" 1361 | resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" 1362 | integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== 1363 | 1364 | safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 1365 | version "5.1.2" 1366 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 1367 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 1368 | 1369 | safe-buffer@^5.0.1: 1370 | version "5.2.0" 1371 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 1372 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 1373 | 1374 | safe-regex@^1.1.0: 1375 | version "1.1.0" 1376 | resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" 1377 | integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= 1378 | dependencies: 1379 | ret "~0.1.10" 1380 | 1381 | "safer-buffer@>= 2.1.2 < 3": 1382 | version "2.1.2" 1383 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1384 | 1385 | semver-diff@^2.0.0: 1386 | version "2.1.0" 1387 | resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 1388 | integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= 1389 | dependencies: 1390 | semver "^5.0.3" 1391 | 1392 | semver@^5.0.3, semver@^5.1.0, semver@^5.5.0: 1393 | version "5.7.1" 1394 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1395 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1396 | 1397 | send@0.16.2: 1398 | version "0.16.2" 1399 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 1400 | integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== 1401 | dependencies: 1402 | debug "2.6.9" 1403 | depd "~1.1.2" 1404 | destroy "~1.0.4" 1405 | encodeurl "~1.0.2" 1406 | escape-html "~1.0.3" 1407 | etag "~1.8.1" 1408 | fresh "0.5.2" 1409 | http-errors "~1.6.2" 1410 | mime "1.4.1" 1411 | ms "2.0.0" 1412 | on-finished "~2.3.0" 1413 | range-parser "~1.2.0" 1414 | statuses "~1.4.0" 1415 | 1416 | serve-static@1.13.2: 1417 | version "1.13.2" 1418 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 1419 | integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== 1420 | dependencies: 1421 | encodeurl "~1.0.2" 1422 | escape-html "~1.0.3" 1423 | parseurl "~1.3.2" 1424 | send "0.16.2" 1425 | 1426 | set-value@^2.0.0, set-value@^2.0.1: 1427 | version "2.0.1" 1428 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" 1429 | integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== 1430 | dependencies: 1431 | extend-shallow "^2.0.1" 1432 | is-extendable "^0.1.1" 1433 | is-plain-object "^2.0.3" 1434 | split-string "^3.0.1" 1435 | 1436 | setprototypeof@1.1.0: 1437 | version "1.1.0" 1438 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 1439 | integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== 1440 | 1441 | shebang-command@^1.2.0: 1442 | version "1.2.0" 1443 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 1444 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 1445 | dependencies: 1446 | shebang-regex "^1.0.0" 1447 | 1448 | shebang-regex@^1.0.0: 1449 | version "1.0.0" 1450 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 1451 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 1452 | 1453 | signal-exit@^3.0.0, signal-exit@^3.0.2: 1454 | version "3.0.2" 1455 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1456 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 1457 | 1458 | snapdragon-node@^2.0.1: 1459 | version "2.1.1" 1460 | resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" 1461 | integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== 1462 | dependencies: 1463 | define-property "^1.0.0" 1464 | isobject "^3.0.0" 1465 | snapdragon-util "^3.0.1" 1466 | 1467 | snapdragon-util@^3.0.1: 1468 | version "3.0.1" 1469 | resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" 1470 | integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== 1471 | dependencies: 1472 | kind-of "^3.2.0" 1473 | 1474 | snapdragon@^0.8.1: 1475 | version "0.8.2" 1476 | resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" 1477 | integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== 1478 | dependencies: 1479 | base "^0.11.1" 1480 | debug "^2.2.0" 1481 | define-property "^0.2.5" 1482 | extend-shallow "^2.0.1" 1483 | map-cache "^0.2.2" 1484 | source-map "^0.5.6" 1485 | source-map-resolve "^0.5.0" 1486 | use "^3.1.0" 1487 | 1488 | source-map-resolve@^0.5.0: 1489 | version "0.5.2" 1490 | resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" 1491 | integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== 1492 | dependencies: 1493 | atob "^2.1.1" 1494 | decode-uri-component "^0.2.0" 1495 | resolve-url "^0.2.1" 1496 | source-map-url "^0.4.0" 1497 | urix "^0.1.0" 1498 | 1499 | source-map-url@^0.4.0: 1500 | version "0.4.0" 1501 | resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" 1502 | integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= 1503 | 1504 | source-map@^0.5.6: 1505 | version "0.5.7" 1506 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1507 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 1508 | 1509 | split-string@^3.0.1, split-string@^3.0.2: 1510 | version "3.1.0" 1511 | resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" 1512 | integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== 1513 | dependencies: 1514 | extend-shallow "^3.0.0" 1515 | 1516 | static-extend@^0.1.1: 1517 | version "0.1.2" 1518 | resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" 1519 | integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= 1520 | dependencies: 1521 | define-property "^0.2.5" 1522 | object-copy "^0.1.0" 1523 | 1524 | "statuses@>= 1.4.0 < 2": 1525 | version "1.5.0" 1526 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 1527 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 1528 | 1529 | statuses@~1.4.0: 1530 | version "1.4.0" 1531 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 1532 | integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== 1533 | 1534 | string-width@^2.0.0, string-width@^2.1.1: 1535 | version "2.1.1" 1536 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1537 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1538 | dependencies: 1539 | is-fullwidth-code-point "^2.0.0" 1540 | strip-ansi "^4.0.0" 1541 | 1542 | string_decoder@~1.1.1: 1543 | version "1.1.1" 1544 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 1545 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 1546 | dependencies: 1547 | safe-buffer "~5.1.0" 1548 | 1549 | strip-ansi@^4.0.0: 1550 | version "4.0.0" 1551 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1552 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 1553 | dependencies: 1554 | ansi-regex "^3.0.0" 1555 | 1556 | strip-eof@^1.0.0: 1557 | version "1.0.0" 1558 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 1559 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= 1560 | 1561 | strip-json-comments@~2.0.1: 1562 | version "2.0.1" 1563 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1564 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 1565 | 1566 | supports-color@^5.2.0, supports-color@^5.3.0: 1567 | version "5.5.0" 1568 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1569 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1570 | dependencies: 1571 | has-flag "^3.0.0" 1572 | 1573 | term-size@^1.2.0: 1574 | version "1.2.0" 1575 | resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" 1576 | integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= 1577 | dependencies: 1578 | execa "^0.7.0" 1579 | 1580 | timed-out@^4.0.0: 1581 | version "4.0.1" 1582 | resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 1583 | integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= 1584 | 1585 | to-object-path@^0.3.0: 1586 | version "0.3.0" 1587 | resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" 1588 | integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= 1589 | dependencies: 1590 | kind-of "^3.0.2" 1591 | 1592 | to-regex-range@^2.1.0: 1593 | version "2.1.1" 1594 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" 1595 | integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= 1596 | dependencies: 1597 | is-number "^3.0.0" 1598 | repeat-string "^1.6.1" 1599 | 1600 | to-regex@^3.0.1, to-regex@^3.0.2: 1601 | version "3.0.2" 1602 | resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" 1603 | integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== 1604 | dependencies: 1605 | define-property "^2.0.2" 1606 | extend-shallow "^3.0.2" 1607 | regex-not "^1.0.2" 1608 | safe-regex "^1.1.0" 1609 | 1610 | touch@^3.1.0: 1611 | version "3.1.0" 1612 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 1613 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 1614 | dependencies: 1615 | nopt "~1.0.10" 1616 | 1617 | type-is@~1.6.16: 1618 | version "1.6.18" 1619 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 1620 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 1621 | dependencies: 1622 | media-typer "0.3.0" 1623 | mime-types "~2.1.24" 1624 | 1625 | undefsafe@^2.0.2: 1626 | version "2.0.2" 1627 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" 1628 | integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= 1629 | dependencies: 1630 | debug "^2.2.0" 1631 | 1632 | union-value@^1.0.0: 1633 | version "1.0.1" 1634 | resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" 1635 | integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== 1636 | dependencies: 1637 | arr-union "^3.1.0" 1638 | get-value "^2.0.6" 1639 | is-extendable "^0.1.1" 1640 | set-value "^2.0.1" 1641 | 1642 | unique-string@^1.0.0: 1643 | version "1.0.0" 1644 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" 1645 | integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= 1646 | dependencies: 1647 | crypto-random-string "^1.0.0" 1648 | 1649 | unpipe@1.0.0, unpipe@~1.0.0: 1650 | version "1.0.0" 1651 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 1652 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 1653 | 1654 | unset-value@^1.0.0: 1655 | version "1.0.0" 1656 | resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" 1657 | integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= 1658 | dependencies: 1659 | has-value "^0.3.1" 1660 | isobject "^3.0.0" 1661 | 1662 | unzip-response@^2.0.1: 1663 | version "2.0.1" 1664 | resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" 1665 | integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= 1666 | 1667 | upath@^1.1.1: 1668 | version "1.2.0" 1669 | resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" 1670 | integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== 1671 | 1672 | update-notifier@^2.3.0: 1673 | version "2.5.0" 1674 | resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" 1675 | integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== 1676 | dependencies: 1677 | boxen "^1.2.1" 1678 | chalk "^2.0.1" 1679 | configstore "^3.0.0" 1680 | import-lazy "^2.1.0" 1681 | is-ci "^1.0.10" 1682 | is-installed-globally "^0.1.0" 1683 | is-npm "^1.0.0" 1684 | latest-version "^3.0.0" 1685 | semver-diff "^2.0.0" 1686 | xdg-basedir "^3.0.0" 1687 | 1688 | urix@^0.1.0: 1689 | version "0.1.0" 1690 | resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" 1691 | integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= 1692 | 1693 | url-parse-lax@^1.0.0: 1694 | version "1.0.0" 1695 | resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" 1696 | integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= 1697 | dependencies: 1698 | prepend-http "^1.0.1" 1699 | 1700 | use@^3.1.0: 1701 | version "3.1.1" 1702 | resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" 1703 | integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== 1704 | 1705 | util-deprecate@~1.0.1: 1706 | version "1.0.2" 1707 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1708 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 1709 | 1710 | utils-merge@1.0.1: 1711 | version "1.0.1" 1712 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 1713 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 1714 | 1715 | vary@~1.1.2: 1716 | version "1.1.2" 1717 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 1718 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 1719 | 1720 | which@^1.2.9: 1721 | version "1.3.1" 1722 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1723 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 1724 | dependencies: 1725 | isexe "^2.0.0" 1726 | 1727 | widest-line@^2.0.0: 1728 | version "2.0.1" 1729 | resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" 1730 | integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== 1731 | dependencies: 1732 | string-width "^2.1.1" 1733 | 1734 | write-file-atomic@^2.0.0: 1735 | version "2.4.3" 1736 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" 1737 | integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== 1738 | dependencies: 1739 | graceful-fs "^4.1.11" 1740 | imurmurhash "^0.1.4" 1741 | signal-exit "^3.0.2" 1742 | 1743 | xdg-basedir@^3.0.0: 1744 | version "3.0.0" 1745 | resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" 1746 | integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= 1747 | 1748 | yallist@^2.1.2: 1749 | version "2.1.2" 1750 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 1751 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 1752 | --------------------------------------------------------------------------------