├── .babelrc ├── .npmignore ├── test ├── index.js ├── spec │ ├── all.json │ ├── master.json │ ├── self.json │ ├── cluster.json │ ├── self.spec.js │ ├── common.spec.js │ ├── all.spec.js │ ├── master.spec.js │ └── cluster.spec.js ├── mime.cmd ├── server.js ├── jsesc.cmd ├── json5.cmd ├── mkdirp.cmd ├── babylon.cmd ├── jasmine.cmd ├── loose-envify.cmd ├── regjsparser.cmd ├── mime ├── jsesc ├── json5 ├── mkdirp ├── babylon ├── jasmine ├── loose-envify ├── regjsparser ├── ecosystem.json ├── mime.ps1 ├── jsesc.ps1 ├── json5.ps1 ├── mkdirp.ps1 ├── babylon.ps1 ├── jasmine.ps1 ├── loose-envify.ps1 ├── regjsparser.ps1 ├── package.json ├── app.js └── package-lock.json ├── lib ├── index.js ├── metadata.js ├── GarbageCollector.js ├── repositories │ ├── index.js │ ├── DataRepository.js │ └── ProcessRepository.js ├── const.js ├── pm2Monitor.js └── ClusterCache.js ├── src ├── repositories │ ├── index.js │ ├── DataRepository.js │ └── ProcessRepository.js ├── metadata.js ├── GarbageCollector.js ├── const.js ├── pm2Monitor.js └── ClusterCache.js ├── .gitignore ├── .github └── workflows │ ├── npm-publish.yml │ ├── node-build.yml │ └── codeql-analysis.yml ├── LICENSE ├── package.json └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.github/ 2 | /src/ 3 | /test/ 4 | .babelrc -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('babel-register')({ 2 | presets: ['es2015'] 3 | }); 4 | 5 | require('./app.js'); 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-register')({ 4 | presets: ['env'] 5 | }); 6 | 7 | require('./app.js'); -------------------------------------------------------------------------------- /src/repositories/index.js: -------------------------------------------------------------------------------- 1 | export {default as dr} from './DataRepository'; 2 | export {default as pr} from './ProcessRepository'; -------------------------------------------------------------------------------- /test/spec/all.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "common.spec.js", 5 | "all.spec.js" 6 | ], 7 | "stopSpecOnExpectationFailure": false, 8 | "random": false 9 | } 10 | -------------------------------------------------------------------------------- /test/spec/master.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "common.spec.js", 5 | "master.spec.js" 6 | ], 7 | "stopSpecOnExpectationFailure": false, 8 | "random": false 9 | } 10 | -------------------------------------------------------------------------------- /test/spec/self.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "common.spec.js", 5 | "self.spec.js" 6 | ], 7 | "stopSpecOnExpectationFailure": false, 8 | "random": false 9 | } 10 | -------------------------------------------------------------------------------- /test/spec/cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "common.spec.js", 5 | "cluster.spec.js" 6 | ], 7 | "stopSpecOnExpectationFailure": false, 8 | "random": false 9 | } 10 | -------------------------------------------------------------------------------- /test/mime.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\mime\cli.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | var pm2 = require('pm2'); 2 | 3 | pm2.connect(function (err) { 4 | if (err) { 5 | console.error(err); 6 | process.exit(2); 7 | } 8 | 9 | pm2.start('ecosystem.json', function (err, apps) { 10 | pm2.disconnect(); // Disconnects from PM2 11 | if (err) throw err 12 | }); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /test/jsesc.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\jsesc\bin\jsesc" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/json5.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\json5\lib\cli.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/mkdirp.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\mkdirp\bin\cmd.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/babylon.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\babylon\bin\babylon.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/jasmine.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\jasmine\bin\jasmine.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/loose-envify.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\loose-envify\cli.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/regjsparser.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\node_modules\regjsparser\bin\parser" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /src/metadata.js: -------------------------------------------------------------------------------- 1 | var Metadata = function (candidates, final) { 2 | 3 | return process.env.pm_id > 0 ? { 4 | storedOn: candidates, 5 | readFrom: parseInt(final) || -1, 6 | servedBy: parseInt(process.env.pm_id) 7 | } : { 8 | storedOn: [], 9 | readFrom: NaN, 10 | servedBy: NaN 11 | } 12 | }; 13 | 14 | module.exports = Metadata; -------------------------------------------------------------------------------- /test/mime: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/mime/cli.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/mime/cli.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules # 2 | ################ 3 | node_modules 4 | 5 | # tmp files # 6 | ############# 7 | *~ 8 | *.bak 9 | 10 | # PhpStorm # 11 | ############ 12 | .idea 13 | 14 | # Logs # 15 | ###################### 16 | *.log 17 | 18 | # OS generated files # 19 | ###################### 20 | .DS_Store 21 | .DS_Store? 22 | ._* 23 | .Spotlight-V100 24 | .Trashes 25 | Icon? 26 | ehthumbs.db 27 | Thumbs.db -------------------------------------------------------------------------------- /test/jsesc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/jsesc/bin/jsesc" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/jsesc/bin/jsesc" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/json5: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/json5/lib/cli.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/json5/lib/cli.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/mkdirp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/mkdirp/bin/cmd.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/mkdirp/bin/cmd.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /lib/metadata.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Metadata = function Metadata(candidates, final) { 4 | 5 | return process.env.pm_id > 0 ? { 6 | storedOn: candidates, 7 | readFrom: parseInt(final) || -1, 8 | servedBy: parseInt(process.env.pm_id) 9 | } : { 10 | storedOn: [], 11 | readFrom: NaN, 12 | servedBy: NaN 13 | }; 14 | }; 15 | 16 | module.exports = Metadata; -------------------------------------------------------------------------------- /test/babylon: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/babylon/bin/babylon.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/babylon/bin/babylon.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/jasmine: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/jasmine/bin/jasmine.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/jasmine/bin/jasmine.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/loose-envify: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/loose-envify/cli.js" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/loose-envify/cli.js" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/regjsparser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") 3 | 4 | case `uname` in 5 | *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; 6 | esac 7 | 8 | if [ -x "$basedir/node" ]; then 9 | "$basedir/node" "$basedir/node_modules/regjsparser/bin/parser" "$@" 10 | ret=$? 11 | else 12 | node "$basedir/node_modules/regjsparser/bin/parser" "$@" 13 | ret=$? 14 | fi 15 | exit $ret 16 | -------------------------------------------------------------------------------- /test/ecosystem.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "pm2cluster", 5 | "script": "index.js", 6 | "port": 8080, 7 | "log_file": "log.log", 8 | "error_file": "error.log", 9 | "wait_ready": true, 10 | "instances": "max", 11 | "instance_var": "INSTANCE_ID", 12 | "exec_mode": "cluster", 13 | "autorestart": false, 14 | "force": true, 15 | "merge_logs": true, 16 | "time": true 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/GarbageCollector.js: -------------------------------------------------------------------------------- 1 | import {dr} from './repositories'; 2 | 3 | var GarbageCollector = { 4 | 5 | timer: undefined, 6 | 7 | start: function (ms) { 8 | if (GarbageCollector.timer === undefined) { 9 | GarbageCollector.timer = setInterval(function () { 10 | dr.optimize(); 11 | }, ms); 12 | GarbageCollector.timer.unref(); 13 | } 14 | } 15 | }; 16 | 17 | module.exports = { 18 | start: GarbageCollector.start 19 | }; 20 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | export const STORAGE_ALL = 'all'; 2 | export const STORAGE_CLUSTER = 'cluster'; 3 | export const STORAGE_MASTER = 'master'; 4 | export const STORAGE_SELF = 'self'; 5 | 6 | export const TOPIC_SET = 'clusterCache:set'; 7 | export const TOPIC_GET = 'clusterCache:get'; 8 | export const TOPIC_INC = 'clusterCache:inc'; 9 | export const TOPIC_DEC = 'clusterCache:dec'; 10 | export const TOPIC_DELETE = 'clusterCache:del'; 11 | export const TOPIC_KEYS = 'clusterCache:keys'; 12 | export const TOPIC_FLUSH = 'clusterCache:flush'; -------------------------------------------------------------------------------- /lib/GarbageCollector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _repositories = require('./repositories'); 4 | 5 | var GarbageCollector = { 6 | 7 | timer: undefined, 8 | 9 | start: function start(ms) { 10 | if (GarbageCollector.timer === undefined) { 11 | GarbageCollector.timer = setInterval(function () { 12 | _repositories.dr.optimize(); 13 | }, ms); 14 | GarbageCollector.timer.unref(); 15 | } 16 | } 17 | }; 18 | 19 | module.exports = { 20 | start: GarbageCollector.start 21 | }; -------------------------------------------------------------------------------- /test/mime.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/mime/cli.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/mime/cli.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/jsesc.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/jsesc/bin/jsesc" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/jsesc/bin/jsesc" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/json5.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/json5/lib/cli.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/json5/lib/cli.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/mkdirp.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/mkdirp/bin/cmd.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/mkdirp/bin/cmd.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/babylon.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/babylon/bin/babylon.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/babylon/bin/babylon.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/jasmine.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/jasmine/bin/jasmine.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/jasmine/bin/jasmine.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/loose-envify.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/loose-envify/cli.js" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/loose-envify/cli.js" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /test/regjsparser.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent 3 | 4 | $exe="" 5 | if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { 6 | # Fix case when both the Windows and Linux builds of Node 7 | # are installed in the same directory 8 | $exe=".exe" 9 | } 10 | $ret=0 11 | if (Test-Path "$basedir/node$exe") { 12 | & "$basedir/node$exe" "$basedir/node_modules/regjsparser/bin/parser" $args 13 | $ret=$LASTEXITCODE 14 | } else { 15 | & "node$exe" "$basedir/node_modules/regjsparser/bin/parser" $args 16 | $ret=$LASTEXITCODE 17 | } 18 | exit $ret 19 | -------------------------------------------------------------------------------- /lib/repositories/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _DataRepository = require('./DataRepository'); 8 | 9 | Object.defineProperty(exports, 'dr', { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_DataRepository).default; 13 | } 14 | }); 15 | 16 | var _ProcessRepository = require('./ProcessRepository'); 17 | 18 | Object.defineProperty(exports, 'pr', { 19 | enumerable: true, 20 | get: function get() { 21 | return _interopRequireDefault(_ProcessRepository).default; 22 | } 23 | }); 24 | 25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/const.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var STORAGE_ALL = exports.STORAGE_ALL = 'all'; 7 | var STORAGE_CLUSTER = exports.STORAGE_CLUSTER = 'cluster'; 8 | var STORAGE_MASTER = exports.STORAGE_MASTER = 'master'; 9 | var STORAGE_SELF = exports.STORAGE_SELF = 'self'; 10 | 11 | var TOPIC_SET = exports.TOPIC_SET = 'clusterCache:set'; 12 | var TOPIC_GET = exports.TOPIC_GET = 'clusterCache:get'; 13 | var TOPIC_INC = exports.TOPIC_INC = 'clusterCache:inc'; 14 | var TOPIC_DEC = exports.TOPIC_DEC = 'clusterCache:dec'; 15 | var TOPIC_DELETE = exports.TOPIC_DELETE = 'clusterCache:del'; 16 | var TOPIC_KEYS = exports.TOPIC_KEYS = 'clusterCache:keys'; 17 | var TOPIC_FLUSH = exports.TOPIC_FLUSH = 'clusterCache:flush'; -------------------------------------------------------------------------------- /src/pm2Monitor.js: -------------------------------------------------------------------------------- 1 | var io = undefined; 2 | if(process.env.pm_id >= 0){ 3 | io = require('@pm2/io'); 4 | } else { 5 | io = { 6 | meter: () => { 7 | return { 8 | mark: () => {} 9 | } 10 | } 11 | } 12 | } 13 | 14 | var pm2monitor = { 15 | hit: io.meter({ 16 | name: 'Cluster Cache hit Rate', 17 | samples: 1, 18 | timeframe: 1, 19 | unit: 'hit/s' 20 | }), 21 | miss: io.meter({ 22 | name: 'Cluster Cache miss Rate', 23 | samples: 1, 24 | timeframe: 1, 25 | unit: 'miss/s' 26 | }), 27 | markHit: function () { 28 | if (process.env.pm_id >= 0) pm2monitor.hit.mark(); 29 | }, 30 | markMiss: function () { 31 | if (process.env.pm_id >= 0) pm2monitor.miss.mark(); 32 | } 33 | }; 34 | 35 | module.exports = { 36 | hit: pm2monitor.markHit, 37 | miss: pm2monitor.markMiss 38 | }; -------------------------------------------------------------------------------- /test/spec/self.spec.js: -------------------------------------------------------------------------------- 1 | const ip = require("ip"); 2 | var pm2 = require('pm2'); 3 | const axios = require('axios'); 4 | 5 | var BASE_URL = "http://" + ip.address() + ":8080"; 6 | 7 | afterAll((done) => { 8 | pm2.connect(function (err) { 9 | pm2.delete('pm2cluster', (err, proc) => { 10 | done(); 11 | }); 12 | }); 13 | }); 14 | 15 | beforeEach(function (done) { 16 | axios.get(BASE_URL + "/flush").then(function () { 17 | done(); 18 | }) 19 | }); 20 | 21 | describe('/storage type self', () => { 22 | 23 | it('should be able to read value', (done) => { 24 | done(); //can't test, because from definition of type 'self' may not return value, if read request fall on different proccess as write request 25 | }); 26 | 27 | it('should store all keys on one process', (done) => { 28 | done(); //todo how to prevent, that all requests fall to same proc? 29 | }); 30 | }); 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 14 18 | - run: npm ci 19 | 20 | publish-npm: 21 | needs: build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-node@v1 26 | with: 27 | node-version: 14 28 | registry-url: https://registry.npmjs.org/ 29 | - run: npm ci 30 | - run: npm publish 31 | env: 32 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 33 | 34 | -------------------------------------------------------------------------------- /lib/pm2Monitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var io = undefined; 4 | if (process.env.pm_id >= 0) { 5 | io = require('@pm2/io'); 6 | } else { 7 | io = { 8 | meter: function meter() { 9 | return { 10 | mark: function mark() {} 11 | }; 12 | } 13 | }; 14 | } 15 | 16 | var pm2monitor = { 17 | hit: io.meter({ 18 | name: 'Cluster Cache hit Rate', 19 | samples: 1, 20 | timeframe: 1, 21 | unit: 'hit/s' 22 | }), 23 | miss: io.meter({ 24 | name: 'Cluster Cache miss Rate', 25 | samples: 1, 26 | timeframe: 1, 27 | unit: 'miss/s' 28 | }), 29 | markHit: function markHit() { 30 | if (process.env.pm_id >= 0) pm2monitor.hit.mark(); 31 | }, 32 | markMiss: function markMiss() { 33 | if (process.env.pm_id >= 0) pm2monitor.miss.mark(); 34 | } 35 | }; 36 | 37 | module.exports = { 38 | hit: pm2monitor.markHit, 39 | miss: pm2monitor.markMiss 40 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michal Pavlik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/node-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - '*' # matches every branch that doesn't contain a '/' 10 | - '*/*' # matches every branch containing a single '/' 11 | - '**' # matches every branch 12 | - '!master' # excludes master 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [10.x, 12.x, 14.x, 15.x] 22 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - run: npm ci 31 | - run: npm run build --if-present 32 | - run: npm run test 33 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm2-cluster-cache-test", 3 | "version": "2.1.0", 4 | "description": "pm2 cluster memory cache with ttl and more possibilities of data store in cluster", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:pavlikm/pm2-cluster-memory-cache.git" 9 | }, 10 | "scripts": { 11 | "self": "node server.js && jasmine JASMINE_CONFIG_PATH=spec/self.json", 12 | "cluster": "node server.js cluster && jasmine JASMINE_CONFIG_PATH=spec/cluster.json", 13 | "all": "node server.js all && jasmine JASMINE_CONFIG_PATH=spec/all.json", 14 | "all2": "jasmine JASMINE_CONFIG_PATH=spec/all.json", 15 | "master": "node server.js master && jasmine JASMINE_CONFIG_PATH=spec/master.json", 16 | "test": "npm run cluster && npm run all && npm run master && npm run self" 17 | }, 18 | "author": "pavlikm", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "babel-preset-es2015": "^6.24.1", 22 | "babel-register": "^6.26.0", 23 | "@babel/polyfill": "^7.12.1", 24 | "express": "^4.17.1", 25 | "frisby": "^2.1.3", 26 | "ip": "^1.1.5", 27 | "jasmine": "^3.6.4", 28 | "axios": "^0.21.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm2-cluster-cache", 3 | "version": "2.1.7", 4 | "description": "pm2 cluster memory cache with ttl and more possibilities of data store in cluster", 5 | "main": "lib/ClusterCache.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:pavlikm/pm2-cluster-memory-cache.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/pavlikm/pm2-cluster-memory-cache/issues" 12 | }, 13 | "keywords": [ 14 | "pm2", 15 | "memory", 16 | "cache", 17 | "cluster", 18 | "ttl", 19 | "key-value" 20 | ], 21 | "scripts": { 22 | "build": "babel ./src --out-dir ./lib", 23 | "prepublish": "npm run build", 24 | "test": "cd test && npm install && npm run test", 25 | "test-self": "cd test && npm install && npm run self", 26 | "test-cluster": "cd test && npm install && npm run cluster", 27 | "test-master": "cd test && npm install && npm run master", 28 | "test-all": "cd test && npm install && npm run all" 29 | }, 30 | "author": "pavlikm", 31 | "license": "MIT", 32 | "dependencies": { 33 | "pm2": "^4.5.6", 34 | "@pm2/io": "^5.0.0", 35 | "to-item": "^2.0.0" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.26.0", 39 | "babel-preset-es2015": "^6.24.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/repositories/DataRepository.js: -------------------------------------------------------------------------------- 1 | import gc from '../GarbageCollector'; 2 | 3 | var DataRepository = { 4 | 5 | data: new Map(), 6 | 7 | keys: function () { 8 | DataRepository.optimize(); 9 | return [ ...DataRepository.data.keys() ]; 10 | }, 11 | 12 | optimize: function () { 13 | DataRepository.data.forEach((record, key) => { 14 | if (!DataRepository.isValid(record)) { 15 | DataRepository.delete(key); 16 | } 17 | }); 18 | }, 19 | 20 | inc: function(key, byVal){ 21 | if(DataRepository.data.has(key)){ 22 | let value = DataRepository.data.get(key); 23 | value.v = parseInt(value.v) + byVal; 24 | DataRepository.data.set(key, value); 25 | return value.v; 26 | } 27 | return 0; 28 | }, 29 | 30 | get: function (key) { 31 | return DataRepository.isValid(DataRepository.data.get(key)) ? DataRepository.data.get(key).v : ''; 32 | }, 33 | 34 | set: function (key, data) { 35 | DataRepository.data.delete(key); 36 | DataRepository.data.set(key, data); 37 | gc.start(1000); 38 | }, 39 | 40 | delete: function (key) { 41 | DataRepository.data.delete(key); 42 | }, 43 | 44 | flush: function () { 45 | DataRepository.data.clear(); 46 | }, 47 | 48 | isValid: function (record) { 49 | if (record === undefined) return false; 50 | 51 | var isValid = parseInt(record.t) >= parseInt(new Date().getTime()); 52 | if (!isValid) { 53 | DataRepository.delete(record.k); 54 | } 55 | return isValid; 56 | } 57 | }; 58 | 59 | module.exports = { 60 | get: DataRepository.get, 61 | set: DataRepository.set, 62 | inc: DataRepository.inc, 63 | delete: DataRepository.delete, 64 | flush: DataRepository.flush, 65 | optimize: DataRepository.optimize, 66 | keys: DataRepository.keys 67 | }; -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill"; 2 | const express = require('express'); 3 | const http = require('http'); 4 | const app = express(); 5 | const server = http.createServer(app); 6 | import ClusterCache from '../src/ClusterCache'; 7 | 8 | let type = "self"; 9 | let cache = ClusterCache.init({storage: process.env.npm_lifecycle_event || type}); 10 | 11 | app.get("/set", (req, res) => { 12 | let key = req.query.key; 13 | let data = req.query.value; 14 | let ttl = req.query.ttl; 15 | cache.withMeta().set(key, data, ttl).then(meta => { 16 | res.send({key: key, value: data, metadata: meta}); 17 | }); 18 | }); 19 | 20 | app.get("/get", (req, res) => { 21 | let key = req.query.key; 22 | let defaultValue = req.query.default || 'undefined'; 23 | cache.withMeta().get(key, defaultValue).then(result => { 24 | res.send({key: key, value: result.data, metadata: result.metadata}); 25 | }); 26 | }); 27 | 28 | app.get("/inc", (req, res) => { 29 | let key = req.query.key; 30 | cache.inc(key).then(result => { 31 | res.send({key: key, value: result}); 32 | }) 33 | }); 34 | 35 | app.get("/dec", (req, res) => { 36 | let key = req.query.key; 37 | cache.dec(key).then(result => { 38 | res.send({key: key, value: result}); 39 | }) 40 | }); 41 | 42 | app.get("/write", (req, res) => { 43 | let key = req.query.key; 44 | let data = req.query.value; 45 | let ttl = req.query.ttl; 46 | cache.set(key, data, ttl).then(result => { 47 | res.send({key: key, value: data, result: result}); 48 | }); 49 | }); 50 | 51 | app.get("/read", (req, res) => { 52 | let key = req.query.key; 53 | let defaultValue = req.query.default || 'undefined'; 54 | cache.get(key, defaultValue).then(result => { 55 | res.send({key: key, value: result}); 56 | }); 57 | }); 58 | 59 | app.get("/delete", (req, res) => { 60 | cache.delete(req.query.key).then(data => { 61 | res.send(data); 62 | }); 63 | }); 64 | 65 | app.get("/flush", (req, res) => { 66 | cache.flush().then(() => { 67 | res.send("ok"); 68 | }); 69 | }); 70 | 71 | app.get("/info", (req, res) => { 72 | cache.keys().then(data => { 73 | res.send(data); 74 | }); 75 | }); 76 | 77 | 78 | server.listen(8080, () => { 79 | //console.log(`Server started`, process.env.npm_lifecycle_event); 80 | }); -------------------------------------------------------------------------------- /lib/repositories/DataRepository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _GarbageCollector = require('../GarbageCollector'); 4 | 5 | var _GarbageCollector2 = _interopRequireDefault(_GarbageCollector); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 8 | 9 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 10 | 11 | var DataRepository = { 12 | 13 | data: new Map(), 14 | 15 | keys: function keys() { 16 | DataRepository.optimize(); 17 | return [].concat(_toConsumableArray(DataRepository.data.keys())); 18 | }, 19 | 20 | optimize: function optimize() { 21 | DataRepository.data.forEach(function (record, key) { 22 | if (!DataRepository.isValid(record)) { 23 | DataRepository.delete(key); 24 | } 25 | }); 26 | }, 27 | 28 | inc: function inc(key, byVal) { 29 | if (DataRepository.data.has(key)) { 30 | var value = DataRepository.data.get(key); 31 | value.v = parseInt(value.v) + byVal; 32 | DataRepository.data.set(key, value); 33 | return value.v; 34 | } 35 | return 0; 36 | }, 37 | 38 | get: function get(key) { 39 | return DataRepository.isValid(DataRepository.data.get(key)) ? DataRepository.data.get(key).v : ''; 40 | }, 41 | 42 | set: function set(key, data) { 43 | DataRepository.data.delete(key); 44 | DataRepository.data.set(key, data); 45 | _GarbageCollector2.default.start(1000); 46 | }, 47 | 48 | delete: function _delete(key) { 49 | DataRepository.data.delete(key); 50 | }, 51 | 52 | flush: function flush() { 53 | DataRepository.data.clear(); 54 | }, 55 | 56 | isValid: function isValid(record) { 57 | if (record === undefined) return false; 58 | 59 | var isValid = parseInt(record.t) >= parseInt(new Date().getTime()); 60 | if (!isValid) { 61 | DataRepository.delete(record.k); 62 | } 63 | return isValid; 64 | } 65 | }; 66 | 67 | module.exports = { 68 | get: DataRepository.get, 69 | set: DataRepository.set, 70 | inc: DataRepository.inc, 71 | delete: DataRepository.delete, 72 | flush: DataRepository.flush, 73 | optimize: DataRepository.optimize, 74 | keys: DataRepository.keys 75 | }; -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '21 11 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /test/spec/common.spec.js: -------------------------------------------------------------------------------- 1 | const frisby = require('frisby'); 2 | const ip = require("ip"); 3 | const axios = require('axios'); 4 | 5 | var BASE_URL = "http://" + ip.address() + ":8080"; 6 | 7 | beforeEach(function (done) { 8 | axios.get(BASE_URL + "/flush").then(function () { 9 | done(); 10 | }) 11 | }); 12 | 13 | describe('/every storage type', () => { 14 | it('should be able to set value', (done) => { 15 | return frisby 16 | .get(BASE_URL + "/set?key=foo&value=bar&ttl=10000") 17 | .expect('status', 200) 18 | .expect('json', 'key', 'foo') 19 | .done(done); 20 | }); 21 | 22 | it('should be able to set value without ttl', (done) => { 23 | return frisby 24 | .get(BASE_URL + "/set?key=foo&value=bar") 25 | .expect('status', 200) 26 | .expect('json', 'key', 'foo') 27 | .done(done); 28 | }); 29 | 30 | 31 | it('should return undefined on non-existing key', (done) => { 32 | return frisby 33 | .get(BASE_URL + "/get?key=foobar") 34 | .expect('status', 200) 35 | .expect('json', 'key', "foobar") 36 | .expect('json', 'value', "undefined") 37 | .done(done); 38 | }); 39 | 40 | it('should return default value on non-existing key', (done) => { 41 | return frisby 42 | .get(BASE_URL + "/get?key=foobar&default=test") 43 | .expect('status', 200) 44 | .expect('json', 'key', "foobar") 45 | .expect('json', 'value', "test") 46 | .done(done); 47 | }); 48 | 49 | it('should return default value on expired key', (done) => { 50 | axios.get(BASE_URL + "/set?key=foo&value=bar&ttl=500").then(() => { 51 | setTimeout(function () { 52 | return frisby 53 | .get(BASE_URL + "/get?key=foo&default=undefined") 54 | .expect('status', 200) 55 | .expect('json', 'key', "foo") 56 | .expect('json', 'value', "undefined") 57 | .done(done); 58 | }, 1000); 59 | }); 60 | 61 | }); 62 | 63 | it('should flush all keys', (done) => { 64 | let homer = axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000"); 65 | let meggie = axios.get(BASE_URL + "/set?key=meggie&value=simpson&ttl=10000"); 66 | let bart = axios.get(BASE_URL + "/set?key=bart&value=simpson&ttl=10000"); 67 | let flush = axios.get(BASE_URL + "/flush"); 68 | axios.all([homer, meggie, bart, flush]).then(resp => { 69 | return frisby 70 | .get(BASE_URL + "/info") 71 | .then(function (res) { 72 | for (const [key, value] of Object.entries(res._json)) { 73 | if (value.length !== 0) { 74 | fail(); //all keys must be removed 75 | } 76 | } 77 | done(); 78 | }); 79 | }); 80 | }); 81 | 82 | }); 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/repositories/ProcessRepository.js: -------------------------------------------------------------------------------- 1 | import {STORAGE_ALL, STORAGE_CLUSTER, STORAGE_MASTER, STORAGE_SELF} from "../const"; 2 | require("to-item"); 3 | 4 | const pm2 = require("pm2"); 5 | 6 | var ProcessRepository = { 7 | 8 | processes: process.env.pm_id < 0 ? [-1] : [], 9 | 10 | init: function () { 11 | if (process.env.pm_id >= 0 && ProcessRepository.processes.length === 0) { 12 | return ProcessRepository.findProcesses(); 13 | } else return new Promise(ok => { 14 | ok(ProcessRepository.processes); 15 | }); 16 | }, 17 | 18 | getReadProcess: function (key, storage) { 19 | return ProcessRepository.init().then(nodes => { 20 | return new Promise(function (ok, fail) { 21 | key = key + ''; 22 | if (storage.toLowerCase() === STORAGE_SELF || storage.toLowerCase() === STORAGE_ALL) { 23 | return ok([process.env.pm_id]); 24 | } 25 | return ok(ProcessRepository.findInCluster(key, storage, nodes)); 26 | }) 27 | }) 28 | 29 | }, 30 | 31 | getWriteProcess: function (key, storage) { 32 | return ProcessRepository.init().then(nodes => { 33 | return new Promise(function (ok, fail) { 34 | key = key + ''; 35 | if (storage.toLowerCase() === STORAGE_SELF) { 36 | return ok([process.env.pm_id]); 37 | } 38 | return ok(ProcessRepository.findInCluster(key, storage, nodes)); 39 | }) 40 | }) 41 | }, 42 | 43 | findProcesses: function () { 44 | return new Promise(function (ok, fail) { 45 | pm2.connect(function (err) { 46 | pm2.list(function (err, proc) { 47 | if (err) { 48 | return ok([]); 49 | } 50 | var nodes = []; 51 | for (var p in proc) { 52 | if (proc[p].name === process.env.name) { 53 | nodes.push(parseInt(proc[p].pm_id)); 54 | } 55 | } 56 | if(nodes.length > 0){ 57 | ProcessRepository.processes = nodes; 58 | } 59 | return ok(nodes); 60 | }); 61 | }); 62 | }); 63 | }, 64 | 65 | findInCluster: function (key, storage, nodes) { 66 | return new Promise(async function (ok, fail) { 67 | switch (storage.toLowerCase()) { 68 | case STORAGE_ALL: { 69 | return ok([...nodes]); 70 | } 71 | case STORAGE_MASTER: { 72 | return ok([Math.min(...nodes)]); 73 | } 74 | default: 75 | case STORAGE_CLUSTER: { 76 | return ok([key.toItem(nodes)]); 77 | } 78 | } 79 | }) 80 | } 81 | }; 82 | 83 | module.exports = { 84 | getReadProcess: ProcessRepository.getReadProcess, 85 | getWriteProcess: ProcessRepository.getWriteProcess, 86 | getAll: ProcessRepository.findProcesses, 87 | init: ProcessRepository.init 88 | }; -------------------------------------------------------------------------------- /lib/repositories/ProcessRepository.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _const = require("../const"); 4 | 5 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 6 | 7 | require("to-item"); 8 | 9 | var pm2 = require("pm2"); 10 | 11 | var ProcessRepository = { 12 | 13 | processes: process.env.pm_id < 0 ? [-1] : [], 14 | 15 | init: function init() { 16 | if (process.env.pm_id >= 0 && ProcessRepository.processes.length === 0) { 17 | return ProcessRepository.findProcesses(); 18 | } else return new Promise(function (ok) { 19 | ok(ProcessRepository.processes); 20 | }); 21 | }, 22 | 23 | getReadProcess: function getReadProcess(key, storage) { 24 | return ProcessRepository.init().then(function (nodes) { 25 | return new Promise(function (ok, fail) { 26 | key = key + ''; 27 | if (storage.toLowerCase() === _const.STORAGE_SELF || storage.toLowerCase() === _const.STORAGE_ALL) { 28 | return ok([process.env.pm_id]); 29 | } 30 | return ok(ProcessRepository.findInCluster(key, storage, nodes)); 31 | }); 32 | }); 33 | }, 34 | 35 | getWriteProcess: function getWriteProcess(key, storage) { 36 | return ProcessRepository.init().then(function (nodes) { 37 | return new Promise(function (ok, fail) { 38 | key = key + ''; 39 | if (storage.toLowerCase() === _const.STORAGE_SELF) { 40 | return ok([process.env.pm_id]); 41 | } 42 | return ok(ProcessRepository.findInCluster(key, storage, nodes)); 43 | }); 44 | }); 45 | }, 46 | 47 | findProcesses: function findProcesses() { 48 | return new Promise(function (ok, fail) { 49 | pm2.connect(function (err) { 50 | pm2.list(function (err, proc) { 51 | if (err) { 52 | return ok([]); 53 | } 54 | var nodes = []; 55 | for (var p in proc) { 56 | if (proc[p].name === process.env.name) { 57 | nodes.push(parseInt(proc[p].pm_id)); 58 | } 59 | } 60 | if (nodes.length > 0) { 61 | ProcessRepository.processes = nodes; 62 | } 63 | return ok(nodes); 64 | }); 65 | }); 66 | }); 67 | }, 68 | 69 | findInCluster: function findInCluster(key, storage, nodes) { 70 | return new Promise(async function (ok, fail) { 71 | switch (storage.toLowerCase()) { 72 | case _const.STORAGE_ALL: 73 | { 74 | return ok([].concat(_toConsumableArray(nodes))); 75 | } 76 | case _const.STORAGE_MASTER: 77 | { 78 | return ok([Math.min.apply(Math, _toConsumableArray(nodes))]); 79 | } 80 | default: 81 | case _const.STORAGE_CLUSTER: 82 | { 83 | return ok([key.toItem(nodes)]); 84 | } 85 | } 86 | }); 87 | } 88 | }; 89 | 90 | module.exports = { 91 | getReadProcess: ProcessRepository.getReadProcess, 92 | getWriteProcess: ProcessRepository.getWriteProcess, 93 | getAll: ProcessRepository.findProcesses, 94 | init: ProcessRepository.init 95 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Node.js CI](https://github.com/pavlikm/pm2-cluster-memory-cache/actions/workflows/node-build.yml/badge.svg)](https://github.com/pavlikm/pm2-cluster-memory-cache/actions/workflows/node-build.yml) 2 | [![CodeQL](https://github.com/pavlikm/pm2-cluster-memory-cache/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/pavlikm/pm2-cluster-memory-cache/actions/workflows/codeql-analysis.yml) 3 | 4 | 5 | # pm2 cluster memory cache 6 | A cluster memory cache for pm2 with some different possibilities of data store. 7 | From version 1.0.5 is safe to use without pm2 too, but storage type will be forced to 'self'. 8 | 9 | ### Instalation 10 | ```javascript 11 | npm install pm2-cluster-cache --save 12 | ``` 13 | 14 | ### Tests 15 | ```javascript 16 | npm run test 17 | ``` 18 | **Remember**: testing environment may vary depending on storage types. Therefore tests are divided into 4 separate running tests. If you run `npm run test`, all test will be performed, but you can run specific test, for example `npm run test-cluster` for cluster type, or `npm run test-master` for master type, and so on. Every test will create server with max processes, and test against api running on this cluster server. 19 | 20 | 21 | ### Usage 22 | ```javascript 23 | const pm2ClusterCache = require('pm2-cluster-cache'); 24 | let cache = pm2ClusterCache.init({storage: "cluster"}); 25 | 26 | //set value to cache for 1s 27 | cache.set('key', 'data', 1000).then(() => { 28 | console.log('ok'); 29 | }); 30 | 31 | //get cached value 32 | cache.get('key', "someDefaultValue").then(result => { 33 | console.log(result); 34 | }); 35 | 36 | //get value from cache with metadata 37 | cache.withMeta().get('key', "someDefaultValue").then(result => { 38 | console.log(result.data); 39 | console.log(result.metadata); 40 | }); 41 | 42 | //get cluster data map 43 | cache.keys().then(map => { 44 | console.log(map); 45 | }); 46 | ``` 47 | 48 | ### API 49 | 50 | - `init(options)` - create new cluster cache. Options object can have following keys: 51 | - `defaultTtl` - default time to live for keys in ms. Default value is `1000`ms. 52 | - `logger` - default console. May be any class with impemented methods `log` and `warn`. 53 | - `storage` - can be one of `self`, `all`, `master`, `cluster`. Default value is `cluster`. 54 | - `self` - store to actual process, read from actual process. Every process has his own cache, so this cache is not shared between processes. 55 | - `all` - store to all processes, read from actual process. Data are duplicated and on every process is stored full replica of all data. If one process restarts, other process are not affected with cache misses. 56 | - `master` - store to master, read from master. If actual process restarts, other process are not affected with cache miss, if actual process is not master. If master restarts, every process in cluster will lost all cached data. 57 | - `cluster` - store to specific process, read from specific process. Every process in cluster has part of data, so if one process restarts, other process will lost only part of data. Targer process for storage is detemined by key. 58 | - `set(key, value, [ttl])` - store value under key, with given ttl (in ms). 59 | - `get(key, [defaultValue])` - get value stored under key 'key'. 60 | - `inc(key)` - increment key by 1. If key not exists, creates it with value 0 and returns. 61 | - `dec(key)` - decrement key by 1. If key not exists, creates it with value 0 and returns. 62 | - `delete(key)` - removes key from all process where is given key stored. Returns in Promise array of processes deleted from. 63 | - `flush()` - removes all keys from all processes. 64 | - `keys()` - returns in Promise map of cluster with numbers of stored keys. 65 | - `withMeta()` - returns decorated `get` and `set` method that are returning metadata with data. 66 | --- 67 | `metadata` object is object with keys: 68 | - `storedOn` - array of int. Processes that have key stored. 69 | - `readFrom` - int. Proccess which provided data. 70 | - `servedBy` - int. Actual process. 71 | 72 | 73 | ### PM2 metrics 74 | with command `pm2 describe ` you can see in Code metrics cache hit rate and miss rate on every process. 75 | 76 | -------------------------------------------------------------------------------- /test/spec/all.spec.js: -------------------------------------------------------------------------------- 1 | const frisby = require('frisby'); 2 | const Joi = frisby.Joi; 3 | const ip = require("ip"); 4 | const axios = require('axios'); 5 | 6 | var BASE_URL = "http://" + ip.address() + ":8080"; 7 | 8 | beforeEach(function (done) { 9 | axios.get(BASE_URL + "/flush").then(function () { 10 | done(); 11 | }) 12 | }); 13 | 14 | describe('/storage type all', () => { 15 | 16 | it('should be able to read value', (done) => { 17 | axios.get(BASE_URL + "/set?key=foo&value=bar&ttl=10000").then(res => { 18 | return frisby 19 | .get(BASE_URL + "/get?key=foo") 20 | .expect('status', 200) 21 | .expect('json', 'key', "foo") 22 | .expect('json', 'value', "bar") //from previous test 23 | .done(done); 24 | }); 25 | }); 26 | 27 | it('should return metadata on get', (done) => { 28 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 29 | return frisby 30 | .get(BASE_URL + "/get?key=homer") 31 | .expect('status', 200) 32 | .expect('json', 'key', 'homer') 33 | .expect('json', 'value', 'simpson') 34 | .expect('jsonTypes', 'metadata', Joi.object()) 35 | .done(done); 36 | }); 37 | }); 38 | 39 | it('should return only value on read', (done) => { 40 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 41 | return frisby 42 | .get(BASE_URL + "/read?key=homer") 43 | .expect('status', 200) 44 | .expect('json', 'key', 'homer') 45 | .expect('json', 'value', 'simpson') 46 | .done(done); 47 | }); 48 | }); 49 | 50 | it('should delete value from all stored processes', (done) => { 51 | return frisby 52 | .get(BASE_URL + "/set?key=foo&value=bar&ttl=10000") 53 | .expect('status', 200) 54 | .expect('json', 'key', 'foo') 55 | .then(res => { 56 | axios.get(BASE_URL + '/delete?key=foo').then(res => { 57 | return frisby 58 | .get(BASE_URL + "/info") 59 | .expect('status', 200) 60 | .then(res => { 61 | for (const [key, value] of Object.entries(res._json)) { 62 | if (value.indexOf("foo") !== -1) { 63 | fail(); 64 | } 65 | } 66 | done(); 67 | }) 68 | }) 69 | }) 70 | }); 71 | 72 | it('should store all keys on all processes', (done) => { 73 | let homer = axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000"); 74 | let meggie = axios.get(BASE_URL + "/set?key=meggie&value=simpson&ttl=10000"); 75 | let bart = axios.get(BASE_URL + "/set?key=bart&value=simpson&ttl=10000"); 76 | let lisa = axios.get(BASE_URL + "/set?key=lisa&value=simpson&ttl=10000"); 77 | let marge = axios.get(BASE_URL + "/set?key=marge&value=simpson&ttl=10000"); 78 | let montgomery = axios.get(BASE_URL + "/set?key=montgomery&value=burns&ttl=10000"); 79 | let net = axios.get(BASE_URL + "/set?key=net&value=flanders&ttl=10000"); 80 | let nelson = axios.get(BASE_URL + "/set?key=nelson&value=muntz&ttl=10000"); 81 | let edna = axios.get(BASE_URL + "/set?key=edna&value=krabappel&ttl=10000"); 82 | let krusty = axios.get(BASE_URL + "/set?key=krusty&value=theClown&ttl=10000"); 83 | axios.all([homer, meggie, bart, lisa, marge, montgomery, net, nelson, edna, krusty]).then(resp => { 84 | return frisby 85 | .get(BASE_URL + "/info") 86 | .then(function (res) { 87 | for (const [key, value] of Object.entries(res._json)) { 88 | if (value.length !== 10) { 89 | fail(); //all values must be on the same proc. 90 | } 91 | } 92 | done(); 93 | }); 94 | }); 95 | }); 96 | 97 | it('should inc key', (done) => { 98 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 99 | return frisby 100 | .get(BASE_URL + "/inc?key=foo") 101 | .expect('status', 200) 102 | .expect('json', 'key', "foo") 103 | .expect('json', 'value', 101) 104 | .done(done); 105 | }); 106 | }); 107 | 108 | it('should dec key', (done) => { 109 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 110 | return frisby 111 | .get(BASE_URL + "/dec?key=foo") 112 | .expect('status', 200) 113 | .expect('json', 'key', "foo") 114 | .expect('json', 'value', 99) 115 | .done(done); 116 | }); 117 | 118 | }); 119 | 120 | it('should inc and dec not exist key', (done) => { 121 | frisby 122 | .get(BASE_URL + "/dec?key=dec") 123 | .expect('status', 200) 124 | .expect('json', 'key', "dec") 125 | .expect('json', 'value', 0) 126 | .done(done); 127 | frisby 128 | .get(BASE_URL + "/inc?key=inc") 129 | .expect('status', 200) 130 | .expect('json', 'key', "inc") 131 | .expect('json', 'value', 0) 132 | .done(done); 133 | }); 134 | }); 135 | 136 | 137 | -------------------------------------------------------------------------------- /test/spec/master.spec.js: -------------------------------------------------------------------------------- 1 | const frisby = require('frisby'); 2 | const Joi = frisby.Joi; 3 | const ip = require("ip"); 4 | const axios = require('axios'); 5 | 6 | var BASE_URL = "http://" + ip.address() + ":8080"; 7 | 8 | beforeEach(function (done) { 9 | axios.get(BASE_URL + "/flush").then(function () { 10 | done(); 11 | }) 12 | }); 13 | 14 | describe('/storage type master', () => { 15 | 16 | it('should be able to read value', (done) => { 17 | axios.get(BASE_URL + "/set?key=foo&value=bar&ttl=10000").then(res => { 18 | return frisby 19 | .get(BASE_URL + "/get?key=foo") 20 | .expect('status', 200) 21 | .expect('json', 'key', "foo") 22 | .expect('json', 'value', "bar") //from previous test 23 | .done(done); 24 | }); 25 | 26 | }); 27 | 28 | it('should return metadata on get', (done) => { 29 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 30 | return frisby 31 | .get(BASE_URL + "/get?key=homer") 32 | .expect('status', 200) 33 | .expect('json', 'key', 'homer') 34 | .expect('json', 'value', 'simpson') 35 | .expect('jsonTypes', 'metadata', Joi.object()) 36 | .done(done); 37 | }); 38 | }); 39 | 40 | it('should return only value on read', (done) => { 41 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 42 | return frisby 43 | .get(BASE_URL + "/read?key=homer") 44 | .expect('status', 200) 45 | .expect('json', 'key', 'homer') 46 | .expect('json', 'value', 'simpson') 47 | .done(done); 48 | }); 49 | }); 50 | 51 | it('should delete value from all stored processes', (done) => { 52 | return frisby 53 | .get(BASE_URL + "/set?key=foo&value=bar&ttl=10000") 54 | .expect('status', 200) 55 | .expect('json', 'key', 'foo') 56 | .then(res => { 57 | axios.get(BASE_URL + '/delete?key=foo').then(res => { 58 | return frisby 59 | .get(BASE_URL + "/info") 60 | .expect('status', 200) 61 | .then(res => { 62 | for (const [key, value] of Object.entries(res._json)) { 63 | if (value.indexOf("foo") !== -1) { 64 | fail(); 65 | } 66 | } 67 | done(); 68 | }) 69 | }) 70 | }) 71 | }); 72 | 73 | it('should store all keys on one process', (done) => { 74 | let homer = axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000"); 75 | let meggie = axios.get(BASE_URL + "/set?key=meggie&value=simpson&ttl=10000"); 76 | let bart = axios.get(BASE_URL + "/set?key=bart&value=simpson&ttl=10000"); 77 | let lisa = axios.get(BASE_URL + "/set?key=lisa&value=simpson&ttl=10000"); 78 | let marge = axios.get(BASE_URL + "/set?key=marge&value=simpson&ttl=10000"); 79 | let montgomery = axios.get(BASE_URL + "/set?key=montgomery&value=burns&ttl=10000"); 80 | let net = axios.get(BASE_URL + "/set?key=net&value=flanders&ttl=10000"); 81 | let nelson = axios.get(BASE_URL + "/set?key=nelson&value=muntz&ttl=10000"); 82 | let edna = axios.get(BASE_URL + "/set?key=edna&value=krabappel&ttl=10000"); 83 | let krusty = axios.get(BASE_URL + "/set?key=krusty&value=theClown&ttl=10000"); 84 | axios.all([homer, meggie, bart, lisa, marge, montgomery, net, nelson, edna, krusty]).then(resp => { 85 | return frisby 86 | .get(BASE_URL + "/info") 87 | .then(function (res) { 88 | for (const [key, value] of Object.entries(res._json)) { 89 | if (!(value.length === 0 || value.length === 10)) { 90 | fail(); //all values must be on the same proc. 91 | } 92 | } 93 | done(); 94 | }); 95 | }); 96 | 97 | }); 98 | 99 | it('should inc key', (done) => { 100 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 101 | return frisby 102 | .get(BASE_URL + "/inc?key=foo") 103 | .expect('status', 200) 104 | .expect('json', 'key', "foo") 105 | .expect('json', 'value', 101) 106 | .done(done); 107 | }); 108 | }); 109 | 110 | it('should dec key', (done) => { 111 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 112 | return frisby 113 | .get(BASE_URL + "/dec?key=foo") 114 | .expect('status', 200) 115 | .expect('json', 'key', "foo") 116 | .expect('json', 'value', 99) 117 | .done(done); 118 | }); 119 | 120 | }); 121 | 122 | it('should inc and dec not exist key', (done) => { 123 | frisby 124 | .get(BASE_URL + "/dec?key=dec") 125 | .expect('status', 200) 126 | .expect('json', 'key', "dec") 127 | .expect('json', 'value', 0) 128 | .done(done); 129 | frisby 130 | .get(BASE_URL + "/inc?key=inc") 131 | .expect('status', 200) 132 | .expect('json', 'key', "inc") 133 | .expect('json', 'value', 0) 134 | .done(done); 135 | }); 136 | }); 137 | 138 | -------------------------------------------------------------------------------- /test/spec/cluster.spec.js: -------------------------------------------------------------------------------- 1 | const frisby = require('frisby'); 2 | const Joi = frisby.Joi; 3 | const ip = require("ip"); 4 | const axios = require('axios'); 5 | 6 | var BASE_URL = "http://" + ip.address() + ":8080"; 7 | 8 | beforeEach(function (done) { 9 | axios.get(BASE_URL + "/flush").then(function () { 10 | done(); 11 | }) 12 | }); 13 | 14 | describe('/storage type cluster', () => { 15 | 16 | it('should be able to read value', (done) => { 17 | axios.get(BASE_URL + "/set?key=test&value=bar&ttl=10000").then(res => { 18 | return frisby 19 | .get(BASE_URL + "/get?key=test") 20 | .expect('status', 200) 21 | .expect('json', 'key', "test") 22 | .expect('json', 'value', "bar") 23 | .done(done); 24 | }); 25 | }); 26 | 27 | it('should return metadata on get', (done) => { 28 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 29 | return frisby 30 | .get(BASE_URL + "/get?key=homer") 31 | .expect('status', 200) 32 | .expect('json', 'key', 'homer') 33 | .expect('json', 'value', 'simpson') 34 | .expect('jsonTypes', 'metadata', Joi.object()) 35 | .done(done); 36 | }); 37 | }); 38 | 39 | it('should return only value on read', (done) => { 40 | axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000").then(() => { 41 | return frisby 42 | .get(BASE_URL + "/read?key=homer") 43 | .expect('status', 200) 44 | .expect('json', 'key', 'homer') 45 | .expect('json', 'value', 'simpson') 46 | .done(done); 47 | }); 48 | }); 49 | 50 | it('should delete value from all stored processes', (done) => { 51 | return frisby 52 | .get(BASE_URL + "/set?key=foo&value=bar&ttl=10000") 53 | .expect('status', 200) 54 | .expect('json', 'key', 'foo') 55 | .then(res => { 56 | axios.get(BASE_URL + '/delete?key=foo').then(res => { 57 | return frisby 58 | .get(BASE_URL + "/info") 59 | .expect('status', 200) 60 | .then(res => { 61 | for (const [key, value] of Object.entries(res._json)) { 62 | if (value.indexOf("foo") !== -1) { 63 | fail(); 64 | } 65 | } 66 | done(); 67 | }) 68 | }) 69 | }) 70 | }); 71 | 72 | it('should store keys on different processes', (done) => { 73 | let homer = axios.get(BASE_URL + "/set?key=homer&value=simpson&ttl=10000"); 74 | let meggie = axios.get(BASE_URL + "/set?key=meggie&value=simpson&ttl=10000"); 75 | let bart = axios.get(BASE_URL + "/set?key=bart&value=simpson&ttl=10000"); 76 | let lisa = axios.get(BASE_URL + "/set?key=lisa&value=simpson&ttl=10000"); 77 | let marge = axios.get(BASE_URL + "/set?key=marge&value=simpson&ttl=10000"); 78 | let montgomery = axios.get(BASE_URL + "/set?key=montgomery&value=burns&ttl=10000"); 79 | let net = axios.get(BASE_URL + "/set?key=net&value=flanders&ttl=10000"); 80 | let nelson = axios.get(BASE_URL + "/set?key=nelson&value=muntz&ttl=10000"); 81 | let edna = axios.get(BASE_URL + "/set?key=edna&value=krabappel&ttl=10000"); 82 | let krusty = axios.get(BASE_URL + "/set?key=krusty&value=theClown&ttl=10000"); 83 | axios.all([homer, meggie, bart, lisa, marge, montgomery, net, nelson, edna, krusty]).then(resp => { 84 | return frisby 85 | .get(BASE_URL + "/info") 86 | .then(function (res) { 87 | let count = 0; 88 | for (const [key, value] of Object.entries(res._json)) { 89 | if (value.length > 0) { 90 | if (value.length === 10) fail(); //all are on one? That's bad 91 | count += value.length; 92 | } 93 | } 94 | if (count === 10) { 95 | done(); 96 | } else { 97 | fail(); 98 | } 99 | }); 100 | }); 101 | 102 | }); 103 | 104 | it('should inc key', (done) => { 105 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 106 | return frisby 107 | .get(BASE_URL + "/inc?key=foo") 108 | .expect('status', 200) 109 | .expect('json', 'key', "foo") 110 | .expect('json', 'value', 101) 111 | .done(done); 112 | }); 113 | }); 114 | 115 | it('should dec key', (done) => { 116 | axios.get(BASE_URL + "/set?key=foo&value=100&ttl=1000").then(() => { 117 | return frisby 118 | .get(BASE_URL + "/dec?key=foo") 119 | .expect('status', 200) 120 | .expect('json', 'key', "foo") 121 | .expect('json', 'value', 99) 122 | .done(done); 123 | }); 124 | 125 | }); 126 | 127 | it('should inc and dec not exist key', (done) => { 128 | frisby 129 | .get(BASE_URL + "/dec?key=dec") 130 | .expect('status', 200) 131 | .expect('json', 'key', "dec") 132 | .expect('json', 'value', 0) 133 | .done(done); 134 | frisby 135 | .get(BASE_URL + "/inc?key=inc") 136 | .expect('status', 200) 137 | .expect('json', 'key', "inc") 138 | .expect('json', 'value', 0) 139 | .done(done); 140 | }); 141 | }); 142 | 143 | 144 | -------------------------------------------------------------------------------- /lib/ClusterCache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _metadata = require("./metadata"); 4 | 5 | var _metadata2 = _interopRequireDefault(_metadata); 6 | 7 | var _repositories = require("./repositories"); 8 | 9 | var _pm2Monitor = require("./pm2Monitor"); 10 | 11 | var _pm2Monitor2 = _interopRequireDefault(_pm2Monitor); 12 | 13 | var _const = require("./const"); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var pm2 = require("pm2"); 18 | var crypto = require("crypto"); 19 | 20 | 21 | var ClusterCache = { 22 | 23 | initialized: false, 24 | options: { 25 | storage: _const.STORAGE_CLUSTER, 26 | defaultTtl: 1000, 27 | logger: console 28 | }, 29 | 30 | init: function init(options) { 31 | if (ClusterCache.initialized) { 32 | if (options.storage && ClusterCache.options.storage !== options.storage) { 33 | ClusterCache.options.logger.warn("pm2-cluster-cache already initialized - storage changed to previous init value - '" + ClusterCache.options.storage + "'"); 34 | } 35 | if (options.defaultTtl && ClusterCache.options.defaultTtl !== options.defaultTtl) { 36 | ClusterCache.options.logger.warn("pm2-cluster-cache already initialized - defaultTtl changed to previous init value - '" + ClusterCache.options.defaultTtl + "'"); 37 | } 38 | return this; 39 | } 40 | if (options.defaultTtl && (typeof options.defaultTtl !== "number" || options.defaultTtl <= 0)) { 41 | ClusterCache.options.logger.warn("invalid value defaultTtl, will use default value: " + ClusterCache.options.defaultTtl); 42 | delete options.defaultTtl; 43 | } 44 | if (options.storage && [_const.STORAGE_CLUSTER, _const.STORAGE_SELF, _const.STORAGE_ALL, _const.STORAGE_MASTER].includes(options.storage) === false) { 45 | ClusterCache.options.logger.warn("invalid value storage, will use default value: " + ClusterCache.options.storage); 46 | delete options.storage; 47 | } 48 | Object.assign(ClusterCache.options, options); 49 | if (process.env.pm_id === undefined) { 50 | process.env.pm_id = -1; 51 | ClusterCache.options.storage = _const.STORAGE_SELF; 52 | ClusterCache.options.logger.warn("not running on pm2 - pm2-cluster-cache storage forced to '" + _const.STORAGE_SELF + "'"); 53 | } else { 54 | _repositories.pr.init(); 55 | process.setMaxListeners(0); 56 | } 57 | 58 | process.on('message', function (packet) { 59 | var data = packet.data; 60 | 61 | if (packet.topic === _const.TOPIC_SET) { 62 | _repositories.dr.set(data.k, data); 63 | } 64 | 65 | if (packet.topic === _const.TOPIC_DELETE) { 66 | _repositories.dr.delete(data.k); 67 | } 68 | 69 | if (packet.topic === _const.TOPIC_FLUSH) { 70 | _repositories.dr.flush(); 71 | } 72 | 73 | if (packet.topic === _const.TOPIC_KEYS) { 74 | pm2.sendDataToProcessId(data.respond, { 75 | data: _repositories.dr.keys(), 76 | topic: data.cb 77 | }, function () {}); 78 | } 79 | 80 | if (packet.topic === _const.TOPIC_GET) { 81 | pm2.sendDataToProcessId(data.respond, { 82 | data: _repositories.dr.get(data.k), 83 | topic: data.cb 84 | }, function (e) {}); 85 | } 86 | 87 | if (packet.topic === _const.TOPIC_INC) { 88 | var newValue = _repositories.dr.inc(data.k, data.v); 89 | pm2.sendDataToProcessId(data.respond, { 90 | data: newValue, 91 | topic: data.cb 92 | }, function (e) {}); 93 | } 94 | }); 95 | ClusterCache.initialized = true; 96 | return this; 97 | }, 98 | 99 | keysOnProc: function keysOnProc(proc) { 100 | return new Promise(function (ok, fail) { 101 | if (parseInt(process.env.pm_id) === parseInt(proc)) { 102 | return ok(_repositories.dr.keys()); 103 | } 104 | var topic = ClusterCache.generateRespondTopic(); 105 | pm2.sendDataToProcessId(proc, { 106 | data: { 107 | respond: process.env.pm_id, 108 | cb: topic 109 | }, 110 | topic: _const.TOPIC_KEYS 111 | }, function (e) { 112 | if (e) return fail(); 113 | process.prependOnceListener('message', function (packet) { 114 | if (packet.topic === topic) { 115 | return ok(packet.data); 116 | } 117 | }); 118 | }); 119 | }); 120 | }, 121 | 122 | generateRespondTopic: function generateRespondTopic() { 123 | return 'clusterCache' + crypto.randomBytes(16).toString("hex"); 124 | }, 125 | 126 | keys: function keys() { 127 | return new Promise(function (ok, fail) { 128 | pm2.connect(function () { 129 | pm2.list(async function (err, processes) { 130 | var map = {}; 131 | for (var p in processes) { 132 | if (processes[p].name === process.env.name) { 133 | var proc = processes[p].pm_id; 134 | map[proc] = await ClusterCache.keysOnProc(proc); 135 | } 136 | } 137 | return ok(map); 138 | }); 139 | }); 140 | }); 141 | }, 142 | 143 | flush: function flush() { 144 | return new Promise(async function (ok, fail) { 145 | var processes = await _repositories.pr.getAll(); 146 | processes.forEach(function (p) { 147 | pm2.sendDataToProcessId(p, { 148 | data: {}, 149 | topic: _const.TOPIC_FLUSH 150 | }, function (e) { 151 | if (e) return fail(); 152 | }); 153 | }); 154 | return ok(); 155 | }); 156 | }, 157 | 158 | delete: function _delete(key) { 159 | return new Promise(function (ok, fail) { 160 | _repositories.pr.getWriteProcess(key, ClusterCache.options.storage).then(function (processes) { 161 | processes.forEach(function (p) { 162 | ClusterCache._deleteFromProc(key, p); 163 | }); 164 | return ok(processes); 165 | }); 166 | }); 167 | }, 168 | 169 | _deleteFromProc: function _deleteFromProc(key, proc) { 170 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 171 | _repositories.dr.delete(key); 172 | } else { 173 | pm2.sendDataToProcessId(proc, { 174 | data: { 175 | k: key 176 | }, 177 | topic: _const.TOPIC_DELETE 178 | }, function (e) {}); 179 | } 180 | }, 181 | 182 | inc: async function inc(key) { 183 | try { 184 | var val = await ClusterCache.read(key, null); 185 | if (val === null) { 186 | await ClusterCache.set(key, 0); 187 | return 0; 188 | } 189 | val = await ClusterCache.read(key, null); 190 | await ClusterCache.incBy(key, 1); 191 | return parseInt(val) + 1; 192 | } catch (e) {} 193 | }, 194 | 195 | dec: async function dec(key) { 196 | try { 197 | var val = await ClusterCache.read(key, null); 198 | if (val === null) { 199 | ClusterCache.write(key, 0); 200 | return 0; 201 | } else { 202 | await ClusterCache.incBy(key, -1); 203 | return val - 1; 204 | } 205 | } catch (e) {} 206 | }, 207 | 208 | read: async function read(key, defaultValue) { 209 | try { 210 | var value = await ClusterCache.get(key, defaultValue); 211 | return value.data; 212 | } catch (e) { 213 | return defaultValue; 214 | } 215 | }, 216 | 217 | get: function get(key, defaultValue) { 218 | return new Promise(function (ok, fail) { 219 | if (typeof key !== "string") { 220 | return fail('non string value passed to key'); 221 | } 222 | _repositories.pr.getReadProcess(key, ClusterCache.options.storage).then(async function (processes) { 223 | var randProc = processes[~~(Math.random() * processes.length)]; 224 | ClusterCache._getFromProc(key, randProc).then(function (value) { 225 | if (value === undefined) { 226 | _pm2Monitor2.default.miss(); 227 | return ok({ 228 | data: defaultValue, 229 | metadata: (0, _metadata2.default)([], randProc) 230 | }); 231 | } else { 232 | _pm2Monitor2.default.hit(); 233 | return ok({ 234 | data: value, 235 | metadata: (0, _metadata2.default)(processes, randProc) 236 | }); 237 | } 238 | }).catch(function (e) { 239 | _pm2Monitor2.default.miss(); 240 | return ok({ 241 | data: defaultValue, 242 | metadata: (0, _metadata2.default)([], randProc) 243 | }); 244 | }); 245 | }); 246 | }); 247 | }, 248 | 249 | _getFromProc: function _getFromProc(key, proc) { 250 | return new Promise(function (ok, fail) { 251 | if (parseInt(process.env.pm_id) === parseInt(proc)) { 252 | var data = _repositories.dr.get(key); 253 | return data !== '' ? ok(data) : fail(); 254 | } 255 | var topic = ClusterCache.generateRespondTopic(); 256 | 257 | pm2.sendDataToProcessId(proc, { 258 | data: { 259 | respond: process.env.pm_id, 260 | cb: topic, 261 | k: key 262 | }, 263 | topic: _const.TOPIC_GET 264 | }, function (err) { 265 | if (err) fail(); 266 | process.prependOnceListener('message', function (packet) { 267 | if (packet.topic === topic) { 268 | return packet.data !== '' ? ok(packet.data) : fail(); 269 | } 270 | }); 271 | }); 272 | }); 273 | }, 274 | 275 | write: async function write(key, value, ttl) { 276 | try { 277 | await ClusterCache.set(key, value, ttl); 278 | return true; 279 | } catch (e) { 280 | return false; 281 | } 282 | }, 283 | 284 | incBy: function incBy(key, incByValue) { 285 | return new Promise(function (ok, fail) { 286 | _repositories.pr.getWriteProcess(key, ClusterCache.options.storage).then(function (processes) { 287 | processes.forEach(function (proc) { 288 | return new Promise(function (ok, fail) { 289 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 290 | _repositories.dr.inc(key, incByValue); 291 | return ok(); 292 | } else { 293 | pm2.sendDataToProcessId(proc, { 294 | data: { 295 | k: key, 296 | v: incByValue 297 | }, 298 | topic: _const.TOPIC_INC 299 | }, function (e) {}); 300 | } 301 | }); 302 | }); 303 | return ok(); 304 | }); 305 | }); 306 | }, 307 | 308 | set: function set(key, value, ttl) { 309 | return new Promise(function (ok, fail) { 310 | if (typeof key !== "string") { 311 | return fail('non string value passed to key'); 312 | } 313 | if (ttl === undefined) { 314 | ttl = ClusterCache.options.defaultTtl; 315 | } 316 | 317 | _repositories.pr.getWriteProcess(key, ClusterCache.options.storage).then(function (processes) { 318 | processes.forEach(async function (p) { 319 | await ClusterCache._setToProc(key, value, ttl, p); 320 | }); 321 | return ok((0, _metadata2.default)(processes)); 322 | }); 323 | }); 324 | }, 325 | 326 | _setToProc: function _setToProc(key, value, ttl, proc) { 327 | return new Promise(function (ok, fail) { 328 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 329 | var data = { 330 | k: key, 331 | v: value, 332 | t: new Date().getTime() + parseInt(ttl) 333 | }; 334 | _repositories.dr.set(key, data); 335 | return ok(); 336 | } else { 337 | pm2.sendDataToProcessId(proc, { 338 | data: { 339 | k: key, 340 | v: value, 341 | t: new Date().getTime() + parseInt(ttl) 342 | }, 343 | topic: _const.TOPIC_SET 344 | }, function (e) { 345 | return ok(); 346 | }); 347 | } 348 | }); 349 | } 350 | }; 351 | 352 | module.exports = { 353 | get: ClusterCache.read, 354 | set: ClusterCache.write, 355 | inc: ClusterCache.inc, 356 | dec: ClusterCache.dec, 357 | delete: ClusterCache.delete, 358 | keys: ClusterCache.keys, 359 | flush: ClusterCache.flush, 360 | init: ClusterCache.init, 361 | withMeta: function withMeta() { 362 | return { 363 | get: ClusterCache.get, 364 | set: ClusterCache.set 365 | }; 366 | } 367 | }; -------------------------------------------------------------------------------- /src/ClusterCache.js: -------------------------------------------------------------------------------- 1 | const pm2 = require("pm2"); 2 | const crypto = require("crypto"); 3 | import metadata from './metadata'; 4 | import {dr, pr} from './repositories'; 5 | import pm2monitor from './pm2Monitor'; 6 | import { 7 | STORAGE_CLUSTER, 8 | STORAGE_SELF, 9 | STORAGE_ALL, 10 | STORAGE_MASTER, 11 | TOPIC_GET, 12 | TOPIC_SET, 13 | TOPIC_INC, 14 | TOPIC_DEC, 15 | TOPIC_DELETE, 16 | TOPIC_KEYS, 17 | TOPIC_FLUSH 18 | } from "./const"; 19 | 20 | 21 | var ClusterCache = { 22 | 23 | initialized: false, 24 | options: { 25 | storage: STORAGE_CLUSTER, 26 | defaultTtl: 1000, 27 | logger: console 28 | }, 29 | 30 | init: function (options) { 31 | if (ClusterCache.initialized) { 32 | if (options.storage && ClusterCache.options.storage !== options.storage) { 33 | ClusterCache.options.logger.warn(`pm2-cluster-cache already initialized - storage changed to previous init value - '${ClusterCache.options.storage}'`); 34 | } 35 | if (options.defaultTtl && ClusterCache.options.defaultTtl !== options.defaultTtl) { 36 | ClusterCache.options.logger.warn(`pm2-cluster-cache already initialized - defaultTtl changed to previous init value - '${ClusterCache.options.defaultTtl}'`); 37 | } 38 | return this; 39 | } 40 | if (options.defaultTtl && (typeof options.defaultTtl !== "number" || options.defaultTtl <= 0)) { 41 | ClusterCache.options.logger.warn(`invalid value defaultTtl, will use default value: ${ClusterCache.options.defaultTtl}`); 42 | delete options.defaultTtl; 43 | } 44 | if (options.storage && [STORAGE_CLUSTER, STORAGE_SELF, STORAGE_ALL, STORAGE_MASTER].includes(options.storage) === false) { 45 | ClusterCache.options.logger.warn(`invalid value storage, will use default value: ${ClusterCache.options.storage}`); 46 | delete options.storage; 47 | } 48 | Object.assign(ClusterCache.options, options); 49 | if (process.env.pm_id === undefined) { 50 | process.env.pm_id = -1; 51 | ClusterCache.options.storage = STORAGE_SELF; 52 | ClusterCache.options.logger.warn(`not running on pm2 - pm2-cluster-cache storage forced to '${STORAGE_SELF}'`); 53 | } else { 54 | pr.init(); 55 | process.setMaxListeners(0); 56 | } 57 | 58 | process.on('message', function (packet) { 59 | let data = packet.data; 60 | 61 | if (packet.topic === TOPIC_SET) { 62 | dr.set(data.k, data); 63 | } 64 | 65 | if (packet.topic === TOPIC_DELETE) { 66 | dr.delete(data.k); 67 | } 68 | 69 | if (packet.topic === TOPIC_FLUSH) { 70 | dr.flush(); 71 | } 72 | 73 | if (packet.topic === TOPIC_KEYS) { 74 | pm2.sendDataToProcessId(data.respond, { 75 | data: dr.keys(), 76 | topic: data.cb 77 | }, function () { 78 | }); 79 | } 80 | 81 | if (packet.topic === TOPIC_GET) { 82 | pm2.sendDataToProcessId(data.respond, { 83 | data: dr.get(data.k), 84 | topic: data.cb 85 | }, function (e) { 86 | }); 87 | } 88 | 89 | if (packet.topic === TOPIC_INC) { 90 | let newValue = dr.inc(data.k, data.v); 91 | pm2.sendDataToProcessId(data.respond, { 92 | data: newValue, 93 | topic: data.cb 94 | }, function (e) { 95 | }); 96 | } 97 | 98 | }); 99 | ClusterCache.initialized = true; 100 | return this; 101 | }, 102 | 103 | keysOnProc: function (proc) { 104 | return new Promise((ok, fail) => { 105 | if (parseInt(process.env.pm_id) === parseInt(proc)) { 106 | return ok(dr.keys()); 107 | } 108 | let topic = ClusterCache.generateRespondTopic(); 109 | pm2.sendDataToProcessId(proc, { 110 | data: { 111 | respond: process.env.pm_id, 112 | cb: topic 113 | }, 114 | topic: TOPIC_KEYS 115 | }, function (e) { 116 | if (e) return fail(); 117 | process.prependOnceListener('message', function (packet) { 118 | if (packet.topic === topic) { 119 | return ok(packet.data); 120 | } 121 | }); 122 | }); 123 | }); 124 | }, 125 | 126 | generateRespondTopic: function () { 127 | return 'clusterCache' + crypto.randomBytes(16).toString("hex"); 128 | }, 129 | 130 | keys: function () { 131 | return new Promise(function (ok, fail) { 132 | pm2.connect(function () { 133 | pm2.list(async function (err, processes) { 134 | let map = {}; 135 | for (var p in processes) { 136 | if (processes[p].name === process.env.name) { 137 | let proc = processes[p].pm_id; 138 | map[proc] = await ClusterCache.keysOnProc(proc); 139 | } 140 | } 141 | return ok(map); 142 | }); 143 | }); 144 | }); 145 | }, 146 | 147 | flush: function () { 148 | return new Promise(async (ok, fail) => { 149 | let processes = await pr.getAll(); 150 | processes.forEach(p => { 151 | pm2.sendDataToProcessId(p, { 152 | data: {}, 153 | topic: TOPIC_FLUSH 154 | }, function (e) { 155 | if (e) return fail(); 156 | }); 157 | }); 158 | return ok(); 159 | }); 160 | }, 161 | 162 | delete: function (key) { 163 | return new Promise((ok, fail) => { 164 | pr.getWriteProcess(key, ClusterCache.options.storage).then(processes => { 165 | processes.forEach((p) => { 166 | ClusterCache._deleteFromProc(key, p); 167 | }); 168 | return ok(processes); 169 | }); 170 | }); 171 | }, 172 | 173 | _deleteFromProc: function (key, proc) { 174 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 175 | dr.delete(key); 176 | } else { 177 | pm2.sendDataToProcessId(proc, { 178 | data: { 179 | k: key 180 | }, 181 | topic: TOPIC_DELETE 182 | }, function (e) { 183 | 184 | }); 185 | } 186 | }, 187 | 188 | inc: async function (key) { 189 | try { 190 | let val = await ClusterCache.read(key, null); 191 | if (val === null) { 192 | await ClusterCache.set(key, 0); 193 | return 0; 194 | } 195 | val = await ClusterCache.read(key, null); 196 | await ClusterCache.incBy(key, 1); 197 | return parseInt(val) + 1; 198 | } catch (e) { 199 | } 200 | }, 201 | 202 | dec: async function (key) { 203 | try { 204 | let val = await ClusterCache.read(key, null); 205 | if (val === null) { 206 | ClusterCache.write(key, 0); 207 | return 0; 208 | } else { 209 | await ClusterCache.incBy(key, -1); 210 | return val - 1; 211 | } 212 | } catch (e) { 213 | } 214 | }, 215 | 216 | read: async function (key, defaultValue) { 217 | try { 218 | let value = await ClusterCache.get(key, defaultValue); 219 | return value.data; 220 | } catch (e) { 221 | return defaultValue; 222 | } 223 | }, 224 | 225 | get: function (key, defaultValue) { 226 | return new Promise((ok, fail) => { 227 | if (typeof key !== "string") { 228 | return fail('non string value passed to key'); 229 | } 230 | pr.getReadProcess(key, ClusterCache.options.storage).then(async processes => { 231 | let randProc = processes[~~(Math.random() * processes.length)]; 232 | ClusterCache._getFromProc(key, randProc).then(value => { 233 | if (value === undefined) { 234 | pm2monitor.miss(); 235 | return ok({ 236 | data: defaultValue, 237 | metadata: metadata([], randProc) 238 | }) 239 | } else { 240 | pm2monitor.hit(); 241 | return ok({ 242 | data: value, 243 | metadata: metadata(processes, randProc) 244 | }); 245 | } 246 | }).catch(e => { 247 | pm2monitor.miss(); 248 | return ok({ 249 | data: defaultValue, 250 | metadata: metadata([], randProc) 251 | }); 252 | }); 253 | }); 254 | }); 255 | 256 | }, 257 | 258 | _getFromProc: function (key, proc) { 259 | return new Promise((ok, fail) => { 260 | if (parseInt(process.env.pm_id) === parseInt(proc)) { 261 | let data = dr.get(key); 262 | return (data !== '') ? ok(data) : fail(); 263 | } 264 | let topic = ClusterCache.generateRespondTopic(); 265 | 266 | pm2.sendDataToProcessId(proc, { 267 | data: { 268 | respond: process.env.pm_id, 269 | cb: topic, 270 | k: key 271 | }, 272 | topic: TOPIC_GET 273 | }, function (err) { 274 | if (err) fail(); 275 | process.prependOnceListener('message', function (packet) { 276 | if (packet.topic === topic) { 277 | return (packet.data !== '') ? ok(packet.data) : fail(); 278 | } 279 | }); 280 | }); 281 | }); 282 | }, 283 | 284 | write: async function (key, value, ttl) { 285 | try { 286 | await ClusterCache.set(key, value, ttl); 287 | return true; 288 | } catch (e) { 289 | return false; 290 | } 291 | }, 292 | 293 | incBy: function (key, incByValue) { 294 | return new Promise((ok, fail) => { 295 | pr.getWriteProcess(key, ClusterCache.options.storage).then(processes => { 296 | processes.forEach((proc) => { 297 | return new Promise((ok, fail) => { 298 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 299 | dr.inc(key, incByValue); 300 | return ok(); 301 | } else { 302 | pm2.sendDataToProcessId(proc, { 303 | data: { 304 | k: key, 305 | v: incByValue, 306 | }, 307 | topic: TOPIC_INC 308 | }, function (e) { 309 | 310 | }); 311 | } 312 | }); 313 | }); 314 | return ok(); 315 | }); 316 | }); 317 | }, 318 | 319 | set: function (key, value, ttl) { 320 | return new Promise((ok, fail) => { 321 | if (typeof key !== "string") { 322 | return fail('non string value passed to key'); 323 | } 324 | if (ttl === undefined) { 325 | ttl = ClusterCache.options.defaultTtl; 326 | } 327 | 328 | pr.getWriteProcess(key, ClusterCache.options.storage).then(processes => { 329 | processes.forEach(async (p) => { 330 | await ClusterCache._setToProc(key, value, ttl, p); 331 | }); 332 | return ok(metadata(processes)); 333 | }); 334 | }); 335 | 336 | }, 337 | 338 | _setToProc: function (key, value, ttl, proc) { 339 | return new Promise((ok, fail) => { 340 | if (parseInt(proc) === parseInt(process.env.pm_id)) { 341 | let data = { 342 | k: key, 343 | v: value, 344 | t: new Date().getTime() + parseInt(ttl) 345 | }; 346 | dr.set(key, data); 347 | return ok(); 348 | } else { 349 | pm2.sendDataToProcessId(proc, { 350 | data: { 351 | k: key, 352 | v: value, 353 | t: new Date().getTime() + parseInt(ttl) 354 | }, 355 | topic: TOPIC_SET 356 | }, function (e) { 357 | return ok(); 358 | }); 359 | } 360 | }); 361 | } 362 | } 363 | ; 364 | 365 | module.exports = { 366 | get: ClusterCache.read, 367 | set: ClusterCache.write, 368 | inc: ClusterCache.inc, 369 | dec: ClusterCache.dec, 370 | delete: ClusterCache.delete, 371 | keys: ClusterCache.keys, 372 | flush: ClusterCache.flush, 373 | init: ClusterCache.init, 374 | withMeta: () => { 375 | return { 376 | get: ClusterCache.get, 377 | set: ClusterCache.set 378 | } 379 | } 380 | }; -------------------------------------------------------------------------------- /test/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pm2-cluster-cache-test", 3 | "version": "2.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/polyfill": { 8 | "version": "7.12.1", 9 | "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", 10 | "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", 11 | "dev": true, 12 | "requires": { 13 | "core-js": "^2.6.5", 14 | "regenerator-runtime": "^0.13.4" 15 | } 16 | }, 17 | "accepts": { 18 | "version": "1.3.7", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 20 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 21 | "dev": true, 22 | "requires": { 23 | "mime-types": "~2.1.24", 24 | "negotiator": "0.6.2" 25 | } 26 | }, 27 | "ansi-regex": { 28 | "version": "2.1.1", 29 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 30 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 31 | "dev": true 32 | }, 33 | "ansi-styles": { 34 | "version": "2.2.1", 35 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 36 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 37 | "dev": true 38 | }, 39 | "array-flatten": { 40 | "version": "1.1.1", 41 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 42 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 43 | "dev": true 44 | }, 45 | "asynckit": { 46 | "version": "0.4.0", 47 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 48 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 49 | "dev": true 50 | }, 51 | "axios": { 52 | "version": "0.21.2", 53 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", 54 | "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", 55 | "dev": true, 56 | "requires": { 57 | "follow-redirects": "^1.14.0" 58 | } 59 | }, 60 | "babel-code-frame": { 61 | "version": "6.26.0", 62 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 63 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 64 | "dev": true, 65 | "requires": { 66 | "chalk": "^1.1.3", 67 | "esutils": "^2.0.2", 68 | "js-tokens": "^3.0.2" 69 | } 70 | }, 71 | "babel-core": { 72 | "version": "6.26.3", 73 | "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", 74 | "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", 75 | "dev": true, 76 | "requires": { 77 | "babel-code-frame": "^6.26.0", 78 | "babel-generator": "^6.26.0", 79 | "babel-helpers": "^6.24.1", 80 | "babel-messages": "^6.23.0", 81 | "babel-register": "^6.26.0", 82 | "babel-runtime": "^6.26.0", 83 | "babel-template": "^6.26.0", 84 | "babel-traverse": "^6.26.0", 85 | "babel-types": "^6.26.0", 86 | "babylon": "^6.18.0", 87 | "convert-source-map": "^1.5.1", 88 | "debug": "^2.6.9", 89 | "json5": "^0.5.1", 90 | "lodash": "^4.17.4", 91 | "minimatch": "^3.0.4", 92 | "path-is-absolute": "^1.0.1", 93 | "private": "^0.1.8", 94 | "slash": "^1.0.0", 95 | "source-map": "^0.5.7" 96 | } 97 | }, 98 | "babel-generator": { 99 | "version": "6.26.1", 100 | "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", 101 | "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", 102 | "dev": true, 103 | "requires": { 104 | "babel-messages": "^6.23.0", 105 | "babel-runtime": "^6.26.0", 106 | "babel-types": "^6.26.0", 107 | "detect-indent": "^4.0.0", 108 | "jsesc": "^1.3.0", 109 | "lodash": "^4.17.4", 110 | "source-map": "^0.5.7", 111 | "trim-right": "^1.0.1" 112 | }, 113 | "dependencies": { 114 | "jsesc": { 115 | "version": "1.3.0", 116 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", 117 | "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", 118 | "dev": true 119 | } 120 | } 121 | }, 122 | "babel-helper-call-delegate": { 123 | "version": "6.24.1", 124 | "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", 125 | "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", 126 | "dev": true, 127 | "requires": { 128 | "babel-helper-hoist-variables": "^6.24.1", 129 | "babel-runtime": "^6.22.0", 130 | "babel-traverse": "^6.24.1", 131 | "babel-types": "^6.24.1" 132 | } 133 | }, 134 | "babel-helper-define-map": { 135 | "version": "6.26.0", 136 | "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", 137 | "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", 138 | "dev": true, 139 | "requires": { 140 | "babel-helper-function-name": "^6.24.1", 141 | "babel-runtime": "^6.26.0", 142 | "babel-types": "^6.26.0", 143 | "lodash": "^4.17.4" 144 | } 145 | }, 146 | "babel-helper-function-name": { 147 | "version": "6.24.1", 148 | "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", 149 | "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", 150 | "dev": true, 151 | "requires": { 152 | "babel-helper-get-function-arity": "^6.24.1", 153 | "babel-runtime": "^6.22.0", 154 | "babel-template": "^6.24.1", 155 | "babel-traverse": "^6.24.1", 156 | "babel-types": "^6.24.1" 157 | } 158 | }, 159 | "babel-helper-get-function-arity": { 160 | "version": "6.24.1", 161 | "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", 162 | "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", 163 | "dev": true, 164 | "requires": { 165 | "babel-runtime": "^6.22.0", 166 | "babel-types": "^6.24.1" 167 | } 168 | }, 169 | "babel-helper-hoist-variables": { 170 | "version": "6.24.1", 171 | "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", 172 | "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", 173 | "dev": true, 174 | "requires": { 175 | "babel-runtime": "^6.22.0", 176 | "babel-types": "^6.24.1" 177 | } 178 | }, 179 | "babel-helper-optimise-call-expression": { 180 | "version": "6.24.1", 181 | "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", 182 | "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", 183 | "dev": true, 184 | "requires": { 185 | "babel-runtime": "^6.22.0", 186 | "babel-types": "^6.24.1" 187 | } 188 | }, 189 | "babel-helper-regex": { 190 | "version": "6.26.0", 191 | "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", 192 | "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", 193 | "dev": true, 194 | "requires": { 195 | "babel-runtime": "^6.26.0", 196 | "babel-types": "^6.26.0", 197 | "lodash": "^4.17.4" 198 | } 199 | }, 200 | "babel-helper-replace-supers": { 201 | "version": "6.24.1", 202 | "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", 203 | "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", 204 | "dev": true, 205 | "requires": { 206 | "babel-helper-optimise-call-expression": "^6.24.1", 207 | "babel-messages": "^6.23.0", 208 | "babel-runtime": "^6.22.0", 209 | "babel-template": "^6.24.1", 210 | "babel-traverse": "^6.24.1", 211 | "babel-types": "^6.24.1" 212 | } 213 | }, 214 | "babel-helpers": { 215 | "version": "6.24.1", 216 | "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", 217 | "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", 218 | "dev": true, 219 | "requires": { 220 | "babel-runtime": "^6.22.0", 221 | "babel-template": "^6.24.1" 222 | } 223 | }, 224 | "babel-messages": { 225 | "version": "6.23.0", 226 | "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", 227 | "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", 228 | "dev": true, 229 | "requires": { 230 | "babel-runtime": "^6.22.0" 231 | } 232 | }, 233 | "babel-plugin-check-es2015-constants": { 234 | "version": "6.22.0", 235 | "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", 236 | "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", 237 | "dev": true, 238 | "requires": { 239 | "babel-runtime": "^6.22.0" 240 | } 241 | }, 242 | "babel-plugin-transform-es2015-arrow-functions": { 243 | "version": "6.22.0", 244 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", 245 | "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", 246 | "dev": true, 247 | "requires": { 248 | "babel-runtime": "^6.22.0" 249 | } 250 | }, 251 | "babel-plugin-transform-es2015-block-scoped-functions": { 252 | "version": "6.22.0", 253 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", 254 | "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", 255 | "dev": true, 256 | "requires": { 257 | "babel-runtime": "^6.22.0" 258 | } 259 | }, 260 | "babel-plugin-transform-es2015-block-scoping": { 261 | "version": "6.26.0", 262 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", 263 | "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", 264 | "dev": true, 265 | "requires": { 266 | "babel-runtime": "^6.26.0", 267 | "babel-template": "^6.26.0", 268 | "babel-traverse": "^6.26.0", 269 | "babel-types": "^6.26.0", 270 | "lodash": "^4.17.4" 271 | } 272 | }, 273 | "babel-plugin-transform-es2015-classes": { 274 | "version": "6.24.1", 275 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", 276 | "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", 277 | "dev": true, 278 | "requires": { 279 | "babel-helper-define-map": "^6.24.1", 280 | "babel-helper-function-name": "^6.24.1", 281 | "babel-helper-optimise-call-expression": "^6.24.1", 282 | "babel-helper-replace-supers": "^6.24.1", 283 | "babel-messages": "^6.23.0", 284 | "babel-runtime": "^6.22.0", 285 | "babel-template": "^6.24.1", 286 | "babel-traverse": "^6.24.1", 287 | "babel-types": "^6.24.1" 288 | } 289 | }, 290 | "babel-plugin-transform-es2015-computed-properties": { 291 | "version": "6.24.1", 292 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", 293 | "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", 294 | "dev": true, 295 | "requires": { 296 | "babel-runtime": "^6.22.0", 297 | "babel-template": "^6.24.1" 298 | } 299 | }, 300 | "babel-plugin-transform-es2015-destructuring": { 301 | "version": "6.23.0", 302 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", 303 | "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", 304 | "dev": true, 305 | "requires": { 306 | "babel-runtime": "^6.22.0" 307 | } 308 | }, 309 | "babel-plugin-transform-es2015-duplicate-keys": { 310 | "version": "6.24.1", 311 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", 312 | "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", 313 | "dev": true, 314 | "requires": { 315 | "babel-runtime": "^6.22.0", 316 | "babel-types": "^6.24.1" 317 | } 318 | }, 319 | "babel-plugin-transform-es2015-for-of": { 320 | "version": "6.23.0", 321 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", 322 | "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", 323 | "dev": true, 324 | "requires": { 325 | "babel-runtime": "^6.22.0" 326 | } 327 | }, 328 | "babel-plugin-transform-es2015-function-name": { 329 | "version": "6.24.1", 330 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", 331 | "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", 332 | "dev": true, 333 | "requires": { 334 | "babel-helper-function-name": "^6.24.1", 335 | "babel-runtime": "^6.22.0", 336 | "babel-types": "^6.24.1" 337 | } 338 | }, 339 | "babel-plugin-transform-es2015-literals": { 340 | "version": "6.22.0", 341 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", 342 | "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", 343 | "dev": true, 344 | "requires": { 345 | "babel-runtime": "^6.22.0" 346 | } 347 | }, 348 | "babel-plugin-transform-es2015-modules-amd": { 349 | "version": "6.24.1", 350 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", 351 | "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", 352 | "dev": true, 353 | "requires": { 354 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 355 | "babel-runtime": "^6.22.0", 356 | "babel-template": "^6.24.1" 357 | } 358 | }, 359 | "babel-plugin-transform-es2015-modules-commonjs": { 360 | "version": "6.26.2", 361 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", 362 | "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", 363 | "dev": true, 364 | "requires": { 365 | "babel-plugin-transform-strict-mode": "^6.24.1", 366 | "babel-runtime": "^6.26.0", 367 | "babel-template": "^6.26.0", 368 | "babel-types": "^6.26.0" 369 | } 370 | }, 371 | "babel-plugin-transform-es2015-modules-systemjs": { 372 | "version": "6.24.1", 373 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", 374 | "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", 375 | "dev": true, 376 | "requires": { 377 | "babel-helper-hoist-variables": "^6.24.1", 378 | "babel-runtime": "^6.22.0", 379 | "babel-template": "^6.24.1" 380 | } 381 | }, 382 | "babel-plugin-transform-es2015-modules-umd": { 383 | "version": "6.24.1", 384 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", 385 | "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", 386 | "dev": true, 387 | "requires": { 388 | "babel-plugin-transform-es2015-modules-amd": "^6.24.1", 389 | "babel-runtime": "^6.22.0", 390 | "babel-template": "^6.24.1" 391 | } 392 | }, 393 | "babel-plugin-transform-es2015-object-super": { 394 | "version": "6.24.1", 395 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", 396 | "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", 397 | "dev": true, 398 | "requires": { 399 | "babel-helper-replace-supers": "^6.24.1", 400 | "babel-runtime": "^6.22.0" 401 | } 402 | }, 403 | "babel-plugin-transform-es2015-parameters": { 404 | "version": "6.24.1", 405 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", 406 | "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", 407 | "dev": true, 408 | "requires": { 409 | "babel-helper-call-delegate": "^6.24.1", 410 | "babel-helper-get-function-arity": "^6.24.1", 411 | "babel-runtime": "^6.22.0", 412 | "babel-template": "^6.24.1", 413 | "babel-traverse": "^6.24.1", 414 | "babel-types": "^6.24.1" 415 | } 416 | }, 417 | "babel-plugin-transform-es2015-shorthand-properties": { 418 | "version": "6.24.1", 419 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", 420 | "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", 421 | "dev": true, 422 | "requires": { 423 | "babel-runtime": "^6.22.0", 424 | "babel-types": "^6.24.1" 425 | } 426 | }, 427 | "babel-plugin-transform-es2015-spread": { 428 | "version": "6.22.0", 429 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", 430 | "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", 431 | "dev": true, 432 | "requires": { 433 | "babel-runtime": "^6.22.0" 434 | } 435 | }, 436 | "babel-plugin-transform-es2015-sticky-regex": { 437 | "version": "6.24.1", 438 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", 439 | "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", 440 | "dev": true, 441 | "requires": { 442 | "babel-helper-regex": "^6.24.1", 443 | "babel-runtime": "^6.22.0", 444 | "babel-types": "^6.24.1" 445 | } 446 | }, 447 | "babel-plugin-transform-es2015-template-literals": { 448 | "version": "6.22.0", 449 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", 450 | "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", 451 | "dev": true, 452 | "requires": { 453 | "babel-runtime": "^6.22.0" 454 | } 455 | }, 456 | "babel-plugin-transform-es2015-typeof-symbol": { 457 | "version": "6.23.0", 458 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", 459 | "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", 460 | "dev": true, 461 | "requires": { 462 | "babel-runtime": "^6.22.0" 463 | } 464 | }, 465 | "babel-plugin-transform-es2015-unicode-regex": { 466 | "version": "6.24.1", 467 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", 468 | "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", 469 | "dev": true, 470 | "requires": { 471 | "babel-helper-regex": "^6.24.1", 472 | "babel-runtime": "^6.22.0", 473 | "regexpu-core": "^2.0.0" 474 | } 475 | }, 476 | "babel-plugin-transform-regenerator": { 477 | "version": "6.26.0", 478 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", 479 | "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", 480 | "dev": true, 481 | "requires": { 482 | "regenerator-transform": "^0.10.0" 483 | } 484 | }, 485 | "babel-plugin-transform-strict-mode": { 486 | "version": "6.24.1", 487 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", 488 | "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", 489 | "dev": true, 490 | "requires": { 491 | "babel-runtime": "^6.22.0", 492 | "babel-types": "^6.24.1" 493 | } 494 | }, 495 | "babel-preset-es2015": { 496 | "version": "6.24.1", 497 | "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", 498 | "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", 499 | "dev": true, 500 | "requires": { 501 | "babel-plugin-check-es2015-constants": "^6.22.0", 502 | "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", 503 | "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", 504 | "babel-plugin-transform-es2015-block-scoping": "^6.24.1", 505 | "babel-plugin-transform-es2015-classes": "^6.24.1", 506 | "babel-plugin-transform-es2015-computed-properties": "^6.24.1", 507 | "babel-plugin-transform-es2015-destructuring": "^6.22.0", 508 | "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", 509 | "babel-plugin-transform-es2015-for-of": "^6.22.0", 510 | "babel-plugin-transform-es2015-function-name": "^6.24.1", 511 | "babel-plugin-transform-es2015-literals": "^6.22.0", 512 | "babel-plugin-transform-es2015-modules-amd": "^6.24.1", 513 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 514 | "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", 515 | "babel-plugin-transform-es2015-modules-umd": "^6.24.1", 516 | "babel-plugin-transform-es2015-object-super": "^6.24.1", 517 | "babel-plugin-transform-es2015-parameters": "^6.24.1", 518 | "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", 519 | "babel-plugin-transform-es2015-spread": "^6.22.0", 520 | "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", 521 | "babel-plugin-transform-es2015-template-literals": "^6.22.0", 522 | "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", 523 | "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", 524 | "babel-plugin-transform-regenerator": "^6.24.1" 525 | } 526 | }, 527 | "babel-register": { 528 | "version": "6.26.0", 529 | "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", 530 | "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", 531 | "dev": true, 532 | "requires": { 533 | "babel-core": "^6.26.0", 534 | "babel-runtime": "^6.26.0", 535 | "core-js": "^2.5.0", 536 | "home-or-tmp": "^2.0.0", 537 | "lodash": "^4.17.4", 538 | "mkdirp": "^0.5.1", 539 | "source-map-support": "^0.4.15" 540 | } 541 | }, 542 | "babel-runtime": { 543 | "version": "6.26.0", 544 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 545 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 546 | "dev": true, 547 | "requires": { 548 | "core-js": "^2.4.0", 549 | "regenerator-runtime": "^0.11.0" 550 | }, 551 | "dependencies": { 552 | "regenerator-runtime": { 553 | "version": "0.11.1", 554 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 555 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", 556 | "dev": true 557 | } 558 | } 559 | }, 560 | "babel-template": { 561 | "version": "6.26.0", 562 | "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", 563 | "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", 564 | "dev": true, 565 | "requires": { 566 | "babel-runtime": "^6.26.0", 567 | "babel-traverse": "^6.26.0", 568 | "babel-types": "^6.26.0", 569 | "babylon": "^6.18.0", 570 | "lodash": "^4.17.4" 571 | } 572 | }, 573 | "babel-traverse": { 574 | "version": "6.26.0", 575 | "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", 576 | "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", 577 | "dev": true, 578 | "requires": { 579 | "babel-code-frame": "^6.26.0", 580 | "babel-messages": "^6.23.0", 581 | "babel-runtime": "^6.26.0", 582 | "babel-types": "^6.26.0", 583 | "babylon": "^6.18.0", 584 | "debug": "^2.6.8", 585 | "globals": "^9.18.0", 586 | "invariant": "^2.2.2", 587 | "lodash": "^4.17.4" 588 | } 589 | }, 590 | "babel-types": { 591 | "version": "6.26.0", 592 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", 593 | "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", 594 | "dev": true, 595 | "requires": { 596 | "babel-runtime": "^6.26.0", 597 | "esutils": "^2.0.2", 598 | "lodash": "^4.17.4", 599 | "to-fast-properties": "^1.0.3" 600 | } 601 | }, 602 | "babylon": { 603 | "version": "6.18.0", 604 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", 605 | "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", 606 | "dev": true 607 | }, 608 | "balanced-match": { 609 | "version": "1.0.0", 610 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 611 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 612 | "dev": true 613 | }, 614 | "body-parser": { 615 | "version": "1.19.0", 616 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 617 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 618 | "dev": true, 619 | "requires": { 620 | "bytes": "3.1.0", 621 | "content-type": "~1.0.4", 622 | "debug": "2.6.9", 623 | "depd": "~1.1.2", 624 | "http-errors": "1.7.2", 625 | "iconv-lite": "0.4.24", 626 | "on-finished": "~2.3.0", 627 | "qs": "6.7.0", 628 | "raw-body": "2.4.0", 629 | "type-is": "~1.6.17" 630 | } 631 | }, 632 | "brace-expansion": { 633 | "version": "1.1.11", 634 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 635 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 636 | "dev": true, 637 | "requires": { 638 | "balanced-match": "^1.0.0", 639 | "concat-map": "0.0.1" 640 | } 641 | }, 642 | "bytes": { 643 | "version": "3.1.0", 644 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 645 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 646 | "dev": true 647 | }, 648 | "chalk": { 649 | "version": "1.1.3", 650 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 651 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 652 | "dev": true, 653 | "requires": { 654 | "ansi-styles": "^2.2.1", 655 | "escape-string-regexp": "^1.0.2", 656 | "has-ansi": "^2.0.0", 657 | "strip-ansi": "^3.0.0", 658 | "supports-color": "^2.0.0" 659 | } 660 | }, 661 | "combined-stream": { 662 | "version": "1.0.8", 663 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 664 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 665 | "dev": true, 666 | "requires": { 667 | "delayed-stream": "~1.0.0" 668 | } 669 | }, 670 | "concat-map": { 671 | "version": "0.0.1", 672 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 673 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 674 | "dev": true 675 | }, 676 | "content-disposition": { 677 | "version": "0.5.3", 678 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 679 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 680 | "dev": true, 681 | "requires": { 682 | "safe-buffer": "5.1.2" 683 | } 684 | }, 685 | "content-type": { 686 | "version": "1.0.4", 687 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 688 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 689 | "dev": true 690 | }, 691 | "convert-source-map": { 692 | "version": "1.7.0", 693 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", 694 | "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", 695 | "dev": true, 696 | "requires": { 697 | "safe-buffer": "~5.1.1" 698 | } 699 | }, 700 | "cookie": { 701 | "version": "0.4.0", 702 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 703 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 704 | "dev": true 705 | }, 706 | "cookie-signature": { 707 | "version": "1.0.6", 708 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 709 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 710 | "dev": true 711 | }, 712 | "core-js": { 713 | "version": "2.6.12", 714 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", 715 | "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", 716 | "dev": true 717 | }, 718 | "debug": { 719 | "version": "2.6.9", 720 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 721 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 722 | "dev": true, 723 | "requires": { 724 | "ms": "2.0.0" 725 | } 726 | }, 727 | "delayed-stream": { 728 | "version": "1.0.0", 729 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 730 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 731 | "dev": true 732 | }, 733 | "depd": { 734 | "version": "1.1.2", 735 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 736 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 737 | "dev": true 738 | }, 739 | "destroy": { 740 | "version": "1.0.4", 741 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 742 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 743 | "dev": true 744 | }, 745 | "detect-indent": { 746 | "version": "4.0.0", 747 | "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", 748 | "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", 749 | "dev": true, 750 | "requires": { 751 | "repeating": "^2.0.0" 752 | } 753 | }, 754 | "ee-first": { 755 | "version": "1.1.1", 756 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 757 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 758 | "dev": true 759 | }, 760 | "encodeurl": { 761 | "version": "1.0.2", 762 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 763 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 764 | "dev": true 765 | }, 766 | "encoding": { 767 | "version": "0.1.13", 768 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", 769 | "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", 770 | "dev": true, 771 | "requires": { 772 | "iconv-lite": "^0.6.2" 773 | }, 774 | "dependencies": { 775 | "iconv-lite": { 776 | "version": "0.6.2", 777 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", 778 | "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", 779 | "dev": true, 780 | "requires": { 781 | "safer-buffer": ">= 2.1.2 < 3.0.0" 782 | } 783 | } 784 | } 785 | }, 786 | "escape-html": { 787 | "version": "1.0.3", 788 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 789 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 790 | "dev": true 791 | }, 792 | "escape-string-regexp": { 793 | "version": "1.0.5", 794 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 795 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 796 | "dev": true 797 | }, 798 | "esutils": { 799 | "version": "2.0.3", 800 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 801 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 802 | "dev": true 803 | }, 804 | "etag": { 805 | "version": "1.8.1", 806 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 807 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 808 | "dev": true 809 | }, 810 | "express": { 811 | "version": "4.17.1", 812 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 813 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 814 | "dev": true, 815 | "requires": { 816 | "accepts": "~1.3.7", 817 | "array-flatten": "1.1.1", 818 | "body-parser": "1.19.0", 819 | "content-disposition": "0.5.3", 820 | "content-type": "~1.0.4", 821 | "cookie": "0.4.0", 822 | "cookie-signature": "1.0.6", 823 | "debug": "2.6.9", 824 | "depd": "~1.1.2", 825 | "encodeurl": "~1.0.2", 826 | "escape-html": "~1.0.3", 827 | "etag": "~1.8.1", 828 | "finalhandler": "~1.1.2", 829 | "fresh": "0.5.2", 830 | "merge-descriptors": "1.0.1", 831 | "methods": "~1.1.2", 832 | "on-finished": "~2.3.0", 833 | "parseurl": "~1.3.3", 834 | "path-to-regexp": "0.1.7", 835 | "proxy-addr": "~2.0.5", 836 | "qs": "6.7.0", 837 | "range-parser": "~1.2.1", 838 | "safe-buffer": "5.1.2", 839 | "send": "0.17.1", 840 | "serve-static": "1.14.1", 841 | "setprototypeof": "1.1.1", 842 | "statuses": "~1.5.0", 843 | "type-is": "~1.6.18", 844 | "utils-merge": "1.0.1", 845 | "vary": "~1.1.2" 846 | } 847 | }, 848 | "finalhandler": { 849 | "version": "1.1.2", 850 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 851 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 852 | "dev": true, 853 | "requires": { 854 | "debug": "2.6.9", 855 | "encodeurl": "~1.0.2", 856 | "escape-html": "~1.0.3", 857 | "on-finished": "~2.3.0", 858 | "parseurl": "~1.3.3", 859 | "statuses": "~1.5.0", 860 | "unpipe": "~1.0.0" 861 | } 862 | }, 863 | "follow-redirects": { 864 | "version": "1.14.7", 865 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", 866 | "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", 867 | "dev": true 868 | }, 869 | "form-data": { 870 | "version": "2.5.1", 871 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", 872 | "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", 873 | "dev": true, 874 | "requires": { 875 | "asynckit": "^0.4.0", 876 | "combined-stream": "^1.0.6", 877 | "mime-types": "^2.1.12" 878 | } 879 | }, 880 | "forwarded": { 881 | "version": "0.1.2", 882 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 883 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 884 | "dev": true 885 | }, 886 | "fresh": { 887 | "version": "0.5.2", 888 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 889 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 890 | "dev": true 891 | }, 892 | "frisby": { 893 | "version": "2.1.3", 894 | "resolved": "https://registry.npmjs.org/frisby/-/frisby-2.1.3.tgz", 895 | "integrity": "sha512-b1HAqx10Ofxj5u9vZbWJRmQNKtYp8sNZuGNqPtUpHIFdwJcS7mzUjjNyEw/miYrILXOah3KbX40h0awjsuXgQg==", 896 | "dev": true, 897 | "requires": { 898 | "encoding": "^0.1.12", 899 | "form-data": "^2.2.0", 900 | "joi": "^13.0.0", 901 | "lodash": "^4.17.15", 902 | "node-fetch": "^2.1.2" 903 | } 904 | }, 905 | "fs.realpath": { 906 | "version": "1.0.0", 907 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 908 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 909 | "dev": true 910 | }, 911 | "glob": { 912 | "version": "7.1.6", 913 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 914 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 915 | "dev": true, 916 | "requires": { 917 | "fs.realpath": "^1.0.0", 918 | "inflight": "^1.0.4", 919 | "inherits": "2", 920 | "minimatch": "^3.0.4", 921 | "once": "^1.3.0", 922 | "path-is-absolute": "^1.0.0" 923 | } 924 | }, 925 | "globals": { 926 | "version": "9.18.0", 927 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 928 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 929 | "dev": true 930 | }, 931 | "has-ansi": { 932 | "version": "2.0.0", 933 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 934 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 935 | "dev": true, 936 | "requires": { 937 | "ansi-regex": "^2.0.0" 938 | } 939 | }, 940 | "hoek": { 941 | "version": "5.0.4", 942 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", 943 | "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==", 944 | "dev": true 945 | }, 946 | "home-or-tmp": { 947 | "version": "2.0.0", 948 | "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", 949 | "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", 950 | "dev": true, 951 | "requires": { 952 | "os-homedir": "^1.0.0", 953 | "os-tmpdir": "^1.0.1" 954 | } 955 | }, 956 | "http-errors": { 957 | "version": "1.7.2", 958 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 959 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 960 | "dev": true, 961 | "requires": { 962 | "depd": "~1.1.2", 963 | "inherits": "2.0.3", 964 | "setprototypeof": "1.1.1", 965 | "statuses": ">= 1.5.0 < 2", 966 | "toidentifier": "1.0.0" 967 | } 968 | }, 969 | "iconv-lite": { 970 | "version": "0.4.24", 971 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 972 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 973 | "dev": true, 974 | "requires": { 975 | "safer-buffer": ">= 2.1.2 < 3" 976 | } 977 | }, 978 | "inflight": { 979 | "version": "1.0.6", 980 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 981 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 982 | "dev": true, 983 | "requires": { 984 | "once": "^1.3.0", 985 | "wrappy": "1" 986 | } 987 | }, 988 | "inherits": { 989 | "version": "2.0.3", 990 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 991 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 992 | "dev": true 993 | }, 994 | "invariant": { 995 | "version": "2.2.4", 996 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 997 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 998 | "dev": true, 999 | "requires": { 1000 | "loose-envify": "^1.0.0" 1001 | } 1002 | }, 1003 | "ip": { 1004 | "version": "1.1.5", 1005 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 1006 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", 1007 | "dev": true 1008 | }, 1009 | "ipaddr.js": { 1010 | "version": "1.9.1", 1011 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1012 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1013 | "dev": true 1014 | }, 1015 | "is-finite": { 1016 | "version": "1.1.0", 1017 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", 1018 | "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", 1019 | "dev": true 1020 | }, 1021 | "isemail": { 1022 | "version": "3.2.0", 1023 | "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", 1024 | "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", 1025 | "dev": true, 1026 | "requires": { 1027 | "punycode": "2.x.x" 1028 | } 1029 | }, 1030 | "jasmine": { 1031 | "version": "3.6.4", 1032 | "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.4.tgz", 1033 | "integrity": "sha512-hIeOou6y0BgCOKYgXYveQvlY+PTHgDPajFf+vLCYbMTQ+VjAP9+EQv0nuC9+gyCAAWISRFauB1XUb9kFuOKtcQ==", 1034 | "dev": true, 1035 | "requires": { 1036 | "glob": "^7.1.6", 1037 | "jasmine-core": "~3.6.0" 1038 | } 1039 | }, 1040 | "jasmine-core": { 1041 | "version": "3.6.0", 1042 | "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", 1043 | "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", 1044 | "dev": true 1045 | }, 1046 | "joi": { 1047 | "version": "13.7.0", 1048 | "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", 1049 | "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", 1050 | "dev": true, 1051 | "requires": { 1052 | "hoek": "5.x.x", 1053 | "isemail": "3.x.x", 1054 | "topo": "3.x.x" 1055 | } 1056 | }, 1057 | "js-tokens": { 1058 | "version": "3.0.2", 1059 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 1060 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 1061 | "dev": true 1062 | }, 1063 | "jsesc": { 1064 | "version": "0.5.0", 1065 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", 1066 | "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", 1067 | "dev": true 1068 | }, 1069 | "json5": { 1070 | "version": "0.5.1", 1071 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", 1072 | "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", 1073 | "dev": true 1074 | }, 1075 | "lodash": { 1076 | "version": "4.17.21", 1077 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1078 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1079 | "dev": true 1080 | }, 1081 | "loose-envify": { 1082 | "version": "1.4.0", 1083 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1084 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1085 | "dev": true, 1086 | "requires": { 1087 | "js-tokens": "^3.0.0 || ^4.0.0" 1088 | } 1089 | }, 1090 | "media-typer": { 1091 | "version": "0.3.0", 1092 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1093 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 1094 | "dev": true 1095 | }, 1096 | "merge-descriptors": { 1097 | "version": "1.0.1", 1098 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1099 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 1100 | "dev": true 1101 | }, 1102 | "methods": { 1103 | "version": "1.1.2", 1104 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1105 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 1106 | "dev": true 1107 | }, 1108 | "mime": { 1109 | "version": "1.6.0", 1110 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1111 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1112 | "dev": true 1113 | }, 1114 | "mime-db": { 1115 | "version": "1.46.0", 1116 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", 1117 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", 1118 | "dev": true 1119 | }, 1120 | "mime-types": { 1121 | "version": "2.1.29", 1122 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", 1123 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", 1124 | "dev": true, 1125 | "requires": { 1126 | "mime-db": "1.46.0" 1127 | } 1128 | }, 1129 | "minimatch": { 1130 | "version": "3.0.4", 1131 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1132 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1133 | "dev": true, 1134 | "requires": { 1135 | "brace-expansion": "^1.1.7" 1136 | } 1137 | }, 1138 | "minimist": { 1139 | "version": "1.2.6", 1140 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 1141 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", 1142 | "dev": true 1143 | }, 1144 | "mkdirp": { 1145 | "version": "0.5.5", 1146 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1147 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1148 | "dev": true, 1149 | "requires": { 1150 | "minimist": "^1.2.5" 1151 | } 1152 | }, 1153 | "ms": { 1154 | "version": "2.0.0", 1155 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1156 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1157 | "dev": true 1158 | }, 1159 | "negotiator": { 1160 | "version": "0.6.2", 1161 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1162 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 1163 | "dev": true 1164 | }, 1165 | "node-fetch": { 1166 | "version": "2.6.1", 1167 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 1168 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", 1169 | "dev": true 1170 | }, 1171 | "on-finished": { 1172 | "version": "2.3.0", 1173 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1174 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1175 | "dev": true, 1176 | "requires": { 1177 | "ee-first": "1.1.1" 1178 | } 1179 | }, 1180 | "once": { 1181 | "version": "1.4.0", 1182 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1183 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1184 | "dev": true, 1185 | "requires": { 1186 | "wrappy": "1" 1187 | } 1188 | }, 1189 | "os-homedir": { 1190 | "version": "1.0.2", 1191 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1192 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1193 | "dev": true 1194 | }, 1195 | "os-tmpdir": { 1196 | "version": "1.0.2", 1197 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1198 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1199 | "dev": true 1200 | }, 1201 | "parseurl": { 1202 | "version": "1.3.3", 1203 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1204 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1205 | "dev": true 1206 | }, 1207 | "path-is-absolute": { 1208 | "version": "1.0.1", 1209 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1210 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1211 | "dev": true 1212 | }, 1213 | "path-to-regexp": { 1214 | "version": "0.1.7", 1215 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1216 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 1217 | "dev": true 1218 | }, 1219 | "private": { 1220 | "version": "0.1.8", 1221 | "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", 1222 | "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", 1223 | "dev": true 1224 | }, 1225 | "proxy-addr": { 1226 | "version": "2.0.6", 1227 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 1228 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 1229 | "dev": true, 1230 | "requires": { 1231 | "forwarded": "~0.1.2", 1232 | "ipaddr.js": "1.9.1" 1233 | } 1234 | }, 1235 | "punycode": { 1236 | "version": "2.1.1", 1237 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1238 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1239 | "dev": true 1240 | }, 1241 | "qs": { 1242 | "version": "6.7.0", 1243 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1244 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 1245 | "dev": true 1246 | }, 1247 | "range-parser": { 1248 | "version": "1.2.1", 1249 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1250 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1251 | "dev": true 1252 | }, 1253 | "raw-body": { 1254 | "version": "2.4.0", 1255 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1256 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1257 | "dev": true, 1258 | "requires": { 1259 | "bytes": "3.1.0", 1260 | "http-errors": "1.7.2", 1261 | "iconv-lite": "0.4.24", 1262 | "unpipe": "1.0.0" 1263 | } 1264 | }, 1265 | "regenerate": { 1266 | "version": "1.4.2", 1267 | "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", 1268 | "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", 1269 | "dev": true 1270 | }, 1271 | "regenerator-runtime": { 1272 | "version": "0.13.7", 1273 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", 1274 | "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", 1275 | "dev": true 1276 | }, 1277 | "regenerator-transform": { 1278 | "version": "0.10.1", 1279 | "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", 1280 | "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", 1281 | "dev": true, 1282 | "requires": { 1283 | "babel-runtime": "^6.18.0", 1284 | "babel-types": "^6.19.0", 1285 | "private": "^0.1.6" 1286 | } 1287 | }, 1288 | "regexpu-core": { 1289 | "version": "2.0.0", 1290 | "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", 1291 | "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", 1292 | "dev": true, 1293 | "requires": { 1294 | "regenerate": "^1.2.1", 1295 | "regjsgen": "^0.2.0", 1296 | "regjsparser": "^0.1.4" 1297 | } 1298 | }, 1299 | "regjsgen": { 1300 | "version": "0.2.0", 1301 | "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", 1302 | "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", 1303 | "dev": true 1304 | }, 1305 | "regjsparser": { 1306 | "version": "0.1.5", 1307 | "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", 1308 | "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", 1309 | "dev": true, 1310 | "requires": { 1311 | "jsesc": "~0.5.0" 1312 | } 1313 | }, 1314 | "repeating": { 1315 | "version": "2.0.1", 1316 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 1317 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 1318 | "dev": true, 1319 | "requires": { 1320 | "is-finite": "^1.0.0" 1321 | } 1322 | }, 1323 | "safe-buffer": { 1324 | "version": "5.1.2", 1325 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1326 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1327 | "dev": true 1328 | }, 1329 | "safer-buffer": { 1330 | "version": "2.1.2", 1331 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1332 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1333 | "dev": true 1334 | }, 1335 | "send": { 1336 | "version": "0.17.1", 1337 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1338 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1339 | "dev": true, 1340 | "requires": { 1341 | "debug": "2.6.9", 1342 | "depd": "~1.1.2", 1343 | "destroy": "~1.0.4", 1344 | "encodeurl": "~1.0.2", 1345 | "escape-html": "~1.0.3", 1346 | "etag": "~1.8.1", 1347 | "fresh": "0.5.2", 1348 | "http-errors": "~1.7.2", 1349 | "mime": "1.6.0", 1350 | "ms": "2.1.1", 1351 | "on-finished": "~2.3.0", 1352 | "range-parser": "~1.2.1", 1353 | "statuses": "~1.5.0" 1354 | }, 1355 | "dependencies": { 1356 | "ms": { 1357 | "version": "2.1.1", 1358 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1359 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 1360 | "dev": true 1361 | } 1362 | } 1363 | }, 1364 | "serve-static": { 1365 | "version": "1.14.1", 1366 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1367 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1368 | "dev": true, 1369 | "requires": { 1370 | "encodeurl": "~1.0.2", 1371 | "escape-html": "~1.0.3", 1372 | "parseurl": "~1.3.3", 1373 | "send": "0.17.1" 1374 | } 1375 | }, 1376 | "setprototypeof": { 1377 | "version": "1.1.1", 1378 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1379 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 1380 | "dev": true 1381 | }, 1382 | "slash": { 1383 | "version": "1.0.0", 1384 | "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", 1385 | "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", 1386 | "dev": true 1387 | }, 1388 | "source-map": { 1389 | "version": "0.5.7", 1390 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1391 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1392 | "dev": true 1393 | }, 1394 | "source-map-support": { 1395 | "version": "0.4.18", 1396 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 1397 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 1398 | "dev": true, 1399 | "requires": { 1400 | "source-map": "^0.5.6" 1401 | } 1402 | }, 1403 | "statuses": { 1404 | "version": "1.5.0", 1405 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1406 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1407 | "dev": true 1408 | }, 1409 | "strip-ansi": { 1410 | "version": "3.0.1", 1411 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1412 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1413 | "dev": true, 1414 | "requires": { 1415 | "ansi-regex": "^2.0.0" 1416 | } 1417 | }, 1418 | "supports-color": { 1419 | "version": "2.0.0", 1420 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1421 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1422 | "dev": true 1423 | }, 1424 | "to-fast-properties": { 1425 | "version": "1.0.3", 1426 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", 1427 | "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", 1428 | "dev": true 1429 | }, 1430 | "toidentifier": { 1431 | "version": "1.0.0", 1432 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1433 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 1434 | "dev": true 1435 | }, 1436 | "topo": { 1437 | "version": "3.0.3", 1438 | "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", 1439 | "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", 1440 | "dev": true, 1441 | "requires": { 1442 | "hoek": "6.x.x" 1443 | }, 1444 | "dependencies": { 1445 | "hoek": { 1446 | "version": "6.1.3", 1447 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", 1448 | "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", 1449 | "dev": true 1450 | } 1451 | } 1452 | }, 1453 | "trim-right": { 1454 | "version": "1.0.1", 1455 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 1456 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", 1457 | "dev": true 1458 | }, 1459 | "type-is": { 1460 | "version": "1.6.18", 1461 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1462 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1463 | "dev": true, 1464 | "requires": { 1465 | "media-typer": "0.3.0", 1466 | "mime-types": "~2.1.24" 1467 | } 1468 | }, 1469 | "unpipe": { 1470 | "version": "1.0.0", 1471 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1472 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 1473 | "dev": true 1474 | }, 1475 | "utils-merge": { 1476 | "version": "1.0.1", 1477 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1478 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 1479 | "dev": true 1480 | }, 1481 | "vary": { 1482 | "version": "1.1.2", 1483 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1484 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 1485 | "dev": true 1486 | }, 1487 | "wrappy": { 1488 | "version": "1.0.2", 1489 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1490 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1491 | "dev": true 1492 | } 1493 | } 1494 | } 1495 | --------------------------------------------------------------------------------