├── .gitignore ├── LICENSE ├── README.md ├── angular ├── README.md ├── client │ ├── .editorconfig │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── angular.json │ ├── browserslist │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.json │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── button │ │ │ │ ├── button.component.html │ │ │ │ ├── button.component.scss │ │ │ │ ├── button.component.spec.ts │ │ │ │ └── button.component.ts │ │ │ ├── response-card │ │ │ │ ├── response-card.component.html │ │ │ │ ├── response-card.component.scss │ │ │ │ ├── response-card.component.spec.ts │ │ │ │ └── response-card.component.ts │ │ │ ├── response-list │ │ │ │ ├── response-list.component.html │ │ │ │ ├── response-list.component.scss │ │ │ │ ├── response-list.component.spec.ts │ │ │ │ └── response-list.component.ts │ │ │ └── stats-card │ │ │ │ ├── stats-card.component.html │ │ │ │ ├── stats-card.component.scss │ │ │ │ ├── stats-card.component.spec.ts │ │ │ │ └── stats-card.component.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── images │ ├── angular-browser.png │ └── angular-terminal.png ├── package-lock.json ├── package.json └── server.js ├── ember ├── .eslintrc.json ├── .gitignore ├── README.md ├── client │ ├── .editorconfig │ ├── .ember-cli │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .template-lintrc.js │ ├── .watchmanconfig │ ├── README.md │ ├── app │ │ ├── app.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── application.js │ │ ├── index.html │ │ ├── resolver.js │ │ ├── router.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ └── application.hbs │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ ├── ember-cli-build.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── robots.txt │ └── tests │ │ ├── helpers │ │ └── .gitkeep │ │ ├── index.html │ │ ├── integration │ │ └── .gitkeep │ │ ├── test-helper.js │ │ └── unit │ │ └── .gitkeep ├── package-lock.json ├── package.json ├── server.js └── start-client.js ├── jquery ├── .eslintrc.json ├── .gitignore ├── README.md ├── app.js ├── index.html ├── index.js ├── package-lock.json └── package.json ├── more-examples ├── database-timeout │ ├── README.md │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── server.js ├── multiple-express │ ├── README.md │ ├── benchmark │ │ └── index.js │ ├── client.js │ ├── express-end.js │ ├── express-middleman.js │ ├── package-lock.json │ └── package.json └── typescript-example │ ├── package-lock.json │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json ├── prometheus ├── .gitignore ├── .nodeshift │ └── deplyoment.yml ├── README.md ├── package-lock.json ├── package.json ├── scripts │ ├── alertmanager.yml │ ├── bench-test.sh │ ├── bench-test2.sh │ ├── bench-test3.sh │ ├── bench-test4.sh │ ├── deploy-app.sh │ ├── get-app-url.sh │ ├── install-prometheus.sh │ ├── minishift-start.sh │ └── prometheus.yml └── src │ └── app.js ├── react ├── README.md ├── client │ ├── .gitignore │ ├── .prettierrc │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── App.css │ │ ├── App.js │ │ └── index.js ├── images │ ├── react-browser.png │ └── react-terminal.png ├── package-lock.json ├── package.json └── server.js ├── seneca ├── README.md ├── app.js ├── index.html ├── index.js ├── package-lock.json ├── package.json └── service.js ├── svelte ├── README.md ├── client │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.png │ │ ├── global.css │ │ └── index.html │ ├── rollup.config.js │ ├── scripts │ │ └── setupTypeScript.js │ └── src │ │ ├── App.svelte │ │ └── main.js ├── images │ ├── svelte-browser.png │ └── svelte-terminal.png ├── package-lock.json ├── package.json └── server.js └── vue ├── README.md ├── client ├── .browserslistrc ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html └── src │ ├── App.vue │ └── main.js ├── images ├── vue-browser.png └── vue-terminal.png ├── package-lock.json ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Mac OS 4 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Red Hat, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opossum examples 2 | 3 | This repository contains a few examples for the https://github.com/nodeshift/opossum circuit breaker. 4 | -------------------------------------------------------------------------------- /angular/README.md: -------------------------------------------------------------------------------- 1 | # Angular Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3000/flakeyService`. 4 | For every request the service receives, the response time is increased. 5 | [The service returns a `423 (Locked)` error if the response time is above 1000ms.](https://github.com/nodeshift-starters/opossum-examples/blob/main/angular9/server.js#L27) This example also has a web frontend at `http://localhost:4200/` for interacting with the service. 6 | 7 | 1. Install Angular CLI 8 | 9 | ```sh 10 | $ npm install -g @angular/cli 11 | ``` 12 | 13 | 2. Install dependencies 14 | 15 | ```sh 16 | $ npm install && cd client/ && npm install && cd .. 17 | ``` 18 | 19 | 3. Run the example 20 | 21 | This example uses the [concurrently](https://www.npmjs.com/package/concurrently) npm module to run the server and the client at the same time. 22 | 23 | ```sh 24 | $ npm start 25 | ``` 26 | 27 |

Terminal

28 | 29 | 30 |

Browser

31 | 32 | -------------------------------------------------------------------------------- /angular/client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /angular/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /angular/client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid" 3 | } -------------------------------------------------------------------------------- /angular/client/README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.4. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/main/README.md). 28 | -------------------------------------------------------------------------------- /angular/client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/angular", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 32 | "src/styles.scss" 33 | ], 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "extractLicenses": true, 50 | "vendorChunk": false, 51 | "buildOptimizer": true, 52 | "budgets": [ 53 | { 54 | "type": "initial", 55 | "maximumWarning": "2mb", 56 | "maximumError": "5mb" 57 | }, 58 | { 59 | "type": "anyComponentStyle", 60 | "maximumWarning": "6kb", 61 | "maximumError": "10kb" 62 | } 63 | ] 64 | } 65 | } 66 | }, 67 | "serve": { 68 | "builder": "@angular-devkit/build-angular:dev-server", 69 | "options": { 70 | "browserTarget": "angular:build" 71 | }, 72 | "configurations": { 73 | "production": { 74 | "browserTarget": "angular:build:production" 75 | } 76 | } 77 | }, 78 | "extract-i18n": { 79 | "builder": "@angular-devkit/build-angular:extract-i18n", 80 | "options": { 81 | "browserTarget": "angular:build" 82 | } 83 | }, 84 | "test": { 85 | "builder": "@angular-devkit/build-angular:karma", 86 | "options": { 87 | "main": "src/test.ts", 88 | "polyfills": "src/polyfills.ts", 89 | "tsConfig": "tsconfig.spec.json", 90 | "karmaConfig": "karma.conf.js", 91 | "assets": [ 92 | "src/favicon.ico", 93 | "src/assets" 94 | ], 95 | "styles": [ 96 | "src/styles.scss" 97 | ], 98 | "scripts": [] 99 | } 100 | }, 101 | "lint": { 102 | "builder": "@angular-devkit/build-angular:tslint", 103 | "options": { 104 | "tsConfig": [ 105 | "tsconfig.app.json", 106 | "tsconfig.spec.json", 107 | "e2e/tsconfig.json" 108 | ], 109 | "exclude": [ 110 | "**/node_modules/**" 111 | ] 112 | } 113 | }, 114 | "e2e": { 115 | "builder": "@angular-devkit/build-angular:protractor", 116 | "options": { 117 | "protractorConfig": "e2e/protractor.conf.js", 118 | "devServerTarget": "angular:serve" 119 | }, 120 | "configurations": { 121 | "production": { 122 | "devServerTarget": "angular:serve:production" 123 | } 124 | } 125 | } 126 | } 127 | } 128 | }, 129 | "defaultProject": "angular" 130 | } -------------------------------------------------------------------------------- /angular/client/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /angular/client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /angular/client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('angular app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular/client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /angular/client/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /angular/client/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /angular/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "14.1.2", 15 | "@angular/common": "14.1.2", 16 | "@angular/compiler": "14.1.2", 17 | "@angular/core": "14.1.2", 18 | "@angular/forms": "14.1.2", 19 | "@angular/platform-browser": "14.1.2", 20 | "@angular/platform-browser-dynamic": "14.1.2", 21 | "@angular/router": "14.1.2", 22 | "axios": "^0.27.2", 23 | "bootstrap": "^5.2.0", 24 | "opossum": "^6.4.0", 25 | "rxjs": "~7.5.6", 26 | "tslib": "^2.4.0", 27 | "zone.js": "~0.11.8" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "14.1.2", 31 | "@angular/cli": "14.1.2", 32 | "@angular/compiler-cli": "14.1.2", 33 | "@angular/language-service": "14.1.2", 34 | "@types/jasmine": "^4.0.3", 35 | "@types/jasminewd2": "^2.0.10", 36 | "@types/node": "^18.7.5", 37 | "codelyzer": "^6.0.2", 38 | "jasmine-core": "~4.3.0", 39 | "jasmine-spec-reporter": "~7.0.0", 40 | "karma": "^6.4.0", 41 | "karma-chrome-launcher": "~3.1.1", 42 | "karma-coverage-istanbul-reporter": "~3.0.3", 43 | "karma-jasmine": "~5.1.0", 44 | "karma-jasmine-html-reporter": "^2.0.0", 45 | "protractor": "~7.0.0", 46 | "ts-node": "~10.9.1", 47 | "tslint": "^6.1.3", 48 | "typescript": "^4.7.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /angular/client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |

7 | {{ title }} 8 |

9 |

10 | This app demonstrates usage of the opossum 11 | circuit breaker in Angular applications. 12 |

13 |
14 |

15 | The application calls a flakey web service that takes longer and longer 16 | to respond. The circuit breaker is configured to timeout after 500ms and 17 | execute a fallback command. Every 20 seconds, the flakey service is reset 18 | and the pattern is repeated. 19 |

20 |
21 |
22 |
23 | 25 | 26 |
27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
-------------------------------------------------------------------------------- /angular/client/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .clear { 2 | cursor: pointer; 3 | color: #3336ff; 4 | } 5 | .success { 6 | color: darkgreen; 7 | } 8 | .open { 9 | color: red; 10 | } 11 | .fallback { 12 | color: darkblue; 13 | } 14 | .rejected { 15 | color: darkorange; 16 | } 17 | .close { 18 | color: green; 19 | } 20 | .timeout { 21 | color: darksalmon; 22 | } 23 | -------------------------------------------------------------------------------- /angular/client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'angular'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('angular'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement; 29 | expect(compiled.querySelector('.content span').textContent).toContain('angular app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /angular/client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent { 9 | title = 'Circuit Breaker Example'; 10 | } 11 | -------------------------------------------------------------------------------- /angular/client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { ResponseCardComponent } from './response-card/response-card.component'; 6 | import { ResponseListComponent } from './response-list/response-list.component'; 7 | import { ButtonComponent } from './button/button.component'; 8 | import { StatsCardComponent } from './stats-card/stats-card.component'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | ResponseCardComponent, 14 | ResponseListComponent, 15 | ButtonComponent, 16 | StatsCardComponent 17 | ], 18 | imports: [ 19 | BrowserModule 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { } 25 | -------------------------------------------------------------------------------- /angular/client/src/app/button/button.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 8 |
-------------------------------------------------------------------------------- /angular/client/src/app/button/button.component.scss: -------------------------------------------------------------------------------- 1 | button { 2 | margin: 0 1rem 1rem 0; 3 | } 4 | 5 | .buttons { 6 | margin-left: 1rem; 7 | } 8 | -------------------------------------------------------------------------------- /angular/client/src/app/button/button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ButtonComponent } from './button.component'; 4 | 5 | describe('ButtonComponent', () => { 6 | let component: ButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /angular/client/src/app/button/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Output, EventEmitter } from '@angular/core'; 2 | import axios from 'axios'; 3 | import opossum from 'opossum'; 4 | 5 | @Component({ 6 | selector: 'app-button', 7 | templateUrl: './button.component.html', 8 | styleUrls: ['./button.component.scss'], 9 | }) 10 | export class ButtonComponent implements OnInit { 11 | constructor() {} 12 | 13 | @Output() onServiceResponse = new EventEmitter(); 14 | @Output() onClearList = new EventEmitter(); 15 | @Output() onSnapshot = new EventEmitter(); 16 | 17 | route = 'http://localhost:3000/flakeyService'; 18 | circuitBreakerOptions = { 19 | timeout: 500, 20 | errorThresholdPercentage: 50, 21 | resetTimeout: 5000, 22 | }; 23 | circuit = new opossum( 24 | () => axios.get(this.route), 25 | this.circuitBreakerOptions 26 | ); 27 | 28 | ngOnInit(): void { 29 | this.circuit.fallback(() => { 30 | return { 31 | data: { 32 | body: `${this.route} unavailable right now. Try later.`, 33 | }, 34 | }; 35 | }); 36 | 37 | this.circuit.status.on('snapshot', data => this.onSnapshot.emit(data)); 38 | 39 | [ 40 | 'success', 41 | 'timeout', 42 | 'reject', 43 | 'open', 44 | 'halfOpen', 45 | 'close', 46 | 'fallback', 47 | ].every(event => 48 | this.circuit.on(event, payload => 49 | this.onServiceResponse.emit({ 50 | event, 51 | data: payload ? JSON.stringify(payload.data) : '', 52 | }) 53 | ) 54 | ); 55 | } 56 | 57 | callService() { 58 | this.circuit.fire(); 59 | } 60 | 61 | clearList() { 62 | this.onClearList.emit(true); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /angular/client/src/app/response-card/response-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Event: {{ card.event }}
4 |

{{ card.data }}

5 |
6 |
-------------------------------------------------------------------------------- /angular/client/src/app/response-card/response-card.component.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | margin-bottom: 0.5rem; 3 | } 4 | 5 | .card-body { 6 | padding: 0.25rem; 7 | } 8 | 9 | .card-title { 10 | color: darkblue; 11 | } 12 | -------------------------------------------------------------------------------- /angular/client/src/app/response-card/response-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ResponseCardComponent } from './response-card.component'; 4 | 5 | describe('ResponseCardComponent', () => { 6 | let component: ResponseCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ResponseCardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ResponseCardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /angular/client/src/app/response-card/response-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-response-card', 5 | templateUrl: './response-card.component.html', 6 | styleUrls: ['./response-card.component.scss'], 7 | }) 8 | export class ResponseCardComponent implements OnInit { 9 | @Input() card: any; 10 | 11 | constructor() {} 12 | 13 | ngOnInit(): void {} 14 | } 15 | -------------------------------------------------------------------------------- /angular/client/src/app/response-list/response-list.component.html: -------------------------------------------------------------------------------- 1 |

Service Responses

2 |
3 | 4 |
-------------------------------------------------------------------------------- /angular/client/src/app/response-list/response-list.component.scss: -------------------------------------------------------------------------------- 1 | app-response-card { 2 | margin-bottom: 0.5rem; 3 | } 4 | -------------------------------------------------------------------------------- /angular/client/src/app/response-list/response-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ResponseListComponent } from './response-list.component'; 4 | 5 | describe('ResponseListComponent', () => { 6 | let component: ResponseListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ResponseListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ResponseListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /angular/client/src/app/response-list/response-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ResponseCardComponent } from '../response-card/response-card.component'; 3 | 4 | @Component({ 5 | selector: 'app-response-list', 6 | templateUrl: './response-list.component.html', 7 | styleUrls: ['./response-list.component.scss'], 8 | }) 9 | export class ResponseListComponent implements OnInit { 10 | cards = []; 11 | 12 | constructor() {} 13 | 14 | ngOnInit(): void {} 15 | 16 | addResponse(response: any) { 17 | if (!response) return; 18 | if (response['data'] === undefined) { 19 | response['data'] = ''; 20 | } 21 | this.cards.push(response); 22 | } 23 | 24 | clearList() { 25 | this.cards = []; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /angular/client/src/app/stats-card/stats-card.component.html: -------------------------------------------------------------------------------- 1 |

Circuit Breaker Statistics

2 |
3 |
4 |
Rolling Stats
5 |
    6 |
  • {{ stat }}
  • 7 |
8 |
9 |
-------------------------------------------------------------------------------- /angular/client/src/app/stats-card/stats-card.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/angular/client/src/app/stats-card/stats-card.component.scss -------------------------------------------------------------------------------- /angular/client/src/app/stats-card/stats-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { StatsCardComponent } from './stats-card.component'; 4 | 5 | describe('StatsCardComponent', () => { 6 | let component: StatsCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ StatsCardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(StatsCardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /angular/client/src/app/stats-card/stats-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-stats-card', 5 | templateUrl: './stats-card.component.html', 6 | styleUrls: ['./stats-card.component.scss'], 7 | }) 8 | export class StatsCardComponent implements OnInit { 9 | stats = []; 10 | 11 | constructor() {} 12 | 13 | ngOnInit(): void {} 14 | 15 | update(data) { 16 | this.stats = Object.keys(data).map( 17 | key => `${key}: ${JSON.stringify(data[key])}` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /angular/client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/angular/client/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular/client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /angular/client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /angular/client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/angular/client/src/favicon.ico -------------------------------------------------------------------------------- /angular/client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /angular/client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /angular/client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /angular/client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | padding-top: 5rem; 4 | font-family: Arial, Helvetica, sans-serif; 5 | margin: 1em; 6 | } 7 | -------------------------------------------------------------------------------- /angular/client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /angular/client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /angular/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /angular/client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /angular/client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef-whitespace": { 98 | "options": [ 99 | { 100 | "call-signature": "nospace", 101 | "index-signature": "nospace", 102 | "parameter": "nospace", 103 | "property-declaration": "nospace", 104 | "variable-declaration": "nospace" 105 | }, 106 | { 107 | "call-signature": "onespace", 108 | "index-signature": "onespace", 109 | "parameter": "onespace", 110 | "property-declaration": "onespace", 111 | "variable-declaration": "onespace" 112 | } 113 | ] 114 | }, 115 | "variable-name": { 116 | "options": [ 117 | "ban-keywords", 118 | "check-format", 119 | "allow-pascal-case" 120 | ] 121 | }, 122 | "whitespace": { 123 | "options": [ 124 | "check-branch", 125 | "check-decl", 126 | "check-operator", 127 | "check-separator", 128 | "check-type", 129 | "check-typecast" 130 | ] 131 | }, 132 | "no-conflicting-lifecycle": true, 133 | "no-host-metadata-property": true, 134 | "no-input-rename": true, 135 | "no-inputs-metadata-property": true, 136 | "no-output-native": true, 137 | "no-output-on-prefix": true, 138 | "no-output-rename": true, 139 | "no-outputs-metadata-property": true, 140 | "template-banana-in-box": true, 141 | "template-no-negated-async": true, 142 | "use-lifecycle-interface": true, 143 | "use-pipe-transform-interface": true 144 | }, 145 | "rulesDirectory": [ 146 | "codelyzer" 147 | ] 148 | } -------------------------------------------------------------------------------- /angular/images/angular-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/angular/images/angular-browser.png -------------------------------------------------------------------------------- /angular/images/angular-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/angular/images/angular-terminal.png -------------------------------------------------------------------------------- /angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-angular", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "server": "node server.js", 8 | "angular": "cd ./client && ng serve --open", 9 | "start": "concurrently -n server,angular -c bgBlue,bgRed \"npm:server\" \"npm:angular\"" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@hapi/boom": "^10.0.0", 16 | "cors": "^2.8.5", 17 | "express": "^4.18.1" 18 | }, 19 | "devDependencies": { 20 | "concurrently": "^7.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /angular/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require('cors'); 3 | const boom = require("@hapi/boom"); 4 | 5 | const app = express(); 6 | 7 | const delayInitValue = 20; 8 | let delay = delayInitValue; 9 | 10 | // reset delay every 20 seconds 11 | setInterval(() => { 12 | if (delay !== delayInitValue) { 13 | delay = delayInitValue; 14 | console.log("Resetting flakey service delay to", delay); 15 | } 16 | }, 20000); 17 | 18 | app.use(cors()); 19 | 20 | app.get("/flakeyService", (req, res) => { 21 | console.log("Flakey service delay", delay); 22 | // if we're really slowing down, just reply with an error 23 | if (delay > 1000) { 24 | console.log("Long delay encountered, returning Error 423 (Locked)"); 25 | const { 26 | output: { statusCode, payload }, 27 | } = boom.locked("Flakey service is flakey"); 28 | res.status(statusCode).send(payload); 29 | return; 30 | } 31 | 32 | setTimeout(() => { 33 | console.log("Replying with flakey response after delay of", delay); 34 | delay = delay * 2; 35 | res.send({ 36 | body: "Flakey service response", 37 | delay, 38 | }); 39 | }, delay); 40 | }); 41 | 42 | const PORT = 3000; 43 | 44 | app.listen(PORT, () => { 45 | console.log(`Server listening on port ${PORT}`); 46 | }); 47 | -------------------------------------------------------------------------------- /ember/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "semistandard" 3 | } -------------------------------------------------------------------------------- /ember/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /ember/README.md: -------------------------------------------------------------------------------- 1 | # Ember Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3001/flakeyService`. As the service receives requests, it gets slower and slower. Once it takes more than 1 second to respond, the service just returns a `423 (Locked)` error. Also at the route `http://localhost:4200` has a server running an Ember.js app responsible for consuming the flakey service. 4 | 5 | Install dependencies. 6 | 7 | 8 | ```sh 9 | $ npm i && cd client && npm i && cd .. 10 | 11 | ``` 12 | 13 | Start the server. 14 | 15 | 16 | ```sh 17 | $ npm start 18 | ``` 19 | 20 | Browse to `http://localhost:4200` and click the button to see the service in action. 21 | -------------------------------------------------------------------------------- /ember/client/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /ember/client/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /ember/client/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /bower.json.ember-try 20 | /package.json.ember-try 21 | -------------------------------------------------------------------------------- /ember/client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended' 13 | ], 14 | env: { 15 | browser: true 16 | }, 17 | rules: { 18 | }, 19 | overrides: [ 20 | // node files 21 | { 22 | files: [ 23 | '.eslintrc.js', 24 | '.template-lintrc.js', 25 | 'ember-cli-build.js', 26 | 'blueprints/*/index.js', 27 | 'config/**/*.js', 28 | 'lib/*/index.js', 29 | 'server/**/*.js' 30 | ], 31 | parserOptions: { 32 | sourceType: 'script' 33 | }, 34 | env: { 35 | browser: false, 36 | node: true 37 | }, 38 | plugins: ['node'], 39 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 40 | // add your custom rules and overrides for node files here 41 | 42 | // this can be removed once the following is fixed 43 | // https://github.com/mysticatea/eslint-plugin-node/issues/77 44 | 'node/no-unpublished-require': 'off' 45 | }) 46 | } 47 | ] 48 | }; 49 | -------------------------------------------------------------------------------- /ember/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | -------------------------------------------------------------------------------- /ember/client/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended' 5 | }; 6 | -------------------------------------------------------------------------------- /ember/client/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /ember/client/README.md: -------------------------------------------------------------------------------- 1 | # ember-opossum 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd ember-opossum` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint:hbs` 39 | * `npm run lint:js` 40 | * `npm run lint:js -- --fix` 41 | 42 | ### Building 43 | 44 | * `ember build` (development) 45 | * `ember build --environment production` (production) 46 | 47 | ### Deploying 48 | 49 | Specify what it takes to deploy your app. 50 | 51 | ## Further Reading / Useful Links 52 | 53 | * [ember.js](https://emberjs.com/) 54 | * [ember-cli](https://ember-cli.com/) 55 | * Development Browser Extensions 56 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 57 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 58 | -------------------------------------------------------------------------------- /ember/client/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /ember/client/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/ember/client/app/controllers/.gitkeep -------------------------------------------------------------------------------- /ember/client/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import circuitBreaker from 'opossum'; 3 | 4 | const circuitrBreakerOptions = { timeout: 500, maxFailures: 3, resetTimeout: 5000 }; 5 | 6 | export default Controller.extend({ 7 | init () { 8 | this._super(...arguments); 9 | const self = this; 10 | this.responses = []; 11 | // Adding this just for access in the opossum events 12 | // We are specifying the name of the service in the fetch below to get around 13 | // CORS. We've added a --proxy flag in the package.json, so Ember knows 14 | // where to the flakeyService is 15 | this.route = 'http://localhost:3001/flakeyService'; 16 | this.circuit = new circuitBreaker(() => fetch('flakeyService').then((result) => { 17 | if (result.status > 399) { 18 | return Promise.reject(result); 19 | } 20 | 21 | return result; 22 | }), circuitrBreakerOptions); 23 | this.circuit.on('success', async (result) => { 24 | self.responses.pushObject({body: `${JSON.stringify(await result.json())}`, state: 'SUCCESS'}); 25 | }); 26 | this.circuit.on('timeout', async () => { 27 | self.responses.pushObject({ state: `TIMEOUT`, body: `${self.route} is taking too long to respond.` }); 28 | }); 29 | this.circuit.on('open', async () => { 30 | self.responses.pushObject({ state: `OPEN`, body: `The breaker for ${self.route} just opened.` }); 31 | }); 32 | this.circuit.on('reject', async () => { 33 | self.responses.pushObject({ state: `REJECTED`, body: `The breaker for ${self.route} is open. Failing fast.` }); 34 | }); 35 | this.circuit.on('halfOpen', async () => { 36 | self.responses.pushObject({ state: `HALF_OPEN`, body: `The breaker for ${self.route} is half open.` }); 37 | }); 38 | this.circuit.on('close', async () => { 39 | self.responses.pushObject({ state: `CLOSE`, body: `The breaker for ${self.route} has closed. Service OK.` }); 40 | }); 41 | this.circuit.on('fallback', async (result) => { 42 | self.responses.pushObject({ state: `FALLBACK`, body: `${JSON.stringify(result)}` }); 43 | }); 44 | this.circuit.fallback(() => { 45 | return {body: `${self.route} unavailable right now. Try later.`}; 46 | }); 47 | }, 48 | actions: { 49 | makeRequest: async function () { 50 | await this.circuit.fire(); 51 | }, 52 | clear: function () { 53 | this.set('responses', []); 54 | } 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /ember/client/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EmberOpossum 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /ember/client/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /ember/client/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /ember/client/app/styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | margin: 1em !important; 4 | } 5 | button { 6 | padding: 1ex 1em; 7 | margin-right: 1em; 8 | font-size: larger; 9 | } 10 | .row { 11 | padding: 1em; 12 | } 13 | h2 { 14 | border-bottom: 1px dotted #999; 15 | } 16 | .clear { 17 | cursor: pointer; 18 | color: #3336ff; 19 | } 20 | .success { 21 | color: darkgreen 22 | } 23 | .open { 24 | color: red 25 | } 26 | .fallback { 27 | color: darkblue 28 | } 29 | .rejected { 30 | color: darkorange 31 | } 32 | .close { 33 | color: green 34 | } 35 | .timeout { 36 | color: darksalmon 37 | } 38 | -------------------------------------------------------------------------------- /ember/client/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember

2 | 3 |
4 |

Opossum Circuit Breaker Example

5 | 6 |

7 | When you click the button here, this simple app calls a flakey web service that takes longer and longer to respond. The app circuit breaker is configured to timeout after 500ms and execute a fallback command. Every 20 seconds, the flakey service is reset and the pattern is repeated. 8 |

9 |

10 | If more than 3 errors are observed by the circuit within a single timeout period, then it begins to fail fast, rejecting the network call outright and executing the fallback function. 11 |

12 |

13 | This should allow you to see all of the various events that occur when using a circuit breaker. 14 |

15 |

16 | The source code for the application is relatively simple, and uses some basic jQuery capabilities to make the ajax calls and update the DOM accordingly. 17 |

18 | 19 |
20 | 23 |
24 |
25 |

FLAKEY RESPONSES

26 | Click to clear 27 | {{#each this.responses as |response|}} 28 |

29 | {{response.state}} 30 | {{response.body}} 31 |

32 | {{/each}} 33 |
34 |
35 | 36 | {{outlet}} 37 | -------------------------------------------------------------------------------- /ember/client/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'ember-opossum', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /ember/client/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": true 3 | } 4 | -------------------------------------------------------------------------------- /ember/client/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /ember/client/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberApp(defaults, { 7 | // Add options here 8 | }); 9 | 10 | // Use `app.import` to add additional libraries to the generated 11 | // output files. 12 | // 13 | // If you need to use different assets in different 14 | // environments, specify an object as the first parameter. That 15 | // object's keys should be the environment name and the values 16 | // should be the asset to use in that environment. 17 | // 18 | // If the library that you are including contains AMD or ES6 19 | // modules that you would like to import into your application 20 | // please specify an object with the list of modules as keys 21 | // along with the exports of each module as its value. 22 | 23 | return app.toTree(); 24 | }; 25 | -------------------------------------------------------------------------------- /ember/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-opossum", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for ember-opossum goes here", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc" 11 | }, 12 | "scripts": { 13 | "build": "ember build", 14 | "lint:hbs": "ember-template-lint .", 15 | "lint:js": "eslint .", 16 | "start": "ember serve --proxy http://localhost:3001" 17 | }, 18 | "devDependencies": { 19 | "@ember/jquery": "^2.0.0", 20 | "@ember/optional-features": "^2.0.0", 21 | "@ember/test-helpers": "^2.4.2", 22 | "broccoli-asset-rev": "^3.0.0", 23 | "ember-ajax": "^5.0.0", 24 | "ember-auto-import": "~2.2.0", 25 | "ember-cli": "~3.28.3", 26 | "ember-cli-app-version": "^5.0.0", 27 | "ember-cli-babel": "^7.26.6", 28 | "ember-cli-dependency-checker": "^3.2.0", 29 | "ember-cli-eslint": "^5.1.0", 30 | "ember-cli-htmlbars": "^5.7.1", 31 | "ember-cli-htmlbars-inline-precompile": "^3.0.2", 32 | "ember-cli-inject-live-reload": "^2.1.0", 33 | "ember-cli-sri": "^2.1.1", 34 | "ember-cli-string-helpers": "~6.1.0", 35 | "ember-cli-template-lint": "^2.0.2", 36 | "ember-cli-uglify": "^3.0.0", 37 | "ember-data": "~3.28.1", 38 | "ember-export-application-global": "^2.0.1", 39 | "ember-load-initializers": "^2.1.2", 40 | "ember-maybe-import-regenerator": "^0.1.6", 41 | "ember-qunit": "^5.1.4", 42 | "ember-resolver": "^8.0.2", 43 | "ember-source": "~3.28.4", 44 | "eslint-plugin-ember": "^10.5.4", 45 | "eslint-plugin-node": "^11.1.0", 46 | "loader.js": "^4.7.0", 47 | "opossum": "~6.2.0", 48 | "qunit": "^2.16.0", 49 | "qunit-dom": "^1.6.0", 50 | "webpack": "^5.60.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ember/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ember/client/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/ember/client/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /ember/client/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NewClient Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /ember/client/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/ember/client/tests/integration/.gitkeep -------------------------------------------------------------------------------- /ember/client/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /ember/client/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/ember/client/tests/unit/.gitkeep -------------------------------------------------------------------------------- /ember/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-opossum", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "concurrently \"npm run server\" \"npm run client\"", 8 | "server": "node server.js", 9 | "client": "node start-client.js" 10 | }, 11 | "author": "tiago fabre", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@hapi/boom": "9.1.4", 15 | "concurrently": "6.2.1", 16 | "@hapi/hapi": "20.1.5", 17 | "url-parse": "1.5.3", 18 | "mime": "3.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ember/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const Boom = require('@hapi/boom'); 5 | 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: 3001 9 | }); 10 | 11 | const baseline = 20; 12 | let delay = baseline; 13 | server.route({ 14 | method: 'GET', 15 | path: '/flakeyService', 16 | handler: function flakeyService (request, h) { 17 | console.log('Flakey service delay', delay); 18 | // if we're really slowing down, just reply with an error 19 | if (delay > 1000) { 20 | console.log('Long delay encountered, returning Error 423 (Locked)'); 21 | return Boom.locked('Flakey service is flakey'); 22 | } 23 | return new Promise((resolve, reject) => { 24 | setTimeout(() => { 25 | console.log('Replying with flakey response after delay of', delay); 26 | delay = delay * 2; 27 | resolve({ 28 | body: 'Flakey service response', 29 | delay 30 | }); 31 | }, delay); 32 | }); 33 | } 34 | }); 35 | 36 | // reset the delay every 10 seconds 37 | setInterval(() => { 38 | if (delay !== baseline) { 39 | delay = baseline; 40 | console.log('Resetting flakey service delay to', delay); 41 | } 42 | }, 20000); 43 | 44 | server.start(err => { 45 | possibleError(err); 46 | console.log(`Server: ${server.info.uri}`); 47 | console.log('Endpoints:'); 48 | server.table().map(entry => { 49 | entry.table.map(route => { 50 | console.log(`${route.method} ${route.path}`); 51 | }); 52 | }); 53 | }); 54 | 55 | process.on('uncaughtException', e => { 56 | process._rawDebug(`Uncaught exception ${e}`); 57 | }); 58 | 59 | function possibleError (err) { 60 | if (err) throw err; 61 | } 62 | -------------------------------------------------------------------------------- /ember/start-client.js: -------------------------------------------------------------------------------- 1 | const args = [ 'start' ]; 2 | const opts = { stdio: 'inherit', cwd: 'client', shell: true }; 3 | require('child_process').spawn('npm', args, opts); 4 | -------------------------------------------------------------------------------- /jquery/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "semistandard" 3 | } -------------------------------------------------------------------------------- /jquery/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /jquery/README.md: -------------------------------------------------------------------------------- 1 | # JQuery Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3000/flakeyService`. 4 | For every request the service receives, the response time is increased. 5 | [The service returns a `423 (Locked)` error if the response time is above 1000ms.](https://github.com/nodeshift-starters/opossum-examples/blob/main/jquery/index.js#L39) This example also has a web frontend at `http://localhost:3000/` for interacting with the service. 6 | 7 | 1. Navigate to the `examples/jquery/` directory 8 | ```sh 9 | $ cd examples/jquery/ 10 | ``` 11 | 12 | 2. Start the server. 13 | ```sh 14 | $ npm install 15 | $ npm start 16 | ``` 17 | 18 | 3. Browse to the web frontend at `http://localhost:3000` and click the _Flaky Service_ button repeatedly to interact with the service. 19 | Notice the response time increases with every interaction. Once the response time is greater than the [timeout setting](https://github.com/nodeshift-starters/opossum-examples/blob/main/jquery/app.js#L16), the [fallback action](https://github.com/nodeshift-starters/opossum-examples/blob/main/jquery/app.js#L23) is triggered. 20 | 21 | -------------------------------------------------------------------------------- /jquery/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global $ circuitBreaker */ 3 | 4 | (function appInitialization () { 5 | $(() => { 6 | $('#flakey').click(_ => circuit.fire().catch(console.error)); 7 | $('.clear').click(_ => { 8 | $('.clear').parent().find('#flakeyResponse').remove(); 9 | }); 10 | }); 11 | 12 | const route = '/flakeyService'; 13 | const element = '#flakeyResponse'; 14 | 15 | const circuitBreakerOptions = { 16 | timeout: 500, 17 | errorThresholdPercentage: 50, 18 | resetTimeout: 5000 19 | }; 20 | 21 | const circuit = new circuitBreaker(_ => $.get(route), circuitBreakerOptions); 22 | 23 | circuit.fallback(_ => 24 | ({ body: `${route} unavailable right now. Try later.` })); 25 | 26 | circuit.status.on('snapshot', stats => { 27 | const response = document.createElement('p'); 28 | $(response).addClass('stats'); 29 | Object.keys(stats).forEach(key => { 30 | const p = document.createElement('p'); 31 | p.append(`${key}: ${stats[key]}`); 32 | $(response).append(p); 33 | }); 34 | 35 | $('#stats').children().replaceWith($(response)); 36 | }); 37 | 38 | circuit.on('success', 39 | result => $(element).prepend( 40 | makeNode(`SUCCESS: ${JSON.stringify(result)}`))); 41 | 42 | circuit.on('timeout', 43 | () => $(element).prepend( 44 | makeNode(`TIMEOUT: ${route} is taking too long to respond.`))); 45 | 46 | circuit.on('reject', 47 | () => $(element).prepend( 48 | makeNode(`REJECTED: The breaker for ${route} is open. Failing fast.`))); 49 | 50 | circuit.on('open', 51 | () => $(element).prepend( 52 | makeNode(`OPEN: The breaker for ${route} just opened.`))); 53 | 54 | circuit.on('halfOpen', 55 | () => $(element).prepend( 56 | makeNode(`HALF_OPEN: The breaker for ${route} is half open.`))); 57 | 58 | circuit.on('close', 59 | () => $(element).prepend( 60 | makeNode(`CLOSE: The breaker for ${route} has closed. Service OK.`))); 61 | 62 | circuit.on('fallback', 63 | data => $(element).prepend( 64 | makeNode(`FALLBACK: ${JSON.stringify(data)}`))); 65 | 66 | function makeNode (body) { 67 | const response = document.createElement('p'); 68 | $(response).addClass(body.substring(0, body.indexOf(':')).toLowerCase()); 69 | response.append(body); 70 | return response; 71 | } 72 | })(); 73 | -------------------------------------------------------------------------------- /jquery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Opossum Circuit Breaker Example 4 | 5 | 6 | 7 | 51 | 52 | 53 |
54 |

Opossum Circuit Breaker Example

55 | 56 |

57 | When you click the button here, this simple app calls a flakey web service that takes longer and longer to respond. The app's circuit breaker is configured to timeout after 500ms and execute a fallback command. Every 20 seconds, the flakey service is reset and the pattern is repeated. 58 |

59 |

60 | If more than half of the requests error, then it begins to fail fast, rejecting the network call outright and executing the fallback function. 61 |

62 |

63 | The source code. 64 |

65 |
66 | 69 |
70 |
71 |

Circuit Breaker Statistics

72 |

...

73 |
74 |
75 |

Flakey Responses

76 | Click to clear 77 |
78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /jquery/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Hapi = require('@hapi/hapi'); 4 | const Boom = require('@hapi/boom'); 5 | const path = require('path'); 6 | 7 | const server = Hapi.Server({ 8 | host: 'localhost', 9 | port: 3000 10 | }); 11 | 12 | async function start () { 13 | // static file serving 14 | await server.register({ plugin: require('@hapi/inert') }); 15 | 16 | [ ['/', path.join(__dirname, 'index.html')], 17 | ['/app.js', path.join(__dirname, 'app.js')], 18 | ['/jquery.js', 19 | path.join(__dirname, 'node_modules', 'jquery', 'dist', 'jquery.js')], 20 | ['/opossum.js', path.join(__dirname, 'node_modules', 'opossum', 'dist', 'opossum.js')] 21 | ].map(entry => { 22 | server.route({ 23 | method: 'GET', 24 | path: entry[0], 25 | handler: (request, h) => h.file(entry[1]) 26 | }); 27 | }); 28 | 29 | const baseline = 20; 30 | let delay = baseline; 31 | server.route({ 32 | method: 'GET', 33 | path: '/flakeyService', 34 | handler: function flakeyService (request, h) { 35 | console.log('Flakey service delay', delay); 36 | // if we're really slowing down, just reply with an error 37 | if (delay > 1000) { 38 | console.log('Long delay encountered, returning Error 423 (Locked)'); 39 | return Boom.locked('Flakey service is flakey'); 40 | } 41 | return new Promise((resolve, reject) => { 42 | setTimeout(() => { 43 | console.log('Replying with flakey response after delay of', delay); 44 | delay = delay * 2; 45 | resolve({ 46 | body: 'Flakey service response', 47 | delay }); 48 | }, delay); 49 | }); 50 | } 51 | }); 52 | 53 | // reset the delay every 10 seconds 54 | setInterval(() => { 55 | if (delay !== baseline) { 56 | delay = baseline; 57 | console.log('Resetting flakey service delay to', delay); 58 | } 59 | }, 20000); 60 | 61 | await server.start(err => { 62 | possibleError(err); 63 | console.log(`Server: ${server.info.uri}`); 64 | console.log('Endpoints:'); 65 | server.table().map(entry => { 66 | entry.table.map(route => { 67 | console.log(`${route.method} ${route.path}`); 68 | }); 69 | }); 70 | }); 71 | } 72 | 73 | process.on('uncaughtException', e => { 74 | process._rawDebug(`Uncaught exception ${e}`); 75 | }); 76 | 77 | function possibleError (err) { 78 | if (err) throw err; 79 | } 80 | 81 | start(); 82 | -------------------------------------------------------------------------------- /jquery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-browser-example", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "lint": "eslint test/*.js index.js app.js", 8 | "start": "node ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/nodeshift/opossum.git" 13 | }, 14 | "devDependencies": { 15 | "eslint": "8.22.0", 16 | "eslint-config-semistandard": "17.0.0", 17 | "eslint-config-standard": "17.0.0", 18 | "eslint-plugin-promise": "6.0.0", 19 | "eslint-plugin-standard": "5.0.0" 20 | }, 21 | "description": "Simple example with opossum and browser.", 22 | "dependencies": { 23 | "@hapi/boom": "10.0.0", 24 | "bootstrap": "5.2.0", 25 | "@hapi/hapi": "20.2.2", 26 | "@hapi/inert": "7.0.0", 27 | "jquery": "3.6.0", 28 | "opossum": "6.4.0" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/nodeshift/opossum/issues" 32 | }, 33 | "homepage": "https://github.com/nodeshift/opossum#readme", 34 | "author": "" 35 | } 36 | -------------------------------------------------------------------------------- /more-examples/database-timeout/README.md: -------------------------------------------------------------------------------- 1 | # Basic example with a query that delays 2 | 3 | 4 | Setup mysql container for the example using either docker or podman: 5 | ``` 6 | docker | podman pull mysql 7 | docker | podman run --name my-db -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 -d mysql 8 | docker | podman container ls 9 | docker | podman exec -it container_id_here bash 10 | mysql -u root -p mysql 11 | mysql> ALTER USER root IDENTIFIED WITH mysql_native_password BY 'password'; 12 | mysql> create database test; 13 | ``` 14 | 15 | Install and run: 16 | ``` 17 | npm install 18 | npm start 19 | ``` 20 | 21 | Open another terminal and run: 22 | ``` 23 | curl -X GET localhost:3000 24 | ``` 25 | 26 | The circuit breaker will fire a mysql SQL statement that delays for 11 seconds 27 | which is more than the default opossum timeout, and it will trigger 28 | the circuit breaker failure. 29 | 30 | ``` 31 | Result: 32 | Error: Timed out after 10000ms failures: 1, timeouts: 1 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /more-examples/database-timeout/index.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql'); 2 | const conn = mysql.createConnection({ 3 | host: 'localhost', 4 | user: 'root', 5 | password: 'password', 6 | database: 'test' 7 | }); 8 | 9 | conn.connect((err) => { 10 | if (err) { 11 | console.error('Error connecting mysql: ', err); 12 | } 13 | console.log('connected'); 14 | }); 15 | 16 | conn.end(() => { 17 | console.log('disconnected'); 18 | }); -------------------------------------------------------------------------------- /more-examples/database-timeout/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "database-timeout", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "bignumber.js": { 22 | "version": "9.0.0", 23 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", 24 | "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" 25 | }, 26 | "body-parser": { 27 | "version": "1.19.0", 28 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 29 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 30 | "requires": { 31 | "bytes": "3.1.0", 32 | "content-type": "~1.0.4", 33 | "debug": "2.6.9", 34 | "depd": "~1.1.2", 35 | "http-errors": "1.7.2", 36 | "iconv-lite": "0.4.24", 37 | "on-finished": "~2.3.0", 38 | "qs": "6.7.0", 39 | "raw-body": "2.4.0", 40 | "type-is": "~1.6.17" 41 | } 42 | }, 43 | "bytes": { 44 | "version": "3.1.0", 45 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 46 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 47 | }, 48 | "content-disposition": { 49 | "version": "0.5.3", 50 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 51 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 52 | "requires": { 53 | "safe-buffer": "5.1.2" 54 | } 55 | }, 56 | "content-type": { 57 | "version": "1.0.4", 58 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 59 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 60 | }, 61 | "cookie": { 62 | "version": "0.4.0", 63 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 64 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 65 | }, 66 | "cookie-signature": { 67 | "version": "1.0.6", 68 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 69 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 70 | }, 71 | "core-util-is": { 72 | "version": "1.0.2", 73 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 74 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 75 | }, 76 | "debug": { 77 | "version": "2.6.9", 78 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 79 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 80 | "requires": { 81 | "ms": "2.0.0" 82 | } 83 | }, 84 | "depd": { 85 | "version": "1.1.2", 86 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 87 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 88 | }, 89 | "destroy": { 90 | "version": "1.0.4", 91 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 92 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 93 | }, 94 | "ee-first": { 95 | "version": "1.1.1", 96 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 97 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 98 | }, 99 | "encodeurl": { 100 | "version": "1.0.2", 101 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 102 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 103 | }, 104 | "escape-html": { 105 | "version": "1.0.3", 106 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 107 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 108 | }, 109 | "etag": { 110 | "version": "1.8.1", 111 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 112 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 113 | }, 114 | "express": { 115 | "version": "4.17.1", 116 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 117 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 118 | "requires": { 119 | "accepts": "~1.3.7", 120 | "array-flatten": "1.1.1", 121 | "body-parser": "1.19.0", 122 | "content-disposition": "0.5.3", 123 | "content-type": "~1.0.4", 124 | "cookie": "0.4.0", 125 | "cookie-signature": "1.0.6", 126 | "debug": "2.6.9", 127 | "depd": "~1.1.2", 128 | "encodeurl": "~1.0.2", 129 | "escape-html": "~1.0.3", 130 | "etag": "~1.8.1", 131 | "finalhandler": "~1.1.2", 132 | "fresh": "0.5.2", 133 | "merge-descriptors": "1.0.1", 134 | "methods": "~1.1.2", 135 | "on-finished": "~2.3.0", 136 | "parseurl": "~1.3.3", 137 | "path-to-regexp": "0.1.7", 138 | "proxy-addr": "~2.0.5", 139 | "qs": "6.7.0", 140 | "range-parser": "~1.2.1", 141 | "safe-buffer": "5.1.2", 142 | "send": "0.17.1", 143 | "serve-static": "1.14.1", 144 | "setprototypeof": "1.1.1", 145 | "statuses": "~1.5.0", 146 | "type-is": "~1.6.18", 147 | "utils-merge": "1.0.1", 148 | "vary": "~1.1.2" 149 | } 150 | }, 151 | "finalhandler": { 152 | "version": "1.1.2", 153 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 154 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 155 | "requires": { 156 | "debug": "2.6.9", 157 | "encodeurl": "~1.0.2", 158 | "escape-html": "~1.0.3", 159 | "on-finished": "~2.3.0", 160 | "parseurl": "~1.3.3", 161 | "statuses": "~1.5.0", 162 | "unpipe": "~1.0.0" 163 | } 164 | }, 165 | "forwarded": { 166 | "version": "0.2.0", 167 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 168 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 169 | }, 170 | "fresh": { 171 | "version": "0.5.2", 172 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 173 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 174 | }, 175 | "http-errors": { 176 | "version": "1.7.2", 177 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 178 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 179 | "requires": { 180 | "depd": "~1.1.2", 181 | "inherits": "2.0.3", 182 | "setprototypeof": "1.1.1", 183 | "statuses": ">= 1.5.0 < 2", 184 | "toidentifier": "1.0.0" 185 | } 186 | }, 187 | "iconv-lite": { 188 | "version": "0.4.24", 189 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 190 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 191 | "requires": { 192 | "safer-buffer": ">= 2.1.2 < 3" 193 | } 194 | }, 195 | "inherits": { 196 | "version": "2.0.3", 197 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 198 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 199 | }, 200 | "ipaddr.js": { 201 | "version": "1.9.1", 202 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 203 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 204 | }, 205 | "isarray": { 206 | "version": "1.0.0", 207 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 208 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 209 | }, 210 | "media-typer": { 211 | "version": "0.3.0", 212 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 213 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 214 | }, 215 | "merge-descriptors": { 216 | "version": "1.0.1", 217 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 218 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 219 | }, 220 | "methods": { 221 | "version": "1.1.2", 222 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 223 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 224 | }, 225 | "mime": { 226 | "version": "1.6.0", 227 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 228 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 229 | }, 230 | "mime-db": { 231 | "version": "1.49.0", 232 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", 233 | "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" 234 | }, 235 | "mime-types": { 236 | "version": "2.1.32", 237 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", 238 | "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", 239 | "requires": { 240 | "mime-db": "1.49.0" 241 | } 242 | }, 243 | "ms": { 244 | "version": "2.0.0", 245 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 246 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 247 | }, 248 | "mysql": { 249 | "version": "2.18.1", 250 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", 251 | "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", 252 | "requires": { 253 | "bignumber.js": "9.0.0", 254 | "readable-stream": "2.3.7", 255 | "safe-buffer": "5.1.2", 256 | "sqlstring": "2.3.1" 257 | } 258 | }, 259 | "negotiator": { 260 | "version": "0.6.2", 261 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 262 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 263 | }, 264 | "on-finished": { 265 | "version": "2.3.0", 266 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 267 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 268 | "requires": { 269 | "ee-first": "1.1.1" 270 | } 271 | }, 272 | "opossum": { 273 | "version": "6.2.0", 274 | "resolved": "https://registry.npmjs.org/opossum/-/opossum-6.2.0.tgz", 275 | "integrity": "sha512-pqemKjWRvgkhNNRsovfKQ/B3t16OIyOp+VPr+jQCf2U/PhqfyFyvur6LxMxw+1Vrj+Q392YSav6z9wS1wwc+2g==" 276 | }, 277 | "parseurl": { 278 | "version": "1.3.3", 279 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 280 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 281 | }, 282 | "path-to-regexp": { 283 | "version": "0.1.7", 284 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 285 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 286 | }, 287 | "process-nextick-args": { 288 | "version": "2.0.1", 289 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 290 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 291 | }, 292 | "proxy-addr": { 293 | "version": "2.0.7", 294 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 295 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 296 | "requires": { 297 | "forwarded": "0.2.0", 298 | "ipaddr.js": "1.9.1" 299 | } 300 | }, 301 | "qs": { 302 | "version": "6.7.0", 303 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 304 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 305 | }, 306 | "range-parser": { 307 | "version": "1.2.1", 308 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 309 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 310 | }, 311 | "raw-body": { 312 | "version": "2.4.0", 313 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 314 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 315 | "requires": { 316 | "bytes": "3.1.0", 317 | "http-errors": "1.7.2", 318 | "iconv-lite": "0.4.24", 319 | "unpipe": "1.0.0" 320 | } 321 | }, 322 | "readable-stream": { 323 | "version": "2.3.7", 324 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 325 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 326 | "requires": { 327 | "core-util-is": "~1.0.0", 328 | "inherits": "~2.0.3", 329 | "isarray": "~1.0.0", 330 | "process-nextick-args": "~2.0.0", 331 | "safe-buffer": "~5.1.1", 332 | "string_decoder": "~1.1.1", 333 | "util-deprecate": "~1.0.1" 334 | } 335 | }, 336 | "safe-buffer": { 337 | "version": "5.1.2", 338 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 339 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 340 | }, 341 | "safer-buffer": { 342 | "version": "2.1.2", 343 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 344 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 345 | }, 346 | "send": { 347 | "version": "0.17.1", 348 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 349 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 350 | "requires": { 351 | "debug": "2.6.9", 352 | "depd": "~1.1.2", 353 | "destroy": "~1.0.4", 354 | "encodeurl": "~1.0.2", 355 | "escape-html": "~1.0.3", 356 | "etag": "~1.8.1", 357 | "fresh": "0.5.2", 358 | "http-errors": "~1.7.2", 359 | "mime": "1.6.0", 360 | "ms": "2.1.1", 361 | "on-finished": "~2.3.0", 362 | "range-parser": "~1.2.1", 363 | "statuses": "~1.5.0" 364 | }, 365 | "dependencies": { 366 | "ms": { 367 | "version": "2.1.1", 368 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 369 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 370 | } 371 | } 372 | }, 373 | "serve-static": { 374 | "version": "1.14.1", 375 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 376 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 377 | "requires": { 378 | "encodeurl": "~1.0.2", 379 | "escape-html": "~1.0.3", 380 | "parseurl": "~1.3.3", 381 | "send": "0.17.1" 382 | } 383 | }, 384 | "setprototypeof": { 385 | "version": "1.1.1", 386 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 387 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 388 | }, 389 | "sqlstring": { 390 | "version": "2.3.1", 391 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 392 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" 393 | }, 394 | "statuses": { 395 | "version": "1.5.0", 396 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 397 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 398 | }, 399 | "string_decoder": { 400 | "version": "1.1.1", 401 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 402 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 403 | "requires": { 404 | "safe-buffer": "~5.1.0" 405 | } 406 | }, 407 | "toidentifier": { 408 | "version": "1.0.0", 409 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 410 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 411 | }, 412 | "type-is": { 413 | "version": "1.6.18", 414 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 415 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 416 | "requires": { 417 | "media-typer": "0.3.0", 418 | "mime-types": "~2.1.24" 419 | } 420 | }, 421 | "unpipe": { 422 | "version": "1.0.0", 423 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 424 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 425 | }, 426 | "util-deprecate": { 427 | "version": "1.0.2", 428 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 429 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 430 | }, 431 | "utils-merge": { 432 | "version": "1.0.1", 433 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 434 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 435 | }, 436 | "vary": { 437 | "version": "1.1.2", 438 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 439 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 440 | } 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /more-examples/database-timeout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "database-timeout", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "^4.17.1", 14 | "mysql": "^2.18.1", 15 | "opossum": "^6.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /more-examples/database-timeout/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const util = require('util'); 3 | const mysql = require('mysql'); 4 | const CircuitBreaker = require('opossum'); 5 | 6 | const app = express(); 7 | 8 | const config = { 9 | host: 'localhost', 10 | user: 'root', 11 | password: 'password', 12 | database: 'test', 13 | }; 14 | 15 | // thanks for this approach: https://codeburst.io/node-js-mysql-and-async-await-6fb25b01b628 16 | function makeDb() { 17 | const connection = mysql.createConnection(config); 18 | return { 19 | query(sql, args) { 20 | return util.promisify(connection.query).call(connection, sql, args); 21 | }, 22 | close() { 23 | return util.promisify(connection.end).call(connection); 24 | }, 25 | }; 26 | } 27 | 28 | const options = {}; 29 | const breaker = new CircuitBreaker(mysqlQuery, options); 30 | 31 | app.get('/', (req, res) => { 32 | breaker 33 | .fire('DO SLEEP(11)') 34 | .then((result) => { 35 | result += `success: ${breaker.stats.successes}`; 36 | res.send(result); 37 | }) 38 | .catch((err) => { 39 | err += ` failures: ${breaker.stats.failures}, timeouts: ${breaker.stats.timeouts}`; 40 | res.send(err); 41 | }); 42 | }); 43 | 44 | async function mysqlQuery(query) { 45 | const db = makeDb(); 46 | try { 47 | return await db.query(query); 48 | } catch (err) { 49 | console.error(err); 50 | } finally { 51 | await db.close(); 52 | } 53 | } 54 | 55 | app.listen(3000); 56 | -------------------------------------------------------------------------------- /more-examples/multiple-express/README.md: -------------------------------------------------------------------------------- 1 | # Opossum Multiple-Express Example 2 | 3 | This example uses multiple express.js servers and a client to perform the requests. The opossum module is present only to the last express.js server in the chain. 4 | 5 | ### Architecture 6 | client -> express (middleman) -> express (end) 7 | 8 | ### Steps 9 | 10 | ```sh 11 | $ npm install 12 | $ npm start 13 | ``` 14 | 15 | Open a new terminal and run: 16 | 17 | ```sh 18 | $ npm test 19 | ``` 20 | 21 | Result: 22 | 23 | ``` 24 | success: 153 25 | 26 | success: 154 27 | 28 | failures: 27, fallbacks: 0, rejects: 0, timeouts: 0 29 | 30 | failures: 28, fallbacks: 0, rejects: 0, timeouts: 0 31 | 32 | success: 155 33 | 34 | success: 156 35 | 36 | success: 157 37 | 38 | success: 158 39 | 40 | success: 159 41 | 42 | success: 160 43 | 44 | success: 161 45 | 46 | failures: 29, fallbacks: 0, rejects: 0, timeouts: 0 47 | 48 | ... 49 | ``` 50 | 51 | This example is based on this use case: https://github.com/nodeshift/opossum/issues/181 52 | -------------------------------------------------------------------------------- /more-examples/multiple-express/benchmark/index.js: -------------------------------------------------------------------------------- 1 | const lowCarb = require('lowcarb'); 2 | 3 | const client = require('../client'); 4 | 5 | lowCarb.add(client.makeRequest, 100); 6 | lowCarb.run('', false, true); 7 | -------------------------------------------------------------------------------- /more-examples/multiple-express/client.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | const options = { 4 | hostname: '127.0.0.1', 5 | port: 8080, 6 | path: '/', 7 | method: 'GET', 8 | }; 9 | 10 | const makeRequest = () => { 11 | const request = http.request(options, response => { 12 | response.on('data', data => { 13 | console.log(data.toString()); 14 | }); 15 | }); 16 | 17 | request.on('error', error => { 18 | console.error(error); 19 | }); 20 | 21 | request.end(); 22 | }; 23 | 24 | module.exports = { 25 | makeRequest, 26 | }; 27 | -------------------------------------------------------------------------------- /more-examples/multiple-express/express-end.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const CircuitBreaker = require('opossum'); 3 | 4 | const randomFailure = (echo) => { 5 | return Date.now() % 5 === 0 ? Promise.reject("\n") : Promise.resolve(echo); 6 | }; 7 | 8 | const options = {}; 9 | const breaker = new CircuitBreaker(randomFailure, options); 10 | const app = express(); 11 | 12 | app.get('/', (req, res) => { 13 | breaker.fire('\n') 14 | .then((result) => { 15 | result += `success: ${breaker.stats.successes}`; 16 | res.send(result); 17 | }) 18 | .catch((err) => { 19 | const { failures, fallbacks, rejects, timeouts } = breaker.stats; 20 | err += `failures: ${failures}, fallbacks: ${fallbacks}, rejects: ${rejects}, timeouts: ${timeouts}`; 21 | res.send(err); 22 | }); 23 | }); 24 | 25 | app.listen(3000); 26 | -------------------------------------------------------------------------------- /more-examples/multiple-express/express-middleman.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const axios = require('axios').default; 3 | 4 | const app = express(); 5 | 6 | app.get('/', (req, res) => { 7 | axios.get('http://localhost:3000') 8 | .then(result => res.send(result.data)) 9 | .catch(err => res.send(err)); 10 | }); 11 | 12 | app.listen(8080); 13 | -------------------------------------------------------------------------------- /more-examples/multiple-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "client.js", 6 | "scripts": { 7 | "start": "concurrently \"node express-end.js\" \"node express-middleman.js\"", 8 | "test": "node benchmark/index.js" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.27.2", 12 | "express": "^4.18.1", 13 | "opossum": "^6.4.0" 14 | }, 15 | "devDependencies": { 16 | "concurrently": "^7.3.0", 17 | "lowcarb": "^0.6.0" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /more-examples/typescript-example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@cspotcode/source-map-support": { 8 | "version": "0.8.1", 9 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 10 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 11 | "dev": true, 12 | "requires": { 13 | "@jridgewell/trace-mapping": "0.3.9" 14 | } 15 | }, 16 | "@jridgewell/resolve-uri": { 17 | "version": "3.1.0", 18 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 19 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 20 | "dev": true 21 | }, 22 | "@jridgewell/sourcemap-codec": { 23 | "version": "1.4.14", 24 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 25 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 26 | "dev": true 27 | }, 28 | "@jridgewell/trace-mapping": { 29 | "version": "0.3.9", 30 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 31 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 32 | "dev": true, 33 | "requires": { 34 | "@jridgewell/resolve-uri": "^3.0.3", 35 | "@jridgewell/sourcemap-codec": "^1.4.10" 36 | } 37 | }, 38 | "@tsconfig/node10": { 39 | "version": "1.0.9", 40 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 41 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 42 | "dev": true 43 | }, 44 | "@tsconfig/node12": { 45 | "version": "1.0.11", 46 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 47 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 48 | "dev": true 49 | }, 50 | "@tsconfig/node14": { 51 | "version": "1.0.3", 52 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 53 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 54 | "dev": true 55 | }, 56 | "@tsconfig/node16": { 57 | "version": "1.0.3", 58 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 59 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 60 | "dev": true 61 | }, 62 | "acorn": { 63 | "version": "8.8.0", 64 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", 65 | "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", 66 | "dev": true 67 | }, 68 | "acorn-walk": { 69 | "version": "8.2.0", 70 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 71 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 72 | "dev": true 73 | }, 74 | "arg": { 75 | "version": "4.1.3", 76 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 77 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 78 | "dev": true 79 | }, 80 | "create-require": { 81 | "version": "1.1.1", 82 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 83 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 84 | "dev": true 85 | }, 86 | "diff": { 87 | "version": "4.0.2", 88 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 89 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 90 | "dev": true 91 | }, 92 | "make-error": { 93 | "version": "1.3.6", 94 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 95 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 96 | "dev": true 97 | }, 98 | "opossum": { 99 | "version": "6.4.0", 100 | "resolved": "https://registry.npmjs.org/opossum/-/opossum-6.4.0.tgz", 101 | "integrity": "sha512-JVSEBsP3L1S8n6OEyFFfXqE9jzmM7QBeDGMT7/EPfv/TMHvbkFUxgXfklE3j1YeOIfIixfpT/4oKbNMhpAsHUQ==" 102 | }, 103 | "ts-node": { 104 | "version": "10.9.1", 105 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 106 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 107 | "dev": true, 108 | "requires": { 109 | "@cspotcode/source-map-support": "^0.8.0", 110 | "@tsconfig/node10": "^1.0.7", 111 | "@tsconfig/node12": "^1.0.7", 112 | "@tsconfig/node14": "^1.0.0", 113 | "@tsconfig/node16": "^1.0.2", 114 | "acorn": "^8.4.1", 115 | "acorn-walk": "^8.1.1", 116 | "arg": "^4.1.0", 117 | "create-require": "^1.1.0", 118 | "diff": "^4.0.1", 119 | "make-error": "^1.1.1", 120 | "v8-compile-cache-lib": "^3.0.1", 121 | "yn": "3.1.1" 122 | } 123 | }, 124 | "typescript": { 125 | "version": "4.7.4", 126 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", 127 | "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", 128 | "dev": true 129 | }, 130 | "v8-compile-cache-lib": { 131 | "version": "3.0.1", 132 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 133 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 134 | "dev": true 135 | }, 136 | "yn": { 137 | "version": "3.1.1", 138 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 139 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 140 | "dev": true 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /more-examples/typescript-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node src/index.ts" 8 | }, 9 | "license": "ISC", 10 | "dependencies": { 11 | "opossum": "^6.4.0" 12 | }, 13 | "devDependencies": { 14 | "ts-node": "^10.9.1", 15 | "typescript": "^4.7.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /more-examples/typescript-example/src/index.ts: -------------------------------------------------------------------------------- 1 | import CircuitBreaker from 'opossum'; 2 | 3 | const delay = (delay) => 4 | new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, delay); 8 | }); 9 | 10 | const breaker = new CircuitBreaker(delay); 11 | 12 | breaker 13 | .fire(100) 14 | .then((_) => { 15 | console.log( 16 | 'fires', 17 | breaker.stats.fires, 18 | 'successes', 19 | breaker.stats.successes 20 | ); 21 | }) 22 | .catch((error) => console.error(error)); 23 | -------------------------------------------------------------------------------- /more-examples/typescript-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "outDir": "dist", 5 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 6 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /prometheus/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp -------------------------------------------------------------------------------- /prometheus/.nodeshift/deplyoment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - readinessProbe: 6 | httpGet: 7 | path: /ready 8 | port: 8080 9 | scheme: HTTP 10 | failureThreshold: 3 11 | initialDelaySeconds: 10 12 | periodSeconds: 5 13 | successThreshold: 1 14 | timeoutSeconds: 1 15 | livenessProbe: 16 | httpGet: 17 | path: /live 18 | port: 8080 19 | scheme: HTTP 20 | failureThreshold: 2 21 | initialDelaySeconds: 60 22 | periodSeconds: 3 23 | successThreshold: 1 24 | timeoutSeconds: 1 25 | -------------------------------------------------------------------------------- /prometheus/README.md: -------------------------------------------------------------------------------- 1 | # prometheus example 2 | 3 | Example based on the blog post: https://developers.redhat.com/blog/2018/12/21/monitoring-node-js-applications-on-openshift-with-prometheus/ 4 | 5 | ## How to setup and run 6 | 7 | 1. Clone this repository 8 | 2. Start minishift: `./scripts/minishift-start.sh` 9 | 3. Install prometheus `./scripts/install-prometheus.sh` 10 | 4. Deploy the app: `./scripts/deploy-app.sh` 11 | 5. Run the bench test `./scripts/bench-test.sh` 12 | 6. Get app URLs: `./scripts/get-app-url.sh` 13 | 7. Access prometheus server URL and execute the PromQL associated with the circuit to see the results. 14 | 15 | -------------------------------------------------------------------------------- /prometheus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-prometheus-minishift", 3 | "version": "1.0.0", 4 | "description": "Example based on the blog post: https://developers.redhat.com/blog/2018/12/21/monitoring-node-js-applications-on-openshift-with-prometheus/", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "start": "node src/app.js" 8 | }, 9 | "files": [ 10 | "package.json", 11 | "src" 12 | ], 13 | "author": "Helio Frota", 14 | "license": "MIT", 15 | "dependencies": { 16 | "express": "^4.17.1", 17 | "nodemon": "^2.0.14", 18 | "opossum": "6.2.1", 19 | "opossum-prometheus": "0.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /prometheus/scripts/alertmanager.yml: -------------------------------------------------------------------------------- 1 | global: 2 | # The root route on which each incoming alert enters. 3 | route: 4 | # default route if none match 5 | receiver: alert-buffer-wh 6 | receivers: 7 | - name: alert-buffer-wh 8 | webhook_configs: 9 | - url: http://localhost:9099/topics/alerts -------------------------------------------------------------------------------- /prometheus/scripts/bench-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APPURL=$(oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/api/greeting") 4 | 5 | # 5 requests 6 | # No concurrent requests 7 | ab -n 5 -c 1 $APPURL 8 | -------------------------------------------------------------------------------- /prometheus/scripts/bench-test2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APPURL=$(oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/api/greeting") 4 | 5 | # 10 requests 6 | # 2 concurrent requests 7 | ab -n 10 -c 2 $APPURL 8 | -------------------------------------------------------------------------------- /prometheus/scripts/bench-test3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APPURL=$(oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/api/greeting") 4 | 5 | # 80 requests 6 | # 10 concurrent requests 7 | # keep alive header along the request 8 | for i in {1..60} ; do 9 | ab -n 1000 -c 100 -k $APPURL 10 | sleep 1 11 | done -------------------------------------------------------------------------------- /prometheus/scripts/bench-test4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APPURL=$(oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/api/greeting") 4 | 5 | # 80K requests 6 | # 100 concurrent requests 7 | ab -n 80000 -c 100 $APPURL -------------------------------------------------------------------------------- /prometheus/scripts/deploy-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'npm install' 4 | npm install 5 | echo 'minishift login' 6 | oc login -u developer 7 | echo 'nodeshift deploy' 8 | npx nodeshift --strictSSL=false --expose --imageTag="10.15.3" --build.recreate=buildConfig 9 | -------------------------------------------------------------------------------- /prometheus/scripts/get-app-url.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 4 | echo "Application API" 5 | oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/api/greeting" 6 | echo -e "\n\nApplication metrics" 7 | oc get -o template route express-prometheus-minishift --template="http://{{.spec.host}}/metrics" 8 | echo -e "\n\nPrometheus server" 9 | oc get -o template route prom --template="http://{{.spec.host}}/" 10 | echo -------------------------------------------------------------------------------- /prometheus/scripts/install-prometheus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create the prom secret 4 | oc create secret generic prom --from-file=scripts/prometheus.yml 5 | 6 | # Create the prom-alerts secret 7 | oc create secret generic prom-alerts --from-file=scripts/alertmanager.yml 8 | 9 | # Create the prometheus instance 10 | oc process -f https://raw.githubusercontent.com/openshift/origin/master/examples/prometheus/prometheus-standalone.yaml | oc apply -f - -------------------------------------------------------------------------------- /prometheus/scripts/minishift-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | minishift config set vm-driver virtualbox 4 | minishift config set cpus 4 5 | minishift config set memory 8GB 6 | 7 | minishift start -------------------------------------------------------------------------------- /prometheus/scripts/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 3 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 4 | # scrape_timeout is set to the global default (10s). 5 | 6 | # Alertmanager configuration 7 | alerting: 8 | alertmanagers: 9 | - static_configs: 10 | - targets: 11 | # - alertmanager:9093 12 | 13 | # A scrape configuration containing exactly one endpoint to scrape: 14 | # Here it's Prometheus itself. 15 | scrape_configs: 16 | # The job name is added as a label `job=` to any timeseries scraped from this config. 17 | - job_name: 'prometheus' 18 | 19 | # metrics_path defaults to '/metrics' 20 | # scheme defaults to 'http'. 21 | 22 | static_configs: 23 | - targets: ['localhost:9090'] 24 | 25 | # Scrape configuration for our hello world app 26 | - job_name: 'express-prometheus-minishift' 27 | static_configs: 28 | - targets: ['express-prometheus-minishift:8080'] -------------------------------------------------------------------------------- /prometheus/src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Opossum = require('opossum'); 4 | const PrometheusMetrics = require('opossum-prometheus'); 5 | const express = require('express'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const app = express(); 9 | const port = process.argv[2] || 8080; 10 | const circuit = new Opossum(somethingThatCouldFail, { 11 | errorThresholdPercentage: 10, 12 | resetTimeout: 2000 13 | }); 14 | 15 | // Provide the circuit to the Opossum Prometheus plugin module 16 | const prometheus = new PrometheusMetrics([circuit]); 17 | 18 | 19 | let failureCounter = 0; 20 | 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({extended: false})); 23 | 24 | app.use('/api/greeting', (request, response) => { 25 | const name = request.query.name ? request.query.name : 'World'; 26 | circuit.fire(`Hello, ${name}`) 27 | .then(result => { 28 | response.send({ 29 | content: result 30 | }); 31 | }) 32 | .catch(err => { 33 | response.send({ 34 | error: err.toString() 35 | }); 36 | }); 37 | }); 38 | 39 | probe(app); 40 | 41 | app.get('/metrics', (request, response) => { 42 | response.send(prometheus.metrics); 43 | }); 44 | 45 | app.use('/ready', (request, response) => { 46 | return response.sendStatus(200); 47 | }); 48 | 49 | app.use('/live', (request, response) => { 50 | return response.sendStatus(200); 51 | }); 52 | 53 | app.listen(port, () => console.log(`Hello world app listening on port ${port}!`)); 54 | 55 | function somethingThatCouldFail(echo) { 56 | if (Date.now() % 5 === 0) { 57 | console.log(++failureCounter); 58 | return Promise.reject(new Error(`Random failure ${failureCounter}`)); 59 | } else { 60 | return Promise.resolve(echo); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | # React Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3000/flakeyService`. 4 | For every request the service receives, the response time is increased. 5 | [The service returns a `423 (Locked)` error if the response time is above 1000ms.](https://github.com/nodeshift-starters/opossum-examples/blob/main/react/server.js#L27) This example also has a web frontend at `http://localhost:8000/` for interacting with the service. 6 | 7 | 1. Install dependencies 8 | 9 | ```sh 10 | $ npm install && cd client/ && npm install && cd .. 11 | ``` 12 | 13 | 2. Run the example 14 | 15 | This example uses the [concurrently](https://www.npmjs.com/package/concurrently) npm module to run the server and the client at the same time. 16 | 17 | ```sh 18 | $ npm start 19 | ``` 20 | 21 |

Terminal

22 | 23 | 24 |

Browser

25 | -------------------------------------------------------------------------------- /react/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react/client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /react/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-react-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^14.4.3", 9 | "axios": "^0.27.2", 10 | "opossum": "^6.4.0", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "5.0.1" 14 | }, 15 | "scripts": { 16 | "start": "PORT=8000 react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /react/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/react/client/public/favicon.ico -------------------------------------------------------------------------------- /react/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react/client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/react/client/public/logo192.png -------------------------------------------------------------------------------- /react/client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/react/client/public/logo512.png -------------------------------------------------------------------------------- /react/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react/client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | color: #2c3e50; 6 | margin: 0 auto; 7 | width: 900px; 8 | } 9 | 10 | button { 11 | box-shadow: inset 0px 1px 3px 0px #91b8b3; 12 | background: linear-gradient(to bottom, #768d87 5%, #6c7c7c 100%); 13 | background-color: #768d87; 14 | border-radius: 5px; 15 | border: 1px solid #566963; 16 | display: inline-block; 17 | cursor: pointer; 18 | color: #ffffff; 19 | font-family: Arial; 20 | font-size: 15px; 21 | font-weight: bold; 22 | padding: 11px 23px; 23 | text-decoration: none; 24 | text-shadow: 0px -1px 0px #2b665e; 25 | outline: none; 26 | margin: 30px 10px 30px 0; 27 | } 28 | button:active { 29 | background: linear-gradient(to bottom, #6c7c7c 5%, #768d87 100%); 30 | background-color: #6c7c7c; 31 | } 32 | 33 | .clear { 34 | cursor: pointer; 35 | color: #3336ff; 36 | } 37 | .success { 38 | color: darkgreen; 39 | } 40 | .open { 41 | color: red; 42 | } 43 | .fallback { 44 | color: darkblue; 45 | } 46 | .rejected { 47 | color: darkorange; 48 | } 49 | .close { 50 | color: green; 51 | } 52 | .timeout { 53 | color: darksalmon; 54 | } -------------------------------------------------------------------------------- /react/client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import opossum from 'opossum'; 4 | 5 | import './App.css'; 6 | 7 | const App = () => { 8 | const [responseList, setResponseList] = useState([]); 9 | const [circuit, setCircuit] = useState(null); 10 | const [route] = useState('http://localhost:3000/flakeyService'); 11 | const [circuitBreakerOptions] = useState({ 12 | timeout: 500, 13 | errorThresholdPercentage: 50, 14 | resetTimeout: 5000, 15 | }); 16 | 17 | useEffect(() => { 18 | const tmpCircuit = new opossum( 19 | () => axios.get(route), 20 | circuitBreakerOptions 21 | ); 22 | setCircuit(tmpCircuit); 23 | }, [route, circuitBreakerOptions]); 24 | 25 | useEffect(() => { 26 | if (circuit === null) { 27 | return; 28 | } 29 | 30 | circuit.fallback(() => ({ 31 | body: `${route} unavailable right now. Try later.`, 32 | })); 33 | 34 | circuit.on('success', result => { 35 | const event = { 36 | state: 'SUCCESS', 37 | body: `${JSON.stringify(result.data)}`, 38 | }; 39 | setResponseList(list => [...list, event]); 40 | }); 41 | 42 | circuit.on('timeout', () => { 43 | const event = { 44 | state: 'TIMEOUT', 45 | body: `${route} is taking too long to respond.`, 46 | }; 47 | setResponseList(list => [...list, event]); 48 | }); 49 | 50 | circuit.on('reject', () => { 51 | const event = { 52 | state: 'REJECTED', 53 | body: `The breaker for ${route} is open. Failing fast.`, 54 | }; 55 | setResponseList(list => [...list, event]); 56 | }); 57 | 58 | circuit.on('open', () => { 59 | const event = { 60 | state: 'OPEN', 61 | body: `The breaker for ${route} just opened.`, 62 | }; 63 | setResponseList(list => [...list, event]); 64 | }); 65 | 66 | circuit.on('halfOpen', () => { 67 | const event = { 68 | state: 'HALF_OPEN', 69 | body: `The breaker for ${route} is half open.`, 70 | }; 71 | setResponseList(list => [...list, event]); 72 | }); 73 | 74 | circuit.on('close', () => { 75 | const event = { 76 | state: 'CLOSE', 77 | body: `The breaker for ${route} has closed. Service OK.`, 78 | }; 79 | setResponseList(list => [...list, event]); 80 | }); 81 | 82 | circuit.on('fallback', data => { 83 | const event = { 84 | state: 'FALLBACK', 85 | body: `${JSON.stringify(data)}`, 86 | }; 87 | setResponseList(list => [...list, event]); 88 | }); 89 | }, [circuit, route]); 90 | 91 | const clearResponseList = () => { 92 | setResponseList([]); 93 | }; 94 | 95 | const makeRequest = () => { 96 | circuit.fire().catch(e => console.error(e)); 97 | }; 98 | 99 | return ( 100 |
101 |

Opossum Circuit Breaker Example

102 |

103 | When you click the button here, this simple app calls a flakey web 104 | service that takes longer and longer to respond. The app circuit breaker 105 | is configured to timeout after 106 | 500ms and execute a fallback command. Every 20 seconds, 107 | the flakey service is reset and the pattern is repeated. 108 |

109 |

110 | If more than 3 errors are observed by the circuit within a single 111 | timeout period, then it begins to fail fast, rejecting the network call 112 | outright and executing the fallback function. 113 |

114 |

115 | This should allow you to see all of the various events that occur when 116 | using a circuit breaker. 117 |

118 |
119 | 120 | 121 |
122 |
123 | {responseList 124 | .slice() 125 | .reverse() 126 | .map((element, index) => ( 127 |

128 | {element.state} 129 | {element.body} 130 |

131 | ))} 132 |
133 |
134 | ); 135 | }; 136 | 137 | export default App; 138 | -------------------------------------------------------------------------------- /react/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); -------------------------------------------------------------------------------- /react/images/react-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/react/images/react-browser.png -------------------------------------------------------------------------------- /react/images/react-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/react/images/react-terminal.png -------------------------------------------------------------------------------- /react/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-react", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@hapi/boom": { 8 | "version": "10.0.0", 9 | "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.0.tgz", 10 | "integrity": "sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==", 11 | "requires": { 12 | "@hapi/hoek": "10.x.x" 13 | } 14 | }, 15 | "@hapi/hoek": { 16 | "version": "10.0.1", 17 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-10.0.1.tgz", 18 | "integrity": "sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw==" 19 | }, 20 | "accepts": { 21 | "version": "1.3.8", 22 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 23 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 24 | "requires": { 25 | "mime-types": "~2.1.34", 26 | "negotiator": "0.6.3" 27 | } 28 | }, 29 | "ansi-regex": { 30 | "version": "5.0.1", 31 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 32 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 33 | "dev": true 34 | }, 35 | "ansi-styles": { 36 | "version": "4.3.0", 37 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 38 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 39 | "dev": true, 40 | "requires": { 41 | "color-convert": "^2.0.1" 42 | } 43 | }, 44 | "array-flatten": { 45 | "version": "1.1.1", 46 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 47 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 48 | }, 49 | "body-parser": { 50 | "version": "1.20.0", 51 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 52 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 53 | "requires": { 54 | "bytes": "3.1.2", 55 | "content-type": "~1.0.4", 56 | "debug": "2.6.9", 57 | "depd": "2.0.0", 58 | "destroy": "1.2.0", 59 | "http-errors": "2.0.0", 60 | "iconv-lite": "0.4.24", 61 | "on-finished": "2.4.1", 62 | "qs": "6.10.3", 63 | "raw-body": "2.5.1", 64 | "type-is": "~1.6.18", 65 | "unpipe": "1.0.0" 66 | } 67 | }, 68 | "bytes": { 69 | "version": "3.1.2", 70 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 71 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 72 | }, 73 | "call-bind": { 74 | "version": "1.0.2", 75 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 76 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 77 | "requires": { 78 | "function-bind": "^1.1.1", 79 | "get-intrinsic": "^1.0.2" 80 | } 81 | }, 82 | "chalk": { 83 | "version": "4.1.2", 84 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 85 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 86 | "dev": true, 87 | "requires": { 88 | "ansi-styles": "^4.1.0", 89 | "supports-color": "^7.1.0" 90 | }, 91 | "dependencies": { 92 | "supports-color": { 93 | "version": "7.2.0", 94 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 95 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 96 | "dev": true, 97 | "requires": { 98 | "has-flag": "^4.0.0" 99 | } 100 | } 101 | } 102 | }, 103 | "cliui": { 104 | "version": "7.0.4", 105 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 106 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 107 | "dev": true, 108 | "requires": { 109 | "string-width": "^4.2.0", 110 | "strip-ansi": "^6.0.0", 111 | "wrap-ansi": "^7.0.0" 112 | } 113 | }, 114 | "color-convert": { 115 | "version": "2.0.1", 116 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 117 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 118 | "dev": true, 119 | "requires": { 120 | "color-name": "~1.1.4" 121 | } 122 | }, 123 | "color-name": { 124 | "version": "1.1.4", 125 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 126 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 127 | "dev": true 128 | }, 129 | "concurrently": { 130 | "version": "7.3.0", 131 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.3.0.tgz", 132 | "integrity": "sha512-IiDwm+8DOcFEInca494A8V402tNTQlJaYq78RF2rijOrKEk/AOHTxhN4U1cp7GYKYX5Q6Ymh1dLTBlzIMN0ikA==", 133 | "dev": true, 134 | "requires": { 135 | "chalk": "^4.1.0", 136 | "date-fns": "^2.16.1", 137 | "lodash": "^4.17.21", 138 | "rxjs": "^7.0.0", 139 | "shell-quote": "^1.7.3", 140 | "spawn-command": "^0.0.2-1", 141 | "supports-color": "^8.1.0", 142 | "tree-kill": "^1.2.2", 143 | "yargs": "^17.3.1" 144 | } 145 | }, 146 | "content-disposition": { 147 | "version": "0.5.4", 148 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 149 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 150 | "requires": { 151 | "safe-buffer": "5.2.1" 152 | } 153 | }, 154 | "content-type": { 155 | "version": "1.0.4", 156 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 157 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 158 | }, 159 | "cookie": { 160 | "version": "0.5.0", 161 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 162 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 163 | }, 164 | "cookie-signature": { 165 | "version": "1.0.6", 166 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 167 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 168 | }, 169 | "cors": { 170 | "version": "2.8.5", 171 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 172 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 173 | "requires": { 174 | "object-assign": "^4", 175 | "vary": "^1" 176 | } 177 | }, 178 | "date-fns": { 179 | "version": "2.29.1", 180 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.1.tgz", 181 | "integrity": "sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw==", 182 | "dev": true 183 | }, 184 | "debug": { 185 | "version": "2.6.9", 186 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 187 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 188 | "requires": { 189 | "ms": "2.0.0" 190 | } 191 | }, 192 | "depd": { 193 | "version": "2.0.0", 194 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 195 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 196 | }, 197 | "destroy": { 198 | "version": "1.2.0", 199 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 200 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 201 | }, 202 | "ee-first": { 203 | "version": "1.1.1", 204 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 205 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 206 | }, 207 | "emoji-regex": { 208 | "version": "8.0.0", 209 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 210 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 211 | "dev": true 212 | }, 213 | "encodeurl": { 214 | "version": "1.0.2", 215 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 216 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 217 | }, 218 | "escalade": { 219 | "version": "3.1.1", 220 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 221 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 222 | "dev": true 223 | }, 224 | "escape-html": { 225 | "version": "1.0.3", 226 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 227 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 228 | }, 229 | "etag": { 230 | "version": "1.8.1", 231 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 232 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 233 | }, 234 | "express": { 235 | "version": "4.18.1", 236 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", 237 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", 238 | "requires": { 239 | "accepts": "~1.3.8", 240 | "array-flatten": "1.1.1", 241 | "body-parser": "1.20.0", 242 | "content-disposition": "0.5.4", 243 | "content-type": "~1.0.4", 244 | "cookie": "0.5.0", 245 | "cookie-signature": "1.0.6", 246 | "debug": "2.6.9", 247 | "depd": "2.0.0", 248 | "encodeurl": "~1.0.2", 249 | "escape-html": "~1.0.3", 250 | "etag": "~1.8.1", 251 | "finalhandler": "1.2.0", 252 | "fresh": "0.5.2", 253 | "http-errors": "2.0.0", 254 | "merge-descriptors": "1.0.1", 255 | "methods": "~1.1.2", 256 | "on-finished": "2.4.1", 257 | "parseurl": "~1.3.3", 258 | "path-to-regexp": "0.1.7", 259 | "proxy-addr": "~2.0.7", 260 | "qs": "6.10.3", 261 | "range-parser": "~1.2.1", 262 | "safe-buffer": "5.2.1", 263 | "send": "0.18.0", 264 | "serve-static": "1.15.0", 265 | "setprototypeof": "1.2.0", 266 | "statuses": "2.0.1", 267 | "type-is": "~1.6.18", 268 | "utils-merge": "1.0.1", 269 | "vary": "~1.1.2" 270 | } 271 | }, 272 | "finalhandler": { 273 | "version": "1.2.0", 274 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 275 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 276 | "requires": { 277 | "debug": "2.6.9", 278 | "encodeurl": "~1.0.2", 279 | "escape-html": "~1.0.3", 280 | "on-finished": "2.4.1", 281 | "parseurl": "~1.3.3", 282 | "statuses": "2.0.1", 283 | "unpipe": "~1.0.0" 284 | } 285 | }, 286 | "forwarded": { 287 | "version": "0.2.0", 288 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 289 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 290 | }, 291 | "fresh": { 292 | "version": "0.5.2", 293 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 294 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 295 | }, 296 | "function-bind": { 297 | "version": "1.1.1", 298 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 299 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 300 | }, 301 | "get-caller-file": { 302 | "version": "2.0.5", 303 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 304 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 305 | "dev": true 306 | }, 307 | "get-intrinsic": { 308 | "version": "1.1.2", 309 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", 310 | "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", 311 | "requires": { 312 | "function-bind": "^1.1.1", 313 | "has": "^1.0.3", 314 | "has-symbols": "^1.0.3" 315 | } 316 | }, 317 | "has": { 318 | "version": "1.0.3", 319 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 320 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 321 | "requires": { 322 | "function-bind": "^1.1.1" 323 | } 324 | }, 325 | "has-flag": { 326 | "version": "4.0.0", 327 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 328 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 329 | "dev": true 330 | }, 331 | "has-symbols": { 332 | "version": "1.0.3", 333 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 334 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 335 | }, 336 | "http-errors": { 337 | "version": "2.0.0", 338 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 339 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 340 | "requires": { 341 | "depd": "2.0.0", 342 | "inherits": "2.0.4", 343 | "setprototypeof": "1.2.0", 344 | "statuses": "2.0.1", 345 | "toidentifier": "1.0.1" 346 | } 347 | }, 348 | "iconv-lite": { 349 | "version": "0.4.24", 350 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 351 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 352 | "requires": { 353 | "safer-buffer": ">= 2.1.2 < 3" 354 | } 355 | }, 356 | "inherits": { 357 | "version": "2.0.4", 358 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 359 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 360 | }, 361 | "ipaddr.js": { 362 | "version": "1.9.1", 363 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 364 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 365 | }, 366 | "is-fullwidth-code-point": { 367 | "version": "3.0.0", 368 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 369 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 370 | "dev": true 371 | }, 372 | "lodash": { 373 | "version": "4.17.21", 374 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 375 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 376 | "dev": true 377 | }, 378 | "media-typer": { 379 | "version": "0.3.0", 380 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 381 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 382 | }, 383 | "merge-descriptors": { 384 | "version": "1.0.1", 385 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 386 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 387 | }, 388 | "methods": { 389 | "version": "1.1.2", 390 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 391 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 392 | }, 393 | "mime": { 394 | "version": "1.6.0", 395 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 396 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 397 | }, 398 | "mime-db": { 399 | "version": "1.52.0", 400 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 401 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 402 | }, 403 | "mime-types": { 404 | "version": "2.1.35", 405 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 406 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 407 | "requires": { 408 | "mime-db": "1.52.0" 409 | } 410 | }, 411 | "ms": { 412 | "version": "2.0.0", 413 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 414 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 415 | }, 416 | "negotiator": { 417 | "version": "0.6.3", 418 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 419 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 420 | }, 421 | "object-assign": { 422 | "version": "4.1.1", 423 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 424 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 425 | }, 426 | "object-inspect": { 427 | "version": "1.12.2", 428 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 429 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" 430 | }, 431 | "on-finished": { 432 | "version": "2.4.1", 433 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 434 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 435 | "requires": { 436 | "ee-first": "1.1.1" 437 | } 438 | }, 439 | "parseurl": { 440 | "version": "1.3.3", 441 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 442 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 443 | }, 444 | "path-to-regexp": { 445 | "version": "0.1.7", 446 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 447 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 448 | }, 449 | "proxy-addr": { 450 | "version": "2.0.7", 451 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 452 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 453 | "requires": { 454 | "forwarded": "0.2.0", 455 | "ipaddr.js": "1.9.1" 456 | } 457 | }, 458 | "qs": { 459 | "version": "6.10.3", 460 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 461 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 462 | "requires": { 463 | "side-channel": "^1.0.4" 464 | } 465 | }, 466 | "range-parser": { 467 | "version": "1.2.1", 468 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 469 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 470 | }, 471 | "raw-body": { 472 | "version": "2.5.1", 473 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 474 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 475 | "requires": { 476 | "bytes": "3.1.2", 477 | "http-errors": "2.0.0", 478 | "iconv-lite": "0.4.24", 479 | "unpipe": "1.0.0" 480 | } 481 | }, 482 | "require-directory": { 483 | "version": "2.1.1", 484 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 485 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 486 | "dev": true 487 | }, 488 | "rxjs": { 489 | "version": "7.5.6", 490 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", 491 | "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", 492 | "dev": true, 493 | "requires": { 494 | "tslib": "^2.1.0" 495 | } 496 | }, 497 | "safe-buffer": { 498 | "version": "5.2.1", 499 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 500 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 501 | }, 502 | "safer-buffer": { 503 | "version": "2.1.2", 504 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 505 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 506 | }, 507 | "send": { 508 | "version": "0.18.0", 509 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 510 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 511 | "requires": { 512 | "debug": "2.6.9", 513 | "depd": "2.0.0", 514 | "destroy": "1.2.0", 515 | "encodeurl": "~1.0.2", 516 | "escape-html": "~1.0.3", 517 | "etag": "~1.8.1", 518 | "fresh": "0.5.2", 519 | "http-errors": "2.0.0", 520 | "mime": "1.6.0", 521 | "ms": "2.1.3", 522 | "on-finished": "2.4.1", 523 | "range-parser": "~1.2.1", 524 | "statuses": "2.0.1" 525 | }, 526 | "dependencies": { 527 | "ms": { 528 | "version": "2.1.3", 529 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 530 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 531 | } 532 | } 533 | }, 534 | "serve-static": { 535 | "version": "1.15.0", 536 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 537 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 538 | "requires": { 539 | "encodeurl": "~1.0.2", 540 | "escape-html": "~1.0.3", 541 | "parseurl": "~1.3.3", 542 | "send": "0.18.0" 543 | } 544 | }, 545 | "setprototypeof": { 546 | "version": "1.2.0", 547 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 548 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 549 | }, 550 | "shell-quote": { 551 | "version": "1.7.3", 552 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", 553 | "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", 554 | "dev": true 555 | }, 556 | "side-channel": { 557 | "version": "1.0.4", 558 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 559 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 560 | "requires": { 561 | "call-bind": "^1.0.0", 562 | "get-intrinsic": "^1.0.2", 563 | "object-inspect": "^1.9.0" 564 | } 565 | }, 566 | "spawn-command": { 567 | "version": "0.0.2-1", 568 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", 569 | "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", 570 | "dev": true 571 | }, 572 | "statuses": { 573 | "version": "2.0.1", 574 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 575 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 576 | }, 577 | "string-width": { 578 | "version": "4.2.3", 579 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 580 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 581 | "dev": true, 582 | "requires": { 583 | "emoji-regex": "^8.0.0", 584 | "is-fullwidth-code-point": "^3.0.0", 585 | "strip-ansi": "^6.0.1" 586 | } 587 | }, 588 | "strip-ansi": { 589 | "version": "6.0.1", 590 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 591 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 592 | "dev": true, 593 | "requires": { 594 | "ansi-regex": "^5.0.1" 595 | } 596 | }, 597 | "supports-color": { 598 | "version": "8.1.1", 599 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 600 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 601 | "dev": true, 602 | "requires": { 603 | "has-flag": "^4.0.0" 604 | } 605 | }, 606 | "toidentifier": { 607 | "version": "1.0.1", 608 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 609 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 610 | }, 611 | "tree-kill": { 612 | "version": "1.2.2", 613 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 614 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 615 | "dev": true 616 | }, 617 | "tslib": { 618 | "version": "2.4.0", 619 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 620 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", 621 | "dev": true 622 | }, 623 | "type-is": { 624 | "version": "1.6.18", 625 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 626 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 627 | "requires": { 628 | "media-typer": "0.3.0", 629 | "mime-types": "~2.1.24" 630 | } 631 | }, 632 | "unpipe": { 633 | "version": "1.0.0", 634 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 635 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 636 | }, 637 | "utils-merge": { 638 | "version": "1.0.1", 639 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 640 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 641 | }, 642 | "vary": { 643 | "version": "1.1.2", 644 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 645 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 646 | }, 647 | "wrap-ansi": { 648 | "version": "7.0.0", 649 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 650 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 651 | "dev": true, 652 | "requires": { 653 | "ansi-styles": "^4.0.0", 654 | "string-width": "^4.1.0", 655 | "strip-ansi": "^6.0.0" 656 | } 657 | }, 658 | "y18n": { 659 | "version": "5.0.8", 660 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 661 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 662 | "dev": true 663 | }, 664 | "yargs": { 665 | "version": "17.5.1", 666 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", 667 | "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", 668 | "dev": true, 669 | "requires": { 670 | "cliui": "^7.0.2", 671 | "escalade": "^3.1.1", 672 | "get-caller-file": "^2.0.5", 673 | "require-directory": "^2.1.1", 674 | "string-width": "^4.2.3", 675 | "y18n": "^5.0.5", 676 | "yargs-parser": "^21.0.0" 677 | } 678 | }, 679 | "yargs-parser": { 680 | "version": "21.1.1", 681 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 682 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 683 | "dev": true 684 | } 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "server": "node server.js", 8 | "react": "cd ./client && npm start", 9 | "start": "concurrently -n server,react -c bgRed,bgBlue \"npm:server\" \"npm:react\"" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@hapi/boom": "^10.0.0", 16 | "cors": "^2.8.5", 17 | "express": "^4.18.1" 18 | }, 19 | "devDependencies": { 20 | "concurrently": "^7.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /react/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require('cors'); 3 | const boom = require("@hapi/boom"); 4 | 5 | const app = express(); 6 | 7 | const delayInitValue = 20; 8 | let delay = delayInitValue; 9 | 10 | // reset delay every 20 seconds 11 | setInterval(() => { 12 | if (delay !== delayInitValue) { 13 | delay = delayInitValue; 14 | console.log("Resetting flakey service delay to", delay); 15 | } 16 | }, 20000); 17 | 18 | app.use(cors()); 19 | 20 | app.get("/flakeyService", (req, res) => { 21 | console.log("Flakey service delay", delay); 22 | // if we're really slowing down, just reply with an error 23 | if (delay > 1000) { 24 | console.log("Long delay encountered, returning Error 423 (Locked)"); 25 | const { 26 | output: { statusCode, payload }, 27 | } = boom.locked("Flakey service is flakey"); 28 | res.status(statusCode).send(payload); 29 | return; 30 | } 31 | 32 | setTimeout(() => { 33 | console.log("Replying with flakey response after delay of", delay); 34 | delay = delay * 2; 35 | res.send({ 36 | body: "Flakey service response", 37 | delay, 38 | }); 39 | }, delay); 40 | }); 41 | 42 | const PORT = 3000; 43 | 44 | app.listen(PORT, () => { 45 | console.log(`Server listening on port ${PORT}`); 46 | }); 47 | -------------------------------------------------------------------------------- /seneca/README.md: -------------------------------------------------------------------------------- 1 | # Seneca.js Example 2 | 3 | This example exposes a simple HTTP service at the route `http://localhost:3000/flakeyService`. The HTTP service calls an in memory seneca micro-service to obtain the response. The Seneca service is configured to progressively get slower with each request. Once a request takes more than 1 second to respond, the service just returns a `423 (Locked)` error. 4 | 5 | Start the server. 6 | 7 | ```sh 8 | $ npm start 9 | ``` 10 | 11 | Browse to `http://localhost:3000` and click the button to see the service in action. 12 | -------------------------------------------------------------------------------- /seneca/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global $ circuitBreaker */ 3 | 4 | (function appInitialization () { 5 | $(() => { 6 | $('#flakey').click(_ => circuit.fire().catch(e => console.error(e))); 7 | $('.clear').click(_ => $('.clear').siblings('p').remove()); 8 | }); 9 | 10 | const route = '/flakeyService'; 11 | const element = '#flakeyResponse'; 12 | 13 | const circuitBreakerOptions = { 14 | timeout: 500, 15 | errorThresholdPercentage: 3, 16 | resetTimeout: 5000 17 | }; 18 | 19 | const circuit = new circuitBreaker(_ => $.get(route), circuitBreakerOptions); 20 | 21 | circuit.fallback(_ => 22 | ({ body: `${route} unavailable right now. Try later.` })); 23 | 24 | circuit.status.on('snapshot', stats => { 25 | const response = document.createElement('p'); 26 | $(response).addClass('stats'); 27 | Object.keys(stats).forEach(key => { 28 | const p = document.createElement('p'); 29 | p.append(`${key}: ${stats[key]}`); 30 | $(response).append(p); 31 | }); 32 | 33 | $('#stats').children().replaceWith($(response)); 34 | }); 35 | 36 | circuit.on('success', 37 | result => $(element).append( 38 | makeNode(`SUCCESS: ${JSON.stringify(result)}`))); 39 | 40 | circuit.on('timeout', 41 | () => $(element).append( 42 | makeNode(`TIMEOUT: ${route} is taking too long to respond.`))); 43 | 44 | circuit.on('reject', 45 | () => $(element).append( 46 | makeNode(`REJECTED: The breaker for ${route} is open. Failing fast.`))); 47 | 48 | circuit.on('open', 49 | () => $(element).append( 50 | makeNode(`OPEN: The breaker for ${route} just opened.`))); 51 | 52 | circuit.on('halfOpen', 53 | () => $(element).append( 54 | makeNode(`HALF_OPEN: The breaker for ${route} is half open.`))); 55 | 56 | circuit.on('close', 57 | () => $(element).append( 58 | makeNode(`CLOSE: The breaker for ${route} has closed. Service OK.`))); 59 | 60 | circuit.on('fallback', 61 | data => $(element).append( 62 | makeNode(`FALLBACK: ${JSON.stringify(data)}`))); 63 | 64 | function makeNode (body) { 65 | const response = document.createElement('p'); 66 | $(response).addClass(body.substring(0, body.indexOf(':')).toLowerCase()); 67 | response.append(body); 68 | return response; 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /seneca/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Opossum Circuit Breaker Example 4 | 5 | 6 | 7 | 51 | 52 | 53 |
54 |

Opossum Circuit Breaker Example

55 | 56 |

57 | When you click the button here, this simple app calls a flakey web service that takes longer and longer to respond. The app's circuit breaker is configured to timeout after 500ms and execute a fallback command. Every 20 seconds, the flakey service is reset and the pattern is repeated. 58 |

59 |

60 | If more than half of the requests error, then it begins to fail fast, rejecting the network call outright and executing the fallback function. 61 |

62 |

63 | The source code. 64 |

65 |
66 | 69 |
70 |
71 |

Circuit Breaker Statistics

72 |

...

73 |
74 |
75 |

Flakey Responses

76 | Click to clear 77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /seneca/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Gateway for our seneca microservices 4 | const Hapi = require('@hapi/hapi'); 5 | const path = require('path'); 6 | const seneca = require('seneca')(); 7 | const opossum = require('opossum'); 8 | 9 | const server = Hapi.Server({ 10 | host: 'localhost', 11 | port: 3000 12 | }); 13 | const circuit = new opossum(serviceFactory({ name: 'flakeyService' })); 14 | 15 | function serviceFactory (service) { 16 | return () => 17 | new Promise((resolve, reject) => { 18 | seneca.act(service, (error, result) => { 19 | error ? reject(error) : resolve(result); 20 | }); 21 | }); 22 | } 23 | 24 | // in process seneca 25 | seneca.use('service', { delay: 20 }); 26 | 27 | async function start () { 28 | // static file serving 29 | await server.register({ plugin: require('@hapi/inert') }); 30 | 31 | [ ['/', path.join(__dirname, 'index.html')], 32 | ['/app.js', path.join(__dirname, 'app.js')], 33 | ['/jquery.js', 34 | path.join(__dirname, 'node_modules', 'jquery', 'dist', 'jquery.js')], 35 | ['/opossum.js', path.join(__dirname, 'node_modules', 'opossum', 'dist', 'opossum.js')] 36 | ].map(entry => { 37 | server.route({ 38 | method: 'GET', 39 | path: entry[0], 40 | handler: (request, h) => h.file(entry[1]) 41 | }); 42 | }); 43 | 44 | server.route({ 45 | method: 'GET', 46 | path: '/flakeyService', 47 | handler: (request, h) => circuit.fire() 48 | }); 49 | 50 | await server.start(err => { 51 | possibleError(err); 52 | console.log(`Server: ${server.info.uri}`); 53 | console.log('Endpoints:'); 54 | server.table().map(entry => { 55 | entry.table.map(route => { 56 | console.log(`${route.method} ${route.path}`); 57 | }); 58 | }); 59 | }); 60 | 61 | process.on('uncaughtException', e => { 62 | process._rawDebug(`Uncaught exception ${e}`); 63 | }); 64 | 65 | function possibleError (err) { 66 | if (err) throw err; 67 | } 68 | } 69 | 70 | start(); 71 | -------------------------------------------------------------------------------- /seneca/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-browser-example", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "lint": "eslint test/*.js index.js app.js", 8 | "start": "node ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/nodeshift/opossum.git" 13 | }, 14 | "devDependencies": { 15 | "eslint": "8.22.0", 16 | "eslint-config-semistandard": "17.0.0", 17 | "eslint-config-standard": "17.0.0", 18 | "eslint-plugin-promise": "6.0.0", 19 | "eslint-plugin-standard": "5.0.0" 20 | }, 21 | "description": "Simple example with opossum and browser.", 22 | "dependencies": { 23 | "@hapi/boom": "10.0.0", 24 | "bootstrap": "5.2.0", 25 | "@hapi/hapi": "20.2.2", 26 | "@hapi/inert": "7.0.0", 27 | "jquery": "3.6.0", 28 | "lodash": "4.17.21", 29 | "opossum": "6.4.0", 30 | "seneca": "3.28.2" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/nodeshift/opossum/issues" 34 | }, 35 | "homepage": "https://github.com/nodeshift/opossum#readme", 36 | "author": "" 37 | } 38 | -------------------------------------------------------------------------------- /seneca/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Boom = require('@hapi/boom'); 4 | 5 | module.exports = exports = function flakeyService (options) { 6 | console.log('Flakey seneca service delay', options.delay); 7 | const baseline = options.delay; 8 | let delay = baseline; 9 | 10 | // reset the delay every 20 seconds 11 | setInterval(() => { 12 | if (delay !== baseline) { 13 | delay = baseline; 14 | console.log('Resetting flakey seneca service delay to', delay); 15 | } 16 | }, 20000); 17 | 18 | this.add('name:flakeyService', (msg, respond) => { 19 | // if we're really slowing down, just reply with an error 20 | if (delay > 1000) { 21 | console.log('Long delay encountered, returning Error 423 (Locked)'); 22 | return respond(Boom.locked('Flakey seneca service is flakey')); 23 | } 24 | const response = { 25 | body: 'Flakey seneca service response', 26 | delay 27 | }; 28 | // simplistic way to block 29 | const now = Date.now(); 30 | const stop = now + delay; 31 | while (Date.now() < stop) { 32 | // sit-n-spin 33 | } 34 | console.log('Replying with flakey response after delay of', delay); 35 | delay = delay * 2; 36 | respond(null, response); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /svelte/README.md: -------------------------------------------------------------------------------- 1 | # Svelte Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3000/flakeyService`. 4 | For every request the service receives, the response time is increased. 5 | [The service returns a `423 (Locked)` error if the response time is above 1000ms.](https://github.com/nodeshift-starters/opossum-examples/blob/main/vue/server.js#L27) This example also has a web frontend at `http://localhost:5000/` for interacting with the service. 6 | 7 | 1. Install dependencies 8 | 9 | ```sh 10 | $ npm install && cd client/ && npm install && cd .. 11 | ``` 12 | 13 | 2. Run the example 14 | 15 | This example uses the [concurrently](https://www.npmjs.com/package/concurrently) npm module to run the server and the client at the same time. 16 | 17 | ```sh 18 | $ npm start 19 | ``` 20 | 21 |

Terminal

22 | 23 | 24 |

Browser

25 | 26 | -------------------------------------------------------------------------------- /svelte/client/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /svelte/client/README.md: -------------------------------------------------------------------------------- 1 | *Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)* 2 | 3 | --- 4 | 5 | # svelte app 6 | 7 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 8 | 9 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 10 | 11 | ```bash 12 | npx degit sveltejs/template svelte-app 13 | cd svelte-app 14 | ``` 15 | 16 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 17 | 18 | 19 | ## Get started 20 | 21 | Install the dependencies... 22 | 23 | ```bash 24 | cd svelte-app 25 | npm install 26 | ``` 27 | 28 | ...then start [Rollup](https://rollupjs.org): 29 | 30 | ```bash 31 | npm run dev 32 | ``` 33 | 34 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 35 | 36 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`. 37 | 38 | If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense. 39 | 40 | ## Building and running in production mode 41 | 42 | To create an optimised version of the app: 43 | 44 | ```bash 45 | npm run build 46 | ``` 47 | 48 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com). 49 | 50 | 51 | ## Single-page app mode 52 | 53 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere. 54 | 55 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json: 56 | 57 | ```js 58 | "start": "sirv public --single" 59 | ``` 60 | 61 | ## Using TypeScript 62 | 63 | This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with: 64 | 65 | ```bash 66 | node scripts/setupTypeScript.js 67 | ``` 68 | 69 | Or remove the script via: 70 | 71 | ```bash 72 | rm scripts/setupTypeScript.js 73 | ``` 74 | 75 | ## Deploying to the web 76 | 77 | ### With [Vercel](https://vercel.com) 78 | 79 | Install `vercel` if you haven't already: 80 | 81 | ```bash 82 | npm install -g vercel 83 | ``` 84 | 85 | Then, from within your project folder: 86 | 87 | ```bash 88 | cd public 89 | vercel deploy --name my-project 90 | ``` 91 | 92 | ### With [surge](https://surge.sh/) 93 | 94 | Install `surge` if you haven't already: 95 | 96 | ```bash 97 | npm install -g surge 98 | ``` 99 | 100 | Then, from within your project folder: 101 | 102 | ```bash 103 | npm run build 104 | surge public my-project.surge.sh 105 | ``` 106 | -------------------------------------------------------------------------------- /svelte/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rollup -c", 6 | "dev": "rollup -c -w", 7 | "start": "sirv public" 8 | }, 9 | "devDependencies": { 10 | "@rollup/plugin-commonjs": "^14.0.0", 11 | "@rollup/plugin-node-resolve": "^8.0.0", 12 | "rollup": "^2.59.0", 13 | "rollup-plugin-livereload": "^2.0.5", 14 | "rollup-plugin-node-polyfills": "^0.2.1", 15 | "rollup-plugin-svelte": "^7.1.0", 16 | "rollup-plugin-terser": "^7.0.0", 17 | "svelte": "^3.42.4" 18 | }, 19 | "dependencies": { 20 | "axios": "^0.21.3", 21 | "opossum": "^6.2.0", 22 | "sirv-cli": "^1.0.14" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /svelte/client/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/svelte/client/public/favicon.png -------------------------------------------------------------------------------- /svelte/client/public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | -webkit-padding: 0.4em 0; 36 | padding: 0.4em; 37 | margin: 0 0 0.5em 0; 38 | box-sizing: border-box; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | input:disabled { 44 | color: #ccc; 45 | } 46 | 47 | button { 48 | color: #333; 49 | background-color: #f4f4f4; 50 | outline: none; 51 | } 52 | 53 | button:disabled { 54 | color: #999; 55 | } 56 | 57 | button:not(:disabled):active { 58 | background-color: #ddd; 59 | } 60 | 61 | button:focus { 62 | border-color: #666; 63 | } 64 | -------------------------------------------------------------------------------- /svelte/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /svelte/client/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import nodePolyfills from 'rollup-plugin-node-polyfills'; 7 | 8 | const production = !process.env.ROLLUP_WATCH; 9 | 10 | function serve() { 11 | let server; 12 | 13 | function toExit() { 14 | if (server) server.kill(0); 15 | } 16 | 17 | return { 18 | writeBundle() { 19 | if (server) return; 20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 21 | stdio: ['ignore', 'inherit', 'inherit'], 22 | shell: true 23 | }); 24 | 25 | process.on('SIGTERM', toExit); 26 | process.on('exit', toExit); 27 | } 28 | }; 29 | } 30 | 31 | export default { 32 | input: 'src/main.js', 33 | output: { 34 | sourcemap: true, 35 | format: 'iife', 36 | name: 'app', 37 | file: 'public/build/bundle.js' 38 | }, 39 | plugins: [ 40 | svelte({ 41 | // enable run-time checks when not in production 42 | dev: !production, 43 | // we'll extract any component CSS out into 44 | // a separate file - better for performance 45 | css: css => { 46 | css.write('bundle.css'); 47 | } 48 | }), 49 | 50 | // If you have external dependencies installed from 51 | // npm, you'll most likely need these plugins. In 52 | // some cases you'll need additional configuration - 53 | // consult the documentation for details: 54 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 55 | resolve({ 56 | browser: true, 57 | dedupe: ['svelte'] 58 | }), 59 | commonjs(), 60 | nodePolyfills(), 61 | 62 | // In dev mode, call `npm run start` once 63 | // the bundle has been generated 64 | !production && serve(), 65 | 66 | // Watch the `public` directory and refresh the 67 | // browser on changes when not in production 68 | !production && livereload('public'), 69 | 70 | // If we're building for production (npm run build 71 | // instead of npm run dev), minify 72 | production && terser() 73 | ], 74 | watch: { 75 | clearScreen: false 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /svelte/client/scripts/setupTypeScript.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** This script modifies the project to support TS code in .svelte files like: 4 | 5 | 8 | 9 | As well as validating the code for CI. 10 | */ 11 | 12 | /** To work on this script: 13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template 14 | */ 15 | 16 | const fs = require("fs") 17 | const path = require("path") 18 | const { argv } = require("process") 19 | 20 | const projectRoot = argv[2] || path.join(__dirname, "..") 21 | 22 | // Add deps to pkg.json 23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8")) 24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, { 25 | "svelte-check": "^1.0.0", 26 | "svelte-preprocess": "^4.0.0", 27 | "@rollup/plugin-typescript": "^6.0.0", 28 | "typescript": "^3.9.3", 29 | "tslib": "^2.0.0", 30 | "@tsconfig/svelte": "^1.0.0" 31 | }) 32 | 33 | // Add script for checking 34 | packageJSON.scripts = Object.assign(packageJSON.scripts, { 35 | "validate": "svelte-check" 36 | }) 37 | 38 | // Write the package JSON 39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " ")) 40 | 41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too 42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js") 43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts") 44 | fs.renameSync(beforeMainJSPath, afterMainTSPath) 45 | 46 | // Switch the app.svelte file to use TS 47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte") 48 | let appFile = fs.readFileSync(appSveltePath, "utf8") 49 | appFile = appFile.replace(" 87 | 88 | 142 | 143 |
144 |

Opossum Circuit Breaker Example

145 |

146 | When you click the button here, this simple app calls a flakey web service 147 | that takes longer and longer to respond. The app circuit breaker is 148 | configured to timeout after 149 | 500ms 150 | and execute a fallback command. Every 151 | 20 seconds, the flakey service is reset and the pattern is repeated. 152 |

153 |

154 | If more than 3 errors are observed by the circuit within a single timeout 155 | period, then it begins to fail fast, rejecting the network call outright and 156 | executing the fallback function. 157 |

158 |

159 | This should allow you to see all of the various events that occur when using 160 | a circuit breaker. 161 |

162 |
163 | 164 | 165 |
166 |
167 | {#each responseList.slice().reverse() as element} 168 |

169 | {element.state} 170 | {element.body} 171 |

172 | {/each} 173 |
174 |
175 | -------------------------------------------------------------------------------- /svelte/client/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.body, 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /svelte/images/svelte-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/svelte/images/svelte-browser.png -------------------------------------------------------------------------------- /svelte/images/svelte-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/svelte/images/svelte-terminal.png -------------------------------------------------------------------------------- /svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-svelte", 3 | "version": "1.0.0", 4 | "description": "Svelte example application using the opossum Node.js circuit breaker", 5 | "main": "index.js", 6 | "scripts": { 7 | "server": "node server.js", 8 | "svelte": "cd ./client && npm run dev", 9 | "start": "concurrently -n server,svelte -c bgBlue,bgRed \"npm:server\" \"npm:svelte\"" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@hapi/boom": "^9.1.4", 16 | "cors": "^2.8.5", 17 | "express": "^4.17.1" 18 | }, 19 | "devDependencies": { 20 | "concurrently": "^6.2.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /svelte/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const boom = require("@hapi/boom"); 4 | 5 | const app = express(); 6 | 7 | const delayInitValue = 20; 8 | let delay = delayInitValue; 9 | 10 | // reset delay every 20 seconds 11 | setInterval(() => { 12 | if (delay !== delayInitValue) { 13 | delay = delayInitValue; 14 | console.log("Resetting flakey service delay to", delay); 15 | } 16 | }, 20000); 17 | 18 | app.use(cors()); 19 | 20 | app.get("/flakeyService", (req, res) => { 21 | console.log("Flakey service delay", delay); 22 | // if we're really slowing down, just reply with an error 23 | if (delay > 1000) { 24 | console.log("Long delay encountered, returning Error 423 (Locked)"); 25 | const { 26 | output: { statusCode, payload }, 27 | } = boom.locked("Flakey service is flakey"); 28 | res.status(statusCode).send(payload); 29 | return; 30 | } 31 | 32 | setTimeout(() => { 33 | console.log("Replying with flakey response after delay of", delay); 34 | delay = delay * 2; 35 | res.send({ 36 | body: "Flakey service response", 37 | delay, 38 | }); 39 | }, delay); 40 | }); 41 | 42 | const PORT = 3000; 43 | 44 | app.listen(PORT, () => { 45 | console.log(`Server listening on port ${PORT}`); 46 | }); 47 | -------------------------------------------------------------------------------- /vue/README.md: -------------------------------------------------------------------------------- 1 | # Vue.js Example 2 | 3 | This example exposes a simple service at the route `http://localhost:3000/flakeyService`. 4 | For every request the service receives, the response time is increased. 5 | [The service returns a `423 (Locked)` error if the response time is above 1000ms.](https://github.com/nodeshift-starters/opossum-examples/blob/main/vue/server.js#L27) This example also has a web frontend at `http://localhost:8080/` for interacting with the service. 6 | 7 | 1. Install Vue CLI 8 | 9 | ```sh 10 | $ npm install -g @vue/cli @vue/cli-service-global 11 | ``` 12 | 13 | 2. Install dependencies 14 | 15 | ```sh 16 | $ npm install && cd client/ && npm install && cd .. 17 | ``` 18 | 19 | 3. Run the example 20 | 21 | This example uses the [concurrently](https://www.npmjs.com/package/concurrently) npm module to run the server and the client at the same time. 22 | 23 | ```sh 24 | $ npm start 25 | ``` 26 | 27 |

Terminal

28 | 29 | 30 |

Browser

31 | -------------------------------------------------------------------------------- /vue/client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /vue/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /vue/client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "arrowParens": "avoid", 4 | "trailingComma": "none" 5 | } -------------------------------------------------------------------------------- /vue/client/README.md: -------------------------------------------------------------------------------- 1 | # vuejs 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /vue/client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /vue/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-vue-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.27.2", 11 | "core-js": "^3.24.1", 12 | "opossum": "^6.4.0", 13 | "vue": "^3.2.37" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "~5.0.8", 17 | "@vue/cli-service": "~5.0.8", 18 | "@vue/eslint-config-prettier": "^7.0.0", 19 | "prettier": "^2.7.1", 20 | "vue-template-compiler": "^2.7.8" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vue/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/vue/client/public/favicon.ico -------------------------------------------------------------------------------- /vue/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue/client/src/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 132 | 133 | 189 | -------------------------------------------------------------------------------- /vue/client/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | // Vue.config.productionTip = false; 5 | 6 | createApp(App).mount('#app'); 7 | -------------------------------------------------------------------------------- /vue/images/vue-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/vue/images/vue-browser.png -------------------------------------------------------------------------------- /vue/images/vue-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodeshift-starters/opossum-examples/821451be248a78dbd58a21f2f2e661ab332bf7ed/vue/images/vue-terminal.png -------------------------------------------------------------------------------- /vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opossum-vue", 3 | "version": "1.0.0", 4 | "description": "Vue.js example application using the opossum Node.js circuit breaker", 5 | "main": "index.js", 6 | "scripts": { 7 | "server": "node server.js", 8 | "vue": "cd ./client && npm run serve", 9 | "start": "concurrently -n server,vue -c bgBlue,bgGreen \"npm:server\" \"npm:vue\"" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@hapi/boom": "^10.0.0", 16 | "cors": "^2.8.5", 17 | "express": "^4.18.1" 18 | }, 19 | "devDependencies": { 20 | "concurrently": "^7.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vue/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const boom = require("@hapi/boom"); 4 | 5 | const app = express(); 6 | 7 | const delayInitValue = 20; 8 | let delay = delayInitValue; 9 | 10 | // reset delay every 20 seconds 11 | setInterval(() => { 12 | if (delay !== delayInitValue) { 13 | delay = delayInitValue; 14 | console.log("Resetting flakey service delay to", delay); 15 | } 16 | }, 20000); 17 | 18 | app.use(cors()); 19 | 20 | app.get("/flakeyService", (req, res) => { 21 | console.log("Flakey service delay", delay); 22 | // if we're really slowing down, just reply with an error 23 | if (delay > 1000) { 24 | console.log("Long delay encountered, returning Error 423 (Locked)"); 25 | const { 26 | output: { statusCode, payload }, 27 | } = boom.locked("Flakey service is flakey"); 28 | res.status(statusCode).send(payload); 29 | return; 30 | } 31 | 32 | setTimeout(() => { 33 | console.log("Replying with flakey response after delay of", delay); 34 | delay = delay * 2; 35 | res.send({ 36 | body: "Flakey service response", 37 | delay, 38 | }); 39 | }, delay); 40 | }); 41 | 42 | const PORT = 3000; 43 | 44 | app.listen(PORT, () => { 45 | console.log(`Server listening on port ${PORT}`); 46 | }); --------------------------------------------------------------------------------