├── example ├── .gitignore ├── webpack.config.js ├── web │ └── index.html ├── backend.ol ├── package.json ├── js │ └── index.js ├── main.ol └── package-lock.json ├── webpack.config.js ├── package.json ├── .gitignore ├── jo.js ├── lib └── jo.ts ├── README.md ├── LICENSE └── tsconfig.json /example/.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: './js/index.js', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'web', 'js'), 9 | }, 10 | resolve: { 11 | extensions: ['.ts', '.js'], 12 | }, 13 | } -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hello, World!

5 |

6 |

Result from Jo

7 | 9 |

Result from Jor

10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/backend.ol: -------------------------------------------------------------------------------- 1 | 2 | 3 | interface BackendInterface { 4 | RequestResponse: 5 | test( void )( int ) 6 | } 7 | 8 | service Backend { 9 | execution: concurrent 10 | 11 | inputPort Input { 12 | location: "local" 13 | interfaces: BackendInterface 14 | } 15 | 16 | main { 17 | test()(res){ 18 | res = 1 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './lib/jo.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | filename: 'jo.js', 19 | path: path.resolve(__dirname, 'dist'), 20 | libraryTarget: "commonjs2" 21 | }, 22 | mode: "development" 23 | }; -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "main.ol", 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "repo": "", 8 | "keywords": [], 9 | "author": "nau", 10 | "license": "ISC", 11 | "scripts": { 12 | "watch": "nodemon --ignore 'web' -e js --signal SIGINT --exec 'npm run build && npm run start-jolie'", 13 | "start-jolie": "jolie main.ol", 14 | "build": "webpack" 15 | }, 16 | "devDependencies": { 17 | "nodemon": "^2.0.19" 18 | }, 19 | "jolie": { 20 | "dependencies": { 21 | "@jolie/leonardo": "0.4.1" 22 | }, 23 | "maven": { 24 | "dependencies": {}, 25 | "indirectDependencies": {} 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jolie/jo", 3 | "version": "1.0.2", 4 | "description": "A thin JavaScript library for accessing microservice APIs", 5 | "main": "dist/jo.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "build-dev": "webpack --mode=development", 13 | "run-example": "npm --prefix example run watch", 14 | "build-prod": "webpack --mode=production" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/fmontesi/jo.git" 19 | }, 20 | "keywords": [ 21 | "jolie", 22 | "microservices", 23 | "jo", 24 | "rest" 25 | ], 26 | "author": { 27 | "name": "Fabrizio Montesi", 28 | "email": "famontesi@gmail.com", 29 | "url": "https://www.fabriziomontesi.com" 30 | }, 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/fmontesi/jo/issues" 34 | }, 35 | "homepage": "https://github.com/fmontesi/jo#readme", 36 | "devDependencies": { 37 | "ts-loader": "^9.3.1", 38 | "typescript": "^4.8.2", 39 | "webpack": "^5.74.0", 40 | "webpack-cli": "^4.10.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/js/index.js: -------------------------------------------------------------------------------- 1 | const {Jo, Jor, JoHelp} = require("../../dist/jo") 2 | // Call operation search on the exposed service ChuckNorris 3 | Jo("ChuckNorris").search({ query: "Computer" }) 4 | .then(response => { 5 | // Pick a random joke 6 | // api.chucknorris.io returns jokes in a "result" array subelement 7 | if (response.result) { 8 | for (const joke of response.result) { 9 | const li = document.createElement("li") 10 | const text = document.createTextNode(joke.value) 11 | li.appendChild(text) 12 | document.getElementById('hello-jo').appendChild(li) 13 | } 14 | } 15 | }) 16 | .catch(JoHelp.parseError).catch(alert); 17 | 18 | Jor("ChuckNorris").search.get({ query: "Earth" }) 19 | .then(response => { 20 | // Pick a random joke 21 | // api.chucknorris.io returns jokes in a "result" array subelement 22 | if (response.result) { 23 | for (const joke of response.result) { 24 | const li = document.createElement("li") 25 | const text = document.createTextNode(joke.value) 26 | li.appendChild(text) 27 | document.getElementById('hello-jor').appendChild(li) 28 | } 29 | } 30 | }) 31 | .catch(JoHelp.parseError).catch(alert); 32 | 33 | 34 | Jo.test() 35 | .then(response => { 36 | // response = {$:1} 37 | console.log("Jo test response", response) 38 | }) 39 | .catch(JoHelp.parseError).catch(alert); 40 | 41 | Jor.test.get() 42 | .then(response => { 43 | // response = {$:1} 44 | console.log("Jor test response", response) 45 | }) 46 | .catch(JoHelp.parseError).catch(alert); 47 | document.getElementById('hello-webpack').innerText = 'Hello from webpack!' 48 | 49 | export default {} -------------------------------------------------------------------------------- /example/main.ol: -------------------------------------------------------------------------------- 1 | from protocols.http import DefaultOperationHttpRequest 2 | from console import Console 3 | from string-utils import StringUtils 4 | from @jolie.leonardo import WebFiles 5 | from .backend import Backend 6 | 7 | 8 | /// Operations offered through the web interface 9 | interface WebInterface { 10 | RequestResponse: 11 | /// Generic GET request 12 | get( DefaultOperationHttpRequest )( undefined ) 13 | } 14 | 15 | service Main { 16 | execution: concurrent 17 | 18 | embed Console as console 19 | embed WebFiles as webFiles 20 | embed StringUtils as stringUtils 21 | embed Backend as backend 22 | 23 | // Output port to contact api.chucknorris.io 24 | outputPort Chuck { 25 | Location: "socket://api.chucknorris.io:443/" // Use TCP/IP, port 443 26 | Protocol: https { 27 | .osc.search.method = "get"; // HTTP method for operation search 28 | .osc.search.alias = "jokes/search?query=%{query}" // URI template for operation search 29 | } 30 | RequestResponse: search // Request-response operation declaration (search) 31 | } 32 | 33 | inputPort WebInput { 34 | location: "socket://localhost:8080" 35 | protocol: http { 36 | format -> httpParams.format 37 | contentType -> httpParams.contentType 38 | cacheControl.maxAge -> httpParams.cacheControl.maxAge 39 | redirect -> redirect 40 | statusCode -> statusCode 41 | default.get = "get" 42 | } 43 | Redirects: 44 | ChuckNorris => Chuck 45 | aggregates: backend 46 | interfaces: WebInterface 47 | } 48 | 49 | init { 50 | global.wwwDir = "web" 51 | format = "html" 52 | 53 | println@console( "Server started at " + global.inputPorts.WebInput.location )() 54 | } 55 | 56 | main { 57 | get( request )( response ) { 58 | scope( get ) { 59 | install( 60 | FileNotFound => 61 | statusCode = 404, 62 | MovedPermanently => 63 | redirect = get.MovedPermanently 64 | statusCode = 301 65 | ) 66 | get@webFiles( { 67 | target = request.operation 68 | wwwDir = global.wwwDir 69 | } )( getResult ) 70 | httpParams -> getResult.httpParams 71 | response -> getResult.content 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # jolie related field 133 | packages/ 134 | lib/ 135 | example/web/js/bundle.js 136 | -------------------------------------------------------------------------------- /jo.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Fabrizio Montesi 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | (function() { 18 | "use strict"; 19 | 20 | function toJson( response ) 21 | { 22 | return response.json(); 23 | } 24 | 25 | function genError( response ) 26 | { 27 | if( response.ok ) { 28 | return Promise.resolve( response ); 29 | } else { 30 | const error = new Error( response.statusText ); 31 | return response.json().then( json => { 32 | error.isFault = true; 33 | error.fault = json.error; 34 | throw error; 35 | } ); 36 | } 37 | } 38 | 39 | function jolieFetch( operation, params ) 40 | { 41 | if ( typeof params.service === 'undefined' ) { 42 | operation = '/' + operation; 43 | } else { 44 | if ( !params.service.startsWith( '/' ) ) { 45 | params.service = '/' + params.service; 46 | } 47 | operation = '/!' + params.service + '!/' + operation; 48 | } 49 | params.headers = { "Content-Type": "application/json" }; 50 | 51 | if ( typeof params.method === 'undefined' ) { 52 | params.method = 'POST'; 53 | } 54 | 55 | if ( typeof params.data !== 'undefined' ) { 56 | if ( params.method === 'GET' || params.method === 'HEAD' ) { 57 | operation += "?" + JSON.stringify( params.data ); 58 | } else { 59 | params.body = JSON.stringify( params.data ); 60 | } 61 | } 62 | 63 | return fetch( operation, params ).then( genError ).then( toJson ); 64 | } 65 | 66 | function initParams( data, params ) 67 | { 68 | if ( typeof params === 'undefined' ) { 69 | params = {}; 70 | } 71 | if ( typeof data !== 'undefined' ) { 72 | params.data = data; 73 | } 74 | return params; 75 | } 76 | 77 | const proxyBuilder = ( service ) => { 78 | return new Proxy( {}, { 79 | get: ( target, prop, receiver ) => { 80 | return ( data, params ) => { 81 | params = initParams( data, params ); 82 | params.service = service; 83 | return jolieFetch( prop, params ); 84 | } 85 | } 86 | } ); 87 | } 88 | 89 | const buildHttpVerbs = ( buildFetch ) => { return { 90 | get: ( data, params ) => { 91 | return buildFetch( data, params, 'GET' ); 92 | }, 93 | post: ( data, params ) => { 94 | return buildFetch( data, params, 'POST' ); 95 | }, 96 | delete: ( data, params ) => { 97 | return buildFetch( data, params, 'DELETE' ); 98 | }, 99 | head: ( data, params ) => { 100 | return buildFetch( data, params, 'HEAD' ); 101 | }, 102 | patch: ( data, params ) => { 103 | return buildFetch( data, params, 'PATCH' ); 104 | }, 105 | options: ( data, params ) => { 106 | return buildFetch( data, params, 'OPTIONS' ); 107 | }, 108 | put: ( data, params ) => { 109 | return buildFetch( data, params, 'PUT' ); 110 | } 111 | } }; 112 | 113 | const resourceProxyBuilder = ( service ) => { 114 | return new Proxy( {}, { 115 | get: ( target, prop, receiver ) => { 116 | const buildFetch = ( data, params, method ) => { 117 | params = initParams( data, params ); 118 | params.service = service; 119 | params.method = method; 120 | return jolieFetch( prop, params ); 121 | }; 122 | return buildHttpVerbs( buildFetch ); 123 | } 124 | } ) 125 | } 126 | 127 | window.Jo = new Proxy( proxyBuilder, { 128 | get: ( target, prop, receiver ) => { 129 | return ( data, params ) => { 130 | params = initParams( data, params ); 131 | return jolieFetch( prop, params ); 132 | } 133 | } 134 | } ); 135 | 136 | window.Jor = new Proxy( resourceProxyBuilder, { 137 | get: ( target, prop, receiver ) => { 138 | return ( data, params ) => { 139 | const buildFetch = ( data, params, method ) => { 140 | params = initParams( data, params ); 141 | params.method = method; 142 | return jolieFetch( prop, params ); 143 | }; 144 | return buildHttpVerbs( buildFetch ); 145 | } 146 | } 147 | } ); 148 | 149 | window.JoHelp = { 150 | parseError: error => { 151 | if ( error.isFault ) { 152 | return Promise.reject( JSON.stringify( error.fault ) ); 153 | } else { 154 | return Promise.reject( error.message ); 155 | } 156 | } 157 | }; 158 | }()); 159 | -------------------------------------------------------------------------------- /lib/jo.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Fabrizio Montesi , et. al. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | interface JolieFault extends Error { 18 | isFault: boolean; 19 | fault: Error; 20 | } 21 | 22 | interface FetchParams extends RequestInit { 23 | service: string; 24 | headers: Record; 25 | method: string; 26 | data: any; 27 | } 28 | 29 | interface FetchBuilder { 30 | ( 31 | data: Object, 32 | params: any, 33 | method: "GET" | "POST" | "DELETE" | "HEAD" | "PATCH" | "OPTIONS" | "PUT" 34 | ): Promise; 35 | } 36 | 37 | function toJson(response: Response): Promise { 38 | return response.json(); 39 | } 40 | 41 | function genError(response: Response): Promise { 42 | if (response.ok) { 43 | return Promise.resolve(response); 44 | } else { 45 | return response.json().then((json) => { 46 | const error: JolieFault = { 47 | name: response.statusText, 48 | message: response.statusText, 49 | isFault: true, 50 | fault: json.error, 51 | }; 52 | error.isFault = true; 53 | error.fault = json.error; 54 | throw error; 55 | }); 56 | } 57 | } 58 | 59 | function jolieFetch(operation: string, params: FetchParams): Promise { 60 | if (typeof params.service === "undefined") { 61 | operation = "/" + operation; 62 | } else { 63 | if (!params.service.startsWith("/")) { 64 | params.service = "/" + params.service; 65 | } 66 | operation = "/!" + params.service + "!/" + operation; 67 | } 68 | params.headers = { "Content-Type": "application/json" }; 69 | 70 | if (typeof params.method === "undefined") { 71 | params.method = "POST"; 72 | } 73 | 74 | if (typeof params.data !== "undefined") { 75 | if (params.method === "GET" || params.method === "HEAD") { 76 | operation += "?" + JSON.stringify(params.data); 77 | } else { 78 | params.body = JSON.stringify(params.data); 79 | } 80 | } 81 | 82 | return fetch(operation, params).then(genError).then(toJson); 83 | } 84 | 85 | function initParams(data: Object, params: any): FetchParams { 86 | if (typeof params === "undefined") { 87 | params = {}; 88 | } 89 | if (typeof data !== "undefined") { 90 | params.data = data; 91 | } 92 | return params; 93 | } 94 | 95 | const proxyBuilder = (service: string) => { 96 | return new Proxy( 97 | {}, 98 | { 99 | get: (target, prop, receiver) => { 100 | return (data: Object, params:any) => { 101 | params = initParams(data, params); 102 | params.service = service; 103 | return jolieFetch(prop.toString(), params); 104 | }; 105 | }, 106 | } 107 | ); 108 | }; 109 | 110 | const buildHttpVerbs = (buildFetch: FetchBuilder) => { 111 | return { 112 | get: (data: Object, params: any) => { 113 | return buildFetch(data, params, "GET"); 114 | }, 115 | post: (data: Object, params: any) => { 116 | return buildFetch(data, params, "POST"); 117 | }, 118 | delete: (data: Object, params: any) => { 119 | return buildFetch(data, params, "DELETE"); 120 | }, 121 | head: (data: Object, params: any) => { 122 | return buildFetch(data, params, "HEAD"); 123 | }, 124 | patch: (data: Object, params: any) => { 125 | return buildFetch(data, params, "PATCH"); 126 | }, 127 | options: (data: Object, params: any) => { 128 | return buildFetch(data, params, "OPTIONS"); 129 | }, 130 | put: (data: Object, params: any) => { 131 | return buildFetch(data, params, "PUT"); 132 | }, 133 | }; 134 | }; 135 | 136 | const resourceProxyBuilder = (service: string) => { 137 | return new Proxy( 138 | {}, 139 | { 140 | get: (target, prop, receiver) => { 141 | const buildFetch: FetchBuilder = (data, params, method) => { 142 | const fetchParams: FetchParams = initParams(data, params); 143 | fetchParams.service = service; 144 | fetchParams.method = method; 145 | return jolieFetch(prop.toString(), fetchParams); 146 | }; 147 | return buildHttpVerbs(buildFetch); 148 | }, 149 | } 150 | ); 151 | }; 152 | 153 | export const Jo = new Proxy(proxyBuilder, { 154 | get: (target, prop, receiver) => { 155 | return (data: Object, params: any) => { 156 | const fetchParams = initParams(data, params); 157 | return jolieFetch(prop.toString(), fetchParams); 158 | }; 159 | }, 160 | }); 161 | 162 | export const Jor = new Proxy(resourceProxyBuilder, { 163 | get: (target, prop, receiver) => { 164 | const buildFetch: FetchBuilder = (data, params, method) => { 165 | const fetchParams = initParams(data, params); 166 | fetchParams.method = method; 167 | return jolieFetch(prop.toString(), fetchParams); 168 | }; 169 | return buildHttpVerbs(buildFetch); 170 | }, 171 | }); 172 | 173 | export const JoHelp = { 174 | parseError: (error: JolieFault) => { 175 | if (error.isFault) { 176 | return Promise.reject(JSON.stringify(error.fault)); 177 | } else { 178 | return Promise.reject(error.message); 179 | } 180 | }, 181 | }; 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jo 2 | 3 | Access web APIs as JavaScript objects. Jo (pronounced "Yo!") is a modest and thin library to write simpler client code. 4 | 5 | ![Chuck Norris, Jo](https://fmontesi.github.io/assets/jo-demo-chuck/joke-workflow.png) 6 | 7 | Jo supports both verb-oriented APIs (e.g., `/getJoke?{id:1}`) and resource-oriented APIs (e.g., `/jokes/1`). 8 | You can use both interchangeably, which is useful when you have to interact with microservices that adopt different API styles. 9 | 10 | Jo can be used with any web server. It uses JSON as data format (more formats could be added in the future). It includes native support for [Jolie](https://www.jolie-lang.org/) API Gateways (aka [Jolie redirections](https://jolielang.gitbook.io/docs/architectural-composition/redirection)). 11 | 12 | # Installation 13 | 14 | ``` 15 | npm i @jolie/jo 16 | ``` 17 | 18 | # Usage: Verb-oriented APIs 19 | 20 | Verb-oriented APIs can be accessed through the global `Jo` object. 21 | 22 | ## Invoking an operation from the server 23 | 24 | Syntax: `Jo.operation( [data, params] )` where 25 | - `operation` is the operation you want to invoke; 26 | - the optional `data` to be sent is a JSON object; 27 | - the optional `params` are parameters for the underlying `fetch` operation (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), for example in case you need to specify HTTP headers. 28 | 29 | Invoking this method returns a promise. 30 | 31 | This method uses POST as default HTTP method. To change it, use the optional parameters. For example, to use GET: `Jo.operation( data, { method: 'GET' } )`. 32 | 33 | ### Example 34 | 35 | Suppose the originating web server offers an operation `greet` that returns a string given a tree with subnode `name`. 36 | (Nanoservices are great for examples, not so much for production: do this at home only!) 37 | 38 | You can invoke it as follows. 39 | 40 | ```javascript 41 | import {Jo} from '@jolie/jo' 42 | 43 | Jo.greet( { name: "Homer" } ) 44 | .then( response => console.log( response.greeting ) ) // Jo uses promises 45 | .catch( error => { // an error occurred 46 | if ( error.isFault ) { // It's an application error 47 | console.log( JSON.stringify( error.fault ) ); 48 | } else { // It's a middleware error 49 | console.log( error.message ); 50 | } 51 | } ); 52 | ``` 53 | 54 | Here is how this operation would be implemented in a Jolie service. 55 | 56 | ```jolie 57 | greet( request )( response ) { 58 | response.greeting = "Hello " + request.name 59 | } 60 | ``` 61 | 62 | ## Catching errors, the alternative way 63 | 64 | Distinguishing application and middleware errors might be boring. 65 | Use `JoHelp.parseError` (`JoHelp` is pronounced "Yo! Help!") to get that part done for you automatically. You will get a string containing the error message, if it is a middleware error, or a JSON.stringify of the carried data, if it is an application error. 66 | 67 | ```javascript 68 | Jo.greet( { name: "Homer" } ) 69 | .then( response => console.log( response.greeting ) ) 70 | .catch( JoHelp.parseError ).catch( console.log ); 71 | ``` 72 | 73 | ## Jolie Redirections 74 | 75 | Jo supports [redirection](https://jolielang.gitbook.io/docs/architectural-composition/redirection) (not to be confused with HTTP redirections), the Jolie primitive to build API gateways with named subservices. (Unnamed subservices in the gateway, obtained by [aggregation](https://jolielang.gitbook.io/docs/architectural-composition/aggregation), are available as normal operations, so they can be called with the previous syntax.) 76 | 77 | Suppose that the originating Jolie service has a redirection table as follows. 78 | ```jolie 79 | Redirects: Greeter => GreeterService 80 | ``` 81 | 82 | If `GreeterService` has our operation `greet`, we can invoke it as follows. 83 | 84 | ```javascript 85 | Jo("Greeter").greet( { name: "Homer" } ) 86 | .then( response => console.log( response.greeting ) ) 87 | .catch( JoHelp.parseError ).catch( console.log ); 88 | ``` 89 | 90 | If your Jolie API gateway points to another API gateway, you can nest them! 91 | 92 | ```javascript 93 | Jo("SubGateway1/SubGateway2/Greeter").greet( { name: "Homer" } ) 94 | .then( response => console.log( response.greeting ) ) 95 | .catch( JoHelp.parseError ).catch( console.log ); 96 | ``` 97 | 98 | # Usage: Resource-oriented APIs 99 | 100 | Resource-oriented APIs can be accessed through the global `Jor` object. 101 | 102 | Syntax: `Jor.resource.http_method( data [, params] )` where 103 | - `resource` is the name of the resource you want to access; 104 | - `http_method` is an HTTP method (use lowercase): `get`, `post`, `delete`, `put`, `head`, `patch`, or `options`; 105 | - the `data` to be sent is a JSON object; 106 | - the optional `params` are parameters for the underlying `fetch` operation (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). 107 | 108 | If `resource` has a name that cannot be written in JavaScript, you can use the alternative syntax `Jor["resource"].http_method( data [, params] )`. 109 | 110 | ### Example 111 | 112 | Suppose the web server offers a resource `/jokes`, and you want to get all of them. 113 | 114 | ```javascript 115 | import {Jor} from '@jolie/jo' 116 | 117 | Jor.jokes.get() 118 | .then( response => /* handle all the jokes */ ) 119 | .catch( JoHelp.parseError ).catch( console.log ); 120 | ``` 121 | 122 | ## Redirections 123 | 124 | Redirections are supported by `Jor` just as for verb-based APIs. Suppose the server offers the `/jokes` resource through the subservice `ChuckNorris`. Then we can access it as follows. 125 | 126 | ```javascript 127 | Jor("ChuckNorris").jokes.get() 128 | .then( response => /* handle all the jokes */ ) 129 | .catch( JoHelp.parseError ).catch( console.log ); 130 | ``` 131 | 132 | # Installation 133 | 134 | ```html 135 | 136 | ``` 137 | 138 | Or, download Jo (https://raw.githubusercontent.com/fmontesi/jo/master/lib/jo.js) and use it locally. 139 | 140 | Pull requests with better ways to distribute Jo are welcome. 141 | 142 | # Dependencies 143 | 144 | There are no dependencies on other libraries. However, Jo uses some recent features offered by web browsers. 145 | 146 | - Jo uses [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to perform asynchronous calls. If you want to use Jo with older browsers, use a [polyfill for fetch](https://github.com/github/fetch). Check which browsers support fetch here: https://caniuse.com/#feat=fetch. 147 | 148 | - Jo uses some modern JavaScript features. [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) is used to implement the magic of calling the operations of your Jolie server as if they were native methods (`Jo.operation`). We also use [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). Check which browsers support Proxy at https://caniuse.com/#feat=proxy, and which support arrow functions at https://caniuse.com/#feat=arrow-functions. If you want to use Jo in browsers that do not support these features, you can try compiling Jo with [Babel](https://babeljs.io/). 149 | 150 | # FAQ 151 | 152 | ## How do I handle basic values in root nodes sent by Jolie? (AKA response.$) 153 | 154 | In JSON, an element can either be a basic value (e.g., strings, numbers), an object, or an array. 155 | In Jolie, there are no restrictions: an element is always a tree, and each node can contain _both_ a basic value and subnodes (similarly to markup languages). 156 | For example, this is valid Jolie: `"Homer" { .children[0] = "Bart", .children[1] = "Lisa" }`. It gives a tree containing the string `Homer` in its root node, which has an array subnode `children` with two elements. If you receive this tree using `Jo` in variable `response`, you can access the value contained in the root node (`"Homer"` in our example) by `response.$`. 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "include": ["lib/**/*"], 104 | } 105 | -------------------------------------------------------------------------------- /example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "example", 9 | "version": "0.1.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "nodemon": "^2.0.19" 13 | } 14 | }, 15 | "node_modules/abbrev": { 16 | "version": "1.1.1", 17 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 18 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 19 | "dev": true 20 | }, 21 | "node_modules/anymatch": { 22 | "version": "3.1.2", 23 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 24 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 25 | "dev": true, 26 | "dependencies": { 27 | "normalize-path": "^3.0.0", 28 | "picomatch": "^2.0.4" 29 | }, 30 | "engines": { 31 | "node": ">= 8" 32 | } 33 | }, 34 | "node_modules/balanced-match": { 35 | "version": "1.0.2", 36 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 37 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 38 | "dev": true 39 | }, 40 | "node_modules/binary-extensions": { 41 | "version": "2.2.0", 42 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 43 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 44 | "dev": true, 45 | "engines": { 46 | "node": ">=8" 47 | } 48 | }, 49 | "node_modules/brace-expansion": { 50 | "version": "1.1.11", 51 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 52 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 53 | "dev": true, 54 | "dependencies": { 55 | "balanced-match": "^1.0.0", 56 | "concat-map": "0.0.1" 57 | } 58 | }, 59 | "node_modules/braces": { 60 | "version": "3.0.2", 61 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 62 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 63 | "dev": true, 64 | "dependencies": { 65 | "fill-range": "^7.0.1" 66 | }, 67 | "engines": { 68 | "node": ">=8" 69 | } 70 | }, 71 | "node_modules/chokidar": { 72 | "version": "3.5.3", 73 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 74 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 75 | "dev": true, 76 | "funding": [ 77 | { 78 | "type": "individual", 79 | "url": "https://paulmillr.com/funding/" 80 | } 81 | ], 82 | "dependencies": { 83 | "anymatch": "~3.1.2", 84 | "braces": "~3.0.2", 85 | "glob-parent": "~5.1.2", 86 | "is-binary-path": "~2.1.0", 87 | "is-glob": "~4.0.1", 88 | "normalize-path": "~3.0.0", 89 | "readdirp": "~3.6.0" 90 | }, 91 | "engines": { 92 | "node": ">= 8.10.0" 93 | }, 94 | "optionalDependencies": { 95 | "fsevents": "~2.3.2" 96 | } 97 | }, 98 | "node_modules/concat-map": { 99 | "version": "0.0.1", 100 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 101 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 102 | "dev": true 103 | }, 104 | "node_modules/debug": { 105 | "version": "3.2.7", 106 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 107 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 108 | "dev": true, 109 | "dependencies": { 110 | "ms": "^2.1.1" 111 | } 112 | }, 113 | "node_modules/fill-range": { 114 | "version": "7.0.1", 115 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 116 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 117 | "dev": true, 118 | "dependencies": { 119 | "to-regex-range": "^5.0.1" 120 | }, 121 | "engines": { 122 | "node": ">=8" 123 | } 124 | }, 125 | "node_modules/fsevents": { 126 | "version": "2.3.2", 127 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 128 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 129 | "dev": true, 130 | "hasInstallScript": true, 131 | "optional": true, 132 | "os": [ 133 | "darwin" 134 | ], 135 | "engines": { 136 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 137 | } 138 | }, 139 | "node_modules/glob-parent": { 140 | "version": "5.1.2", 141 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 142 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 143 | "dev": true, 144 | "dependencies": { 145 | "is-glob": "^4.0.1" 146 | }, 147 | "engines": { 148 | "node": ">= 6" 149 | } 150 | }, 151 | "node_modules/has-flag": { 152 | "version": "3.0.0", 153 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 154 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 155 | "dev": true, 156 | "engines": { 157 | "node": ">=4" 158 | } 159 | }, 160 | "node_modules/ignore-by-default": { 161 | "version": "1.0.1", 162 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 163 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 164 | "dev": true 165 | }, 166 | "node_modules/is-binary-path": { 167 | "version": "2.1.0", 168 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 169 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 170 | "dev": true, 171 | "dependencies": { 172 | "binary-extensions": "^2.0.0" 173 | }, 174 | "engines": { 175 | "node": ">=8" 176 | } 177 | }, 178 | "node_modules/is-extglob": { 179 | "version": "2.1.1", 180 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 181 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 182 | "dev": true, 183 | "engines": { 184 | "node": ">=0.10.0" 185 | } 186 | }, 187 | "node_modules/is-glob": { 188 | "version": "4.0.3", 189 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 190 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 191 | "dev": true, 192 | "dependencies": { 193 | "is-extglob": "^2.1.1" 194 | }, 195 | "engines": { 196 | "node": ">=0.10.0" 197 | } 198 | }, 199 | "node_modules/is-number": { 200 | "version": "7.0.0", 201 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 202 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 203 | "dev": true, 204 | "engines": { 205 | "node": ">=0.12.0" 206 | } 207 | }, 208 | "node_modules/minimatch": { 209 | "version": "3.1.2", 210 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 211 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 212 | "dev": true, 213 | "dependencies": { 214 | "brace-expansion": "^1.1.7" 215 | }, 216 | "engines": { 217 | "node": "*" 218 | } 219 | }, 220 | "node_modules/ms": { 221 | "version": "2.1.3", 222 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 223 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 224 | "dev": true 225 | }, 226 | "node_modules/nodemon": { 227 | "version": "2.0.19", 228 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", 229 | "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", 230 | "dev": true, 231 | "hasInstallScript": true, 232 | "dependencies": { 233 | "chokidar": "^3.5.2", 234 | "debug": "^3.2.7", 235 | "ignore-by-default": "^1.0.1", 236 | "minimatch": "^3.0.4", 237 | "pstree.remy": "^1.1.8", 238 | "semver": "^5.7.1", 239 | "simple-update-notifier": "^1.0.7", 240 | "supports-color": "^5.5.0", 241 | "touch": "^3.1.0", 242 | "undefsafe": "^2.0.5" 243 | }, 244 | "bin": { 245 | "nodemon": "bin/nodemon.js" 246 | }, 247 | "engines": { 248 | "node": ">=8.10.0" 249 | }, 250 | "funding": { 251 | "type": "opencollective", 252 | "url": "https://opencollective.com/nodemon" 253 | } 254 | }, 255 | "node_modules/nopt": { 256 | "version": "1.0.10", 257 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 258 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 259 | "dev": true, 260 | "dependencies": { 261 | "abbrev": "1" 262 | }, 263 | "bin": { 264 | "nopt": "bin/nopt.js" 265 | }, 266 | "engines": { 267 | "node": "*" 268 | } 269 | }, 270 | "node_modules/normalize-path": { 271 | "version": "3.0.0", 272 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 273 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 274 | "dev": true, 275 | "engines": { 276 | "node": ">=0.10.0" 277 | } 278 | }, 279 | "node_modules/picomatch": { 280 | "version": "2.3.1", 281 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 282 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 283 | "dev": true, 284 | "engines": { 285 | "node": ">=8.6" 286 | }, 287 | "funding": { 288 | "url": "https://github.com/sponsors/jonschlinkert" 289 | } 290 | }, 291 | "node_modules/pstree.remy": { 292 | "version": "1.1.8", 293 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 294 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 295 | "dev": true 296 | }, 297 | "node_modules/readdirp": { 298 | "version": "3.6.0", 299 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 300 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 301 | "dev": true, 302 | "dependencies": { 303 | "picomatch": "^2.2.1" 304 | }, 305 | "engines": { 306 | "node": ">=8.10.0" 307 | } 308 | }, 309 | "node_modules/semver": { 310 | "version": "5.7.1", 311 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 312 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 313 | "dev": true, 314 | "bin": { 315 | "semver": "bin/semver" 316 | } 317 | }, 318 | "node_modules/simple-update-notifier": { 319 | "version": "1.0.7", 320 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", 321 | "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", 322 | "dev": true, 323 | "dependencies": { 324 | "semver": "~7.0.0" 325 | }, 326 | "engines": { 327 | "node": ">=8.10.0" 328 | } 329 | }, 330 | "node_modules/simple-update-notifier/node_modules/semver": { 331 | "version": "7.0.0", 332 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", 333 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", 334 | "dev": true, 335 | "bin": { 336 | "semver": "bin/semver.js" 337 | } 338 | }, 339 | "node_modules/supports-color": { 340 | "version": "5.5.0", 341 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 342 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 343 | "dev": true, 344 | "dependencies": { 345 | "has-flag": "^3.0.0" 346 | }, 347 | "engines": { 348 | "node": ">=4" 349 | } 350 | }, 351 | "node_modules/to-regex-range": { 352 | "version": "5.0.1", 353 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 354 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 355 | "dev": true, 356 | "dependencies": { 357 | "is-number": "^7.0.0" 358 | }, 359 | "engines": { 360 | "node": ">=8.0" 361 | } 362 | }, 363 | "node_modules/touch": { 364 | "version": "3.1.0", 365 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 366 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 367 | "dev": true, 368 | "dependencies": { 369 | "nopt": "~1.0.10" 370 | }, 371 | "bin": { 372 | "nodetouch": "bin/nodetouch.js" 373 | } 374 | }, 375 | "node_modules/undefsafe": { 376 | "version": "2.0.5", 377 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 378 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 379 | "dev": true 380 | } 381 | }, 382 | "dependencies": { 383 | "abbrev": { 384 | "version": "1.1.1", 385 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 386 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 387 | "dev": true 388 | }, 389 | "anymatch": { 390 | "version": "3.1.2", 391 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 392 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 393 | "dev": true, 394 | "requires": { 395 | "normalize-path": "^3.0.0", 396 | "picomatch": "^2.0.4" 397 | } 398 | }, 399 | "balanced-match": { 400 | "version": "1.0.2", 401 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 402 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 403 | "dev": true 404 | }, 405 | "binary-extensions": { 406 | "version": "2.2.0", 407 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 408 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 409 | "dev": true 410 | }, 411 | "brace-expansion": { 412 | "version": "1.1.11", 413 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 414 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 415 | "dev": true, 416 | "requires": { 417 | "balanced-match": "^1.0.0", 418 | "concat-map": "0.0.1" 419 | } 420 | }, 421 | "braces": { 422 | "version": "3.0.2", 423 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 424 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 425 | "dev": true, 426 | "requires": { 427 | "fill-range": "^7.0.1" 428 | } 429 | }, 430 | "chokidar": { 431 | "version": "3.5.3", 432 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 433 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 434 | "dev": true, 435 | "requires": { 436 | "anymatch": "~3.1.2", 437 | "braces": "~3.0.2", 438 | "fsevents": "~2.3.2", 439 | "glob-parent": "~5.1.2", 440 | "is-binary-path": "~2.1.0", 441 | "is-glob": "~4.0.1", 442 | "normalize-path": "~3.0.0", 443 | "readdirp": "~3.6.0" 444 | } 445 | }, 446 | "concat-map": { 447 | "version": "0.0.1", 448 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 449 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 450 | "dev": true 451 | }, 452 | "debug": { 453 | "version": "3.2.7", 454 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 455 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 456 | "dev": true, 457 | "requires": { 458 | "ms": "^2.1.1" 459 | } 460 | }, 461 | "fill-range": { 462 | "version": "7.0.1", 463 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 464 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 465 | "dev": true, 466 | "requires": { 467 | "to-regex-range": "^5.0.1" 468 | } 469 | }, 470 | "fsevents": { 471 | "version": "2.3.2", 472 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 473 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 474 | "dev": true, 475 | "optional": true 476 | }, 477 | "glob-parent": { 478 | "version": "5.1.2", 479 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 480 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 481 | "dev": true, 482 | "requires": { 483 | "is-glob": "^4.0.1" 484 | } 485 | }, 486 | "has-flag": { 487 | "version": "3.0.0", 488 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 489 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 490 | "dev": true 491 | }, 492 | "ignore-by-default": { 493 | "version": "1.0.1", 494 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 495 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 496 | "dev": true 497 | }, 498 | "is-binary-path": { 499 | "version": "2.1.0", 500 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 501 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 502 | "dev": true, 503 | "requires": { 504 | "binary-extensions": "^2.0.0" 505 | } 506 | }, 507 | "is-extglob": { 508 | "version": "2.1.1", 509 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 510 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 511 | "dev": true 512 | }, 513 | "is-glob": { 514 | "version": "4.0.3", 515 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 516 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 517 | "dev": true, 518 | "requires": { 519 | "is-extglob": "^2.1.1" 520 | } 521 | }, 522 | "is-number": { 523 | "version": "7.0.0", 524 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 525 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 526 | "dev": true 527 | }, 528 | "minimatch": { 529 | "version": "3.1.2", 530 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 531 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 532 | "dev": true, 533 | "requires": { 534 | "brace-expansion": "^1.1.7" 535 | } 536 | }, 537 | "ms": { 538 | "version": "2.1.3", 539 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 540 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 541 | "dev": true 542 | }, 543 | "nodemon": { 544 | "version": "2.0.19", 545 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", 546 | "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", 547 | "dev": true, 548 | "requires": { 549 | "chokidar": "^3.5.2", 550 | "debug": "^3.2.7", 551 | "ignore-by-default": "^1.0.1", 552 | "minimatch": "^3.0.4", 553 | "pstree.remy": "^1.1.8", 554 | "semver": "^5.7.1", 555 | "simple-update-notifier": "^1.0.7", 556 | "supports-color": "^5.5.0", 557 | "touch": "^3.1.0", 558 | "undefsafe": "^2.0.5" 559 | } 560 | }, 561 | "nopt": { 562 | "version": "1.0.10", 563 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 564 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 565 | "dev": true, 566 | "requires": { 567 | "abbrev": "1" 568 | } 569 | }, 570 | "normalize-path": { 571 | "version": "3.0.0", 572 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 573 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 574 | "dev": true 575 | }, 576 | "picomatch": { 577 | "version": "2.3.1", 578 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 579 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 580 | "dev": true 581 | }, 582 | "pstree.remy": { 583 | "version": "1.1.8", 584 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 585 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 586 | "dev": true 587 | }, 588 | "readdirp": { 589 | "version": "3.6.0", 590 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 591 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 592 | "dev": true, 593 | "requires": { 594 | "picomatch": "^2.2.1" 595 | } 596 | }, 597 | "semver": { 598 | "version": "5.7.1", 599 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 600 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 601 | "dev": true 602 | }, 603 | "simple-update-notifier": { 604 | "version": "1.0.7", 605 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", 606 | "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", 607 | "dev": true, 608 | "requires": { 609 | "semver": "~7.0.0" 610 | }, 611 | "dependencies": { 612 | "semver": { 613 | "version": "7.0.0", 614 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", 615 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", 616 | "dev": true 617 | } 618 | } 619 | }, 620 | "supports-color": { 621 | "version": "5.5.0", 622 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 623 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 624 | "dev": true, 625 | "requires": { 626 | "has-flag": "^3.0.0" 627 | } 628 | }, 629 | "to-regex-range": { 630 | "version": "5.0.1", 631 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 632 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 633 | "dev": true, 634 | "requires": { 635 | "is-number": "^7.0.0" 636 | } 637 | }, 638 | "touch": { 639 | "version": "3.1.0", 640 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 641 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 642 | "dev": true, 643 | "requires": { 644 | "nopt": "~1.0.10" 645 | } 646 | }, 647 | "undefsafe": { 648 | "version": "2.0.5", 649 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 650 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 651 | "dev": true 652 | } 653 | } 654 | } 655 | --------------------------------------------------------------------------------