Showing preview only (358K chars total). Download the full file or copy to clipboard to get everything.
Repository: brainhubeu/hadron
Branch: master
Commit: 28cfe5143fad
Files: 315
Total size: 283.2 KB
Directory structure:
gitextract_tdgrkyma/
├── .circleci/
│ └── config.yml
├── .dockerignore
├── .gitignore
├── .gitlab-ci.yml
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .rebuild.ts
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── entrypoint.sh
├── features/
│ ├── step_definitions/
│ │ ├── steps.ts
│ │ └── theBestSteps.ts
│ ├── support/
│ │ ├── hooks/
│ │ │ └── mock.ts
│ │ ├── scripts/
│ │ │ ├── Client.ts
│ │ │ └── Response.ts
│ │ └── world.ts
│ └── theBest.feature
├── lerna.json
├── ormconfig.json
├── package-test.sh
├── package.json
├── packages/
│ ├── hadron-auth/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── HadronAuth.ts
│ │ │ ├── IRoute.ts
│ │ │ ├── ISecurityOptions.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── HadronAuth.ts
│ │ │ │ ├── hierarchyProvider.ts
│ │ │ │ └── urlGlob.ts
│ │ │ ├── constants.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── helpers/
│ │ │ │ ├── flattenDeep.ts
│ │ │ │ └── urlGlob.ts
│ │ │ ├── hierarchyProvider.ts
│ │ │ ├── password/
│ │ │ │ ├── IHashMethod.ts
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── hashMethodProvider.ts
│ │ │ │ ├── bcrypt/
│ │ │ │ │ ├── IBcryptOptions.ts
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ └── bcrypt.ts
│ │ │ │ │ └── bcrypt.ts
│ │ │ │ └── hashMethodProvider.ts
│ │ │ └── providers/
│ │ │ └── expressMiddlewareAuthorization.ts
│ │ └── tsconfig.json
│ ├── hadron-core/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ └── hadronCore.test.ts
│ │ │ ├── constants/
│ │ │ │ └── eventNames.ts
│ │ │ ├── container/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── container.ts
│ │ │ │ │ └── containerItem.ts
│ │ │ │ ├── container.ts
│ │ │ │ ├── containerItem.ts
│ │ │ │ ├── lifecycle.ts
│ │ │ │ └── types.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── errors/
│ │ │ │ ├── IncorrectContainerKeyNameError.ts
│ │ │ │ └── LoadingPackageError.ts
│ │ │ ├── hadronCore.ts
│ │ │ └── helpers/
│ │ │ ├── __tests__/
│ │ │ │ └── isVarName.ts
│ │ │ └── isVarName.ts
│ │ └── tsconfig.json
│ ├── hadron-demo/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── declarations.d.ts
│ │ ├── entity/
│ │ │ ├── Role.ts
│ │ │ ├── Team.ts
│ │ │ ├── User.ts
│ │ │ └── validation/
│ │ │ ├── schemas.ts
│ │ │ ├── team/
│ │ │ │ ├── insertTeam.json
│ │ │ │ └── updateTeam.json
│ │ │ ├── user/
│ │ │ │ ├── insertUser.json
│ │ │ │ └── updateUser.json
│ │ │ └── validate.ts
│ │ ├── event-emitter/
│ │ │ ├── case1/
│ │ │ │ └── index.ts
│ │ │ ├── case2/
│ │ │ │ └── index.ts
│ │ │ ├── case3/
│ │ │ │ └── index.ts
│ │ │ └── config.ts
│ │ ├── express-demo/
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── logger/
│ │ │ ├── adapters/
│ │ │ │ └── winstonAdapter.ts
│ │ │ └── index.ts
│ │ ├── package.json
│ │ ├── performance-tests/
│ │ │ ├── README.md
│ │ │ ├── teamService/
│ │ │ │ ├── getTeams.yml
│ │ │ │ ├── insertTeam.yml
│ │ │ │ ├── team.yml
│ │ │ │ └── updateTeam.yml
│ │ │ └── userService/
│ │ │ ├── getUsers.yml
│ │ │ ├── insertUser.yml
│ │ │ ├── updateUser.yml
│ │ │ └── user.yml
│ │ ├── routing/
│ │ │ ├── home.config.js
│ │ │ ├── nested-routes.config.js
│ │ │ ├── team.config.js
│ │ │ └── user.config.js
│ │ ├── security/
│ │ │ ├── loginRoute.ts
│ │ │ └── securedRoutesConfig.ts
│ │ ├── serialization/
│ │ │ ├── routing/
│ │ │ │ ├── index.ts
│ │ │ │ ├── princesses.ts
│ │ │ │ └── unicorns.ts
│ │ │ ├── schemas/
│ │ │ │ ├── princess.json
│ │ │ │ └── unicorn.json
│ │ │ ├── serialization-demo.ts
│ │ │ └── unicorns-and-princesses.ts
│ │ ├── services/
│ │ │ ├── teamService.ts
│ │ │ └── userService.ts
│ │ ├── tsconfig.json
│ │ └── typeorm-demo/
│ │ └── index.ts
│ ├── hadron-error-handler/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── errorHandler.ts
│ │ └── tsconfig.json
│ ├── hadron-events/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ └── eventManagerProvider.ts
│ │ │ ├── constants.ts
│ │ │ ├── eventManagerProvider.ts
│ │ │ ├── helpers/
│ │ │ │ └── functionHelper.ts
│ │ │ ├── registerProcessEvents.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── hadron-express/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── __mocks__/
│ │ │ │ │ └── routes/
│ │ │ │ │ └── route.js
│ │ │ │ ├── createContainerProxy.ts
│ │ │ │ ├── generateMiddlewares.ts
│ │ │ │ └── hadronToExpress.ts
│ │ │ ├── constants/
│ │ │ │ ├── eventNames.ts
│ │ │ │ └── routing.ts
│ │ │ ├── createContainerProxy.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── errors/
│ │ │ │ ├── CreateRouteError.ts
│ │ │ │ ├── GenerateMiddlewareError.ts
│ │ │ │ ├── InvalidRouteMethodError.ts
│ │ │ │ └── NoRouterMethodSpecifiedError.ts
│ │ │ ├── generateMiddlewares.ts
│ │ │ ├── hadronToExpress.ts
│ │ │ ├── handleResponseSpec.ts
│ │ │ ├── prepareRequest.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── hadron-file-locator/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── file-locator.ts
│ │ │ │ └── mock/
│ │ │ │ ├── app/
│ │ │ │ │ ├── config/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ ├── config.xml
│ │ │ │ │ │ ├── config_development.js
│ │ │ │ │ │ ├── config_development.json
│ │ │ │ │ │ └── config_test.js
│ │ │ │ │ ├── ext/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ └── config.xml
│ │ │ │ │ └── universal/
│ │ │ │ │ ├── dog.config.js
│ │ │ │ │ ├── other.json
│ │ │ │ │ ├── team.config.js
│ │ │ │ │ ├── test.js
│ │ │ │ │ └── user.config.js
│ │ │ │ └── plugins/
│ │ │ │ ├── plugin1/
│ │ │ │ │ └── config/
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config_development.js
│ │ │ │ │ ├── config_development.ts
│ │ │ │ │ └── config_test.js
│ │ │ │ ├── plugin2/
│ │ │ │ │ └── config/
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config_development.js
│ │ │ │ │ └── config_test.js
│ │ │ │ └── plugin3/
│ │ │ │ └── config/
│ │ │ │ ├── config.js
│ │ │ │ ├── config_development.js
│ │ │ │ └── config_test.js
│ │ │ ├── declarations.d.ts
│ │ │ ├── file-locator.ts
│ │ │ └── glob-promise.ts
│ │ └── tsconfig.json
│ ├── hadron-json-provider/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── config-json-provider.ts
│ │ │ │ ├── js-loader.ts
│ │ │ │ ├── json-loader.ts
│ │ │ │ ├── json-provider.ts
│ │ │ │ ├── mock/
│ │ │ │ │ ├── app/
│ │ │ │ │ │ ├── config/
│ │ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ │ ├── config.xml
│ │ │ │ │ │ │ ├── config_development.js
│ │ │ │ │ │ │ ├── config_development.json
│ │ │ │ │ │ │ └── config_test.js
│ │ │ │ │ │ ├── ext/
│ │ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ │ └── config.xml
│ │ │ │ │ │ └── universal/
│ │ │ │ │ │ ├── dog.config.js
│ │ │ │ │ │ ├── exportDefaultConfig.js
│ │ │ │ │ │ ├── other.json
│ │ │ │ │ │ ├── team.config.js
│ │ │ │ │ │ ├── test.js
│ │ │ │ │ │ └── user.config.js
│ │ │ │ │ └── plugins/
│ │ │ │ │ ├── plugin1/
│ │ │ │ │ │ └── config/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ ├── config_development.js
│ │ │ │ │ │ ├── config_development.ts
│ │ │ │ │ │ └── config_test.js
│ │ │ │ │ ├── plugin2/
│ │ │ │ │ │ └── config/
│ │ │ │ │ │ ├── config.js
│ │ │ │ │ │ ├── config_development.js
│ │ │ │ │ │ └── config_test.js
│ │ │ │ │ └── plugin3/
│ │ │ │ │ └── config/
│ │ │ │ │ ├── config.js
│ │ │ │ │ ├── config_development.js
│ │ │ │ │ └── config_test.js
│ │ │ │ └── xml-loader.ts
│ │ │ ├── declarations.d.ts
│ │ │ └── json-provider.ts
│ │ └── tsconfig.json
│ ├── hadron-logger/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ └── logger.ts
│ │ │ ├── adapters/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── bunyan.ts
│ │ │ │ ├── bunyan.ts
│ │ │ │ └── index.ts
│ │ │ ├── errors/
│ │ │ │ ├── ConfigNotDefinedError.ts
│ │ │ │ ├── CouldNotRegisterLoggerInContainerError.ts
│ │ │ │ ├── LoggerAdapterNotDefinedError.ts
│ │ │ │ └── LoggerNameIsRequiredError.ts
│ │ │ ├── logger.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── hadron-oauth/
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── facebook.ts
│ │ │ │ ├── formatQueryString.ts
│ │ │ │ ├── github.ts
│ │ │ │ └── google.ts
│ │ │ ├── facebook/
│ │ │ │ ├── redirect.ts
│ │ │ │ └── token.ts
│ │ │ ├── github/
│ │ │ │ ├── redirect.ts
│ │ │ │ └── token.ts
│ │ │ ├── google/
│ │ │ │ ├── redirect.ts
│ │ │ │ └── token.ts
│ │ │ ├── types.ts
│ │ │ └── util/
│ │ │ ├── constants.ts
│ │ │ └── formatQueryString.ts
│ │ └── tsconfig.json
│ ├── hadron-serialization/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── mocks.ts
│ │ │ │ ├── schema-provider.ts
│ │ │ │ └── serializer.ts
│ │ │ ├── constants.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── schema-provider.ts
│ │ │ ├── serializer.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── hadron-typeorm/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ ├── connectionHelper.ts
│ │ │ │ └── mocks/
│ │ │ │ ├── entity/
│ │ │ │ │ ├── Team.ts
│ │ │ │ │ ├── User.ts
│ │ │ │ │ └── UserStatus.ts
│ │ │ │ └── schema/
│ │ │ │ └── User.ts
│ │ │ ├── connectionHelper.ts
│ │ │ ├── constants.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── hadron-utils/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __tests__/
│ │ │ │ └── getArgs.ts
│ │ │ └── getArgs.ts
│ │ └── tsconfig.json
│ └── hadron-validation/
│ ├── LICENSE
│ ├── README.md
│ ├── index.ts
│ ├── package.json
│ ├── src/
│ │ ├── __tests__/
│ │ │ ├── __mocks__/
│ │ │ │ ├── Team.ts
│ │ │ │ ├── User.ts
│ │ │ │ ├── declarations.d.ts
│ │ │ │ ├── test-schemas/
│ │ │ │ │ ├── email.json
│ │ │ │ │ ├── person.json
│ │ │ │ │ ├── team.json
│ │ │ │ │ ├── user.json
│ │ │ │ │ └── users.json
│ │ │ │ ├── test-schemas.ts
│ │ │ │ └── test-validate.ts
│ │ │ └── validate.ts
│ │ └── validator-factory.ts
│ └── tsconfig.json
├── pm2.config.js
├── scripts/
│ ├── clean
│ └── copy-tsconfig
├── test.sh
├── tools/
│ └── testSetup.ts
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build_test:
docker:
- image: circleci/node:9.11.1
working_directory: ~/repo
steps:
- checkout
- run: npm install
- run: npm run test
workflows:
version: 2
test:
jobs:
- build_test
================================================
FILE: .dockerignore
================================================
node_modules
docs
features
.npmrc
.nvmrc
================================================
FILE: .gitignore
================================================
/dist
/logs
/npm-debug.log
/node_modules
.DS_Store
.idea/
.env
.vscode
node_modules
dist
package-lock.json
.nyc_output
/**/package-lock.json
/**/node_modules
/**/dist
package-lock.json
lerna-debug.log
*.swp
================================================
FILE: .gitlab-ci.yml
================================================
image: node:8.9.0
stages:
- build
- lint
- test
before_script:
- npm install
- apt-get update
- apt-get install -y netcat
# This folder is cached between builds
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- node_modules/
test:
script:
- npm run build
- npm run test
lint:
script:
- npm run lint
build:
script:
- npm run build
================================================
FILE: .npmrc
================================================
save-exact=true
package-lock=false
================================================
FILE: .nvmrc
================================================
9
================================================
FILE: .prettierignore
================================================
/.history
/node_modules
/.vscode
/.idea
/dist
*/**/*.temp
*/**/*.temp.*
*/**/package.json
*/**/package-lock.json
package.json
package-lock.json
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "all",
"singleQuote": true,
"arrowParens" :"always"
}
================================================
FILE: .rebuild.ts
================================================
================================================
FILE: Dockerfile
================================================
FROM keymetrics/pm2:8-alpine
COPY .env /.env
COPY entrypoint.sh /
COPY package.json /usr/src/app/package.json
WORKDIR /tmp/app
RUN apk --no-cache add git openssh-client curl
COPY . /tmp/app
RUN export $(cat /.env | xargs) && NODE_ENV=development npm install --progress=false
RUN export $(cat /.env | xargs) && /tmp/app/node_modules/.bin/tsc --project /tmp/app --outDir /usr/src/app/
RUN export $(cat /.env | xargs) && npm install --progress=false --prefix /usr/src/app/
WORKDIR /usr/src/app
CMD ["npm", "run", "start:production"]
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2017 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://hadron.pro/" target="blank">
<img src="./logo3.png" alt="Hadron Logo" /></a>
</p>
[](https://circleci.com/gh/brainhubeu/hadron)
## Why?
**Hadron's purpose is to facilitate the building of Node.js applications:**
### Low-level framework-agnostic
Your application is built independently from other frameworks (Express, Koa). Hadron creates a layer between HTTP requests and your app written in plain Javascript.
Hadron abstracts away underlying request and response objects, providing simple data structures as input/output of your routes' handlers, making them simple to test and easy to deal with.
### Dependency injection
The dependency injection pattern enables you to easily change interface implementation. Hadron gives us the power to create SOLID applications.
Containers as a dependency management solution provides a convenient way to access all dependencies in functions.
### Modular structure
The modular structure enables you to add/remove packages or create your own extensions. Hadron provides a complete solution for request processing using separate packages.
Current packages:
* security management
* input validation
* database integration (through TypeORM)
* data serialization
* logging
* events handling
* CLI tool
Built with TypeScript, but it's primary target is JavaScript apps. Hadron’s API embraces current ECMAScript standards, with the cherry of good IDE support via codebase types declarations on top.
> To read more about hadron check out our article: [How to use Hadron?](https://brainhub.eu/blog/building-api-expressjs-and-hadron/)
## Installation
* Install Node.js. We recommend using the latest version, installation details on [nodejs.org](https://nodejs.org)
* Install following modules from npm:
```bash
npm install @brainhubeu/hadron-core @brainhubeu/hadron-express express --save
```
## Hello World app
Let's start with a simple Hello World app. It will give you a quick grasp of the framework.
```javascript
const hadron = require('@brainhubeu/hadron-core').default;
const express = require('express');
const port = 8080;
const expressApp = express();
const config = {
routes: {
helloWorldRoute: {
path: '/',
callback: () => 'Hello world!',
methods: ['get'],
},
},
};
hadron(expressApp, [require('@brainhubeu/hadron-express')], config).then(() => {
expressApp.listen(port, () =>
console.log(`Listening on http://localhost:${port}`),
);
});
```
## Documentation
Hadron documentation can be found at [http://hadron.pro](http://hadron.pro)
## Getting Started
#### Requirements
* Installed GIT
* Installed node.js (we recommend using [nvm](https://github.com/creationix/nvm) to run multiple versions of node).
We recommend using latest version of node. If you want to use older versions you may need to add [babel-polyfill](https://babeljs.io/docs/usage/polyfill/) to use [some features](http://node.green/).
#### Clone it
```sh
git clone git@github.com:brainhubeu/hadron.git
cd brainhub-framework-app
```
#### Install dependencies
```sh
npm install
```
#### Run development server
```sh
npm run dev
```
#### Run production server
```sh
npm start
```
## Running tests
#### All tests
```sh
npm run test
# or
PORT=8181 npm run test
```
#### Unit tests
Run unit tests for each package:
```sh
npm run test:unit
```
Run unit tests for a single package:
```sh
npm run test:package <package name>
```
#### E2E tests
```sh
PORT=8181 npm run test:e2e
```
It will run `test.sh` script which in turn, will run app, wait for it to start listening and run `npm run test:cucumber` command.
You need to provide the script with valid PORT or default (8080) will be used.
#### Linter
```sh
npm run lint # to just show linter errors and warnings
npm run lint:fix # to fix the errors and show what's left
```
### Typescript types management
**Note!** Because we're using `"noImplicitAny": true`, we are required to have a `.d.ts` file for **every** library we use. While we could set `noImplicitAny` to `false` to silence errors about missing `.d.ts` files, it is a best practice to have a `.d.ts` file for every library.
1. After installing any npm package as a dependency or dev dependency, immediately try to install the `.d.ts` file via `@types`. If it succeeds, you are done. If not, continue to next step.
2. Try to generate a `.d.ts` file with dts-gen. If it succeeds, you are done. If not, continue to next step.
3. Create a file called `<some-library>.d.ts` in `types` folder.
4. Add the following code:
```ts
declare module '<some-library>';
```
5. At this point everything should compile with no errors and you can either improve the types in the `.d.ts` file by following this [guide on authoring `.d.ts` files](http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) or continue with no types.
## Lerna
1. To run `npm i` on all packages, and compile them, just run
```bash
lerna bootstrap
```
2. To run any command on all packages, just can use `exec` command.
F.e. to compile all packages, you can run
```bash
lerna exec tsc
```
3. To clean all `node_modules` in packages, run
```bash
lerna clean
```
4. To clean all `node_modules` AND `dist` directories, run
```bash
npm run clean
```
5. To add dependency between packages, run
```bash
lerna add <source-package-name> --scope=<target-package-name1>, <target-package-name2>
```
6. To publish to npm, run
```bash
lerna publish
```
To get more command, please visit this [link](https://github.com/lerna/lerna).
================================================
FILE: docker-compose.yml
================================================
version: '2'
networks:
brainhub: ~
services:
brainhub-framework-app:
image: brainhub-framework-app
build:
context: .
container_name: fs-brainhub-framework-app
volumes:
- ./:/usr/src/app
ports:
- 8080:8080
environment:
NODE_ENV: development
command: npm run start:development
networks:
brainhub: ~
================================================
FILE: entrypoint.sh
================================================
#!/usr/bin/env bash
set -e
if [ ! -f /usr/src/app/node_modules/.lock ] && [ -f /usr/src/app/package.json ]; then
npm install --progress=false
touch /usr/src/app/node_modules/.lock
fi
exec "$@"
================================================
FILE: features/step_definitions/steps.ts
================================================
import { defineSupportCode } from 'cucumber';
import stepsSupport from '@brainhubeu/cucumber-steps';
defineSupportCode(stepsSupport);
================================================
FILE: features/step_definitions/theBestSteps.ts
================================================
import { defineSupportCode } from 'cucumber';
/* tslint:disable:only-arrow-functions ter-prefer-arrow-callback */
defineSupportCode(function({ Given, When, Then }) {
Given('brainhub is the best', function(callback) {
callback();
});
When('add {string} to {string}', function(number1, number2) {
this.result = parseInt(number1, 10) + parseInt(number2, 10);
});
Then('the result is {string}', function(result) {
if (this.result !== parseInt(result, 10)) {
throw new Error(
`Expected result to be equal ${result} but it is ${this.result}`,
);
}
});
Then('the result is null', function() {
if (this.result !== null) {
throw new Error(`Expected result to be null but it is ${this.result}`);
}
});
});
================================================
FILE: features/support/hooks/mock.ts
================================================
import { defineSupportCode } from 'cucumber';
import * as superagent from 'superagent';
import Client from '../scripts/Client';
defineSupportCode(({ Before }) => {
Before(function(scenarioResult) {
this.client = new Client(superagent);
this.client.setHost('http://localhost:8080');
});
});
================================================
FILE: features/support/scripts/Client.ts
================================================
import Response from './Response';
const METHOD = {
DELETE: 'del',
GET: 'get',
PATCH: 'patch',
POST: 'post',
PUT: 'put',
};
export default class Client {
public superagent: any;
public host: string;
public headers: object;
constructor(superagent) {
this.superagent = superagent;
this.headers = {};
Object.keys(METHOD).forEach((methodKey) => {
const method = METHOD[methodKey.toUpperCase()];
this[methodKey.toLowerCase()] = (path, body) => {
return this.createRequest(method, path, body);
};
});
}
public setHost(host) {
this.host = host;
}
public createRequest(method, path, body) {
const request = this.superagent[method.toLowerCase()](this.host + path);
this.addRequestHeaders(request);
const createdRequest =
method.toLowerCase() !== 'get' ? request.send(body) : request;
//
return createdRequest.then(
// tslint:disable:no-shadowed-variable
({ body, status }) => new Response(body, status),
({ response: { body, status } }) => new Response(body, status),
);
}
public addRequestHeaders(request) {
Object.keys(this.headers).map((name) => {
request.set(name, this.headers[name]);
});
}
public setHeader(name, value) {
this.headers[name] = value;
}
}
================================================
FILE: features/support/scripts/Response.ts
================================================
export default class Response {
public status: any;
public body: any;
constructor(body, status) {
this.body = body;
this.status = status;
}
}
================================================
FILE: features/support/world.ts
================================================
import { defineSupportCode } from 'cucumber';
/* tslint:disable:only-arrow-functions ter-prefer-arrow-callback */
defineSupportCode(function({ setWorldConstructor }) {
const customWorld = function() {
this.result = null;
};
setWorldConstructor(customWorld);
});
================================================
FILE: features/theBest.feature
================================================
Feature: The Best
As a Brainhub
We want to become best in the world
Scenario: Adding two numbers
Given brainhub is the best
When add "2" to "3"
Then the result is "5"
Scenario: Not adding two numbers
Then the result is null
================================================
FILE: lerna.json
================================================
{
"lerna": "2.9.0",
"packages": [
"packages/hadron-logger",
"packages/hadron-utils",
"packages/hadron-error-handler",
"packages/hadron-core",
"packages/hadron-file-locator",
"packages/hadron-json-provider",
"packages/hadron-serialization",
"packages/hadron-validation",
"packages/hadron-typeorm",
"packages/hadron-events",
"packages/hadron-express",
"packages/hadron-oauth",
"packages/hadron-auth",
"packages/hadron-demo"
],
"version": "independent"
}
================================================
FILE: ormconfig.json
================================================
[
{
"name": "postgres",
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "postgres",
"password":"mysecretpassword",
"database": "_test",
"synchronize": true,
"logging": false,
"autoSchemaSync": true,
"entities": [
"../../src/entity/**/*.ts"
],
"migrations": [
"../../src/migration/**/*.ts"
],
"subscribers": [
"../../src/subscriber/**/*.ts"
]
},
{
"name": "mysql",
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password":"my-secret-pw",
"database": "_test",
"synchronize": true,
"logging": false,
"autoSchemaSync": true,
"entities": [
"../../src/entity/**/*.ts"
],
"migrations": [
"../../src/migration/**/*.ts"
],
"subscribers": [
"../../src/subscriber/**/*.ts"
]
}
]
================================================
FILE: package-test.sh
================================================
#!/usr/bin/env sh
# this runs unit tests on an individual package
# Usage example: ./package-test.sh oauth
./node_modules/mocha/bin/mocha -r ts-node/register packages/hadron-$1/src/__tests__/*
================================================
FILE: package.json
================================================
{
"name": "brainhub-framework-app",
"version": "1.0.0-alpha.1",
"description": "Brainhub framework app",
"main": "dist",
"scripts": {
"start": "NODE_ENV=production NODE_PATH=./packages/hadron-demo/dist npm start --prefix packages/hadron-demo",
"start:dev": "NODE_ENV=development NODE_PATH=./packages/hadron-demo nodemon --watch 'packages/hadron-demo/**/*.ts' --watch .rebuild.ts --ignore 'packages/hadron-demo/**/*.spec.ts' --exec 'ts-node' packages/hadron-demo/index.ts",
"start:test": "NODE_ENV=development NODE_PATH=./packages/hadron-demo ts-node packages/hadron-demo",
"start:lerna": "nodemon --watch 'packages/**/*.ts' --exec 'npm run build && touch .rebuild.ts' -e ts",
"build": "lerna bootstrap",
"prestart": "npm run build",
"precommit": "lint-staged",
"lint": "lerna exec --bail=false -- tslint -c \\$LERNA_ROOT_PATH/tslint.json -p ./tsconfig.json --format stylish",
"lint:fix": "lerna exec --bail=false -- tslint -c \\$LERNA_ROOT_PATH/tslint.json -p \\packages/$LERNA_PACKAGE_NAME/tsconfig.json --format stylish",
"test": "lerna bootstrap && npm run -s test:unit && npm run -s test:e2e",
"test:unit": "NODE_ENV=test NODE_PATH=src:lib mocha -r ts-node/register tools/testSetup.ts './**/__tests__/*.ts'",
"test:package": "./package-test.sh",
"test:unit:watch": "npm run test:unit -- --reporter min --watch-extensions ts --watch",
"test:unit:coverage": "NODE_ENV=test NODE_PATH=src:lib nyc mocha -r ts-node/register tools/testSetup.ts './**/__tests__/*.ts'",
"test:e2e": "./test.sh",
"test:cucumber": "NODE_ENV=test ./node_modules/.bin/cucumberjs --compiler=ts:ts-node/register",
"docker:build": "docker build -t brainhub-framework-app .",
"format": "prettier --write \"*/**/*.ts\"",
"tsc": "lerna exec tsc --parallel",
"clean": "bash ./scripts/clean",
"postinstall": "lerna bootstrap"
},
"author": "Brainhub",
"license": "MIT",
"dependencies": {
"@types/bunyan": "1.8.4",
"body-parser": "1.18.2",
"dotenv": "4.0.0",
"mysql": "2.15.0",
"pg": "7.4.1",
"pm2": "github:Unitech/pm2#development"
},
"devDependencies": {
"@types/chai": "4.1.2",
"@types/chai-as-promised": "7.1.0",
"@types/cucumber": "4.0.1",
"@types/dotenv": "4.0.2",
"@types/fs-extra": "5.0.1",
"@types/mocha": "2.2.48",
"@types/multer": "1.3.6",
"@types/node": "9.4.6",
"@types/sinon": "4.1.3",
"@types/sinon-chai": "2.7.29",
"chai": "4.1.2",
"chai-as-promised": "7.1.1",
"concurrently": "3.5.1",
"cucumber": "3.1.0",
"@brainhubeu/cucumber-steps": "git+https://github.com/brainhubeu/cucumber-steps.git",
"dirty-chai": "2.0.1",
"husky": "0.14.3",
"lerna": "^2.9.0",
"lint-staged": "7.0.0",
"mocha": "4.0.1",
"nodemon": "1.12.1",
"nyc": "11.6.0",
"prettier": "1.11.1",
"reflect-metadata": "0.1.12",
"sinon": "4.1.2",
"sinon-chai": "2.14.0",
"superagent": "3.8.2",
"ts-node": "5.0.0",
"tslint": "5.9.1",
"tslint-config-airbnb": "5.7.0",
"tslint-config-prettier": "1.10.0",
"tslint-eslint-rules": "5.1.0",
"typescript": "2.7.2"
},
"nyc": {
"extension": [
".ts"
]
},
"lint-staged": {
"*.{ts,tsx}": [
"npm run lint:fix",
"git add"
],
"*.{ts,js,json,css,md}": [
"prettier --write",
"git add"
]
},
"repository": {
"type": "git",
"url": "https://github.com/brainhubeu/hadron.git"
},
"keywords": [
"hadron"
]
}
================================================
FILE: packages/hadron-auth/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-auth/README.md
================================================
## Installation
```bash
npm install @brainhubeu/hadron-auth --save
```
## Overview
**hadron-auth** provides back-end authorization layer for routes You will choose.
### Configuration with Hadron Core
If You want to use **hadron-auth** with **hadron-core** You should also use **hadron-typeorm** and **hadron-express**.
All You need to provide is two schemas for typeorm:
* `User` (id, username, and roles many-to-many relation required)
Here is the example schema:
```javascript
// schemas/User
const userSchema = {
name: 'User',
columns: {
id: {
primary: true,
type: 'int',
generated: true,
},
username: {
type: 'varchar',
unique: true,
},
passwordHash: {
type: 'varchar',
},
addedOn: {
type: 'timestamp',
},
},
relations: {
roles: {
target: 'Role',
type: 'many-to-many',
joinTable: {
name: 'user_role',
},
onDelete: 'CASCADE',
},
},
};
module.exports = userSchema;
```
* `Role` (id and name required)
Example schema:
```javascript
// schemas/Role
const roleSchema = {
name: 'Role',
columns: {
id: {
primary: true,
type: 'int',
generated: true,
},
name: {
type: 'varchar',
unique: true,
},
addedOn: {
type: 'timestamp',
},
},
};
module.exports = roleSchema;
```
Don't forget to add schemas to Your database config, example below:
```javascript
// config/db.js
const userSchema = require('../schemas/User');
const roleSchema = require('../schemas/Role');
const connection = {
name: 'mysql-connection',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'my-secret-pw',
database: 'done-it',
entitySchemas: [roleSchema, userSchema],
synchronize: true,
};
module.exports = connection,
```
Now You need to prepare Your hadron configuration file, where You can add secured routes, for example:
```javascript
// index.js
const config = {
routes: {
helloWorldRoute: {
path: '/',
methods: ['GET'],
callback: () => 'Hello World',
},
adminRoute: {
path: '/admin',
methods: ['GET'],
callback: () => 'Hello Admin',
},
userRoute: {
path: '/user',
methods: ['GET'],
callback: () => 'Hello User',
},
},
securedRoutes: [
{
path: '/admin/*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
roles: 'Admin',
},
{
path: '/user/*',
roles: ['Admin', 'User'],
},
],
};
```
Finally You need to add **hadron-auth** to hadron initialization method:
```javascript
const hadron = require('@brainhubeu/hadron-core').default;
const hadronExpress = require('@brainhubeu/hadron-express');
const hadronTypeOrm = require('@brainhubeu/hadron-typeorm');
const hadronAuth = require('@brainhubeu/hadron-auth');
const express = require('express');
const expressApp = express();
const hadronInit = async () => {
const config = {
routes: {
helloWorldRoute: {
path: '/',
methods: ['GET'],
callback: () => 'Hello World',
},
adminRoute: {
path: '/admin',
methods: ['GET'],
callback: () => 'Hello Admin',
},
userRoute: {
path: '/user',
methods: ['GET'],
callback: () => 'Hello User',
},
},
securedRoutes: [
{
path: '/admin/*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
roles: 'Admin',
},
{
path: '/user/*',
roles: ['Admin', 'User'],
},
],
};
const container = await hadron(
expressApp,
[hadronAuth, hadronExpress, hadronTypeOrm],
config,
);
};
```
---
Warning, You should pass hadronAuth as first to hadron packages array.
---
Now Your routes are secured, by default, **hadron-auth** authorize user by **JWT Token**, passed as `Authorization` header.
### Creating custom auth middleware
You can pass Your own function in hadron configuration to check if a user is authorized to the secured route.
Here is the skeleton for the authorization middleware:
```javascript
const authorizationMiddleware = (container) => {
return (req, res, next) => {};
};
```
**hadron-auth** provides `isAllowed` function, to check if a user is allowed to specified route:
```javascript
isAllowed(path, method, user, allRoles);
```
Where:
* `path` - path to secured route, for example /api/admin/1
* `method` - HTTP method
* `user` - User object, which need to contain roles
* `allRoles` - All roles stored in database (only role names)
Here is an example authorization middleware:
```javascript
const jwt = require('jsonwebtoken');
const { isRouteSecure, isAllowed } = require('@brainhubeu/hadron-auth');
const errorResponse = {
message: 'Unauthorized',
};
const expressMiddlewareAuthorization = (container) => {
return async (req, res, next) => {
try {
if (!isRouteSecure(req.path)) {
return next();
}
const userRepository = container.take('userRepository');
const roleRepository = container.take('roleRepository');
const token = req.headers.authorization;
const decoded: any = jwt.decode(token);
const user = await userRepository.findOne({
where: { id: decoded.id },
relations: ['roles'],
});
if (!user) {
return res.status(403).json({ error: errorResponse });
}
const allRoles = await roleRepository.find();
if (
isAllowed(req.path, req.method, user, allRoles.map((role) => role.name))
) {
return next();
}
return res.status(403).json({ error: errorResponse });
} catch (error) {
return res.status(403).json({ error: errorResponse });
}
};
};
module.exports = expressMiddlewareAuthorization;
```
To use it, You need to pass an expressMiddlewareAuthorization function as `authorizationMiddleware` key in hadron config.
```javascript
const config = {
authorizationMiddleware: YourCustomFunction,
};
```
### Usage:
```javascript
const securedRoutes = [
{
path: '/api/**',
methods: ['GET'],
roles: ['Admin', 'User'],
},
{
path: '/api/**',
methods: ['POST', 'PUT', 'DELETE'],
roles: 'Admin',
},
{
path: '/admin/*',
roles: 'Admin',
},
{
path: 'product/info',
methods: ['GET'],
roles: [['Admin', 'User'], 'Manager'],
},
];
```
* `Path` - here we can specify the route path we want to secure, we can use a static path like `/api/admin/tasks` or by pattern:
* `/api/admin/*` - route after `/api/admin/` is secured, for example `/api/admin/tasks` - is secured, but `/api/admin/tasks/5` - will be not secured
* `/api/admin/**` - every route after `/api/admin` is secured
* `methods` - an array of strings, where You can pass role names, if You will not provide any role, then the route is secured and user with **ANY** role can access this if a user does not have any role he will be unauthorized.
* `roles` - here You can pass single role name, an array of role names or array of arrays of strings, which add some logic functionality, for example, if we declare:
```javascript
roles[(['Admin', 'User'], 'Manager')];
```
The user needs Admin **AND** User **OR** Manager role to access the route.
================================================
FILE: packages/hadron-auth/index.ts
================================================
import { IUser, IRole } from './src/hierarchyProvider';
import * as bcrypt from './src/password/bcrypt/bcrypt';
import * as HadronAuth from './src/HadronAuth';
export const register = (container: any, config: any) => {
HadronAuth.register(container, config);
};
export default HadronAuth;
export { IUser, IRole, bcrypt };
================================================
FILE: packages/hadron-auth/package.json
================================================
{
"name": "@brainhubeu/hadron-auth",
"version": "0.0.2",
"description": "Security package for hadron",
"main": "dist/index.js",
"files": [
"dist",
"LICENSE"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"keywords": [
"security",
"hadron"
],
"author": "Brainhub",
"license": "MIT",
"dependencies": {
"@types/bcrypt": "^2.0.0",
"@types/jsonwebtoken": "^7.2.7",
"bcrypt": "2.0.0",
"glob-to-regexp": "^0.4.0",
"jsonwebtoken": "8.3.0"
},
"publishConfig": {
"access": "public"
}
}
================================================
FILE: packages/hadron-auth/src/HadronAuth.ts
================================================
import { IRoute, IMethod } from './IRoute';
import urlGlob, { convertToPattern } from './helpers/urlGlob';
import { IUser } from '..';
import { isUserGranted } from './hierarchyProvider';
import expressMiddlewareAuthorization from './providers/expressMiddlewareAuthorization';
let routes: IRoute[] = [];
export interface ISecuredRoute {
path: string;
methods?: string[];
roles: string | Array<string | string[]>;
}
export const getExistsingRoute = (path: string, routes: IRoute[]): IRoute => {
for (const route of routes) {
if (route.path === path) {
return route;
}
}
return null;
};
export const getRoleArray = (roles: string | Array<string | string[]>) => {
const arr: any[] = [];
if (typeof roles === 'string') {
arr.push(roles);
} else {
roles.forEach((role) => arr.push(role));
}
return [...new Set(arr)];
};
export const getMethodsForExistsingRoute = (
existingRoute: IRoute,
methods: string[],
roles: string | Array<string | string[]>,
): IMethod[] => {
const newMethods: IMethod[] = methods.map((method) => ({
allowedRoles: getRoleArray(roles),
name: method,
}));
const existingMethods = existingRoute.methods.filter(
(method) => newMethods.map((el) => el.name).indexOf(method.name) >= 0,
);
const nonExistingMethods = newMethods.filter(
(method) =>
existingRoute.methods.map((el) => el.name).indexOf(method.name) === -1,
);
existingMethods.forEach((method) => {
method.allowedRoles = [
...new Set(method.allowedRoles.concat(getRoleArray(roles))),
];
});
let methodsFromRoute = existingRoute.methods.filter(
(method) =>
existingMethods.map((el) => el.name).indexOf(method.name) === -1,
);
methodsFromRoute = methodsFromRoute.concat(existingMethods);
return [...methodsFromRoute, ...nonExistingMethods];
};
export const createNewRoute = (
path: string,
methods: string[] = [],
roles: string | Array<string | string[]>,
) => {
const methodsForRoute: IMethod[] = [];
if (methods.length > 0) {
methods = [...new Set(methods)];
methods.forEach((methodName) => {
methodsForRoute.push({
name: methodName,
allowedRoles: getRoleArray(roles),
});
});
} else {
methodsForRoute.push({
name: '*',
allowedRoles: getRoleArray(roles),
});
}
const route: IRoute = {
path: convertToPattern(path),
methods: methodsForRoute,
};
return route;
};
export const initRoutes = (securedRoutes: ISecuredRoute[]): IRoute[] => {
const routes: IRoute[] = [];
securedRoutes.forEach((route) => {
const existingRoute = getExistsingRoute(
convertToPattern(route.path),
routes,
);
if (existingRoute) {
existingRoute.methods = getMethodsForExistsingRoute(
existingRoute,
route.methods,
route.roles,
);
} else {
routes.push(createNewRoute(route.path, route.methods, route.roles));
}
});
return routes;
};
export const register = (container: any, config: any) => {
if (config.authSecret) {
container.register('authSecret', config.authSecret);
}
routes = initRoutes(config.securedRoutes || []);
const server = container.take('server');
server.use(
config.authorizationMiddleware
? config.authorizationMiddleware(container)
: expressMiddlewareAuthorization(container),
);
};
export const getRouteFromPath = (path: string, routes: IRoute[]): IRoute => {
const route = routes.filter((r) => urlGlob(r.path, path));
if (route.length === 0) return null;
return route[0];
};
export const isRouteNotSecure = (path: string) => {
console.warn("HadronAuth: isRouteNotSecure is being deprecated. Use isRouteSecure instead.");
return getRouteFromPath(path, routes) === null;
}
export const isRouteSecure = (path: string) =>
getRouteFromPath(path, routes) !== null;
export const isAllowed = (
path: string,
allowedMethod: string,
user: IUser,
allRoles: string[],
): boolean => {
try {
const route = getRouteFromPath(path, routes);
let isGranted = false;
route.methods.forEach((method) => {
if (
method.name === '*' ||
method.name.toLowerCase() === allowedMethod.toLowerCase()
) {
if (method.allowedRoles.includes('*') && user.roles.length > 0) {
isGranted = true;
} else {
isGranted = isUserGranted(user, method.allowedRoles, {
ALL: allRoles,
});
}
}
});
return isGranted;
} catch (error) {
throw new Error('Unauthorized');
}
};
================================================
FILE: packages/hadron-auth/src/IRoute.ts
================================================
export interface IRoute {
path: string;
methods: IMethod[];
}
export interface IMethod {
name: string;
allowedRoles: Array<string | string[]>;
}
================================================
FILE: packages/hadron-auth/src/ISecurityOptions.ts
================================================
import { IRolesMap } from './hierarchyProvider';
import IHashMethod from './password/IHashMethod';
export default interface ISecurityOptions {
roles: IRolesMap | string[];
hash?: {
method: IHashMethod | string;
options: any;
};
};
================================================
FILE: packages/hadron-auth/src/__tests__/HadronAuth.ts
================================================
import { expect } from 'chai';
import {
initRoutes,
ISecuredRoute,
getRouteFromPath,
getExistsingRoute,
createNewRoute,
getMethodsForExistsingRoute,
} from '../HadronAuth';
import { convertToPattern } from '../helpers/urlGlob';
describe('Hadron Authorization module', () => {
it('initRoutes should return array of prepared IRoute from ISecuredRoute array', () => {
const securedRoutes: ISecuredRoute[] = [
{
path: '/admin/**',
methods: ['POST', 'PUT'],
roles: ['Admin', 'User'],
},
];
const routes = initRoutes(securedRoutes);
expect(routes).to.be.instanceOf(Array);
expect(Object.keys(routes[0])).to.be.deep.equal(['path', 'methods']);
});
it('initRoutes should join two same paths with different methods/roles', () => {
const securedRoutes: ISecuredRoute[] = [
{
path: '/admin/**',
methods: ['POST', 'PUT'],
roles: 'Admin',
},
{
path: '/admin/**',
methods: ['GET'],
roles: 'User',
},
];
const routes = initRoutes(securedRoutes);
expect(routes.length).to.be.equal(1);
expect(Object.keys(routes[0])).to.be.deep.equal(['path', 'methods']);
});
it('initRoutes should convert path from securedRoute to regex pattern', () => {
const securedRoutes: ISecuredRoute[] = [
{
path: '/admin/*',
methods: ['POST'],
roles: 'Admin',
},
];
const routes = initRoutes(securedRoutes);
const matcher = new RegExp(routes[0].path);
const testPath = '/admin/1';
expect(matcher.test('admin/1')).to.be.equal(true);
});
describe('getRouteFromPath', () => {
const routes = initRoutes([
{
path: '/admin/*',
methods: ['POST'],
roles: 'Admin',
},
{
path: '/user/*',
methods: ['POST'],
roles: 'Admin',
},
]);
it('getRouteFromPath should return IRoute if path is already exists by regex in routes', () => {
expect(getRouteFromPath('/admin/1', routes)).to.be.an('object');
});
it('getRouteFromPath should return null if path does not exists by regex in routes', () => {
expect(getRouteFromPath('/qwe', routes)).to.be.equal(null);
});
});
describe('getExistingRoute', () => {
const routes = initRoutes([
{
path: '/admin/*',
methods: ['POST'],
roles: 'Admin',
},
{
path: '/user/*',
methods: ['POST'],
roles: 'Admin',
},
]);
it('getExistingRoute should return route if route exists in array', () => {
expect(
getExistsingRoute(convertToPattern('/admin/*'), routes),
).to.be.equal(routes[0]);
});
it('getExistingRoute should return null if route does not exists in array', () => {
expect(
getExistsingRoute(convertToPattern('/guest/*'), routes),
).to.be.equal(null);
});
});
describe('createNewRoute', () => {
const path = '/admin/**';
const methods: string[] = [];
const roles = 'Admin';
const route = createNewRoute(path, methods, roles);
it('createNewRoute should create IRoute from path, methods and roles', () => {
expect(Object.keys(route)).to.be.deep.equal(['path', 'methods']);
});
it('createNewRoute should create regex pattern from path string', () => {
expect(route.path).to.be.equal(convertToPattern('/admin/**'));
});
it('createNewRoute should push "*" to methods array if array is empty', () => {
expect(route.methods[0].name).to.be.equal('*');
});
it('createNewRoute should create IMethod with name and roles in route object', () => {
expect(Object.keys(route.methods[0])).to.be.deep.equal([
'name',
'allowedRoles',
]);
});
});
describe('getMethodsForExistingRoute', () => {
const path = '/admin/**';
const methods: string[] = ['GET'];
const roles = 'Admin';
const route = createNewRoute(path, methods, roles);
it('getMethodsForExistsinRoute should push new methods to existing route', () => {
const newMethods = getMethodsForExistsingRoute(
route,
['POST', 'PUT'],
['User', 'Admin', 'Guest'],
);
expect(newMethods.length).to.be.equal(3);
});
it('getMethodsForExistsinRoute should push new roles to existing method', () => {
const newMethods = getMethodsForExistsingRoute(
route,
['GET'],
['User', 'Guest'],
);
expect(newMethods.length).to.be.equal(1);
});
});
});
================================================
FILE: packages/hadron-auth/src/__tests__/hierarchyProvider.ts
================================================
import hierarchyProvider, {
fillMissingRoles,
checkRole,
checkRoles,
getDeeperRoles,
excludeRoles,
} from '../hierarchyProvider';
import { expect } from 'chai';
describe('hierarchyProvider', () => {
const basicHierarchy = {
ADMIN: ['USER', 'MANAGER'],
MANAGER: ['USER'],
GUEST: [],
};
describe('fillMissingRoles', () => {
it('should add missing roles from hierarchy', () => {
const roles = {
ROLE1: ['ROLE2', 'ROLE3'],
ROLE2: ['ROLE4'],
};
expect(fillMissingRoles(roles)).to.contain.keys([
'ROLE1',
'ROLE2',
'ROLE3',
'ROLE4',
]);
});
it('should keep hierarchy of previously mentioned role', () => {
const roles = {
ROLE1: ['ROLE2', 'ROLE3'],
ROLE2: ['ROLE4'],
};
expect(fillMissingRoles(roles).ROLE2).to.eql(['ROLE4']);
});
it('should handle array as roles', () => {
const roles = ['ROLE1', 'ROLE2'];
expect(fillMissingRoles(roles)).to.contain.keys(['ROLE1', 'ROLE2']);
});
});
describe('checkRole', () => {
it('should return true if role is exactly the same as requested', () =>
expect(checkRole(['ADMIN'], 'ADMIN', basicHierarchy)).to.be.eql(true));
it('should return false if role is not exactly the same and does not contain it in hierarchy', () =>
expect(checkRole(['USER'], 'MANAGER', basicHierarchy)).to.be.eql(false));
it('should return true if role is not exactly the same but does contain it in hierarchy', () =>
expect(checkRole(['MANAGER'], 'USER', basicHierarchy)).to.be.eql(true));
it('should return true if one of roles is matching', () =>
expect(
checkRole(['MANAGER', 'USER'], 'MANAGER', basicHierarchy),
).to.be.eql(true));
it('should return false if none of roles is matching', () =>
expect(checkRole(['MANAGER', 'USER'], 'ADMIN', basicHierarchy)).to.be.eql(
false,
));
it('should return true if role is in hierarchy of user roles', () =>
expect(checkRole(['ADMIN', 'USER'], 'MANAGER', basicHierarchy)).to.be.eql(
true,
));
it('should return true if role is in deeper hierarchy of user roles', () => {
const roles = {
...basicHierarchy,
MANAGER: ['TESTUSER', 'USER'],
};
return expect(checkRole(['ADMIN'], 'TESTUSER', roles)).to.be.eql(true);
});
it('should handle recurrent hierarchy', () => {
const roles = {
ADMIN: ['MANAGER'],
MANAGER: ['ADMIN'],
};
return expect(checkRole(['ADMIN'], 'TESTUSER', roles)).to.be.eql(false);
});
it("should return false, if role doesn't exists", () =>
expect(checkRole(['ADMIN'], 'UNEXISTING_ROLE', basicHierarchy)).to.be.eql(
false,
));
});
describe('checkRoles', () => {
it('should return true if role is one of given', () =>
expect(
checkRoles(['MANAGER'], ['MANAGER', 'ADMIN'], basicHierarchy),
).to.be.eql(true));
it('should return false, if none of roles is matching given one', () =>
expect(
checkRoles(['USER'], ['MANAGER', 'ADMIN'], basicHierarchy),
).to.be.eql(false));
it('should return true if user has both roles', () =>
expect(
checkRoles(
['MANAGER', 'ADMIN'],
['MANAGER', 'ADMIN'],
basicHierarchy,
true,
),
).to.be.eql(true));
it("should return false if user doesn't have one of roles", () =>
expect(
checkRoles(['MANAGER'], ['MANAGER', 'ADMIN'], basicHierarchy, true),
).to.be.eql(false));
it('should return true if role contains both required roles', () =>
expect(
checkRoles(['ADMIN'], ['MANAGER', 'USER'], basicHierarchy, true),
).to.be.eql(true));
});
describe('getDeeperRoles', () => {
it('should return roles that are related to given one', () =>
expect(getDeeperRoles(['ADMIN'], basicHierarchy)).to.has.members([
'MANAGER',
'USER',
]));
it('should return roles that are related to all given roles', () => {
const hierarchy = {
ROLE1: ['ROLE2', 'ROLE3'],
ROLE4: ['ROLE5'],
};
return expect(
getDeeperRoles(['ROLE1', 'ROLE4'], hierarchy),
).to.has.members(['ROLE2', 'ROLE3', 'ROLE5']);
});
it('should return roles that are related to all given roles distinctly', () => {
const hierarchy = {
ROLE1: ['ROLE2', 'ROLE3'],
ROLE4: ['ROLE5', 'ROLE3'],
};
return expect(
getDeeperRoles(['ROLE1', 'ROLE4'], hierarchy),
).to.has.members(['ROLE2', 'ROLE3', 'ROLE5']);
});
});
describe('excludeRoles', () => {
it('should remove given role from list', () =>
expect(excludeRoles(['ADMIN'], basicHierarchy)).to.contain.keys([
'MANAGER',
'GUEST',
]));
it('should remove given roles from list', () =>
expect(
excludeRoles(['ADMIN', 'MANAGER'], basicHierarchy),
).to.contain.keys(['GUEST']));
});
describe('isGranted', () => {
const { isGranted } = hierarchyProvider(basicHierarchy);
it('should pass if single matching role has been provided', () =>
expect(isGranted(['ADMIN'], 'ADMIN')).to.be.eql(true));
it('should fail if single not matching role has been provided', () =>
expect(isGranted(['ADMIN'], 'GUEST')).to.be.eql(false));
it('should pass if array of roles has been provided, with single matching one', () =>
expect(isGranted(['ADMIN'], ['ADMIN', 'GUEST'])).to.be.eql(true));
it('should fail if array of roles has been provided, with none that matches', () =>
expect(isGranted(['MANAGER'], ['ADMIN', 'GUEST'])).to.be.eql(false));
it('should pass if array of arrays of roles has been provided and all of them are matching', () =>
expect(isGranted(['ADMIN', 'GUEST'], [['ADMIN', 'GUEST']])).to.be.eql(
true,
));
it('should fail if array of arrays of roles has been provided and one of them is not matching', () =>
expect(isGranted(['ADMIN'], [['ADMIN', 'GUEST']])).to.be.eql(false));
it('should pass if array of arrays and single role has been provided and one of them are matching', () =>
expect(isGranted(['MANAGER'], [['ADMIN', 'GUEST'], 'MANAGER'])).to.be.eql(
true,
));
it('should fail if array of arrays of roles has been provided and none of them are matching', () =>
expect(
isGranted(['MANAGER'], [['ADMIN', 'GUEST'], ['MANAGER', 'GUEST']]),
).to.be.eql(false));
it('should pass if array of arrays of roles has been provided and one of them are matching', () =>
expect(
isGranted(
['MANAGER', 'GUEST'],
[['ADMIN', 'GUEST'], ['MANAGER', 'GUEST']],
),
).to.be.eql(true));
});
});
================================================
FILE: packages/hadron-auth/src/__tests__/urlGlob.ts
================================================
import { expect } from 'chai';
import urlGlob from '../helpers/urlGlob';
describe('Glob URL pattern', () => {
it('should return true if URL: /api/admin/qwe is valid for PATTERN: /api/admin/*', () => {
expect(urlGlob('/api/admin/*', '/api/admin/qwe')).to.be.equal(true);
});
it('should return false if URL: /api/adm/qwe is invalid for PATTERN: /api/admin/*', () => {
expect(urlGlob('/api/admin/*', '/api/adm/qwe')).to.be.equal(false);
});
it('should return false if URL: /api/admin/qwe/1 is invalid for PATTERN: /api/admin/*', () => {
expect(urlGlob('/api/admin/*', '/api/adm/qwe')).to.be.equal(false);
});
it('should return true if URL: /api/admin/tasks/1 is valid for PATTERN: /api/admin/**', () => {
expect(urlGlob('/api/admin/**', '/api/admin/tasks/1')).to.be.equal(true);
});
it('should return true if URL: /api/something/admin/more/user/in/manager/string for PATTERN: /api/**/admin/**/manager/**', () => {
expect(
urlGlob(
'/api/**/admin/**/user/**/manager/**',
'/api/something/admin/more/user/in/manager/string',
),
).to.be.equal(true);
});
it('should return false if URL: /api/something/admin/more/user/in/mage/string for PATTERN: /api/**/admin/**/manager/**', () => {
expect(
urlGlob(
'/api/**/admin/**/user/**/manager/**',
'/api/something/admin/more/user/in/mage/string',
),
).to.be.equal(false);
});
it('should return false if URL: /api/something/admin/more is invalid for PATTERNL /api/**/user/**', () => {
expect(urlGlob('/api/**/admin/**', '/api/something/user/more')).to.be.equal(
false,
);
});
it('should return true if URL: /api/admin is valid for strict PATTERN /api/admin', () => {
expect(urlGlob('/api/admin', '/api/admin')).to.be.equal(true);
});
it('should return false if URL: /api/adm or /api.admin/1 is invalid for strict PATTERN /api/admin', () => {
expect(urlGlob('/api/admin', '/api/adm')).to.be.equal(false);
expect(urlGlob('/api/admin', '/api/admin/1')).to.be.equal(false);
});
});
================================================
FILE: packages/hadron-auth/src/constants.ts
================================================
export const CONTAINER_NAME = 'isGranted';
================================================
FILE: packages/hadron-auth/src/declarations.d.ts
================================================
declare module 'glob-to-regexp';
================================================
FILE: packages/hadron-auth/src/helpers/flattenDeep.ts
================================================
const flattenDeep = (arr: any[]): any[] =>
Array.isArray(arr)
? arr.reduce((a, b) => [...flattenDeep(a), ...flattenDeep(b)], [])
: [arr];
export default flattenDeep;
================================================
FILE: packages/hadron-auth/src/helpers/urlGlob.ts
================================================
const countStars = (input: string) => {
try {
return input.match(/\*\*/g).length;
} catch (error) {
return 0;
}
};
export const convertToPattern = (url: string): string => {
const regexp = url[0] === '/' ? url.substring(1) : url;
if (url.endsWith('/*') && countStars(url) === 0) {
return `^/?${regexp.replace(/\/\*/g, '($|/$|/[^/]*$)')}$`;
}
if (url.endsWith('/**') && countStars(url) === 1) {
return `^/?${regexp.replace(/\/\*\*/g, '($|/.*$)')}$`;
}
if (countStars(url) === 0) {
return `^/?${url}$`;
}
return convertToPattern(regexp.replace('**', '[^/]*'));
};
const urlGlob = (pattern: string, input: string): boolean => {
const re = new RegExp(convertToPattern(pattern));
return re.test(input);
};
export default urlGlob;
================================================
FILE: packages/hadron-auth/src/hierarchyProvider.ts
================================================
export interface IRolesMap {
[s: string]: string[];
}
export interface IRole {
id: number | string;
name: string;
}
export interface IUser {
id: number | string;
username: string;
passwordHash: string;
roles: IRole[];
}
/**
* Function adds roles from dependency of other roles, to make sure that all roles are available
* @param roles available roles with all "dependent" ones
* @returns {IRolesMap}
*/
export function fillMissingRoles(roles: IRolesMap | string[]): IRolesMap {
if (roles instanceof Array) {
return roles.reduce(
(accumulator: IRolesMap, role: string) => ({
...accumulator,
[role]: [],
}),
{},
);
}
return Object.entries(roles).reduce(
(accumulator: IRolesMap, [key, value]: [string, string[]]) => {
accumulator[key] = value;
value.forEach((role: string) => {
if (!accumulator[role]) {
accumulator[role] = [];
}
});
return accumulator;
},
{} as IRolesMap,
);
}
/**
* Get array of all roles below in hierarchy distinctly
* @param userRoles
* @param availableRoles
* @returns {string[]}
*/
export function getDeeperRoles(userRoles: string[], availableRoles: IRolesMap) {
return userRoles
.filter((role: string) => !!availableRoles[role])
.reduce(
(accumulator: string[], role: string) => [
...accumulator,
...availableRoles[role].filter(
(roleToAdd) => !accumulator.includes(roleToAdd),
),
],
[],
);
}
/**
* Returns array of all given roles, without ones given in first parameter
* @param userRoles
* @param availableRoles
* @returns {string[]}
*/
export function excludeRoles(userRoles: string[], availableRoles: IRolesMap) {
return Object.entries(availableRoles)
.filter(([key, value]: [string, any]) => userRoles.indexOf(key) < 0)
.reduce(
(accumulator: object, [key, value]: [string, any]) => ({
...accumulator,
[key]: value,
}),
{},
);
}
/**
* Checks if user role contains required role
* @param userRoles
* @param requiredRole
* @param availableRoles
* @returns {boolean}
*/
export function checkRole(
userRoles: string[],
requiredRole: string,
availableRoles: IRolesMap,
): boolean {
if (userRoles.length <= 0) {
return false;
}
if (userRoles.indexOf(requiredRole) >= 0) {
return true;
}
return checkRole(
getDeeperRoles(userRoles, availableRoles),
requiredRole,
// excludes currently checked roles to avoid endless recurrency
excludeRoles(userRoles, availableRoles),
);
}
/**
* Checks list of roles
*
* @param userRoles
* @param requiredRoles
* @param availableRoles
* @param exact specifies if user needs all roles from requiredRoles (true), or only one of them (false)
* @return {boolean}
*/
export function checkRoles(
userRoles: string[],
requiredRoles: string[],
availableRoles: IRolesMap,
exact = false,
): boolean {
if (userRoles.length <= 0) {
return false;
}
return requiredRoles
.map(
(role) =>
typeof role === 'object'
? checkRoles(userRoles, role, availableRoles, true)
: checkRole(userRoles, role, availableRoles),
)
.reduce(
(accumulator, currentValue) =>
exact ? accumulator && currentValue : accumulator || currentValue,
);
}
/**
* Returns true if given roles are matching expected roles in hierarchy
* @param {IUser} user
* @param roles
* @param allRoles
* @returns {boolean}
*/
export function isGranted(
userRoles: string[],
roles: any,
allRoles: IRolesMap,
): boolean {
if (typeof roles === 'string') {
return checkRole(userRoles, roles, allRoles);
}
if (typeof roles === 'object') {
return checkRoles(userRoles, roles, allRoles);
}
throw new Error('Unknown role type');
}
/**
* Returns true if user has matching role in hierarchy
* @param {IUser} user
* @param roles
* @param allRoles
* @returns {boolean}
*/
export function isUserGranted(
user: IUser,
roles: any,
allRoles: IRolesMap,
): boolean {
return isGranted(user.roles.map((role) => role.name), roles, allRoles);
}
/**
* Provider for hierarchy manager
* @param rolesHierarchy
* @returns {function<boolean>}
*/
export default function hierarchyProvider(
rolesHierarchy: IRolesMap | string[],
) {
const fullRoles: IRolesMap = fillMissingRoles(rolesHierarchy);
return {
isGranted: (userRoles: string[], roles: any) =>
isGranted(userRoles, roles, fullRoles),
isUserGranted: (user: IUser, roles: any) =>
isUserGranted(user, roles, fullRoles),
};
}
================================================
FILE: packages/hadron-auth/src/password/IHashMethod.ts
================================================
export default interface IHashMethod {
hash(password: string, salt?: string, options?: object): Promise<string>;
compare(
userPassword: string,
hashedPassword: string,
salt?: string,
options?: object,
): Promise<boolean>;
};
================================================
FILE: packages/hadron-auth/src/password/__tests__/hashMethodProvider.ts
================================================
import hashMethodProvider from '../hashMethodProvider';
import { expect } from 'chai';
import * as sinon from 'sinon';
import ISecurityOptions from '../../ISecurityOptions';
import IHashMethod from '../IHashMethod';
describe('hashMethodProvider', () => {
const defaultOptions = {
roles: [],
} as ISecurityOptions;
it('should return bcrypt on default', () => {
const bcryptSpy = sinon.spy();
hashMethodProvider(defaultOptions, { bcrypt: bcryptSpy });
return expect(bcryptSpy.calledOnce).to.be.equal(true);
});
it('should return method, which name was defined in config', () => {
const testSpy = sinon.spy();
const options = {
...defaultOptions,
hash: {
method: 'testMethod',
},
} as ISecurityOptions;
hashMethodProvider(options, { testMethod: testSpy });
return expect(testSpy.calledOnce).to.be.equal(true);
});
it("should return default method, if method name from config doesn't exists", () => {
const bcryptSpy = sinon.spy();
const options = {
...defaultOptions,
hash: {
method: 'testMethod',
},
} as ISecurityOptions;
hashMethodProvider(options, { bcrypt: bcryptSpy });
return expect(bcryptSpy.calledOnce).to.be.equal(true);
});
it('should return method defined in config', () => {
const hashStub = sinon.spy();
const method = {
hash: hashStub,
compare: (userPassword: string, hashedPassword: string) =>
Promise.resolve(true),
} as IHashMethod;
const options = {
...defaultOptions,
hash: {
method,
},
} as ISecurityOptions;
hashMethodProvider(options).hash('smth');
return expect(hashStub.calledOnce).to.be.equal(true);
});
it('should return default method, if hash method defined in config is incorrect', () => {
const bcryptSpy = sinon.spy();
const method = {
hassh: (userPassword: string, hashedPassword: string) =>
Promise.resolve(true),
compaare: (userPassword: string, hashedPassword: string) =>
Promise.resolve(true),
};
const options = {
...defaultOptions,
hash: {
method,
},
};
hashMethodProvider(options as any, { bcrypt: bcryptSpy });
return expect(bcryptSpy.calledOnce).to.be.equal(true);
});
it('should pass options to hash method call of hashMethod', () => {
const hashStub = sinon.spy();
const method = {
hash: hashStub,
compare: (userPassword: string, hashedPassword: string) =>
Promise.resolve(true),
} as IHashMethod;
const options = {
...defaultOptions,
hash: {
method,
options: { lorem: 'ipsum' },
},
} as ISecurityOptions;
hashMethodProvider(options).hash('smth');
return expect(
hashStub.calledWith('smth', undefined, { lorem: 'ipsum' }),
).to.be.equal(true);
});
it('should pass options to compare method call of hashMethod', () => {
const compareStub = sinon.spy();
const method = {
hash: (userPassword: string) => Promise.resolve('password'),
compare: compareStub,
} as IHashMethod;
const options = {
...defaultOptions,
hash: {
method,
options: { lorem: 'ipsum' },
},
} as ISecurityOptions;
hashMethodProvider(options).compare('simple', 'hashed');
return expect(
compareStub.calledWith('simple', 'hashed', undefined, { lorem: 'ipsum' }),
).to.be.equal(true);
});
});
================================================
FILE: packages/hadron-auth/src/password/bcrypt/IBcryptOptions.ts
================================================
export default interface IBcryptOptions {
saltRounds?: number;
};
================================================
FILE: packages/hadron-auth/src/password/bcrypt/__tests__/bcrypt.ts
================================================
import { hash, compare } from '../bcrypt';
import * as bcrypt from 'bcrypt';
import { expect } from 'chai';
import * as sinon from 'sinon';
describe('bcrypt', () => {
describe('hash', () => {
let hashStub: any = null;
let genSaltStub: any = null;
before(() => {
hashStub = sinon.stub(bcrypt, 'hash');
genSaltStub = sinon.stub(bcrypt, 'genSalt');
});
beforeEach(() => {
hashStub.reset();
genSaltStub.reset();
hashStub.returns(Promise.resolve('h45h'));
genSaltStub.returns(Promise.resolve('54lt'));
});
after(() => {
hashStub.restore();
genSaltStub.restore();
});
it('should run bcrypt hash method, if salt given', () => {
const password = 'loremIpsum';
const salt = 'd0n7-b3-s0-s4l7y';
return hash(password, salt).then(() =>
expect(hashStub.calledWithExactly(password, salt)).to.be.equal(true),
);
});
it('should generate salt if none given', () => {
const password = 'loremIpsum';
return hash(password, null).then(() =>
expect(hashStub.calledWithExactly(password, '54lt')).to.be.equal(true),
);
});
it('should run genSalt method of bcrypt, if no salt was passed', () => {
const password = 'loremIpsum';
return hash(password, null).then(() =>
expect(genSaltStub.calledWith()).to.be.equal(true),
);
});
it('should run genSalt method of bcrypt, if no salt was passed, with saltRound property from options', () => {
const password = 'loremIpsum';
const options = {
saltRounds: 12,
};
return hash(password, null, options).then(() =>
expect(genSaltStub.calledWithExactly(12)).to.be.equal(true),
);
});
});
describe('compare', () => {
let compareStub: any = null;
before(() => {
compareStub = sinon.stub(bcrypt, 'compare');
});
beforeEach(() => {
compareStub.reset();
compareStub.returns(Promise.resolve(true));
});
after(() => {
compareStub.restore();
});
it('should run compare method', () =>
compare('loremIpsum', 'loremIpsum1').then(() =>
expect(
compareStub.calledWithExactly('loremIpsum', 'loremIpsum1'),
).to.be.equal(true),
));
it('should resolves with true if passwords are matching', () =>
expect(compare('loremIpsum', 'loremIpsum1')).to.be.eventually.equal(
true,
));
it('should resolves with false if passwords are not matching', () => {
compareStub.returns(Promise.resolve(false));
return expect(
compare('loremIpsum', 'loremIpsum1'),
).to.be.eventually.equal(false);
});
});
});
================================================
FILE: packages/hadron-auth/src/password/bcrypt/bcrypt.ts
================================================
import * as bcrypt from 'bcrypt';
import IHashMethod from '../IHashMethod';
import IBcryptOptions from './IBcryptOptions';
export function hash(
password: string,
salt?: string,
options: IBcryptOptions = {},
): Promise<string> {
if (!salt) {
return bcrypt
.genSalt(options.saltRounds)
.then((salt) => bcrypt.hash(password, salt));
}
return bcrypt.hash(password, salt);
}
export function compare(
userPassword: string,
hashedPassword: string,
options?: IBcryptOptions,
): Promise<boolean> {
return bcrypt.compare(userPassword, hashedPassword);
}
export default function bcryptProvider(options: IBcryptOptions): IHashMethod {
return {
hash: (password: string, salt?: string) => hash(password, salt, options),
compare: (userPassword: string, hashedPassword: string) =>
compare(userPassword, hashedPassword, options),
} as IHashMethod;
}
================================================
FILE: packages/hadron-auth/src/password/hashMethodProvider.ts
================================================
import bcrypt from './bcrypt/bcrypt';
import ISecurityOptions from '../ISecurityOptions';
import IHashMethod from './IHashMethod';
export interface IHashProviderMap {
[s: string]: (options?: object) => IHashMethod;
}
const availableMethod: IHashProviderMap = {
bcrypt,
};
/**
* Function checks if given object is implementing IHashMethod interface
* @param {IHashMethod | string} hashMethod
* @returns {boolean}
*/
function isHashMethod(
hashMethod: IHashMethod | string,
): hashMethod is IHashMethod {
return (
(hashMethod as IHashMethod).compare !== undefined &&
(hashMethod as IHashMethod).hash !== undefined
);
}
/**
* Funtion exctracts hashing method from config. Bcrypt on default.
* @param config
* @param methods
*/
export default function hashMethodProvider(
config: ISecurityOptions,
methods?: IHashProviderMap,
) {
const allMethods: IHashProviderMap = { ...availableMethod, ...methods };
if (config.hash) {
const { method, options } = config.hash;
if (typeof method === 'string' && allMethods[method]) {
return allMethods[method](options);
}
if (isHashMethod(method)) {
return {
hash: (password: string, salt?: string) =>
method.hash(password, salt, options),
compare: (
userPassword: string,
hashedPassword: string,
salt?: string,
) => method.compare(userPassword, hashedPassword, salt, options),
};
}
}
return allMethods.bcrypt();
}
================================================
FILE: packages/hadron-auth/src/providers/expressMiddlewareAuthorization.ts
================================================
import * as jwt from 'jsonwebtoken';
import { isRouteSecure, isAllowed } from '../HadronAuth';
const errorResponse = {
message: 'Unauthorized',
};
const expressMiddlewareAuthorization = (container: any) => {
return async (req: any, res: any, next: any) => {
try {
if (!isRouteSecure(req.path)) {
return next();
}
const userRepository = container.take('userRepository');
const roleRepository = container.take('roleRepository');
const token = req.headers.authorization.split(' ')[1];
const secret = container.take('authSecret');
const id: any = jwt.verify(token, secret);
const user = await userRepository.findOne({
where: { id },
relations: ['roles'],
});
if (!user) {
return res.status(401).json({ error: errorResponse });
}
const allRoles = await roleRepository.find();
if (
// @ts-ignore
isAllowed(req.path, req.method, user, allRoles.map((role) => role.name))
) {
return next();
}
return res.status(401).json({ error: errorResponse });
} catch (error) {
return res.status(401).json({ error: errorResponse });
}
};
};
export default expressMiddlewareAuthorization;
================================================
FILE: packages/hadron-auth/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"noEmitOnError": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"allowJs": true,
"paths": {
"*": ["./*"]
},
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSources": true,
"lib": ["es2017", "dom"]
},
"include": ["src/**/*.*", "index.ts", "LICENSE"],
"exclude": ["node_modules", "src/__tests__/**"]
}
================================================
FILE: packages/hadron-core/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-core/README.md
================================================
## Installation
* Install Node.js. We recommend using the latest version, installation details on [nodejs.org](https://nodejs.org)
* Install following modules from npm:
```bash
npm install @brainhubeu/hadron-core @brainhubeu/hadron-express express --save
```
## Hello world app
Let's start with traditional Hello World app. It will give you a quick grasp of the framework.
```javascript
const hadron = require('@brainhubeu/hadron-core').default;
const express = require('express');
const port = 8080;
const expressApp = express();
const config = {
routes: {
helloWorldRoute: {
path: '/',
callback: () => 'Hello world!',
methods: ['get'],
},
},
};
hadron(expressApp, [require('@brainhubeu/hadron-express')], config).then(() => {
expressApp.listen(port, () =>
console.log(`Listening on http://localhost:${port}`),
);
});
```
In the sections below, we will describe step by step what just happened.
## Bootstrapping an app
The main hadron-core function is responsible for bootstrapping the app. It registers packages based on passed config and server instance:
```javascript
const hadron = require('hadron-core').default;
hadron(serverInstance, [...packages], config);
```
The purpose of the main function is to initialize DI container and register package dependencies according to correspondent sections in config object (described in details in next chapters).
Main function returns a promise that resolves to created DI container instance. In the promise `.then()` method, besides performing operations on the container instance, we can actually start our server, by calling Express `listen` method:
```javascript
hadron(serverInstance, ...rest).then((container) => {
// do some things on container...
serverInstance.listen(PORT, callback);
});
```
Now, let's move to DI container itself.
## Dependency Injection
The whole framework is built around DI Container concept. Its purpose is to automatically supply proper arguments for routes callbacks and other framework's building blocks.
DI container instance is created and used internally by bootstrapping function, it is also returned (as a promise) from bootstrapping function, as mentioned in the previous section.
### Container methods
#### Registering items
```javascript
container.register(key, item, lifetime);
```
* `key` - item name on which it will be registered inside the container
* `item` - any value (primitive, data structure, function, class, etc.)
* `lifetime` - the type of item's life-span
Lifetime options:
* `'value'` - container returns registered item as is [default]
* `'singleton'` - returns always the same instance of registered class / constructor function
* `'transient'` - returns always a new instance of registered class / constructor function
#### Retrieving items
```javascript
container.take(key);
```
* `key` - item name (same as provided during registration)
The method returns item or item instance according to item type and lifetime option.
#### Example usage in bootstrapping function
```javascript
const { default: hadron, Lifetime } = require('hadron-core');
hadron(...args).then((container) => {
container.register('foo', 123);
container.register('bar', class Bar {}, Lifetime.Singleton);
container.register('baz', class Baz {}, Lifetime.Transient);
// other stuff...
});
```
### Accessing container items from routes' callbacks
To access container items from callbacks, you can just set arguments' names to match container keys, and required dependency will be provided.
See an example [here](../routing/#retrieving-items-from-container-in-callback)
================================================
FILE: packages/hadron-core/index.ts
================================================
import hadronCore from './src/hadronCore';
export { default as Container } from './src/container/container';
export * from './src/container/lifecycle';
export * from './src/container/types';
export default hadronCore;
================================================
FILE: packages/hadron-core/package.json
================================================
{
"name": "@brainhubeu/hadron-core",
"version": "1.0.0",
"description": "Hadron core module",
"main": "dist/index.js",
"files": [
"dist",
"./LICENSE"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"keywords": [
"hadron",
"brainhub",
"hadron-core"
],
"author": "Brainhub",
"license": "MIT",
"dependencies": {
"@brainhubeu/hadron-error-handler": "^1.0.0",
"@brainhubeu/hadron-utils": "^1.0.0",
"bunyan": "^1.8.12"
},
"publishConfig": {
"access": "public"
}
}
================================================
FILE: packages/hadron-core/src/__tests__/hadronCore.test.ts
================================================
import { expect } from 'chai';
import * as sinon from 'sinon';
import container from '../container/container';
import hadronCore, { prepareConfig } from '../hadronCore';
describe('prepareConfig()', () => {
const logger = { error: sinon.spy() };
const defaultConfig = { prop1: 1, prop2: 2 };
it('should accept object', () => {
const config = { prop2: 4, prop3: 5 };
const expectedResult = { prop1: 1, prop2: 4, prop3: 5 };
return prepareConfig(defaultConfig, config, logger).then((resolvedConfig) =>
expect(resolvedConfig).to.deep.equal(expectedResult),
);
});
it('should accept promise, which returns object', () => {
const config = new Promise((res, rej) => res({ prop2: 4, prop3: 5 }));
const expectedResult = { prop1: 1, prop2: 4, prop3: 5 };
return prepareConfig(defaultConfig, config, logger).then((resolvedConfig) =>
expect(resolvedConfig).to.deep.equal(expectedResult),
);
});
it('should log error when config promise is rejected', () => {
const config = new Promise((res, rej) => rej());
const loggerMock = { error: sinon.spy() };
return prepareConfig(defaultConfig, config, loggerMock).catch((error) =>
expect(loggerMock.error.called).to.be.eql(true),
);
});
it('should return default config when config promise is rejected', () => {
const config = new Promise((res, rej) => rej());
return prepareConfig(defaultConfig, config, logger).then((resolvedConfig) =>
expect(resolvedConfig).to.deep.equal(defaultConfig),
);
});
});
describe('hadronCore()', () => {
const mockRegister = sinon.stub(container, 'register');
beforeEach(() => {
return mockRegister.reset();
});
after(() => {
return mockRegister.restore();
});
it('should return promise with Container instance', () => {
return hadronCore({}).then((returnedContainer) =>
expect(returnedContainer).to.equal(container),
);
});
it('should register server in container', () => {
const server = { call: 'I am server!' };
return hadronCore(server).then((returnedContainer) =>
expect(mockRegister.calledWith('server', server)).to.be.eql(true),
);
});
it('should run register function from given package', () => {
const server = { call: 'I am server!' };
const mockPackage = { register: sinon.spy() };
return hadronCore(server, [mockPackage]).then((returnedContainer) =>
expect(mockPackage.register.calledOnce).to.be.eql(true),
);
});
it('should include given config to hadron configuration and pass it to register function of package', () => {
const testConfig = {
testField: 'I am test!',
};
const server = { call: 'I am server!' };
const mockPackage = { register: sinon.spy() };
return hadronCore(server, [mockPackage], testConfig).then(
(returnedContainer) =>
// second argument, which should be config
expect(mockPackage.register.args[0][1]).to.contain(testConfig),
);
});
});
================================================
FILE: packages/hadron-core/src/constants/eventNames.ts
================================================
export enum eventNames {
HANDLE_INITIALIZE_APPLICATION_EVENT = 'handleInitializeApplicationEvent',
HANDLE_TERMINATE_APPLICATION_EVENT = 'handleTerminateApplicationEvent',
}
================================================
FILE: packages/hadron-core/src/container/__tests__/container.ts
================================================
/* tslint:disable:max-classes-per-file */
import { expect } from 'chai';
import container from '../container';
import { Lifecycle } from '../lifecycle';
describe('container register', () => {
it('should overrive value for the the same key', () => {
const itemName = 'test';
container.register(itemName, 'given');
container.register(itemName, 'given2');
expect('given2').to.equal(container.take(itemName));
});
it('should always return the same object - Singleton', () => {
const itemName = 'test';
class Foo {
public value: string;
constructor() {
this.value = new Date().getTime().toString();
}
}
container.register(itemName, Foo, Lifecycle.Singleton);
const item1 = container.take(itemName);
const item2 = container.take(itemName);
expect(item2).to.deep.equal(item1);
});
it('should always return a new same object - Transient', () => {
class Foo {
public value: string;
constructor() {
this.value = 'xxxx';
}
}
container.register('test', Foo, Lifecycle.Transient);
const item1 = container.take('test');
const item2 = container.take('test');
expect(item1).to.deep.equal(item2);
});
});
describe('container items with parameters in constructor', () => {
it('second level of injection', () => {
class Foo {
public value: number;
constructor() {
this.value = 4;
}
}
class Foo2 {
public value: number;
constructor(parameterName: Foo) {
this.value = parameterName.value;
}
}
container.register('parameterName', Foo, Lifecycle.Transient);
container.register('foo2', Foo2, Lifecycle.Transient);
const item = container.take('parameterName') as Foo;
expect(4).to.be.equal(item.value);
});
});
describe("list of container's items keys ", () => {
it('should return array of keys of earlier registered items', () => {
container.register('key1', 'item1');
container.register('key2', 'item2');
const keys = container.keys();
expect(keys).to.include('key1');
expect(keys).to.include('key2');
});
});
================================================
FILE: packages/hadron-core/src/container/__tests__/containerItem.ts
================================================
/* tslint:disable:max-classes-per-file */
import { expect } from 'chai';
import containerItem from '../containerItem';
import { Lifecycle } from '../lifecycle';
describe('containerItem set lifecycle', () => {
it('should be default(value)', () => {
const item = containerItem.containerItemFactory('object', Object);
expect(item.constructor.name).to.equal('ContainerItem');
});
it('should be transient', () => {
const item = containerItem.containerItemFactory(
'object1',
Object,
'transient',
);
expect(item.constructor.name).to.equal('ContainerItemTransient');
});
it('should be singleton', () => {
const item = containerItem.containerItemFactory(
'object2',
Object,
Lifecycle.Singleton,
);
expect(item.constructor.name).to.equal('ContainerItemSingleton');
});
});
describe('containerItem set value', () => {
it('should return 1', () => {
const item = containerItem.containerItemFactory('number', 5);
expect(item.Item).to.equal(5);
});
it("should return 'oko'", () => {
const item = containerItem.containerItemFactory('string', 'oko');
expect(item.Item).to.equal('oko');
});
it("should return '{}'", () => {
const item = containerItem.containerItemFactory('object', {});
expect(item.Item).to.deep.equal({});
});
it('should return the same object twice', () => {
class Foo {
public value: string;
constructor() {
this.value = new Date().getTime().toString();
}
}
const item = containerItem.containerItemFactory(
'Fooooo',
Foo,
Lifecycle.Singleton,
);
const item1 = item.Item;
const item2 = item.Item;
expect(item1).to.equal(item2);
});
it('should always return new instance of given type', (done) => {
class Foo {
public value: string;
constructor() {
this.value = new Date().getTime().toString();
}
}
const item = containerItem.containerItemFactory(
'Foo',
Foo,
Lifecycle.Transient,
);
const item1 = item.Item;
setTimeout(() => {
const item2 = item.Item;
expect(item1.value).to.not.equal(item2.value);
done();
}, 10);
});
it('should always return new instance of given type 2', () => {
class Foo {
public value: string;
constructor() {
this.value = new Date().getTime().toString();
}
}
const item = containerItem.containerItemFactory(
'Foo',
Foo,
Lifecycle.Transient,
);
const item1 = item.Item;
const item2 = item.Item;
expect(item1).to.not.equal(item2);
});
});
================================================
FILE: packages/hadron-core/src/container/container.ts
================================================
import containerItem from './containerItem';
import { IContainerItem, IContainer } from './types';
import isVarName from '../helpers/isVarName';
import IncorrectContainerKeyNameError from '../errors/IncorrectContainerKeyNameError';
const containerRegister = new Array<IContainerItem>();
const takeContainerByKey = (key: string): IContainerItem[] =>
containerRegister.filter((x) => x.getKey() === key);
/* method for registering items in container */
/**
* Method for registering items in container, optionally setting a lifespan for items
* @param key for representing item in register, second use of the same key override previous item
* @param item stored item, can be any type: value, type, function....
* @param lifecycle setting type of life-span [value, singleton, transient] - value is default
*/
const register = (key: string, item: any, lifecycle?: string): void => {
if (!isVarName(key)) {
throw new IncorrectContainerKeyNameError(key);
}
const containerItems = takeContainerByKey(key);
if (containerItems.length === 0) {
const ci = containerItem.containerItemFactory(key, item, lifecycle);
containerRegister.push(ci);
} else {
containerItems[0].Item = item;
}
};
/** method for getting item from register
* Method which returns item previously registered for passed key
* @param key for representing item in register
* @return { any } return stored item
*/
const take = (key: string): any => {
const containerItems = takeContainerByKey(key);
return containerItems.length === 0 ? null : containerItems[0].Item;
};
/** method for getting all the keys in container
* @return array of keys
*/
const keys = (): string[] => containerRegister.map((x) => x.getKey());
const container = {
register,
take,
keys,
};
export default container as IContainer;
================================================
FILE: packages/hadron-core/src/container/containerItem.ts
================================================
import container from './container';
import { IContainerItem } from './types';
import { Lifecycle } from './lifecycle';
import { getArgs } from '@brainhubeu/hadron-utils';
export class ContainerItem implements IContainerItem {
// tslint:disable-next-line:variable-name
constructor(protected key: string, protected item: any) {}
get Item(): any {
return this.item;
}
set Item(item: any) {
this.item = item;
}
public getKey() {
return this.key;
}
public getArgs(): string[] {
return getArgs(this.item);
}
}
// tslint:disable-next-line:max-classes-per-file
class ContainerItemSingleton extends ContainerItem {
// tslint:disable-next-line:variable-name
private _itemInstanse: any;
constructor(key: string, item: any) {
super(key, item);
this._itemInstanse = null;
}
set Item(item: any) {
this.item = item;
}
get Item(): any {
const parameters = this.getArgs();
if (parameters.length > 0) {
const parameterInstances = parameters.map((paramName) =>
container.take(paramName),
);
if (this._itemInstanse === null) {
try {
this._itemInstanse = new this.item(...parameterInstances);
} catch (error) {
throw new Error(`can not create an instance of ${this.key}`);
}
}
return this._itemInstanse;
}
if (this._itemInstanse === null) {
try {
this._itemInstanse = new this.item();
} catch (error) {
throw new Error(`can not create an instance of ${this.key}`);
}
}
return this._itemInstanse;
}
}
// tslint:disable-next-line:max-classes-per-file
class ContainerItemTransient extends ContainerItem {
constructor(key: string, item: any) {
super(key, item);
}
set Item(item: any) {
this.item = item;
}
get Item(): any {
const parameters = this.getArgs();
if (parameters.length > 0) {
const parameterInstances = parameters.map((paramName) =>
container.take(paramName),
);
try {
return new this.item(...parameterInstances);
} catch (error) {
throw new Error(`can not create a new instance of ${this.key}`);
}
} else {
try {
return new this.item();
} catch (error) {
throw new Error(`can not create a new instance of ${this.key}`);
}
}
}
}
const containerItemFactory = (
key: string,
item: any,
lifecycle?: string,
): ContainerItem => {
switch (lifecycle) {
case Lifecycle.Singleton:
return new ContainerItemSingleton(key, item);
case Lifecycle.Transient:
return new ContainerItemTransient(key, item);
default:
return new ContainerItem(key, item);
}
};
export default { containerItemFactory };
================================================
FILE: packages/hadron-core/src/container/lifecycle.ts
================================================
enum Lifecycle {
Transient = 'transient',
Singleton = 'singleton',
Value = 'value',
}
export { Lifecycle };
================================================
FILE: packages/hadron-core/src/container/types.ts
================================================
interface IContainerItem {
Item(): any;
Item(key: string): void;
getKey(): string;
getArgs(): string[];
}
interface IContainer {
take: (key: string) => any;
register: (key: string, value: any, lifecycle?: string) => any;
keys: () => string[];
}
export { IContainerItem, IContainer };
================================================
FILE: packages/hadron-core/src/declarations.d.ts
================================================
declare module '@hadron/utils';
================================================
FILE: packages/hadron-core/src/errors/IncorrectContainerKeyNameError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class IncorrectContainerKeyNameError extends HadronErrorHandler {
constructor(key: string, err: Error = new Error()) {
super();
this.message = `The key name '${key}' is incorrect to register in container, should be a valid variable name.`;
this.name = 'IncorrectContainerKeyNameError';
this.stack = null;
this.error = err;
}
}
================================================
FILE: packages/hadron-core/src/errors/LoadingPackageError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class LoadingPackageError extends HadronErrorHandler {
constructor(err: Error) {
super();
this.message = `Problem with loading package`;
this.name = 'LoadingPackageError';
this.stack = null;
this.error = err;
}
}
================================================
FILE: packages/hadron-core/src/hadronCore.ts
================================================
import container from './container/container';
import { IContainer } from './container/types';
import { createLogger } from 'bunyan';
const hadronDefaultConfig = {};
export const prepareConfig = (
defaultConfig: object | Promise<object>,
config: object | Promise<object>,
logger: any,
) => {
return Promise.all([defaultConfig, config])
.then(([resolvedDefaultConfig, resolvedConfig]) => ({
...resolvedDefaultConfig,
...resolvedConfig,
}))
.catch((err) => {
logger.error(`Config promise rejected: ${err}`);
return defaultConfig;
});
};
export default (
server: any,
packages: any[] = [],
config: any = {},
): Promise<IContainer> => {
container.register('server', server);
const logger = createLogger({ name: 'hadron-logger' });
container.register('hadronLogger', logger);
return prepareConfig(hadronDefaultConfig, config, logger).then(
(hadronConfig) => {
return Promise.all(
packages
.filter(({ register }) => !!register)
.map(({ register }) => register(container, hadronConfig)),
).then(() => container);
},
);
};
================================================
FILE: packages/hadron-core/src/helpers/__tests__/isVarName.ts
================================================
import { assert } from 'chai';
import isVarName from '../isVarName';
describe('isVarName', () => {
it('when provided "variable" should return true', () => {
assert(isVarName('variable'));
});
it('when provided "someLongVariableNameMayBeCorrect" should return true', () => {
assert(isVarName('someLongVariableNameMayBeCorrect'));
});
it('when provided "delete" should return true', () => {
assert(isVarName('delete'));
});
it('when provided "var" should return true', () => {
assert(isVarName('var'));
});
it('when provided "do" should return true', () => {
assert(isVarName('do'));
});
it('when provided "1" should return false', () => {
assert(!isVarName('1'));
});
it('when provided "1variable" should return false', () => {
assert(!isVarName('1variable'));
});
it('when provided "-v" should return false', () => {
assert(!isVarName('-v'));
});
it('when provided "variable-name" should return false', () => {
assert(!isVarName('variable-name'));
});
it('when provided "variable name" should return false', () => {
assert(!isVarName('variable name'));
});
});
================================================
FILE: packages/hadron-core/src/helpers/isVarName.ts
================================================
// tslint:disable:no-eval
export default function isVarName(str: string): boolean {
if (typeof str !== 'string') {
return false;
}
if (str.trim() !== str) {
return false;
}
if (!isNaN(parseInt(str[0], null))) {
return false;
}
try {
eval(`var temp = { ${str}: null }`);
} catch (e) {
return false;
}
return true;
}
================================================
FILE: packages/hadron-core/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"noEmitOnError": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["./*"]
},
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSources": true,
"lib": ["es2017", "dom"]
},
"include": ["src/**/*.*", "index.ts", "LICENSE"],
"exclude": ["node_modules", "**/__tests__/**", "**/*/*.d.ts"]
}
================================================
FILE: packages/hadron-demo/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-demo/README.md
================================================
# Demo App for Hadron framework
To start just run
```bash
npm run start
```
<b>Requires TS-Node (for now)</b>
================================================
FILE: packages/hadron-demo/declarations.d.ts
================================================
declare module 'xmljson';
declare module 'glob';
declare module 'xml2js';
declare module '*.json' {
const value: any;
// @ts-ignore
export default value;
}
declare module '*.js' {
const value: any;
// @ts-ignore
export default value;
}
================================================
FILE: packages/hadron-demo/entity/Role.ts
================================================
import { IRole } from '@brainhubeu/hadron-auth';
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Role implements IRole {
@PrimaryGeneratedColumn() public id: string | number;
@Column({ type: 'text' })
public name: string;
}
================================================
FILE: packages/hadron-demo/entity/Team.ts
================================================
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './User';
@Entity()
export class Team {
@PrimaryGeneratedColumn() public id: number;
@Column({ type: 'text' })
public name: string;
@OneToMany((type) => User, (user) => user.team)
public users: User[];
}
================================================
FILE: packages/hadron-demo/entity/User.ts
================================================
import {
Column,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
ManyToMany,
JoinTable,
} from 'typeorm';
import { Team } from './Team';
import { IUser, IRole } from '@brainhubeu/hadron-auth';
import { Role } from './Role';
@Entity()
export class User implements IUser {
@PrimaryGeneratedColumn() public id: number;
@Column({ type: 'text' })
public username: string;
@Column({ type: 'text' })
public passwordHash: string;
@ManyToOne((type) => Team, (team) => team.users)
public team: Team;
@ManyToMany((type) => Role, {
cascadeInsert: true,
cascadeUpdate: true,
})
@JoinTable({ name: 'user_role' })
public roles: IRole[];
}
================================================
FILE: packages/hadron-demo/entity/validation/schemas.ts
================================================
import insertTeam = require('./team/insertTeam.json');
import updateTeam = require('./team/updateTeam.json');
import insertUser = require('./user/insertUser.json');
import updateUser = require('./user/updateUser.json');
export default {
insertTeam,
updateTeam,
insertUser,
updateUser,
};
================================================
FILE: packages/hadron-demo/entity/validation/team/insertTeam.json
================================================
{
"type": "object",
"properties": {
"teamName": {
"type": "string"
}
},
"required": ["teamName"],
"additionalProperties": false
}
================================================
FILE: packages/hadron-demo/entity/validation/team/updateTeam.json
================================================
{
"type": "object",
"properties": {
"id": {
"type": "number"
},
"teamName": {
"type": "string"
}
},
"required": ["id", "teamName"],
"additionalProperties": false
}
================================================
FILE: packages/hadron-demo/entity/validation/user/insertUser.json
================================================
{
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"teamId": {
"type": "number"
}
},
"required": ["username", "password", "teamId"],
"additionalProperties": false
}
================================================
FILE: packages/hadron-demo/entity/validation/user/updateUser.json
================================================
{
"type": "object",
"properties": {
"id": {
"type": "number"
},
"username": {
"type": "string"
},
"teamId": {
"type": "number"
}
},
"required": ["id", "username", "teamId"],
"additionalProperties": false
}
================================================
FILE: packages/hadron-demo/entity/validation/validate.ts
================================================
import validatorFactory from '../../../hadron-validation';
import schemas from './schemas';
export default validatorFactory(schemas);
================================================
FILE: packages/hadron-demo/event-emitter/case1/index.ts
================================================
import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
const config = {} as IEventsConfig;
const eventManager = eventManagerProvider(emitter, config);
const listeners = [
{
name: 'LISTENER',
event: 'testEvent', // event to listen to
handler: (callback: any, ...args: any[]) => {
const result = callback(...args);
return `${result}-changed`;
},
},
];
eventManager.registerEvents(listeners);
const callback = () => 'testcase';
const newCallback = eventManager.emitEvent('testEvent', callback);
newCallback();
================================================
FILE: packages/hadron-demo/event-emitter/case2/index.ts
================================================
import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
const config = {} as IEventsConfig;
const eventManager = eventManagerProvider(emitter, config);
const listeners = [
{
name: 'LISTENER',
event: 'testEvent', // event to listen to
handler: () => 'test console log',
},
];
eventManager.registerEvents(listeners);
const callback = () => 'testcase';
const newCallback = eventManager.emitEvent('testEvent', callback);
newCallback();
================================================
FILE: packages/hadron-demo/event-emitter/case3/index.ts
================================================
import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
const config = {} as IEventsConfig;
const eventManager = eventManagerProvider(emitter, config);
const listeners = [
{
name: 'LISTENER',
event: 'testEvent', // event to listen to
handler: (callback: any, ...args: any[]) => {
const time1 = Date.now();
callback(...args);
const time2 = Date.now();
return time2 - time1;
},
},
];
eventManager.registerEvents(listeners);
const callback = () => 'testcase';
const newCallback = eventManager.emitEvent('testEvent', callback);
newCallback();
================================================
FILE: packages/hadron-demo/event-emitter/config.ts
================================================
import { IEventsConfig } from '@brainhubeu/hadron-events';
const emitterConfig: IEventsConfig = {
listeners: [
{
name: 'LISTENER-1',
event: 'handleTerminateApplicationEvent', // event to listen to
handler: (callback: any, ...args: any[]) => {
const cb = () => {
callback(...args);
};
return cb();
},
},
{
name: 'LISTENER-2',
event: 'handleInitializeApplicationEvent', // event to listen to
handler: () => {
// console.log('-----------app started-----------')
},
},
],
};
export default emitterConfig;
================================================
FILE: packages/hadron-demo/express-demo/index.ts
================================================
import { Container as container } from '@brainhubeu/hadron-core';
const getDate = () => {
const d = new Date();
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
};
export default {
routes: {
//
// Basic Hello World
//
helloWorldRoute: {
path: '/',
callback: () => 'Hello world',
methods: ['get'],
},
//
// Route containing single middleware
//
singleMiddleware: {
path: '/singleMiddleware',
callback: () => `Hey! See console`,
methods: ['get'],
middleware: [
(req: any, res: any, next: any) => {
// tslint:disable:no-console
console.log(`Hello, it's me, the very first middleware!`);
next();
},
],
},
//
// Using multiple middlewares
//
multipleMiddlewares: {
path: '/multipleMiddlewares',
callback: () => `Hey! See console`,
methods: ['get'],
middleware: [
(req: any, res: any, next: any) => {
// tslint:disable:no-console
console.log(
`${getDate()}> First middleware, jump to next middleware in one second.`,
);
setTimeout(() => {
next();
}, 1000);
},
(req: any, res: any, next: any) => {
// tslint:disable:no-console
console.log(
`${getDate()}> Finally second middleware. One second passed i think !`,
);
next();
},
],
},
//
// Load value registered in container
//
containerKey: {
path: '/getContainerValue',
methods: ['get'],
callback: (req: any, { customValue }: any) => customValue,
},
//
// Load custom value registered in container
//
customContainerKey: {
path: '/getContainerValue/:key',
methods: ['get'],
callback: (req: any, { key }: { key: string }) => container.take(key),
},
//
// Display URL parameter
//
routeWithParam: {
path: '/:param',
methods: ['get'],
callback: ({ params }: any) => `First route param: ${params.param}`,
},
//
// Display multiple URL parameters
//
routeWithMultipleParams: {
path: '/:param/:param2',
methods: ['get'],
callback: ({ params }: any) =>
`First param value: ${params.param}; Second param value: ${
params.param2
}`,
},
//
// Register container value under specific key
//
registerCustomValue: {
path: '/:key',
methods: ['post'],
callback: ({ params, body }: any) => {
container.register(params.key, body.value);
return `Value under key '${params.key}' is registered`;
},
},
//
// Clear value under specific key in container
//
deleteCustomValue: {
path: '/:key',
methods: ['delete'],
callback: ({ params }: any) => {
container.register(params.key, null);
return `Value under key '${params.key}' has been deleted`;
},
},
},
};
================================================
FILE: packages/hadron-demo/index.ts
================================================
import * as bodyParser from 'body-parser';
import * as express from 'express';
import hadron, { IContainer } from '@brainhubeu/hadron-core';
import * as hadronEvents from '@brainhubeu/hadron-events';
import * as hadronSerialization from '@brainhubeu/hadron-serialization';
import * as hadronExpress from '@brainhubeu/hadron-express';
import * as hadronLogger from '@brainhubeu/hadron-logger';
import * as hadronTypeOrm from '@brainhubeu/hadron-typeorm';
import * as hadronAuth from '@brainhubeu/hadron-auth';
import jsonProvider from '@brainhubeu/hadron-json-provider';
import expressConfig from './express-demo';
import typeormConfig from './typeorm-demo/index';
import emitterConfig from './event-emitter/config';
import serializationRoutes from './serialization/routing';
import { setupSerializer } from './serialization/serialization-demo';
import 'reflect-metadata';
import securedRoutes from './security/securedRoutesConfig';
const port = process.env.PORT || 8080;
const expressApp = express();
expressApp.use(bodyParser.json());
jsonProvider([`${__dirname}/routing/*`], ['config.js']).then((routes: any) => {
const config = {
securedRoutes,
...typeormConfig,
...hadronLogger,
events: emitterConfig,
routes: {
...serializationRoutes,
...routes,
...expressConfig.routes,
},
};
hadron(
expressApp,
[
hadronAuth,
hadronEvents,
hadronSerialization,
hadronTypeOrm,
hadronExpress,
],
config,
)
.then((container: IContainer) => {
expressApp.use((req, res, next) =>
res.status(404).json('Endpoint not found.'),
);
container.register('customValue', 'From Brainhub with ❤️');
setupSerializer();
expressApp.listen(port);
})
.catch(console.log);
return;
});
================================================
FILE: packages/hadron-demo/logger/adapters/winstonAdapter.ts
================================================
import * as winston from 'winston';
import { ILogger } from '@brainhubeu/hadron-logger';
export default (config: any): ILogger => {
winston.loggers.add(config.name, config);
const logger = winston.loggers.get(config.name);
return {
log: (message: string) => {
logger.info(message);
},
debug: (message: string) => {
logger.debug(message);
},
warn: (message: string) => {
logger.warn(message);
},
error: (message: string) => {
logger.error(message);
},
};
};
================================================
FILE: packages/hadron-demo/logger/index.ts
================================================
export default {
logger: [
{
type: 'bunyan',
name: 'first logger',
},
{
type: 'winston',
name: 'second',
},
{
name: 'third ',
},
],
};
================================================
FILE: packages/hadron-demo/package.json
================================================
{
"name": "@brainhubeu/hadron-demo",
"version": "1.1.4",
"description": "Hadron demo example app",
"main": "dist/index.js",
"scripts": {
"start": "ts-node index.ts",
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"keywords": [
"hadron",
"brainhub",
"hadron-demo"
],
"dependencies": {
"@brainhubeu/hadron-auth": "^0.0.2",
"@brainhubeu/hadron-core": "^1.0.0",
"@brainhubeu/hadron-events": "^1.0.1",
"@brainhubeu/hadron-express": "^2.0.0",
"@brainhubeu/hadron-json-provider": "^1.0.0",
"@brainhubeu/hadron-logger": "^1.0.1",
"@brainhubeu/hadron-serialization": "^1.0.0",
"@brainhubeu/hadron-typeorm": "^1.0.3",
"@brainhubeu/hadron-utils": "^1.0.0",
"@brainhubeu/hadron-validation": "^1.0.0",
"bcrypt": "^2.0.1",
"body-parser": "1.18.2",
"cors": "2.8.4",
"dotenv": "4.0.0",
"express": "4.16.2",
"jsonwebtoken": "^8.2.2",
"multer": "1.3.0",
"mysql": "2.15.0",
"reflect-metadata": "^0.1.12",
"typeorm": "0.1.18"
},
"devDependencies": {
"@types/body-parser": "1.16.8",
"@types/cors": "2.8.3",
"@types/dotenv": "4.0.2",
"@types/express": "4.11.1",
"@types/multer": "1.3.6",
"@types/node": "9.6.0"
},
"author": "Brainhub",
"license": "MIT",
"publishConfig": {
"access": "public"
}
}
================================================
FILE: packages/hadron-demo/performance-tests/README.md
================================================
# Performance Tests
## Testing toolkit
Artillery - [Artillery website](https://artillery.io/)
## Basic commmands
* duration: x - phase will last for x seconds
* arrivalRate: x - x new virtual users will arrive every second in phase
## Example
insertUser.yml
```
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- loop:
- post:
url: '/insertUser'
json:
userName: 'Test'
teamId: 1
count: 3
```
## How to run
* First we need to install Artillery with npm
```
npm install -g artillery
```
* Now we can do a quick test:
```
artillery quick --count 10 -n 20 http://localhost:8080/user
```
Where: count - amount of virtual users, n - each virtual user will send 20 GET requests.
Of course to make it work on localhost:8080/\* **_we need to run hadron-demo with sql_**
* Artillery also provides test scripts as in example section. We can run test script with command:
```
artillery run filename.yml
```
## Output
```
All virtual users finished
Summary report @ 15:10:41(+0200) 2018-04-05
Scenarios launched: 609
Scenarios completed: 609
Requests completed: 1827
RPS sent: 29.64
Request latency:
min: 4.2
max: 4905.6
median: 184.7
p95: 2472.6
p99: 4358.4
Scenario counts:
Inserting, updating, deleting and searching teams: 609 (100%)
Codes:
200: 609
201: 1218
```
Where:
* **_Scenarios launched_** - number of virtual users created
* **_Scenarios completed_** - number of virtual users completed their scenarios
* **_Request completed_** - number of HTTP requests or responses sent
* **_RPS sent_** - average number of requests per second
* **_Request latency_** - are in milliseconds. (a request latency p99 value of 500ms means that 99 out of 100 requests took 500ms or less to complete)
================================================
FILE: packages/hadron-demo/performance-tests/teamService/getTeams.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- get:
url: '/team'
================================================
FILE: packages/hadron-demo/performance-tests/teamService/insertTeam.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- loop:
- post:
url: '/insertTeam'
json:
teamName: 'Test Team'
count: 3
================================================
FILE: packages/hadron-demo/performance-tests/teamService/team.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
- duration: 20
arrivalRate: 10
rampTo: 30
- duration: 30
arrivalRate: 5
scenarios:
- name: 'Inserting, updating, deleting and searching teams'
flow:
- post:
url: '/insertTeam'
json:
teamName: 'Team Test'
- put:
url: '/updateTeam'
json:
id: 2
teamName: 'Team 2'
- get:
url: '/team'
================================================
FILE: packages/hadron-demo/performance-tests/teamService/updateTeam.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- loop:
- put:
url: '/updateTeam'
json:
id: 1
teamName: 'Updated team!'
count: 3
================================================
FILE: packages/hadron-demo/performance-tests/userService/getUsers.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- get:
url: '/user'
================================================
FILE: packages/hadron-demo/performance-tests/userService/insertUser.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- loop:
- post:
url: '/insertUser'
json:
userName: 'Test'
teamId: 1
count: 3
================================================
FILE: packages/hadron-demo/performance-tests/userService/updateUser.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
defaults:
scenarios:
- flow:
- loop:
- put:
url: '/updateUser'
json:
id: 2
userName: 'Test'
teamId: 1
count: 3
================================================
FILE: packages/hadron-demo/performance-tests/userService/user.yml
================================================
config:
target: 'http://localhost:8080'
phases:
- duration: 10
arrivalRate: 5
- duration: 20
arrivalRate: 10
rampTo: 30
- duration: 30
arrivalRate: 5
scenarios:
- name: 'Inserting, updating, deleting and searching users'
flow:
- post:
url: '/insertUser'
json:
userName: 'Test'
teamId: 1
- put:
url: '/updateUser'
json:
id: 2
userName: 'BB'
teamId: 1
- delete:
url: '/deleteUser/3'
- get:
url: '/user'
================================================
FILE: packages/hadron-demo/routing/home.config.js
================================================
const helloWorldCallback = () => 'Hello world';
const versionCallback = () => `Version: ${process.env.VERSION || '0.0.1'}`;
const login = require('../security/loginRoute').default;
const homeConfig = () => {
return {
helloWorldRoute: {
callback: helloWorldCallback,
methods: ['GET'],
path: '/',
},
versionRoute: {
callback: versionCallback,
methods: ['get'],
path: '/version',
},
login: {
callback: login,
methods: ['POST'],
path: '/login',
},
};
};
module.exports = homeConfig;
================================================
FILE: packages/hadron-demo/routing/nested-routes.config.js
================================================
const testMiddleware = (req, res, next) => {
res.locals.injected = 'I was injected here!';
next();
};
const teamRoutsConfig = () => {
return {
nestedRoutes: {
callback: (req, container, locals) => ({
body: { response: 'Hello There!', locals },
}),
path: '/test/',
methods: ['GET'],
middleware: [testMiddleware],
routes: {
route1: {
callback: (req, container, locals) => ({
body: { response: 'General Kenobi', locals },
}),
methods: ['GET'],
path: '/route1/',
},
deepRoute1: {
callback: (req, container, locals) => ({
body: { response: 'You are a bold one!', locals },
}),
methods: ['POST'],
$middleware: [],
path: '/route2/',
},
route3: {
callback: (req, container, locals) => ({
body: { response: 'Kill him...', locals },
}),
methods: ['GET'],
middleware: [
(req, res, next) => {
res.locals.additionalInjection = 'Some tasty addition';
next();
},
],
$path: '/route3/',
routes: {
deepRoute1: {
path: '/deepRoute/',
callback: (req, container, locals) => ({
body: { response: 'Kill him...', locals },
}),
},
},
},
},
},
};
};
module.exports = teamRoutsConfig;
================================================
FILE: packages/hadron-demo/routing/team.config.js
================================================
const teamService = require('../services/teamService');
const teamRoutsConfig = () => {
return {
getTeams: {
callback: teamService.getAllTeams,
methods: ['GET'],
path: '/team/',
},
getTeamById: {
callback: teamService.getTeamById,
methods: ['GET'],
path: '/team/:id',
},
insertTeam: {
callback: teamService.insertTeam,
methods: ['POST'],
path: '/team',
},
updateTeam: {
callback: teamService.updateTeam,
methods: ['PUT'],
path: '/team',
},
deleteTeam: {
callback: teamService.deleteTeam,
methods: ['DELETE'],
path: '/team/:id',
},
};
};
module.exports = teamRoutsConfig;
================================================
FILE: packages/hadron-demo/routing/user.config.js
================================================
const userService = require('../services/userService');
const userRoutsConfig = () => {
return {
getAllUsers: {
callback: userService.getAllUsers,
methods: ['GET'],
path: '/user/',
},
getUserById: {
callback: userService.getUserById,
methods: ['GET'],
path: '/user/:id',
},
insertUser: {
callback: userService.insertUser,
methods: ['POST'],
path: '/user/',
},
updateUser: {
callback: userService.updateUser,
methods: ['PUT'],
path: '/user/',
},
deleteUser: {
callback: userService.deleteUser,
methods: ['DELETE'],
path: '/user/:id',
},
};
};
module.exports = userRoutsConfig;
================================================
FILE: packages/hadron-demo/security/loginRoute.ts
================================================
import * as jwt from 'jsonwebtoken';
import { bcrypt } from '@brainhubeu/hadron-auth';
const secret = process.env.JWT_SECRET || 'H4DR0N_S3CUR17Y';
const unauthorized = {
status: 403,
body: {
error: {
message: 'Unauthorized',
},
},
};
const login = async (req: any, { userRepository }) => {
try {
const user = await userRepository.findOne({
where: { username: req.body.username },
});
if (!user) {
return unauthorized;
}
const validPassword = await bcrypt.compare(
req.body.password,
user.passwordHash,
);
if (validPassword) {
const token = jwt.sign(
{
id: user.id,
username: user.username,
},
secret,
{
expiresIn: '2h',
},
);
return {
status: 200,
body: {
token,
},
};
}
return unauthorized;
} catch (error) {
return unauthorized;
}
};
export default login;
================================================
FILE: packages/hadron-demo/security/securedRoutesConfig.ts
================================================
const securedRoutesConfig = [
{
path: '/team/*',
roles: [['Admin', 'User'], 'Manager'],
},
{
path: '/user/*',
methods: ['GET'],
roles: ['NotExists', 'User', 'Admin'],
},
{
path: '/user/*',
methods: ['POST', 'PUT', 'DELETE'],
roles: 'Admin',
},
];
export default securedRoutesConfig;
================================================
FILE: packages/hadron-demo/serialization/routing/index.ts
================================================
import unicornsRoutes from './unicorns';
import princessesRoutes from './princesses';
export default {
...unicornsRoutes,
...princessesRoutes,
};
================================================
FILE: packages/hadron-demo/serialization/routing/princesses.ts
================================================
import { Container } from '@brainhubeu/hadron-core';
import { princesses } from '../unicorns-and-princesses';
import { ISerializer } from '@brainhubeu/hadron-serialization';
Container.register('princesses', princesses);
export default {
getPrincess: {
path: '/princesses/:name',
callback: ({ params }: any, { princesses }: any) => {
return {
body: princesses[params.name],
};
},
methods: ['get'],
},
getPrincessWithRole: {
path: '/princesses/:role/:name',
callback: ({ params }: any, { princesses, serializer }: any) => {
return {
body: serializer.serialize(
princesses[params.name],
[params.role],
'Princess',
),
};
},
methods: ['get'],
},
getPrincessWithRoleAndSerializer: {
path: '/princesses/:role/:name',
callback: ({ params }: any, { princesses, princessSerializer }: any) => {
return {
body: princessSerializer(princesses[params.name], [params.role]),
};
},
methods: ['get'],
},
};
================================================
FILE: packages/hadron-demo/serialization/routing/unicorns.ts
================================================
import { Container } from '@brainhubeu/hadron-core';
import { unicorns } from '../unicorns-and-princesses';
import { ISerializer } from '@brainhubeu/hadron-serialization';
Container.register('unicorns', unicorns);
export default {
getUnicorn: {
path: '/unicorns/:name',
callback: ({ params }: any, { unicorns }: any) => {
return { body: unicorns[params.name] };
},
methods: ['get'],
},
getUnicornWithRole: {
path: '/unicorns/:role/:name',
callback: ({ params }: any, { serializer, unicorns }: any) => {
return {
body: serializer.serialize(
unicorns[params.name],
[params.role],
'Unicorn',
),
};
},
methods: ['get'],
},
getUnicornWithRoleAndSerializer: {
path: '/unicorns/:role/:name',
callback: ({ params }: any, { unicorns, unicornSerializer }: any) => {
return {
body: unicornSerializer(unicorns[params.name], [params.role]),
};
},
methods: ['get'],
},
};
================================================
FILE: packages/hadron-demo/serialization/schemas/princess.json
================================================
{
"name": "Princess",
"properties": [
{ "name": "name", "type": "string" },
{ "name": "address", "type": "string", "groups": ["admin"] },
{ "name": "money", "type": "number", "parsers": ["currency"], "groups": ["admin"]},
{
"name": "friends",
"type": "array",
"properties": [
{ "name": "name", "type": "string" },
{ "name": "profession", "type": "string", "groups": ["admin"] },
{ "name": "salary", "type": "number", "parsers": ["currency"] }
]
}
]
}
================================================
FILE: packages/hadron-demo/serialization/schemas/unicorn.json
================================================
{
"name": "Unicorn",
"properties": [
{ "name": "name", "type": "string" },
{ "name": "hornLength", "type": "number", "groups": ["expert", "admin", "buyer"] },
{
"name": "magicPower",
"type": "object",
"groups": ["expert", "admin", "buyer"],
"properties" : [
{ "name": "name", "type": "string" },
{ "name": "power", "type": "number", "serializationName": "powerLevel" },
{ "name": "magicSchool", "type": "string", "groups": ["expert"] }
]
},
{ "name": "price", "type": "number", "parsers": ["currency"], "groups": ["buyer", "admin"]}
]
}
================================================
FILE: packages/hadron-demo/serialization/serialization-demo.ts
================================================
import { Container } from '@brainhubeu/hadron-core';
import {
schemaProvider,
CONTAINER_NAME,
ISerializer,
} from '@brainhubeu/hadron-serialization';
import { resolve } from 'path';
const paths = [resolve(__dirname, 'schemas/*')];
export const setupSerializer = () =>
schemaProvider(paths).then((schemas: any) => {
const serializer: ISerializer = Container.take(CONTAINER_NAME);
schemas.forEach(serializer.addSchema);
serializer.addParser((value: any) => `${value}$`, 'currency');
});
export const serializeUnicorn = (unicornData: any, groups: string[] = []) => {
const serializer = Container.take(CONTAINER_NAME) as ISerializer;
return serializer.serialize(unicornData, groups, 'Unicorn');
};
Container.register('unicornSerializer', serializeUnicorn);
export const serializePrincess = (unicornData: any, groups: string[] = []) => {
const serializer = Container.take(CONTAINER_NAME) as ISerializer;
return serializer.serialize(unicornData, groups, 'Princess');
};
Container.register('princessSerializer', serializePrincess);
================================================
FILE: packages/hadron-demo/serialization/unicorns-and-princesses.ts
================================================
export const unicorns = {
arthur: {
hornLength: '20',
id: '10002',
magicPower: {
magicSchool: 'Fake',
name: 'Power of Truth',
power: '12',
usability: '0',
},
name: 'RainbowHoof',
price: '2100',
secretName: 'RainbowFart',
},
cssdash: {
hornLength: '13',
id: 'd4sh',
magicPower: {
magicSchool: 'CSS',
name: 'Power of Vertically Align Things',
power: '8',
usability: '100',
},
name: 'Css Dash',
price: '10000',
psychologyProfile: 'Suicide Thoughts',
},
};
export class Princess {
public address: any = null;
public friends: any = null;
public id: any = null;
public money: any = null;
public name: any = null;
constructor({ address, friends, id, money, name }: any) {
this.address = address;
this.friends = friends;
this.id = id;
this.money = money;
this.name = name;
}
}
export const princesses = {
jasmine: new Princess({
address: 'Górnych Wałów 26/5',
friends: [
{ name: 'Francesca', salary: '5120', profession: 'Cooker', id: '123' },
{ name: 'Marina', salary: '2010', profession: 'Gardener' },
{ name: 'Robin', salary: '0', profession: 'Crime Fighter' },
],
id: '10002',
money: '21000',
name: 'Jasmine',
}),
veronica: new Princess({
address: 'Red Lanterns 66/6',
friends: [
{
name: 'Hilary',
salary: '6000',
profession: 'Not President',
id: '123',
},
{
name: 'Andrzej L',
salary: '3678.23',
profession: 'Farmer',
lpr: false,
},
],
id: 'RT123',
money: '12000',
name: 'Veronic',
}),
jozin: new Princess({
address: 'Bazin',
friends: [],
id: '222',
money: 0,
name: 'Jozin',
}),
};
================================================
FILE: packages/hadron-demo/services/teamService.ts
================================================
import { Team } from '../entity/Team';
import { User } from '../entity/User';
import { Repository } from 'typeorm';
import validate from '../entity/validation/validate';
class TeamDto {
constructor(public id: number, public name: string, public amount: number) {}
}
const getAllTeams = async (req, { teamRepository }) => {
const teams = await teamRepository.find({ relations: ['users'] });
return {
body: teams.map(
(team) => new TeamDto(team.id, team.name, team.users.length),
),
};
};
const getTeamById = async ({ params }, { teamRepository }) => {
return {
body: await teamRepository.findOneById(params.id),
};
};
const updateTeam = async ({ body }, { teamRepository }) => {
try {
await validate('updateTeam', body);
const team = teamRepository.findOneById(body.id);
team.name = body.teamName;
await teamRepository.save(team);
return {
body: { message: `team id: ${body.id} has new name ${body.teamName}` },
};
} catch (error) {
return {
status: 400,
body: { error: error.message },
};
}
};
const insertTeam = async ({ body }, { teamRepository }) => {
try {
await validate('insertTeam', body);
await teamRepository.insert({ name: body.teamName });
const amount = await teamRepository.count();
return {
status: 201,
body: { message: `total amount of teams: ${amount}` },
};
} catch (error) {
return {
status: 400,
body: { error: error.message },
};
}
};
const deleteTeam = async (
{ params }: any,
{ teamRepository, userRepository }: any,
) => {
const team = await teamRepository.findOneById(params.id, {
relations: ['users'],
});
await userRepository.removeByIds(team.users.map((user) => user.id));
return {
body: await teamRepository.removeById(params.id),
};
};
export { getAllTeams, getTeamById, updateTeam, insertTeam, deleteTeam };
================================================
FILE: packages/hadron-demo/services/userService.ts
================================================
import { User } from '../entity/User';
import validate from '../entity/validation/validate';
import { Container } from '@brainhubeu/hadron-core';
import { bcrypt } from '@brainhubeu/hadron-auth';
import { Role } from '../entity/Role';
class UserDto {
constructor(
public id: number,
public username: string,
public teamName: string,
public roles: Role[],
) {}
}
const getAllUsers = async (req, { userRepository }) => {
const users = await userRepository.find({ relations: ['team', 'roles'] });
return {
body: users.map(
(user: User) =>
new UserDto(user.id, user.username, user.team.name, user.roles),
),
};
};
const getUserById = async ({ params }, { userRepository }) => {
return {
body: await userRepository.findOneById(params.id),
};
};
const insertUser = async (
req,
{ teamRepository, userRepository, roleRepository },
) => {
try {
await validate('insertUser', req.body);
const existingUser = await userRepository.findOne({
username: req.body.username,
});
if (existingUser) {
throw new Error(`User: ${existingUser.username} already exists.`);
}
const team = await teamRepository.findOneById(req.body.teamId);
const userRole = await roleRepository.findOne({ name: 'User' });
const password = await bcrypt.hash(req.body.password);
const user = new User();
user.username = req.body.username;
user.passwordHash = password;
user.team = team;
user.roles = [userRole];
await userRepository.insert(user);
const amount = await userRepository.count();
return {
status: 201,
body: { message: `Amount of users: ${amount}` },
};
} catch (error) {
return {
status: 400,
body: { error: error.message },
};
}
};
const updateUser = async ({ body }, { userRepository }) => {
try {
await validate('updateUser', body);
const user = await userRepository.findOneById(body.id);
user.username = body.username;
await userRepository.save(user);
return {
body: { message: `user id: ${body.id} has new name: ${body.username}` },
};
} catch (error) {
return {
status: 400,
body: { error: error.message },
};
}
};
const deleteUser = async ({ params }, { userRepository }) => {
return {
body: await userRepository.removeById(params.id),
};
};
export {
UserDto,
getAllUsers,
getUserById,
insertUser,
updateUser,
deleteUser,
};
================================================
FILE: packages/hadron-demo/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"noEmitOnError": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["./*"]
},
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSources": true,
"lib": ["es2017", "dom"]
},
"include": ["src/**/*.*", "index.ts", "LICENSE"],
"exclude": ["node_modules", "**/__tests__/**", "**/*/*.d.ts"]
}
================================================
FILE: packages/hadron-demo/typeorm-demo/index.ts
================================================
import { ConnectionOptions } from 'typeorm';
import { User } from '../entity/User';
import { Team } from '../entity/Team';
import { Role } from '../entity/Role';
const connection: ConnectionOptions = {
name: 'mysql-connection',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'my-secret-pw',
database: 'test',
entities: [User, Team, Role],
synchronize: true,
};
export default {
connection,
entities: [User, Team, Role],
};
================================================
FILE: packages/hadron-error-handler/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-error-handler/README.md
================================================
# Default Error for Hadron
<b>(Currently there is nothing to see here)</b>
================================================
FILE: packages/hadron-error-handler/index.ts
================================================
import HadronError from './src/errorHandler';
export { HadronError as default };
================================================
FILE: packages/hadron-error-handler/package.json
================================================
{
"name": "@brainhubeu/hadron-error-handler",
"version": "1.0.0",
"description": "Error handler for hadron",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"files": [
"dist",
"./LICENSE"
],
"keywords": [
"error",
"hadron"
],
"author": "Brainhub",
"license": "MIT",
"publishConfig": {
"access": "public"
}
}
================================================
FILE: packages/hadron-error-handler/src/errorHandler.ts
================================================
class HadronError extends Error {
public error?: Error;
constructor(message: string = 'Hadron unhandled error') {
super(message);
this.stack = new Error().stack;
}
}
export default HadronError;
================================================
FILE: packages/hadron-error-handler/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"noEmitOnError": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["./*"]
},
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSources": true,
"lib": ["es2017", "dom"]
},
"include": ["src/**/*.*", "index.ts", "LICENSE"],
"exclude": ["node_modules", "**/__tests__/**", "**/*/*.d.ts"]
}
================================================
FILE: packages/hadron-events/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-events/README.md
================================================
## Installation
```bash
npm install @brainhubeu/hadron-events --save
```
[More info about installation](/core/#installation)
## Overview
Event Manager is a tool which allows manipulating Hadron's default behavior without the need to change the code base. It can be achieved via custom listeners defined by the developer. There are a bunch of extension points spread all over the hadron framework where listeners can be hooked up.
## Initializing
Pass package as an argument for hadron bootstrapping function:
```javascript
const hadronEvents = require('@brainhubeu/hadron-events');
// ... importing and initializing other components
hadron(expressApp, [hadronEvents], config).then(() => {
console.log('Hadron with eventManager initialized');
});
```
After initialization you can retrieve event manager from DI container - it is registered under the key `eventManager`.
## Event Manager methods
### Registering listeners for events
```javascript
eventManager.registerEvents(listeners);
```
* `listeners` - an array of objects which have to follow convention showed below:
```javascript
{
name: 'string', // listener name
event: 'string', // event to register to
handler: 'function' // function to handle the event
}
```
Example:
```javascript
const config = {
events: {
listeners: [
{
name: 'Listener1',
event: 'createRoutesEvent',
handler: (callback, ...args) => {
const myCustomCallback = () => {
console.log("Hey! I've changed the original hadron function!");
return callback(...args);
};
return myCustomCallback();
},
},
{
name: 'Listener2',
event: 'myCustomEvent',
handler: (callback, ...args) => {
const myCustomCallback = () => {
console.log('My custom event!');
return callback(...args);
};
return myCustomCallback();
},
},
],
},
};
hadron(app, [hadronEvents], config).then((container) => {
container.take('eventManager').emitEvent('myCustomEvent'); // "My custom event!"
});
```
### Emitting events
```javascript
eventEmitter.emitEvent(eventName);
```
Calls all listeners handlers registered for the event with event name passed to it.
* `eventName` - name of the event which will be fired
## Listeners
You can create your listeners in the main config file.
As a first argument listener's handler method will receive a callback function originally called by hadron, so you can change/override it however you want and then return a call of newly created function or a call of existing callback if you don't want to change it.
To be able to receive callback mentioned above, the first argument should be named exactly `callback`, otherwise, you will not receive the callback.
You can also, define your listener's handler without `callback` argument or even without any arguments, which is also a valid way to create listeners, you just won't be able to access the callback.
The second argument of listeners handler method is `...args`, which can be used as arguments for the callback function.
An example of a listener:
```javascript
{
name: 'Listener',
event: 'createRoutesEvent',
handler: (callback, ...args) => {
const myCustomCallback = () => {
console.log("Hey! I've changed the original hadron function!");
return callback(...args);
}
return myCustomCallback();
}
}
```
## Extension points in hadron
As said before, there are a couple of extension points in the hadron framework to which you can hook up your listeners.
The extension depends from packages that You are using and are listed below:
--- hadron-express
`HANDLE_REQUEST_CALLBACK_EVENT`
Event fires, before route callback function is called, passes route callback to the listener.
Example:
```javascript
const ExpressEvent = require('@brainhubeu/hadron-express').Event;
const listeners = [
{
name: 'Listener',
event: ExpressEvent.HANDLE_REQUEST_CALLBACK_EVENT, // or simply event: 'HANDLE_REQUEST_CALLBACK_EVENT'
handler: (callback, ...args) => {
console.log('Request Handled!');
callback(...args);
},
},
];
```
---
`HANDLE_TERMINATE_APPLICATION_EVENT`
Event fires when the application is terminated with <kbd>CTRL</kbd> + <kbd>C</kbd>, passes default hadron callback to the listener.
```javascript
const Event = require('@brainhubeu/hadron-events').Event;
const listeners = [
{
name: 'Listener',
event: Event.HANDLE_TERMINATE_APPLICATION_EVENT, // or simply event: 'HANDLE_TERMINATE_APPLICATION_EVENT'
handler: () => {
console.log('Application is going to close');
},
},
];
```
================================================
FILE: packages/hadron-events/index.ts
================================================
import { EventEmitter } from 'events';
import { Lifecycle, IContainer } from '@brainhubeu/hadron-core';
import eventManagerProvider from './src/eventManagerProvider';
import { IHadronEventsConfig } from './src/types';
import registerProcessEvents from './src/registerProcessEvents';
export * from './src/types';
export * from './src/constants';
export default eventManagerProvider;
export const register = (
container: IContainer,
config: IHadronEventsConfig,
) => {
if (container.take('eventEmitter') === null) {
container.register('eventEmitter', EventEmitter, Lifecycle.Singleton);
}
const eventManager = eventManagerProvider(
container.take('eventEmitter'),
config.events,
);
eventManager.registerEvents(config.events.listeners);
container.register('eventManager', eventManager);
registerProcessEvents(eventManager);
};
================================================
FILE: packages/hadron-events/package.json
================================================
{
"name": "@brainhubeu/hadron-events",
"version": "1.0.1",
"description": "Hadron event emitter module",
"main": "dist/index.js",
"files": [
"dist",
"./LICENSE"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"keywords": [
"hadron",
"brainhub",
"hadron-events"
],
"author": "Brainhub",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@brainhubeu/hadron-core": "^1.0.0",
"@brainhubeu/hadron-utils": "^1.0.0"
},
"devDependencies": {
"@types/events": "1.2.0"
}
}
================================================
FILE: packages/hadron-events/src/__tests__/eventManagerProvider.ts
================================================
import { expect } from 'chai';
import { EventEmitter } from 'events';
import * as sinon from 'sinon';
import { IEventListener, IEventsConfig } from '../types';
import eventManagerProvider from '../eventManagerProvider';
describe('events registration', () => {
let emitter: EventEmitter = null;
beforeEach(() => {
emitter = new EventEmitter();
});
afterEach(() => {
emitter = null;
});
it('throws an error if eventName is either null or empty string', () => {
const listeners: IEventListener[] = [
{
name: 'my-listener-1',
event: '', // event to listen to
handler: (callback, ...args) => {
return callback(...args);
},
},
{
name: 'my-listener-2',
event: 'someEvent',
handler: (callback, ...args) => {
return callback(...args);
},
},
{
name: 'my-listener-3',
event: 'someEvent', // event to listen to
handler: (callback, ...args) => {
return callback(...args);
},
},
];
const config = {} as IEventsConfig;
const eventManager = eventManagerProvider(emitter, config);
expect(() => eventManager.registerEvents(listeners)).to.throw();
});
it('registers listeners', () => {
const spy1 = () => sinon.spy();
const spy2 = (callback: any, ...args: any[]) => sinon.spy();
const listeners = [
{
name: 'my-listener-1',
event: 'someEvent', // event to listen to
handler: spy1,
},
{
name: 'my-listener-2',
event: 'someEvent',
handler: spy2,
},
];
const eventManager = eventManagerProvider(emitter, { listeners });
eventManager.registerEvents(listeners);
expect(emitter.listeners('someEvent').length).to.equal(2);
expect(emitter.listeners('someEvent')[0]).to.equal(spy1);
expect(emitter.listeners('someEvent')[1]).to.equal(spy2);
});
});
/////////////////////////////////////////////////////
describe('events emitting', () => {
let emitter: EventEmitter = null;
let eventManager: any = null;
beforeEach(() => {
emitter = new EventEmitter();
});
afterEach(() => {
emitter = null;
eventManager = null;
});
it('throws error when eventName argument is either null or empty string', () => {
const listeners: IEventListener[] = [
{
name: 'my-listener-1',
event: 'someEvent', // event to listen to
handler: () => {
return 'test';
},
},
{
name: 'my-listener-2',
event: 'someEvent',
handler: () => {
return 'test';
},
},
{
name: 'my-listener-3',
event: 'changeCallbackEvent', // event to listen to
handler: (callback, ...args) => {
const newCallback = (...args: any[]) => {
return 'changed';
};
return newCallback(...args);
},
},
];
const config = {} as IEventsConfig;
eventManager = eventManagerProvider(emitter, config);
eventManager.registerEvents(listeners);
const callback = () => 'test';
expect(() => eventManager.emitEvent('', callback)).throw();
});
it('calls emitter.listeners with eventName argument', () => {
const listeners: IEventListener[] = [];
const config = {} as IEventsConfig;
eventManager = eventManagerProvider(emitter, config);
eventManager.registerEvents(listeners);
const eventName = 'someEvent';
const listenersMethodSpy = sinon.spy(emitter, 'listeners');
const callback = () => 'test';
eventManager.emitEvent(eventName, callback);
expect(listenersMethodSpy.alwaysCalledWithExactly(eventName)).to.equal(
true,
);
});
it('returns new callback function based on event listeners', () => {
const listeners: IEventListener[] = [
{
name: 'my-listener-3',
event: 'changeCallbackEvent', // event to listen to
handler: (callback, ...args) => {
const newCallback = (...args: any[]) => {
return 'changed';
};
return newCallback(...args);
},
},
];
const config = {} as IEventsConfig;
eventManager = eventManagerProvider(emitter, config);
eventManager.registerEvents(listeners);
const callback = () => 'original function';
const cb = eventManager.emitEvent('changeCallbackEvent', callback);
expect(cb()).to.equal('changed');
});
it('calls listeners handlers without "callback" argument', () => {
const spy1 = sinon.spy();
const spy2 = sinon.spy();
const listenersWithoutCallback: IEventListener[] = [
{
name: 'my-listener-1',
event: 'someEvent', // event to listen to
handler: (callback, ...args) => {
return callback(...args);
},
},
{
name: 'my-listener-2',
event: 'someEvent',
handler: () => spy2(),
},
];
eventManager = eventManagerProvider(emitter, {
listeners: listenersWithoutCallback,
});
eventManager.registerEvents(listenersWithoutCallback);
const callback = () => 'test';
eventManager.emitEvent('someEvent', callback)();
return expect(spy1.calledOnce) && expect(spy2.calledOnce);
});
it('works if handler returns callback call', () => {
const callback = () => 'test';
const listeners: IEventListener[] = [
{
name: 'my-listener-1',
event: 'someEvent', // event to listen to
handler: (callback, ...args) => {
return callback(...args);
},
},
];
const config = {} as IEventsConfig;
eventManager = eventManagerProvider(emitter, config);
eventManager.registerEvents(listeners);
const eventFunc = eventManager.emitEvent('someEvent', callback);
expect(eventFunc()).to.equal(callback());
});
it('does not throw an error when callback parameter is not passed', () => {
const listeners: IEventListener[] = [
{
name: 'my-listener-1',
event: 'someEvent', // event to listen to
handler: () => {
return null;
},
},
];
const config = {} as IEventsConfig;
eventManager = eventManagerProvider(emitter, config);
eventManager.registerEvents(listeners);
expect(eventManager.emitEvent('someEvent')).to.not.throw();
});
});
================================================
FILE: packages/hadron-events/src/constants.ts
================================================
export enum Event {
HANDLE_TERMINATE_APPLICATION_EVENT = 'HANDLE_TERMINATE_APPLICATION_EVENT',
}
================================================
FILE: packages/hadron-events/src/eventManagerProvider.ts
================================================
import { hasFunctionArgument } from './helpers/functionHelper';
import {
IEventEmitter,
IEventListener,
CallbackEvent,
IEventsConfig,
IEventManager,
} from './types';
/**
* Provider function to inject emitter and config into variable scope
* @param emitter event emitter
* @param config config parameters
*/
const eventManagerProvider = (emitter: IEventEmitter, config: IEventsConfig) =>
({
registerEvents: (listeners: IEventListener[]) => {
listeners.forEach((listener: IEventListener) => {
if (listener.event === '' || listener.event === null) {
throw new Error('eventName can not be empty');
}
emitter.on(listener.event, listener.handler);
});
},
emitEvent: (eventName: string, callback: CallbackEvent) => {
if (eventName === '' || eventName === null) {
throw new Error('eventName can not be empty');
}
if (callback === undefined || callback === null) {
callback = () => null;
}
return emitter
.listeners(eventName)
.reduce((prevCallback, currentHandler) => {
// is first argument called "callback?"
if (!hasFunctionArgument(currentHandler, 'callback')) {
return (...args: any[]) => {
currentHandler(...args);
// manually run callback
return prevCallback(...args);
};
}
return (...args: any[]) => currentHandler(prevCallback, ...args);
}, callback);
},
} as IEventManager);
export default eventManagerProvider;
================================================
FILE: packages/hadron-events/src/helpers/functionHelper.ts
================================================
import { getArgs } from '@brainhubeu/hadron-utils';
// tslint:disable-next-line:ban-types
function hasFunctionArgument(func: (args: any) => any, argumentName: string) {
return getArgs(func).indexOf(argumentName) >= 0;
}
export { hasFunctionArgument };
================================================
FILE: packages/hadron-events/src/registerProcessEvents.ts
================================================
import { IEventManager } from './types';
import { Event } from './constants';
export default (eventEmitter: IEventManager) => {
process.on('exit', () => {
eventEmitter.emitEvent(Event.HANDLE_TERMINATE_APPLICATION_EVENT);
});
};
================================================
FILE: packages/hadron-events/src/types.ts
================================================
export type CallbackEvent = (...args: any[]) => any;
export type EventHandler = (callback: CallbackEvent, ...args: any[]) => any;
export interface IEventEmitter {
listeners: (event: string) => any[];
on: (eventName: string, handler: EventHandler) => void;
emit: (eventName: string, event: object) => void;
}
export interface IEventListener {
name: string;
event: string;
handler: EventHandler;
}
export interface IHadronEventsConfig {
events: IEventsConfig;
}
export interface IEventsConfig {
listeners: IEventListener[];
}
export interface IEventManager {
registerEvents: (listeners: IEventListener[]) => null;
emitEvent: (eventName: string, callback?: CallbackEvent) => null;
}
================================================
FILE: packages/hadron-events/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"noEmitOnError": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["./*"]
},
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSources": true,
"lib": ["es2017", "dom"]
},
"include": ["src/**/*.*", "index.ts", "LICENSE"],
"exclude": ["node_modules", "**/__tests__/**", "**/*/*.d.ts"]
}
================================================
FILE: packages/hadron-express/LICENSE
================================================
MIT License
Copyright (c) 2018 Brainhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: packages/hadron-express/README.md
================================================
## Installation
INFO: Currently routing with hadron works only with the Express framework.
```bash
npm install @brainhubeu/hadron-express --save
```
[More info about installation](/core/#installation)
## Express integration
We need to include `hadron-express` package while initializing hadron.
```javascript
const express = require('express');
const bodyParser = require('body-parser');
const port = process.env.PORT || 8080;
const expressApp = express();
expressApp.use(bodyParser.json());
hadron(expressApp, [require('../hadron-express')], config).then((container) => {
expressApp.listen(port);
});
```
## Basic routing setup
To set up routes with Hadron, we are able to include them as objects in config object under key `routes`.
```javascript
const config = {
routes: {
helloWorldRoute: {
callback: () ='Hello world !',
methods: ['GET'],
path: '/',
},
},
};
```
Basic, required structure of route config object includes:
* `callback` - function called when request is made, returned value will be send as a response (except if you call `res` methods directly)
* `methods` - array of [HTTP methods](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)
* `path` - route path
## Callback
The callback function can take route parameters as an arguments. Hadron also allows us to grab a container value easily.
```javascript
routeWithParam: {
callback: (firstParam) => `firstParam value: ${firstParam}`,
methods: ['GET'],
path: '/:firstParam',
}
```
Using this simple example, if we send a request, for example `http://localhost/foobar` will provide a response as below:
```json
"firstParam value: foobar"
```
---
When you would like to implement multiple route parameters, their order as arguments in callback does not matter, argument name needs only to match parameter name.
```javascript
multipleParams: {
callback: (secondParam, firstParam) =`${firstParam} ${secondParam}`,
methods: ['GET'],
path: '/:firstParam/:secondParam',
}
```
GET request with path: `http://localhost/Hello/World` will result with following response:
```json
"Hello World"
```
### Locals (available from 2.0.0)
As a third parameter, hadron delivers `locals` from your response. You can inject its content in middlewares, e.g.
```javascript
const route = {
callback: (req, container, locals) => locals.testValue,
middlewares: [
(req, res, next) => {
res.locals.testValue = 'I am test!';
next();
},
],
};
```
## Retrieving items from container in callback
Callback function provides a simple way to retrieve items from container with ease. Simply set item's key as callback function's argument. Let's see an example below:
```javascript
hadron(expressApp, [require('../hadron-express')], {
routes: {
routeWithContainerValue: {
// sayHello argument will refer to container value
callback: (sayHello = `hadron says: ${sayHello}`),
methods: ['GET'],
path: '/',
},
},
}).then((container) => {
// Register value under key sayHello
container.register('sayHello', 'Hello World');
});
```
After sending a request to the `/` path, the response will look like that:
```json
"hadron says: Hello World"
```
---
Hadron will first look for request parameters and next if not found any, it will look for value in the container. So if you register a key `foo` in a container and set the route param under the same name, it will inject param's value into callback's argument foo.
```javascript
container.register('foo', 'container');
```
```javascript
exampleRoute: {
callback: (foo) =`foo value: ${foo}`,
methods: ['GET'],
path: '/:foo',
},
```
Response for `GET` request _/param_ will look like this:
```json
"foo value: param"
```
## Middlewares
_Note: Currently middlewares only refer to express._
Routing with Hadron provides a middleware support. You need to pass array with middleware functions to a `middleware` key in route config.
For example:
```javascript
middlewareExample: {
callback: () => {
console.log('Callback function');
},
methods: ['GET'],
middleware: [
(req, res, next) => {
console.log(`First middleware`);
next();
},
(req, res, next) => {
console.log(`Second middleware`);
next();
},
],
path: '/',
},
```
`GET` request to `/` will log to the console following:
```sh
First middleware
Second middleware
Callback function
```
Middlewares take three arguments: `request`, `response` and `next`. First two are objects and third one - function which executed continues request flow.
You can read more about middlewares in [express guide](https://expressjs.com/en/guide/using-middleware.html)
## Routes nesting (available from 2.0.0)
In case of more complicated routing, hadron-express offers possibility to nest routes. That way, You can specify bunch of route properties, that all child routes will inherit.
Route properties that can be inherited:
* path (will add parent's path beforehand new path),
* middlewares
* methods,
```javascript
const nestedRoute = {
middleware: [myTestMiddleware],
method: ['GET'],
path: '/test',
routes: {
route1: {
// path here is going to be /test/test1/, it's going to have 'GET' method on default and middleware myTestMiddleware will be called before
path: '/test1',
callback: () => 'It works! Trust me...',
},
route1: {
// path here is going to be /test/test2/, it's going to have 'GET' and 'POST' methods on default and middlewares myCustomMiddleware and myTestMiddleware will be called before
path: '/test2',
method: ['POST'],
middleware: [myCustomMiddleware],
callback: () => 'It works! Trust me...',
},
},
};
```
If You would like to override parent property, just define new one with `$` sign before, e.g. `$middlewares`, `$path`, `$method`.
```javascript
const nestedRoute = {
middleware: [myTestMiddleware],
method: ['GET'],
path: '/test',
routes: {
route1: {
// path here is going to be /test1/, it's going to have 'GET' method on default and middleware myTestMiddleware will be called before
$path: '/test1',
callback: () => 'It works! Trust me...',
},
route1: {
// path here is going to be /test/test2/, it's going to have 'GET' and 'POST' methods on default and no middlewares
path: '/test2',
method: ['POST'],
$middleware: [],
callback: () => 'It works! Trust me...',
},
},
};
```
You can define nested route endlessly, all of them will inherit properties of it's parent and other ancestors (of course if they were not overwritten with `$` sign).
```javascript
const nestedRoute = {
middleware: [myTestMiddleware],
method: ['GET'],
path: '/test',
routes: {
route1: {
// path here is going to be /test/test2/, it's going to have 'GET' and 'POST' methods on default and no middlewares
path: '/test2',
method: ['POST'],
$middleware: [],
callback: () => 'It works! Trust me...',
routes: {
// path here is going to be /test/test2/deep/, it's going to have 'GET' and 'POST' methods on default and no middlewares
deepRoute1: {
path: 'deep',
callback: () => 'The Dwarves delved too greedily and too deep',
},
},
},
},
};
```
================================================
FILE: packages/hadron-express/index.ts
================================================
import hadronExpress from './src/hadronToExpress';
export {
Callback,
IRoutesConfig,
IRoute,
Middleware,
IContainer,
IHadronExpressConfig,
} from './src/types';
import {
IRoutesConfig,
IContainer,
IHadronExpressConfig,
RoutePathsConfig,
} from './src/types';
export { Event } from './src/constants/eventNames';
export default hadronExpress;
export const register = (container: IContainer, config: IHadronExpressConfig) =>
hadronExpress(
{
routes: config.routes as IRoutesConfig,
routePaths: config.routePaths as RoutePathsConfig,
},
container,
);
================================================
FILE: packages/hadron-express/package.json
================================================
{
"name": "@brainhubeu/hadron-express",
"version": "2.0.0",
"description": "Hadron module implementing express elements",
"main": "dist/index.js",
"files": [
"dist",
"LICENSE"
],
"directories": {
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "tsc"
},
"keywords": [
"hadron",
"hadron-express"
],
"author": "Brainhub",
"license": "MIT",
"dependencies": {
"@brainhubeu/hadron-core": "^1.0.0",
"@brainhubeu/hadron-error-handler": "^1.0.0",
"@brainhubeu/hadron-events": "^1.0.1",
"@brainhubeu/hadron-json-provider": "^1.0.0",
"@brainhubeu/hadron-utils": "^1.0.0",
"express": "4.16.2",
"http-status": "1.1.0"
},
"devDependencies": {
"@types/express": "4.11.1",
"@types/http-status": "0.2.30",
"@types/multer": "1.3.6",
"@types/ramda": "0.25.23",
"@types/supertest": "2.0.4",
"multer": "1.3.0",
"ramda": "0.25.0",
"supertest": "3.0.0"
},
"publishConfig": {
"access": "public"
}
}
================================================
FILE: packages/hadron-express/src/__tests__/__mocks__/routes/route.js
================================================
routes = () => ({
testRoute: {
callback: () => null,
methods: ['GET'],
path: '/',
},
});
module.exports = routes;
================================================
FILE: packages/hadron-express/src/__tests__/createContainerProxy.ts
================================================
import { Container } from '@brainhubeu/hadron-core';
import createContainerProxy from '../createContainerProxy';
import * as sinon from 'sinon';
import { expect } from 'chai';
describe('proxy container', () => {
it('calls keys() method on the container ', () => {
const keys = sinon.spy(Container, 'keys');
const containerProxy = createContainerProxy(Container);
containerProxy.keys();
expect(keys.calledOnce).to.be.equal(true);
});
it('calls take() method on the container', () => {
Container.register('key', 'item');
const take = sinon.spy(Container, 'take');
const containerProxy = createContainerProxy(Container);
// tslint:disable-next-line
containerProxy.key;
expect(take.calledOnce).to.be.equal(true);
});
});
================================================
FILE: packages/hadron-express/src/__tests__/generateMiddlewares.ts
================================================
import { expect } from 'chai';
import { isRawMiddleware, createRawMiddleware } from '../generateMiddlewares';
import { getArgs } from '@brainhubeu/hadron-utils';
import * as sinon from 'sinon';
import * as express from 'express';
describe('isRawMiddleware', () => {
it('returns true when passed middleware is Express middleware', () => {
const middleware = (req, res, next) => {
next();
};
const actual = isRawMiddleware(middleware);
expect(actual).to.equal(true);
});
it('returns false when passed middleware is Hadron middleware', () => {
const middleware = (req, deps) => {
return;
};
const actual = isRawMiddleware(middleware);
expect(actual).to.equal(false);
});
});
describe('createRawMiddleware', () => {
it('creates Express middleware from Hadron Middleware', () => {
const hadronMiddleware = (req, deps) => {
return null;
};
const containerProxy = {};
const expressMiddleware = createRawMiddleware(
hadronMiddleware,
containerProxy,
);
expect(getArgs(expressMiddleware as any)).to.eql(['req', 'res', 'next']);
});
it('creates middleware that sends response', async () => {
const hadronMiddleware = (req, deps) => {
return {
status: 201,
body: 'hello',
};
};
const containerProxy = {};
const expressMiddleware = createRawMiddleware(
hadronMiddleware as any,
containerProxy,
);
const req = {};
const res = {
status: sinon.spy(),
json: sinon.spy(),
};
const next = sinon.spy();
await expressMiddleware(req as express.Request, res as any, next);
expect(res.status.firstCall.args).to.eql([201]);
expect(res.json.firstCall.args).to.eql(['hello']);
});
it('creates middleware that sets response headers and status without sending response', async () => {
const hadronMiddleware = (req, deps) => {
return {
type: 'PARTIAL_RESPONSE',
status: 300,
headers: {
'custom-header': 'foo',
},
};
};
const containerProxy = {};
const expressMiddleware = createRawMiddleware(
hadronMiddleware as any,
containerProxy,
);
const req = {};
const res = {
status: sinon.spy(),
json: sinon.spy(),
set: sinon.spy(),
};
const next = sinon.spy();
await expressMiddleware(req as express.Request, res as any, next);
expect(res.status.firstCall.args).to.eql([300]);
expect(res.set.firstCall.args).to.eql(['custom-header', 'foo']);
expect(res.json.called).to.equal(false);
});
it('creates middleware that modifies request', async () => {
const hadronMiddleware = (req, deps) => {
return {
type: 'PARTIAL_REQUEST',
values: {
body: 'bar',
customKey: 'foo',
},
};
};
const containerProxy = {};
const expressMiddleware = createRawMiddleware(
hadronMiddleware as any,
containerProxy,
);
const req = {
body: null,
existingKey: 'baz',
};
const res = {};
const next = sinon.spy();
await expressMiddleware(req as any, res as any, next);
expect(req).to.eql({
body: 'bar',
customKey: 'foo',
existingKey: 'baz',
});
});
});
================================================
FILE: packages/hadron-express/src/__tests__/hadronToExpress.ts
================================================
import { expect } from 'chai';
import * as express from 'express';
import * as fs from 'fs-extra';
import * as HTTPStatus from 'http-status';
import * as multer from 'multer';
import * as R from 'ramda';
import * as sinon from 'sinon';
import * as request from 'supertest';
import { Container } from '@brainhubeu/hadron-core';
import { json as bodyParser } from 'body-parser';
import NoRouterMethodSpecifiedError from '../errors/NoRouterMethodSpecifiedError';
import routesToExpress, {
prepareMiddlewares,
preparePath,
prepareMethods,
} from '../hadronToExpress';
import { Event } from '../constants/eventNames';
import { IRequest, IResponseSpec } from '../types';
let app = express();
const createTestRoute = (
path: string,
methods: string[],
callback: (req: IRequest, dependencies: any) => IResponseSpec,
middleware?: Array<(req: any, res: any, next: any) => any>,
) => ({
routes: {
testRoute: {
callback,
methods,
middleware,
path,
},
},
});
const getRouteProp = (expressApp: any, prop: string) =>
expressApp._router.stack // registered routes
.filter((r: any) => r.route) // take out all the middleware
.map((r: any) => r.route[prop]); // get all the props
describe('router config', () => {
beforeEach(() => {
app = express();
app.use(bodyParser());
Container.register('server', app);
Container.register('eventManager', null);
});
describe('generating routes', () => {
it('should generate express route based on config file', () => {
const testRoute = createTestRoute('/index', ['GET'], () => null);
return routesToExpress(testRoute, Container).then(() =>
expect(getRouteProp(app, 'path')[0]).to.equal('/index'),
);
});
it('should generate correct router method based on config file', () => {
const testRoute = createTestRoute('/index', ['POST'], () => null);
return routesToExpress(testRoute, Container).then(() =>
expect(getRouteProp(app, 'methods')[0].post).to.equal(true),
);
});
it('returns status OK for request from generated route', () => {
const testRoute = createTestRoute('/testRequest', ['GET'], () => null);
routesToExpress(testRoute, Container);
return request(app)
.get('/testRequest')
.expect(HTTPStatus.OK);
});
it('throws a NoRouterMethodSpecifiedError if no methods were specified', () => {
const testRoute = createTestRoute('/index', [], () => null);
return routesToExpress(testRoute, Container).catch((error) =>
expect(error).to.be.instanceOf(NoRouterMethodSpecifiedError),
);
});
it('generate multiple methods based on config', () => {
const callback = (req: any, res: any) =>
req.params.testParam + req.params.anotherParam;
const middle = sinon.spy();
const testRoute = createTestRoute(
'/testRoute',
['PUT', 'DELETE'],
callback,
[middle],
);
routesToExpress(testRoute, Container).then(
() =>
expect(getRouteProp(app, 'methods')[0].put).to.equal(true) &&
expect(getRouteProp(app, 'methods')[1].delete).to.equal(true),
);
});
it('should return HTTP Status 500 if callback is not defined', () => {
const testRoute = createTestRoute('/testRoute', ['GET'], null);
routesToExpress(testRoute, Container);
return request(app)
.get(`/testRoute`)
.expect(HTTPStatus[500]);
});
it('calls emitEvent method', () => {
const eventManager = {
emitEvent: sinon.spy(),
};
Container.register('eventManager', eventManager);
const testRoute = createTestRoute('/index', ['GET'], () => null);
return routesToExpress(testRoute, Container).then(() =>
request(app)
.get(`/index`)
.then(() =>
expect(
eventManager.emitEvent.calledWith(
Event.HANDLE_REQUEST_CALLBACK_EVENT,
),
).to.equal(true),
),
);
});
it('calls the response event if eventManager is present', () => {
const eventManager = {
emitEvent: sinon.spy(() => () => null),
};
Container.register('eventManager', eventManager);
const testRoute = createTestRoute('/index', ['GET'], () => null);
return routesToExpress(testRoute, Container).then(() =>
request(app)
.get('/index')
.then(() => {
expect(
eventManager.emitEvent.calledWith(Event.HANDLE_RESPONSE_EVENT),
).to.equal(true);
}),
);
});
it('should load routes using json-provider and load them', () => {
routesToExpress(
{
routePaths: [
[
'./packages/hadron-express/src/__tests__/__mocks__/routes/*',
'js',
],
],
},
Container,
);
return request(app)
.get(`/`)
.then(() => {
expect(getRouteProp(app, 'path')[0]).to.equal('/');
});
});
});
describe('routes nesting', () => {
describe('prepareMiddlewares()', () => {
it('should return parents middlewares', () => {
const middleware = () => null;
const result = prepareMiddlewares([], null, [middleware]);
expect(result).to.contain(middleware);
});
it('should return joined middlewares of parent and current route', () => {
const middleware = () => null;
const middleware2 = () => null;
const result = prepareMiddlewares([middleware2], null, [middleware]);
expect(result).to.have.members([middleware, middleware2]);
});
it('should exclude parent and route middleware, if $middlewares exists', () => {
const middleware = () => null;
const middleware2 = () => null;
const middleware3 = () => null;
const result = prepareMiddlewares(
[middleware2],
[middleware3],
[middleware],
);
expect(result).to.not.have.members([middleware2, middleware]);
});
it('should contain $middlewares, if they exists', () => {
const middleware = () => null;
const middleware2 = () => null;
const middleware3 = () => null;
const result = prepareMiddlewares(
[middleware2],
[middleware3],
[middleware],
);
expect(result).to.have.members([middleware3]);
});
});
describe('preparePath()', () => {
it('should return just a path of route', () => {
expect(preparePath('path', null, '')).to.equal('path');
});
it('should return merged parent path and route path', () => {
expect(preparePath('path', null, 'previousPath')).to.equal(
'previousPath/path',
);
});
it('should overwrite parent path to $path', () => {
expect(preparePath('path', 'path2', 'previousPath')).to.equal('path2');
});
});
describe('prepareMethods()', () => {
it('should return methods of route', () => {
expect(prepareMethods(['GET'], null, [])).to.have.members(['GET']);
});
it('should return join methods of route and parent route', () => {
expect(prepareMethods(['GET'], null, ['POST'])).to.have.members([
'GET',
'POST',
]);
});
it('should unique methods of route and parent route', () => {
expect(prepareMethods(['GET'], null, ['POST', 'GET'])).to.be.length(2);
});
});
});
describe('router params', () => {
it('should pass parameter to callback func - request param', () => {
const callback = ({ params }) => params.valueA;
const testParam = 'This is a test';
const testRoute = createTestRoute('/index/:valueA', ['GET'], callback);
routesToExpress(testRoute, Container);
return request(app)
.get(`/index/${testParam}`)
.expect(HTTPStatus.OK)
.then((res: any) => {
expect(res.body).to.equal(testParam);
});
});
it('should pass parameter to callback func - query', () => {
const callback = ({ query }) => query.valueA;
const testParam = 'This is a test';
const testRoute = createTestRoute('/index', ['GET'], callback);
routesToExpress(testRoute, Container);
return request(app)
.get(`/index?valueA=${testParam}`)
.expect(HTTPStatus.OK)
.then((res: any) => {
expect(res.body).to.equal(testParam);
});
});
it('should pass multiple parameters to callback func - params', () => {
const callback = ({ params }) => params.valueA + params.valueB;
const testParam = 'This is a test';
const secondParam = ' This is a second param';
const testRoute = createTestRoute(
'/index/:valueA/:valueB',
['GET'],
callback,
);
routesToExpress(testRoute, Container);
return request(app)
.get(`/index/${testParam}/${secondParam}`)
.expect(HTTPStatus.OK)
.then((res: any) => expect(res.body).to.equal(testParam + secondParam));
});
it('should pass multiple parameters to callback func - query', () => {
const callback = ({ query }) => query.valueA + query.valueB;
const testParam = 'This is a test';
const secondParam = ' This is a second param';
const testRoute = createTestRoute('/index', ['GET'], callback);
routesToExpress(testRoute, Container);
return request(app)
.get(`/index?valueA=${testParam}&valueB=${secondParam}`)
.expect(HTTPStatus.OK)
.then((res: any) => expect(res.body).to.equal(testParam + secondParam));
});
it('should pass query to callback func', () => {
const callback = ({ query }) => query.foo;
const testQuery = 'bar';
const testRoute = createTestRoute('/index', ['GET'], callback);
routesToExpress(testRoute, Container);
return request(app)
.get(`/index/?foo=${testQuery}`)
.expect(HTTPStatus.OK)
.then((res) => {
expect(res.body).to.equal(testQuery);
});
});
it('should pass body to callback func', () => {
const callback = ({ body }) => body.testData;
const postData = {
testData: 'some value',
};
const testRoute = createTestRoute('/index', ['POST'], callback);
routesToExpress(testRoute, Container);
return request(app)
.post(`/index`)
.send(postData)
.expect(HTTPStatus.OK)
.then((res) => {
expect(res.body).to.equal(postData.testData);
});
});
});
describe('router middleware', () => {
it('calls middleware passed in router config', () => {
const callback = (): any => null;
const spy = sinon.spy();
const middle = (req: any, res: any, next: any) => {
spy();
next();
};
const testRoute = createTestRoute('/testRoute', ['GET'], callback, [
middle,
]);
routesToExpress(testRoute, Container);
return request(app)
.get(`/testRoute`)
.expect(HTTPStatus.OK)
.then(() => expect(spy.called).to.be.eq(true));
});
it('calls multiple middlewares passed in router config', () => {
const callback = (): any => null;
const firstSpy = sinon.spy();
const secondSpy = sinon.spy();
const firstMiddleware = (req: any, res: any, next: any) => {
firstSpy();
next();
};
const secondMiddleware = (req: any, res: any, next: any) => {
secondSpy();
next();
};
const testRoute = createTestRoute('/testRoute', ['GET'], callback, [
firstMiddleware,
secondMiddleware,
]);
routesToExpress(testRoute, Container);
return request(app)
.get(`/testRoute`)
.expect(HTTPStatus.OK)
.then(() => {
expect(firstSpy.called).to.be.eq(true);
expect(secondSpy.called).to.be.eq(true);
});
});
});
describe('file handling', () => {
const upload = multer({ dest: `${__dirname}/testUploads` });
const callback = ({ files, file }) => ({ body: files || file });
const uploadMiddleware = (req: any, res: any, next: any) =>
upload.any()(req, res, next);
after(() => {
fs.remove(`${__dirname}/testUploads`);
});
it('should save file passed to route', () => {
const testRoute = createTestRoute('/testUploads', ['POST'], callback, [
uploadMiddleware,
]);
routesToExpress(testRoute, Container);
const mockDir = `${__dirname}/testUploads`;
return request(app)
.post('/testUploads')
.attach('image', `${__dirname}/__mocks__/sample.jpeg`)
.expect(HTTPStatus.OK)
.then(() => {
const files = fs.readdirSync(mockDir);
expect(files.length).to.equal(1);
});
});
it('should pass file to callback func', () => {
const testRoute = createTestRoute('/testUpload', ['POST'], callback, [
uploadMiddleware,
]);
routesToExpress(testRoute, Container);
return request(app)
.post('/testUpload')
.attach('image', `${__dirname}/__mocks__/sample.jpeg`)
.expect(HTTPStatus.OK)
.then((res) => {
expect(R.omit(['filename', 'path', 'size'], res.body[0])).to.eql({
destination: `${__dirname}/testUploads`,
encoding: '7bit',
fieldname: 'image',
mimetype: 'image/jpeg',
originalname: 'sample.jpeg',
});
});
});
});
});
================================================
FILE: packages/hadron-express/src/constants/eventNames.ts
================================================
export enum Event {
HANDLE_REQUEST_CALLBACK_EVENT = 'HANDLE_REQUEST_CALLBACK_EVENT',
HANDLE_RESPONSE_EVENT = 'HANDLE_RESPONSE_EVENT',
}
================================================
FILE: packages/hadron-express/src/constants/routing.ts
================================================
export enum HTTPRequestMethods {
GET = 'GET',
POST = 'POST',
PATCH = 'PATCH',
DELETE = 'DELETE',
PUT = 'PUT',
}
================================================
FILE: packages/hadron-express/src/createContainerProxy.ts
================================================
import { IContainer } from './types';
const createContainerProxy = (container: IContainer): any => {
return new Proxy(
{
keys() {
return container.keys();
},
},
{
get(target: any, name) {
if (typeof target[name] === 'function') {
return target[name];
}
return container.take(name as string);
},
},
);
};
export default createContainerProxy;
================================================
FILE: packages/hadron-express/src/declarations.d.ts
================================================
declare module '@hadron/events';
declare module '@hadron/utils';
================================================
FILE: packages/hadron-express/src/errors/CreateRouteError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class CreateRouteError extends HadronErrorHandler {
constructor(routeName: string, error: Error) {
super(`Cannot create route ${routeName}.`);
this.stack = error.stack;
}
}
================================================
FILE: packages/hadron-express/src/errors/GenerateMiddlewareError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class GenerateMiddlewareError extends HadronErrorHandler {
constructor(error: Error) {
super();
this.name = 'GenerateMiddlewareError';
this.error = error;
}
}
================================================
FILE: packages/hadron-express/src/errors/InvalidRouteMethodError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class InvalidRouteMethodError extends HadronErrorHandler {
constructor(route: string, method: string) {
super();
this.message = `Invalid route method '${method}' in ${route} route`;
this.name = 'InvalidRouteError';
}
}
================================================
FILE: packages/hadron-express/src/errors/NoRouterMethodSpecifiedError.ts
================================================
import HadronErrorHandler from '@brainhubeu/hadron-error-handler';
export default class NoRouterMethodSpecifiedError extends HadronErrorHandler {
constructor(route: string) {
super();
this.message = `No route methods were specified for ${route} route`;
this.name = 'NoRouterMethodSpecifiedError';
}
}
================================================
FILE: packages/hadron-express/src/generateMiddlewares.ts
================================================
import * as express from 'express';
import { Container } from '@brainhubeu/hadron-core';
import { IRoute, Middleware, HadronMiddleware } from './types';
import GenerateMiddlewareError from './errors/GenerateMiddlewareError';
import prepareRequest from './prepareRequest';
import handleResponseSpec from './handleResponseSpec';
import { getArgs } from '@brainhubeu/hadron-utils';
export const isRawMiddleware = (middleware: any) => {
const [firstArg, secondArg, thirdArg] = getArgs(middleware);
return (
(firstArg === 'req' || firstArg === 'request') &&
(secondArg === 'res' || secondArg === 'response') &&
thirdArg === 'next'
);
};
export const createRawMiddleware = (
hadronMiddleware: HadronMiddleware,
containerProxy: any,
) => {
return async (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
const hadronReq = prepareRequest(req);
const result = await hadronMiddleware(hadronReq, containerProxy);
switch (result.type) {
case 'PARTIAL_REQUEST': {
Object.assign(req, result.values);
next();
break;
}
case 'PARTIAL_RESPONSE': {
handleResponseSpec(res)(result);
next();
break;
}
default: {
handleResponseSpec(res)(result);
}
}
};
};
const generateMiddlewares = (
middlewares: Middleware[],
containerProxy: any,
) => {
if (!middlewares) {
return middlewares;
}
return middlewares.map((middleware: any) => {
const rawMiddleware: Middleware = isRawMiddleware(middleware)
? middleware
: createRawMiddleware(middleware as HadronMiddleware, containerProxy);
return (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
Promise.resolve()
.then(() => rawMiddleware(req, res, next))
.catch((error) => {
const logger = Container.take('hadronLogger');
if (logger) {
logger.warn(new GenerateMiddlewareError(error));
}
res.sendStatus(500);
});
};
});
};
export default generateMiddlewares;
================================================
FILE: packages/hadron-express/src/hadronToExpress.ts
================================================
import * as express from 'express';
import * as nodePath from 'path';
import {
IContainer,
IRoute,
Middleware,
IHadronExpressConfig,
IRoutesConfig,
} from './types';
import { Event } from './constants/eventNames';
import CreateRouteError from './errors/CreateRouteError';
import createContainerProxy from './createContainerProxy';
import prepareRequest from './prepareRequest';
import generateMiddlewares from './generateMiddlewares';
import handleResponseSpec from './handleResponseSpec';
import jsonProvider from '@brainhubeu/hadron-json-provider';
const createRoutes = (
app: express.Application,
route: IRoute,
containerProxy: any,
routeName: string,
) => {
return route.methods.map((method: string) => {
(app as any)[method.toLowerCase()](
route.path,
...route.middleware,
(req: express.Request, res: express.Response) => {
const request = prepareRequest(req);
const eventManager = containerProxy.take('eventManager');
Promise.resolve()
.then(() => {
if (!eventManager) {
return route.callback(request, containerProxy, res.locals);
}
const newRouteCallback = eventManager.emitEvent(
Event.HANDLE_REQUEST_CALLBACK_EVENT,
route.callback,
);
return newRouteCallback(request, containerProxy, res.locals);
})
.then((callback) => {
if (!eventManager) {
return handleResponseSpec(res)(callback);
}
const newResponseHandler = eventManager.emitEvent(
Event.HANDLE_RESPONSE_EVENT,
handleResponseSpec,
);
return newResponseHandler(res)(callback);
})
.catch((error) => {
const logger = containerProxy.hadronLogger;
const createRouteError = new CreateRouteError(routeName, error);
if (logger) {
logger.error(createRouteError);
}
res.sendStatus(500);
});
},
);
});
};
const convertToExpress = (
config: IHadronExpressConfig,
container: IContainer,
) => {
const app = container.take('server');
const promises: Array<Promise<object>> = [];
const containerProxy = createContainerProxy(container);
if (config.routes) {
promises.push(Promise.resolve(config.routes));
}
if (config.routePaths) {
const paths: string[] = [];
const extensions: string[] = [];
(config.routePaths as any).forEach((path: string[]) => {
paths.push(path[0]);
if (path.length > 1) {
extensions.push(path[1]);
}
});
promises.push(jsonProvider(paths, extensions));
}
return (
Promise.all(promises)
.then((results) =>
results.reduce(
(accumulator, current) => ({ ...accumulator, ...current }),
{},
),
)
// flatten routes and prepare all components
.then((routes: any) =>
(Object as any)
.keys(routes)
.map((key: string) => prepareRoute(routes[key], key, containerProxy))
.reduce((accumulator: any, current: any) => ({
...accumulator,
...current,
})),
)
.then((preparedRoutes: IRoutesConfig) => {
(Object as any).keys(preparedRoutes).map((key: string) => {
const route: IRoute = preparedRoutes[key];
createRoutes(app, route, container, key);
});
})
);
};
const prepareRoute = (
route: IRoute,
key: string,
container: IContainer,
parentRoute: IRoute = {},
parentKey: string = null,
) => {
const middlewares = prepareMiddlewares(
generateMiddlewares(route.middleware, container),
generateMiddlewares(route.$middleware, container),
parentRoute.middleware,
);
const path = preparePath(route.path, route.$path, parentRoute.path);
const methods = prepareMethods(
route.methods,
route.$methods,
parentRoute.methods,
);
const routeKey = parentKey ? `${parentKey}.${key}` : key;
const preparedRoute = {
...route,
path,
methods,
middleware: middlewares,
};
const result = {
[routeKey]: preparedRoute,
};
if (route.routes) {
return {
...result,
...(Object as any)
.keys(route.routes)
.map((childKey: string) =>
prepareRoute(
(route.routes as any)[childKey],
childKey,
container,
preparedRoute,
routeKey,
),
)
.reduce((accumulator: any, current: any) => ({
...accumulator,
...current,
})),
};
}
return result;
};
export const prepareMiddlewares = (
middleware: any[],
$middleware: any[],
parentMiddleware: Middleware[],
) => {
if ($middleware) {
return $middleware;
}
return [...(parentMiddleware || []), ...(middleware || [])];
};
export const preparePath = (
path: string,
$path: string,
parentPath: string = '',
) => {
if ($path) {
return $path;
}
return nodePath.join(parentPath, path);
};
export const prepareMethods = (
methods: string[],
$methods: string[],
parentMethods: string[] = [],
) => {
if ($methods) {
return $methods;
}
return [...(methods || []), ...parentMethods].reduce(
(accumulator, next) =>
accumulator.indexOf(next) >= 0 ? accumulator : [...accumulator, next],
[],
);
};
export default convertToExpress;
================================================
FILE: packages/hadron-express/src/handleResponseSpec.ts
================================================
import * as express from 'express';
import * as HTTPStatus from 'http-status';
import { IResponseSpec, IPartialResponseSpec } from './types';
const getClass = (val: any) => Object.prototype.toString.call(val).slice(8, -1);
const isPrimitive = (val: any) => {
return ['String', 'Number', 'Null', 'Undefined'].includes(getClass(val));
};
const handleResponseSpec = (res: express.Response) => (
responseSpec: IResponseSpec | IPartialResponseSpec,
) => {
if (isPrimitive(responseSpec)) {
return res.json(responseSpec);
}
const status = responseSpec.status
? responseSpec.status
: responseSpec.type === 'RESPONSE' && responseSpec.redirect
? HTTPStatus.FOUND
: HTTPStatus.OK;
const headers = responseSpec.headers || {};
Object.keys(headers).forEach((headerName) => {
res.set(headerName, headers[headerName]);
});
res.status(status);
if (responseSpec.type === 'PARTIAL_RESPONSE') {
return;
}
const body = responseSpec.body || {};
if (responseSpec.redirect) {
return res.redirect(responseSpec.redirect);
}
if (responseSpec.view) {
const { name, bindings } = responseSpec.view;
return res.render(name, bindings);
}
res.json(body);
};
export default handleResponseSpec;
================================================
FILE: packages/hadron-express/src/prepareRequest.ts
================================================
import * as express from 'express';
import { IRequest } from './types';
const prepareRequest = (
expressRequest: express.Request,
locals = {},
): IRequest => {
return {
locals,
headers: expressRequest.headers,
body: expressRequest.body,
params: expressRequest.params,
query: expressRequest.query,
file: expressRequest.file,
files: expressRequest.files,
};
};
export default prepareRequest;
================================================
FILE: packages/hadron-express/src/types.ts
================================================
import * as express from 'express';
export type Middleware = (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => any;
export type Callback = (...args: any[]) => any;
export interface IRoute {
callback?: Callback;
middleware?: Middleware[];
$middleware?: Middlew
gitextract_tdgrkyma/ ├── .circleci/ │ └── config.yml ├── .dockerignore ├── .gitignore ├── .gitlab-ci.yml ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .rebuild.ts ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── entrypoint.sh ├── features/ │ ├── step_definitions/ │ │ ├── steps.ts │ │ └── theBestSteps.ts │ ├── support/ │ │ ├── hooks/ │ │ │ └── mock.ts │ │ ├── scripts/ │ │ │ ├── Client.ts │ │ │ └── Response.ts │ │ └── world.ts │ └── theBest.feature ├── lerna.json ├── ormconfig.json ├── package-test.sh ├── package.json ├── packages/ │ ├── hadron-auth/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── HadronAuth.ts │ │ │ ├── IRoute.ts │ │ │ ├── ISecurityOptions.ts │ │ │ ├── __tests__/ │ │ │ │ ├── HadronAuth.ts │ │ │ │ ├── hierarchyProvider.ts │ │ │ │ └── urlGlob.ts │ │ │ ├── constants.ts │ │ │ ├── declarations.d.ts │ │ │ ├── helpers/ │ │ │ │ ├── flattenDeep.ts │ │ │ │ └── urlGlob.ts │ │ │ ├── hierarchyProvider.ts │ │ │ ├── password/ │ │ │ │ ├── IHashMethod.ts │ │ │ │ ├── __tests__/ │ │ │ │ │ └── hashMethodProvider.ts │ │ │ │ ├── bcrypt/ │ │ │ │ │ ├── IBcryptOptions.ts │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ └── bcrypt.ts │ │ │ │ │ └── bcrypt.ts │ │ │ │ └── hashMethodProvider.ts │ │ │ └── providers/ │ │ │ └── expressMiddlewareAuthorization.ts │ │ └── tsconfig.json │ ├── hadron-core/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ └── hadronCore.test.ts │ │ │ ├── constants/ │ │ │ │ └── eventNames.ts │ │ │ ├── container/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── container.ts │ │ │ │ │ └── containerItem.ts │ │ │ │ ├── container.ts │ │ │ │ ├── containerItem.ts │ │ │ │ ├── lifecycle.ts │ │ │ │ └── types.ts │ │ │ ├── declarations.d.ts │ │ │ ├── errors/ │ │ │ │ ├── IncorrectContainerKeyNameError.ts │ │ │ │ └── LoadingPackageError.ts │ │ │ ├── hadronCore.ts │ │ │ └── helpers/ │ │ │ ├── __tests__/ │ │ │ │ └── isVarName.ts │ │ │ └── isVarName.ts │ │ └── tsconfig.json │ ├── hadron-demo/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── declarations.d.ts │ │ ├── entity/ │ │ │ ├── Role.ts │ │ │ ├── Team.ts │ │ │ ├── User.ts │ │ │ └── validation/ │ │ │ ├── schemas.ts │ │ │ ├── team/ │ │ │ │ ├── insertTeam.json │ │ │ │ └── updateTeam.json │ │ │ ├── user/ │ │ │ │ ├── insertUser.json │ │ │ │ └── updateUser.json │ │ │ └── validate.ts │ │ ├── event-emitter/ │ │ │ ├── case1/ │ │ │ │ └── index.ts │ │ │ ├── case2/ │ │ │ │ └── index.ts │ │ │ ├── case3/ │ │ │ │ └── index.ts │ │ │ └── config.ts │ │ ├── express-demo/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── logger/ │ │ │ ├── adapters/ │ │ │ │ └── winstonAdapter.ts │ │ │ └── index.ts │ │ ├── package.json │ │ ├── performance-tests/ │ │ │ ├── README.md │ │ │ ├── teamService/ │ │ │ │ ├── getTeams.yml │ │ │ │ ├── insertTeam.yml │ │ │ │ ├── team.yml │ │ │ │ └── updateTeam.yml │ │ │ └── userService/ │ │ │ ├── getUsers.yml │ │ │ ├── insertUser.yml │ │ │ ├── updateUser.yml │ │ │ └── user.yml │ │ ├── routing/ │ │ │ ├── home.config.js │ │ │ ├── nested-routes.config.js │ │ │ ├── team.config.js │ │ │ └── user.config.js │ │ ├── security/ │ │ │ ├── loginRoute.ts │ │ │ └── securedRoutesConfig.ts │ │ ├── serialization/ │ │ │ ├── routing/ │ │ │ │ ├── index.ts │ │ │ │ ├── princesses.ts │ │ │ │ └── unicorns.ts │ │ │ ├── schemas/ │ │ │ │ ├── princess.json │ │ │ │ └── unicorn.json │ │ │ ├── serialization-demo.ts │ │ │ └── unicorns-and-princesses.ts │ │ ├── services/ │ │ │ ├── teamService.ts │ │ │ └── userService.ts │ │ ├── tsconfig.json │ │ └── typeorm-demo/ │ │ └── index.ts │ ├── hadron-error-handler/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ └── errorHandler.ts │ │ └── tsconfig.json │ ├── hadron-events/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ └── eventManagerProvider.ts │ │ │ ├── constants.ts │ │ │ ├── eventManagerProvider.ts │ │ │ ├── helpers/ │ │ │ │ └── functionHelper.ts │ │ │ ├── registerProcessEvents.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hadron-express/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── __mocks__/ │ │ │ │ │ └── routes/ │ │ │ │ │ └── route.js │ │ │ │ ├── createContainerProxy.ts │ │ │ │ ├── generateMiddlewares.ts │ │ │ │ └── hadronToExpress.ts │ │ │ ├── constants/ │ │ │ │ ├── eventNames.ts │ │ │ │ └── routing.ts │ │ │ ├── createContainerProxy.ts │ │ │ ├── declarations.d.ts │ │ │ ├── errors/ │ │ │ │ ├── CreateRouteError.ts │ │ │ │ ├── GenerateMiddlewareError.ts │ │ │ │ ├── InvalidRouteMethodError.ts │ │ │ │ └── NoRouterMethodSpecifiedError.ts │ │ │ ├── generateMiddlewares.ts │ │ │ ├── hadronToExpress.ts │ │ │ ├── handleResponseSpec.ts │ │ │ ├── prepareRequest.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hadron-file-locator/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── file-locator.ts │ │ │ │ └── mock/ │ │ │ │ ├── app/ │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ ├── config.json │ │ │ │ │ │ ├── config.xml │ │ │ │ │ │ ├── config_development.js │ │ │ │ │ │ ├── config_development.json │ │ │ │ │ │ └── config_test.js │ │ │ │ │ ├── ext/ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ ├── config.json │ │ │ │ │ │ └── config.xml │ │ │ │ │ └── universal/ │ │ │ │ │ ├── dog.config.js │ │ │ │ │ ├── other.json │ │ │ │ │ ├── team.config.js │ │ │ │ │ ├── test.js │ │ │ │ │ └── user.config.js │ │ │ │ └── plugins/ │ │ │ │ ├── plugin1/ │ │ │ │ │ └── config/ │ │ │ │ │ ├── config.js │ │ │ │ │ ├── config_development.js │ │ │ │ │ ├── config_development.ts │ │ │ │ │ └── config_test.js │ │ │ │ ├── plugin2/ │ │ │ │ │ └── config/ │ │ │ │ │ ├── config.js │ │ │ │ │ ├── config_development.js │ │ │ │ │ └── config_test.js │ │ │ │ └── plugin3/ │ │ │ │ └── config/ │ │ │ │ ├── config.js │ │ │ │ ├── config_development.js │ │ │ │ └── config_test.js │ │ │ ├── declarations.d.ts │ │ │ ├── file-locator.ts │ │ │ └── glob-promise.ts │ │ └── tsconfig.json │ ├── hadron-json-provider/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── config-json-provider.ts │ │ │ │ ├── js-loader.ts │ │ │ │ ├── json-loader.ts │ │ │ │ ├── json-provider.ts │ │ │ │ ├── mock/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ │ ├── config.json │ │ │ │ │ │ │ ├── config.xml │ │ │ │ │ │ │ ├── config_development.js │ │ │ │ │ │ │ ├── config_development.json │ │ │ │ │ │ │ └── config_test.js │ │ │ │ │ │ ├── ext/ │ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ │ ├── config.json │ │ │ │ │ │ │ └── config.xml │ │ │ │ │ │ └── universal/ │ │ │ │ │ │ ├── dog.config.js │ │ │ │ │ │ ├── exportDefaultConfig.js │ │ │ │ │ │ ├── other.json │ │ │ │ │ │ ├── team.config.js │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── user.config.js │ │ │ │ │ └── plugins/ │ │ │ │ │ ├── plugin1/ │ │ │ │ │ │ └── config/ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ ├── config_development.js │ │ │ │ │ │ ├── config_development.ts │ │ │ │ │ │ └── config_test.js │ │ │ │ │ ├── plugin2/ │ │ │ │ │ │ └── config/ │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ ├── config_development.js │ │ │ │ │ │ └── config_test.js │ │ │ │ │ └── plugin3/ │ │ │ │ │ └── config/ │ │ │ │ │ ├── config.js │ │ │ │ │ ├── config_development.js │ │ │ │ │ └── config_test.js │ │ │ │ └── xml-loader.ts │ │ │ ├── declarations.d.ts │ │ │ └── json-provider.ts │ │ └── tsconfig.json │ ├── hadron-logger/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ └── logger.ts │ │ │ ├── adapters/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── bunyan.ts │ │ │ │ ├── bunyan.ts │ │ │ │ └── index.ts │ │ │ ├── errors/ │ │ │ │ ├── ConfigNotDefinedError.ts │ │ │ │ ├── CouldNotRegisterLoggerInContainerError.ts │ │ │ │ ├── LoggerAdapterNotDefinedError.ts │ │ │ │ └── LoggerNameIsRequiredError.ts │ │ │ ├── logger.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hadron-oauth/ │ │ ├── .eslintrc │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── facebook.ts │ │ │ │ ├── formatQueryString.ts │ │ │ │ ├── github.ts │ │ │ │ └── google.ts │ │ │ ├── facebook/ │ │ │ │ ├── redirect.ts │ │ │ │ └── token.ts │ │ │ ├── github/ │ │ │ │ ├── redirect.ts │ │ │ │ └── token.ts │ │ │ ├── google/ │ │ │ │ ├── redirect.ts │ │ │ │ └── token.ts │ │ │ ├── types.ts │ │ │ └── util/ │ │ │ ├── constants.ts │ │ │ └── formatQueryString.ts │ │ └── tsconfig.json │ ├── hadron-serialization/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── mocks.ts │ │ │ │ ├── schema-provider.ts │ │ │ │ └── serializer.ts │ │ │ ├── constants.ts │ │ │ ├── declarations.d.ts │ │ │ ├── schema-provider.ts │ │ │ ├── serializer.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hadron-typeorm/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── connectionHelper.ts │ │ │ │ └── mocks/ │ │ │ │ ├── entity/ │ │ │ │ │ ├── Team.ts │ │ │ │ │ ├── User.ts │ │ │ │ │ └── UserStatus.ts │ │ │ │ └── schema/ │ │ │ │ └── User.ts │ │ │ ├── connectionHelper.ts │ │ │ ├── constants.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hadron-utils/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ └── getArgs.ts │ │ │ └── getArgs.ts │ │ └── tsconfig.json │ └── hadron-validation/ │ ├── LICENSE │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── src/ │ │ ├── __tests__/ │ │ │ ├── __mocks__/ │ │ │ │ ├── Team.ts │ │ │ │ ├── User.ts │ │ │ │ ├── declarations.d.ts │ │ │ │ ├── test-schemas/ │ │ │ │ │ ├── email.json │ │ │ │ │ ├── person.json │ │ │ │ │ ├── team.json │ │ │ │ │ ├── user.json │ │ │ │ │ └── users.json │ │ │ │ ├── test-schemas.ts │ │ │ │ └── test-validate.ts │ │ │ └── validate.ts │ │ └── validator-factory.ts │ └── tsconfig.json ├── pm2.config.js ├── scripts/ │ ├── clean │ └── copy-tsconfig ├── test.sh ├── tools/ │ └── testSetup.ts ├── tsconfig.json └── tslint.json
SYMBOL INDEX (166 symbols across 56 files)
FILE: features/support/scripts/Client.ts
constant METHOD (line 3) | const METHOD = {
class Client (line 11) | class Client {
method constructor (line 15) | constructor(superagent) {
method setHost (line 27) | public setHost(host) {
method createRequest (line 31) | public createRequest(method, path, body) {
method addRequestHeaders (line 47) | public addRequestHeaders(request) {
method setHeader (line 53) | public setHeader(name, value) {
FILE: features/support/scripts/Response.ts
class Response (line 1) | class Response {
method constructor (line 4) | constructor(body, status) {
FILE: packages/hadron-auth/src/HadronAuth.ts
type ISecuredRoute (line 9) | interface ISecuredRoute {
FILE: packages/hadron-auth/src/IRoute.ts
type IRoute (line 1) | interface IRoute {
type IMethod (line 6) | interface IMethod {
FILE: packages/hadron-auth/src/ISecurityOptions.ts
type ISecurityOptions (line 4) | interface ISecurityOptions {
FILE: packages/hadron-auth/src/constants.ts
constant CONTAINER_NAME (line 1) | const CONTAINER_NAME = 'isGranted';
FILE: packages/hadron-auth/src/hierarchyProvider.ts
type IRolesMap (line 1) | interface IRolesMap {
type IRole (line 5) | interface IRole {
type IUser (line 10) | interface IUser {
function fillMissingRoles (line 22) | function fillMissingRoles(roles: IRolesMap | string[]): IRolesMap {
function getDeeperRoles (line 52) | function getDeeperRoles(userRoles: string[], availableRoles: IRolesMap) {
function excludeRoles (line 72) | function excludeRoles(userRoles: string[], availableRoles: IRolesMap) {
function checkRole (line 91) | function checkRole(
function checkRoles (line 121) | function checkRoles(
function isGranted (line 151) | function isGranted(
function isUserGranted (line 174) | function isUserGranted(
function hierarchyProvider (line 187) | function hierarchyProvider(
FILE: packages/hadron-auth/src/password/IHashMethod.ts
type IHashMethod (line 1) | interface IHashMethod {
FILE: packages/hadron-auth/src/password/bcrypt/IBcryptOptions.ts
type IBcryptOptions (line 1) | interface IBcryptOptions {
FILE: packages/hadron-auth/src/password/bcrypt/bcrypt.ts
function hash (line 5) | function hash(
function compare (line 18) | function compare(
function bcryptProvider (line 26) | function bcryptProvider(options: IBcryptOptions): IHashMethod {
FILE: packages/hadron-auth/src/password/hashMethodProvider.ts
type IHashProviderMap (line 5) | interface IHashProviderMap {
function isHashMethod (line 18) | function isHashMethod(
function hashMethodProvider (line 32) | function hashMethodProvider(
FILE: packages/hadron-core/src/constants/eventNames.ts
type eventNames (line 1) | enum eventNames {
FILE: packages/hadron-core/src/container/__tests__/container.ts
class Foo (line 15) | class Foo {
method constructor (line 17) | constructor() {
method constructor (line 29) | constructor() {
method constructor (line 43) | constructor() {
class Foo (line 27) | class Foo {
method constructor (line 17) | constructor() {
method constructor (line 29) | constructor() {
method constructor (line 43) | constructor() {
class Foo (line 41) | class Foo {
method constructor (line 17) | constructor() {
method constructor (line 29) | constructor() {
method constructor (line 43) | constructor() {
class Foo2 (line 47) | class Foo2 {
method constructor (line 49) | constructor(parameterName: Foo) {
FILE: packages/hadron-core/src/container/__tests__/containerItem.ts
class Foo (line 43) | class Foo {
method constructor (line 45) | constructor() {
method constructor (line 61) | constructor() {
method constructor (line 81) | constructor() {
class Foo (line 59) | class Foo {
method constructor (line 45) | constructor() {
method constructor (line 61) | constructor() {
method constructor (line 81) | constructor() {
class Foo (line 79) | class Foo {
method constructor (line 45) | constructor() {
method constructor (line 61) | constructor() {
method constructor (line 81) | constructor() {
FILE: packages/hadron-core/src/container/containerItem.ts
class ContainerItem (line 6) | class ContainerItem implements IContainerItem {
method constructor (line 8) | constructor(protected key: string, protected item: any) {}
method Item (line 10) | get Item(): any {
method Item (line 13) | set Item(item: any) {
method getKey (line 17) | public getKey() {
method getArgs (line 20) | public getArgs(): string[] {
class ContainerItemSingleton (line 26) | class ContainerItemSingleton extends ContainerItem {
method constructor (line 30) | constructor(key: string, item: any) {
method Item (line 35) | set Item(item: any) {
method Item (line 38) | get Item(): any {
class ContainerItemTransient (line 65) | class ContainerItemTransient extends ContainerItem {
method constructor (line 66) | constructor(key: string, item: any) {
method Item (line 70) | set Item(item: any) {
method Item (line 73) | get Item(): any {
FILE: packages/hadron-core/src/container/lifecycle.ts
type Lifecycle (line 1) | enum Lifecycle {
FILE: packages/hadron-core/src/container/types.ts
type IContainerItem (line 1) | interface IContainerItem {
type IContainer (line 8) | interface IContainer {
FILE: packages/hadron-core/src/errors/IncorrectContainerKeyNameError.ts
class IncorrectContainerKeyNameError (line 3) | class IncorrectContainerKeyNameError extends HadronErrorHandler {
method constructor (line 4) | constructor(key: string, err: Error = new Error()) {
FILE: packages/hadron-core/src/errors/LoadingPackageError.ts
class LoadingPackageError (line 3) | class LoadingPackageError extends HadronErrorHandler {
method constructor (line 4) | constructor(err: Error) {
FILE: packages/hadron-core/src/helpers/isVarName.ts
function isVarName (line 2) | function isVarName(str: string): boolean {
FILE: packages/hadron-demo/entity/Role.ts
class Role (line 5) | class Role implements IRole {
FILE: packages/hadron-demo/entity/Team.ts
class Team (line 5) | class Team {
FILE: packages/hadron-demo/entity/User.ts
class User (line 14) | class User implements IUser {
FILE: packages/hadron-demo/serialization/unicorns-and-princesses.ts
class Princess (line 30) | class Princess {
method constructor (line 37) | constructor({ address, friends, id, money, name }: any) {
FILE: packages/hadron-demo/services/teamService.ts
class TeamDto (line 6) | class TeamDto {
method constructor (line 7) | constructor(public id: number, public name: string, public amount: num...
FILE: packages/hadron-demo/services/userService.ts
class UserDto (line 7) | class UserDto {
method constructor (line 8) | constructor(
FILE: packages/hadron-error-handler/src/errorHandler.ts
class HadronError (line 1) | class HadronError extends Error {
method constructor (line 3) | constructor(message: string = 'Hadron unhandled error') {
FILE: packages/hadron-events/src/constants.ts
type Event (line 1) | enum Event {
FILE: packages/hadron-events/src/helpers/functionHelper.ts
function hasFunctionArgument (line 4) | function hasFunctionArgument(func: (args: any) => any, argumentName: str...
FILE: packages/hadron-events/src/types.ts
type CallbackEvent (line 1) | type CallbackEvent = (...args: any[]) => any;
type EventHandler (line 2) | type EventHandler = (callback: CallbackEvent, ...args: any[]) => any;
type IEventEmitter (line 4) | interface IEventEmitter {
type IEventListener (line 10) | interface IEventListener {
type IHadronEventsConfig (line 16) | interface IHadronEventsConfig {
type IEventsConfig (line 20) | interface IEventsConfig {
type IEventManager (line 24) | interface IEventManager {
FILE: packages/hadron-express/src/constants/eventNames.ts
type Event (line 1) | enum Event {
FILE: packages/hadron-express/src/constants/routing.ts
type HTTPRequestMethods (line 1) | enum HTTPRequestMethods {
FILE: packages/hadron-express/src/createContainerProxy.ts
method keys (line 6) | keys() {
method get (line 11) | get(target: any, name) {
FILE: packages/hadron-express/src/errors/CreateRouteError.ts
class CreateRouteError (line 3) | class CreateRouteError extends HadronErrorHandler {
method constructor (line 4) | constructor(routeName: string, error: Error) {
FILE: packages/hadron-express/src/errors/GenerateMiddlewareError.ts
class GenerateMiddlewareError (line 3) | class GenerateMiddlewareError extends HadronErrorHandler {
method constructor (line 4) | constructor(error: Error) {
FILE: packages/hadron-express/src/errors/InvalidRouteMethodError.ts
class InvalidRouteMethodError (line 3) | class InvalidRouteMethodError extends HadronErrorHandler {
method constructor (line 4) | constructor(route: string, method: string) {
FILE: packages/hadron-express/src/errors/NoRouterMethodSpecifiedError.ts
class NoRouterMethodSpecifiedError (line 3) | class NoRouterMethodSpecifiedError extends HadronErrorHandler {
method constructor (line 4) | constructor(route: string) {
FILE: packages/hadron-express/src/types.ts
type Middleware (line 3) | type Middleware = (
type Callback (line 9) | type Callback = (...args: any[]) => any;
type IRoute (line 11) | interface IRoute {
type IRoutesConfig (line 22) | interface IRoutesConfig {
type RoutePathsConfig (line 26) | type RoutePathsConfig = string[][];
type IContainer (line 28) | interface IContainer {
type IHeaders (line 33) | interface IHeaders {
type IView (line 37) | interface IView {
type StatusCode (line 42) | type StatusCode =
type IResponseSpec (line 106) | interface IResponseSpec {
type IPartialResponseSpec (line 115) | interface IPartialResponseSpec {
type IRequest (line 121) | interface IRequest {
type IPartialRequest (line 131) | interface IPartialRequest {
type IHadronExpressConfig (line 137) | interface IHadronExpressConfig {
type MiddlewareResult (line 142) | type MiddlewareResult =
type HadronMiddleware (line 147) | type HadronMiddleware = (
FILE: packages/hadron-logger/src/errors/ConfigNotDefinedError.ts
class ConfigNotDefinedError (line 3) | class ConfigNotDefinedError extends HadronErrorHandler {
method constructor (line 4) | constructor(error: Error = new Error()) {
FILE: packages/hadron-logger/src/errors/CouldNotRegisterLoggerInContainerError.ts
class CouldNotRegisterLoggerInContainerError (line 3) | class CouldNotRegisterLoggerInContainerError extends HadronErrorHandler {
method constructor (line 4) | constructor(name: string, error: Error = new Error()) {
FILE: packages/hadron-logger/src/errors/LoggerAdapterNotDefinedError.ts
class LoggerAdapterNotDefinedError (line 3) | class LoggerAdapterNotDefinedError extends HadronErrorHandler {
method constructor (line 4) | constructor(name: string, error: Error = new Error()) {
FILE: packages/hadron-logger/src/errors/LoggerNameIsRequiredError.ts
class LoggerNameIsRequiredError (line 3) | class LoggerNameIsRequiredError extends HadronErrorHandler {
method constructor (line 4) | constructor(error: Error = new Error()) {
FILE: packages/hadron-logger/src/types.ts
type IHadronLoggerConfig (line 1) | interface IHadronLoggerConfig {
type ILoggerConfig (line 5) | interface ILoggerConfig {
type ILogger (line 10) | interface ILogger {
type ILoggerFactory (line 17) | type ILoggerFactory = (config: ILoggerConfig) => ILogger;
FILE: packages/hadron-oauth/src/types.ts
type IContainer (line 1) | interface IContainer {
type IOAuthConfig (line 7) | interface IOAuthConfig {
type IQueryObject (line 38) | interface IQueryObject {
FILE: packages/hadron-oauth/src/util/constants.ts
constant GOOGLE_AUTH_URL (line 1) | const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
constant GOOGLE_TOKEN_URL (line 2) | const GOOGLE_TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
constant FACEBOOK_AUTH_URL (line 4) | const FACEBOOK_AUTH_URL = 'https://www.facebook.com/v3.0/dialog/oauth';
constant FACEBOOK_TOKEN_URL (line 5) | const FACEBOOK_TOKEN_URL =
constant GITHUB_AUTH_URL (line 8) | const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
constant GITHUB_TOKEN_URL (line 9) | const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
FILE: packages/hadron-serialization/index.ts
type ISerializer (line 13) | type ISerializer = interfaces.ISerializer;
type ISerializerConfig (line 14) | type ISerializerConfig = interfaces.ISerializerConfig;
type ISerializationSchema (line 15) | type ISerializationSchema = interfaces.ISerializationSchema;
type IProperty (line 16) | type IProperty = interfaces.IProperty;
FILE: packages/hadron-serialization/src/constants.ts
constant DATA_TYPE (line 1) | const DATA_TYPE = {
constant CONTAINER_NAME (line 10) | const CONTAINER_NAME = 'serializer';
FILE: packages/hadron-serialization/src/types.ts
type ISerializationSchema (line 1) | interface ISerializationSchema {
type IProperty (line 6) | interface IProperty {
type ISerializerConfig (line 15) | interface ISerializerConfig {
type IHadronSerializerConfig (line 20) | interface IHadronSerializerConfig {
type ISerializer (line 24) | interface ISerializer {
FILE: packages/hadron-typeorm/src/__tests__/mocks/entity/Team.ts
class Team (line 5) | class Team {
FILE: packages/hadron-typeorm/src/__tests__/mocks/entity/User.ts
class User (line 6) | class User {
FILE: packages/hadron-typeorm/src/__tests__/mocks/entity/UserStatus.ts
class UserStatus (line 5) | class UserStatus {
FILE: packages/hadron-typeorm/src/constants.ts
constant CONNECTION (line 1) | const CONNECTION = 'connection';
FILE: packages/hadron-typeorm/src/types.ts
type IHadronTypeormConfig (line 3) | interface IHadronTypeormConfig {
FILE: packages/hadron-utils/src/__tests__/getArgs.ts
function foo (line 7) | function foo(bar: any, bar2: any): any {
class Foo (line 22) | class Foo {
method constructor (line 23) | constructor(bar: any, bar2: any) {
method bar (line 51) | public bar(bar: any, bar2: any): any {
method bar (line 61) | public static bar(bar: any, bar2: any): any {
method constructor (line 32) | constructor(bar: any, bar2: any) {
method foo (line 42) | foo(bar: any, bar2: any): any {
class Foo (line 50) | class Foo {
method constructor (line 23) | constructor(bar: any, bar2: any) {
method bar (line 51) | public bar(bar: any, bar2: any): any {
method bar (line 61) | public static bar(bar: any, bar2: any): any {
class Foo (line 60) | class Foo {
method constructor (line 23) | constructor(bar: any, bar2: any) {
method bar (line 51) | public bar(bar: any, bar2: any): any {
method bar (line 61) | public static bar(bar: any, bar2: any): any {
FILE: packages/hadron-validation/src/__tests__/__mocks__/Team.ts
class Team (line 3) | class Team {
FILE: packages/hadron-validation/src/__tests__/__mocks__/User.ts
class User (line 3) | class User {
Condensed preview — 315 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (329K chars).
[
{
"path": ".circleci/config.yml",
"chars": 380,
"preview": "# Javascript Node CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-javascript/ for more "
},
{
"path": ".dockerignore",
"chars": 41,
"preview": "node_modules\ndocs\nfeatures\n.npmrc\n.nvmrc\n"
},
{
"path": ".gitignore",
"chars": 207,
"preview": "/dist\n/logs\n/npm-debug.log\n/node_modules\n.DS_Store\n.idea/\n.env\n.vscode\nnode_modules\ndist\npackage-lock.json\n.nyc_output\n/"
},
{
"path": ".gitlab-ci.yml",
"chars": 391,
"preview": "image: node:8.9.0\n\nstages:\n - build\n - lint\n - test\n\nbefore_script:\n - npm install\n - apt-get update\n - apt-get in"
},
{
"path": ".npmrc",
"chars": 35,
"preview": "save-exact=true\npackage-lock=false\n"
},
{
"path": ".nvmrc",
"chars": 2,
"preview": "9\n"
},
{
"path": ".prettierignore",
"chars": 143,
"preview": "/.history\n/node_modules\n/.vscode\n/.idea\n/dist\n*/**/*.temp\n*/**/*.temp.*\n*/**/package.json\n*/**/package-lock.json\npackage"
},
{
"path": ".prettierrc",
"chars": 78,
"preview": "{\n \"trailingComma\": \"all\",\n \"singleQuote\": true,\n \"arrowParens\" :\"always\"\n}"
},
{
"path": ".rebuild.ts",
"chars": 0,
"preview": ""
},
{
"path": "Dockerfile",
"chars": 538,
"preview": "FROM keymetrics/pm2:8-alpine\n\nCOPY .env /.env\nCOPY entrypoint.sh /\nCOPY package.json /usr/src/app/package.json\n\nWORKDIR "
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 5666,
"preview": "<p align=\"center\">\n <a href=\"https://hadron.pro/\" target=\"blank\">\n <img src=\"./logo3.png\" alt=\"Hadron Logo\" /></a>\n</p"
},
{
"path": "docker-compose.yml",
"chars": 365,
"preview": "version: '2'\n\nnetworks:\n brainhub: ~\n\nservices:\n brainhub-framework-app:\n image: brainhub-framework-app\n build:\n"
},
{
"path": "entrypoint.sh",
"chars": 203,
"preview": "#!/usr/bin/env bash\nset -e\n\nif [ ! -f /usr/src/app/node_modules/.lock ] && [ -f /usr/src/app/package.json ]; then\n np"
},
{
"path": "features/step_definitions/steps.ts",
"chars": 135,
"preview": "import { defineSupportCode } from 'cucumber';\nimport stepsSupport from '@brainhubeu/cucumber-steps';\n\ndefineSupportCode("
},
{
"path": "features/step_definitions/theBestSteps.ts",
"chars": 767,
"preview": "import { defineSupportCode } from 'cucumber';\n\n/* tslint:disable:only-arrow-functions ter-prefer-arrow-callback */\n\ndefi"
},
{
"path": "features/support/hooks/mock.ts",
"chars": 303,
"preview": "import { defineSupportCode } from 'cucumber';\nimport * as superagent from 'superagent';\nimport Client from '../scripts/C"
},
{
"path": "features/support/scripts/Client.ts",
"chars": 1309,
"preview": "import Response from './Response';\n\nconst METHOD = {\n DELETE: 'del',\n GET: 'get',\n PATCH: 'patch',\n POST: 'post',\n "
},
{
"path": "features/support/scripts/Response.ts",
"chars": 158,
"preview": "export default class Response {\n public status: any;\n public body: any;\n constructor(body, status) {\n this.body = "
},
{
"path": "features/support/world.ts",
"chars": 275,
"preview": "import { defineSupportCode } from 'cucumber';\n\n/* tslint:disable:only-arrow-functions ter-prefer-arrow-callback */\n\ndefi"
},
{
"path": "features/theBest.feature",
"chars": 251,
"preview": "Feature: The Best\n\n As a Brainhub\n We want to become best in the world\n\n Scenario: Adding two numbers\n Given brain"
},
{
"path": "lerna.json",
"chars": 516,
"preview": "{\n \"lerna\": \"2.9.0\",\n \"packages\": [\n \"packages/hadron-logger\",\n \"packages/hadron-utils\",\n \"packages/hadron-er"
},
{
"path": "ormconfig.json",
"chars": 1057,
"preview": "[\n {\n \"name\": \"postgres\",\n \"type\": \"postgres\",\n \"host\": \"localhost\",\n \"port\": 5432,\n "
},
{
"path": "package-test.sh",
"chars": 194,
"preview": "#!/usr/bin/env sh\n\n# this runs unit tests on an individual package\n# Usage example: ./package-test.sh oauth\n./node_modul"
},
{
"path": "package.json",
"chars": 3517,
"preview": "{\n \"name\": \"brainhub-framework-app\",\n \"version\": \"1.0.0-alpha.1\",\n \"description\": \"Brainhub framework app\",\n \"main\":"
},
{
"path": "packages/hadron-auth/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-auth/README.md",
"chars": 7281,
"preview": "## Installation\n\n```bash\nnpm install @brainhubeu/hadron-auth --save\n```\n\n## Overview\n\n**hadron-auth** provides back-end "
},
{
"path": "packages/hadron-auth/index.ts",
"chars": 326,
"preview": "import { IUser, IRole } from './src/hierarchyProvider';\nimport * as bcrypt from './src/password/bcrypt/bcrypt';\nimport *"
},
{
"path": "packages/hadron-auth/package.json",
"chars": 600,
"preview": "{\n \"name\": \"@brainhubeu/hadron-auth\",\n \"version\": \"0.0.2\",\n \"description\": \"Security package for hadron\",\n \"main\": \""
},
{
"path": "packages/hadron-auth/src/HadronAuth.ts",
"chars": 4584,
"preview": "import { IRoute, IMethod } from './IRoute';\nimport urlGlob, { convertToPattern } from './helpers/urlGlob';\nimport { IUse"
},
{
"path": "packages/hadron-auth/src/IRoute.ts",
"chars": 154,
"preview": "export interface IRoute {\n path: string;\n methods: IMethod[];\n}\n\nexport interface IMethod {\n name: string;\n allowedR"
},
{
"path": "packages/hadron-auth/src/ISecurityOptions.ts",
"chars": 246,
"preview": "import { IRolesMap } from './hierarchyProvider';\nimport IHashMethod from './password/IHashMethod';\n\nexport default inter"
},
{
"path": "packages/hadron-auth/src/__tests__/HadronAuth.ts",
"chars": 4572,
"preview": "import { expect } from 'chai';\nimport {\n initRoutes,\n ISecuredRoute,\n getRouteFromPath,\n getExistsingRoute,\n create"
},
{
"path": "packages/hadron-auth/src/__tests__/hierarchyProvider.ts",
"chars": 6836,
"preview": "import hierarchyProvider, {\n fillMissingRoles,\n checkRole,\n checkRoles,\n getDeeperRoles,\n excludeRoles,\n} from '../"
},
{
"path": "packages/hadron-auth/src/__tests__/urlGlob.ts",
"chars": 2070,
"preview": "import { expect } from 'chai';\nimport urlGlob from '../helpers/urlGlob';\n\ndescribe('Glob URL pattern', () => {\n it('sho"
},
{
"path": "packages/hadron-auth/src/constants.ts",
"chars": 43,
"preview": "export const CONTAINER_NAME = 'isGranted';\n"
},
{
"path": "packages/hadron-auth/src/declarations.d.ts",
"chars": 33,
"preview": "declare module 'glob-to-regexp';\n"
},
{
"path": "packages/hadron-auth/src/helpers/flattenDeep.ts",
"chars": 177,
"preview": "const flattenDeep = (arr: any[]): any[] =>\n Array.isArray(arr)\n ? arr.reduce((a, b) => [...flattenDeep(a), ...flatte"
},
{
"path": "packages/hadron-auth/src/helpers/urlGlob.ts",
"chars": 776,
"preview": "const countStars = (input: string) => {\n try {\n return input.match(/\\*\\*/g).length;\n } catch (error) {\n return 0"
},
{
"path": "packages/hadron-auth/src/hierarchyProvider.ts",
"chars": 4627,
"preview": "export interface IRolesMap {\n [s: string]: string[];\n}\n\nexport interface IRole {\n id: number | string;\n name: string;"
},
{
"path": "packages/hadron-auth/src/password/IHashMethod.ts",
"chars": 247,
"preview": "export default interface IHashMethod {\n hash(password: string, salt?: string, options?: object): Promise<string>;\n com"
},
{
"path": "packages/hadron-auth/src/password/__tests__/hashMethodProvider.ts",
"chars": 3493,
"preview": "import hashMethodProvider from '../hashMethodProvider';\nimport { expect } from 'chai';\nimport * as sinon from 'sinon';\n\n"
},
{
"path": "packages/hadron-auth/src/password/bcrypt/IBcryptOptions.ts",
"chars": 68,
"preview": "export default interface IBcryptOptions {\n saltRounds?: number;\n};\n"
},
{
"path": "packages/hadron-auth/src/password/bcrypt/__tests__/bcrypt.ts",
"chars": 2703,
"preview": "import { hash, compare } from '../bcrypt';\nimport * as bcrypt from 'bcrypt';\nimport { expect } from 'chai';\nimport * as "
},
{
"path": "packages/hadron-auth/src/password/bcrypt/bcrypt.ts",
"chars": 891,
"preview": "import * as bcrypt from 'bcrypt';\nimport IHashMethod from '../IHashMethod';\nimport IBcryptOptions from './IBcryptOptions"
},
{
"path": "packages/hadron-auth/src/password/hashMethodProvider.ts",
"chars": 1493,
"preview": "import bcrypt from './bcrypt/bcrypt';\nimport ISecurityOptions from '../ISecurityOptions';\nimport IHashMethod from './IHa"
},
{
"path": "packages/hadron-auth/src/providers/expressMiddlewareAuthorization.ts",
"chars": 1254,
"preview": "import * as jwt from 'jsonwebtoken';\nimport { isRouteSecure, isAllowed } from '../HadronAuth';\n\nconst errorResponse = {\n"
},
{
"path": "packages/hadron-auth/tsconfig.json",
"chars": 521,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-core/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-core/README.md",
"chars": 3637,
"preview": "## Installation\n\n* Install Node.js. We recommend using the latest version, installation details on [nodejs.org](https://"
},
{
"path": "packages/hadron-core/index.ts",
"chars": 218,
"preview": "import hadronCore from './src/hadronCore';\nexport { default as Container } from './src/container/container';\nexport * fr"
},
{
"path": "packages/hadron-core/package.json",
"chars": 577,
"preview": "{\n \"name\": \"@brainhubeu/hadron-core\",\n \"version\": \"1.0.0\",\n \"description\": \"Hadron core module\",\n \"main\": \"dist/inde"
},
{
"path": "packages/hadron-core/src/__tests__/hadronCore.test.ts",
"chars": 2993,
"preview": "import { expect } from 'chai';\nimport * as sinon from 'sinon';\nimport container from '../container/container';\n\nimport h"
},
{
"path": "packages/hadron-core/src/constants/eventNames.ts",
"chars": 177,
"preview": "export enum eventNames {\n HANDLE_INITIALIZE_APPLICATION_EVENT = 'handleInitializeApplicationEvent',\n HANDLE_TERMINATE_"
},
{
"path": "packages/hadron-core/src/container/__tests__/container.ts",
"chars": 2134,
"preview": "/* tslint:disable:max-classes-per-file */\nimport { expect } from 'chai';\nimport container from '../container';\nimport { "
},
{
"path": "packages/hadron-core/src/container/__tests__/containerItem.ts",
"chars": 2628,
"preview": "/* tslint:disable:max-classes-per-file */\nimport { expect } from 'chai';\nimport containerItem from '../containerItem';\ni"
},
{
"path": "packages/hadron-core/src/container/container.ts",
"chars": 1814,
"preview": "import containerItem from './containerItem';\nimport { IContainerItem, IContainer } from './types';\nimport isVarName from"
},
{
"path": "packages/hadron-core/src/container/containerItem.ts",
"chars": 2752,
"preview": "import container from './container';\nimport { IContainerItem } from './types';\nimport { Lifecycle } from './lifecycle';\n"
},
{
"path": "packages/hadron-core/src/container/lifecycle.ts",
"chars": 115,
"preview": "enum Lifecycle {\n Transient = 'transient',\n Singleton = 'singleton',\n Value = 'value',\n}\n\nexport { Lifecycle };\n"
},
{
"path": "packages/hadron-core/src/container/types.ts",
"chars": 300,
"preview": "interface IContainerItem {\n Item(): any;\n Item(key: string): void;\n getKey(): string;\n getArgs(): string[];\n}\n\ninter"
},
{
"path": "packages/hadron-core/src/declarations.d.ts",
"chars": 32,
"preview": "declare module '@hadron/utils';\n"
},
{
"path": "packages/hadron-core/src/errors/IncorrectContainerKeyNameError.ts",
"chars": 434,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class IncorrectContainerKeyNameError "
},
{
"path": "packages/hadron-core/src/errors/LoadingPackageError.ts",
"chars": 320,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class LoadingPackageError extends Had"
},
{
"path": "packages/hadron-core/src/hadronCore.ts",
"chars": 1131,
"preview": "import container from './container/container';\nimport { IContainer } from './container/types';\nimport { createLogger } f"
},
{
"path": "packages/hadron-core/src/helpers/__tests__/isVarName.ts",
"chars": 1142,
"preview": "import { assert } from 'chai';\nimport isVarName from '../isVarName';\n\ndescribe('isVarName', () => {\n it('when provided "
},
{
"path": "packages/hadron-core/src/helpers/isVarName.ts",
"chars": 359,
"preview": "// tslint:disable:no-eval\nexport default function isVarName(str: string): boolean {\n if (typeof str !== 'string') {\n "
},
{
"path": "packages/hadron-core/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-demo/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-demo/README.md",
"chars": 115,
"preview": "# Demo App for Hadron framework\n\nTo start just run\n\n```bash\n npm run start\n```\n\n<b>Requires TS-Node (for now)</b>\n"
},
{
"path": "packages/hadron-demo/declarations.d.ts",
"chars": 249,
"preview": "declare module 'xmljson';\ndeclare module 'glob';\ndeclare module 'xml2js';\n\ndeclare module '*.json' {\n const value: any;"
},
{
"path": "packages/hadron-demo/entity/Role.ts",
"chars": 272,
"preview": "import { IRole } from '@brainhubeu/hadron-auth';\nimport { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';\n\n@Ent"
},
{
"path": "packages/hadron-demo/entity/Team.ts",
"chars": 315,
"preview": "import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';\nimport { User } from './User';\n\n@Entity()\ne"
},
{
"path": "packages/hadron-demo/entity/User.ts",
"chars": 666,
"preview": "import {\n Column,\n Entity,\n ManyToOne,\n PrimaryGeneratedColumn,\n ManyToMany,\n JoinTable,\n} from 'typeorm';\nimport "
},
{
"path": "packages/hadron-demo/entity/validation/schemas.ts",
"chars": 297,
"preview": "import insertTeam = require('./team/insertTeam.json');\nimport updateTeam = require('./team/updateTeam.json');\nimport ins"
},
{
"path": "packages/hadron-demo/entity/validation/team/insertTeam.json",
"chars": 177,
"preview": "{\n \"type\": \"object\",\n \"properties\": {\n \"teamName\": {\n \"type\": \"string\"\n }\n },\n \"req"
},
{
"path": "packages/hadron-demo/entity/validation/team/updateTeam.json",
"chars": 239,
"preview": "{\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"teamName\": "
},
{
"path": "packages/hadron-demo/entity/validation/user/insertUser.json",
"chars": 270,
"preview": "{\n \"type\": \"object\",\n \"properties\": {\n \"username\": {\n \"type\": \"string\"\n },\n \"password\": {\n \"type\": "
},
{
"path": "packages/hadron-demo/entity/validation/user/updateUser.json",
"chars": 258,
"preview": "{\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"username\": {\n \"type\": \"strin"
},
{
"path": "packages/hadron-demo/entity/validation/validate.ts",
"chars": 135,
"preview": "import validatorFactory from '../../../hadron-validation';\nimport schemas from './schemas';\n\nexport default validatorFac"
},
{
"path": "packages/hadron-demo/event-emitter/case1/index.ts",
"chars": 646,
"preview": "import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';\nimport { EventEmitter } from 'events';\n"
},
{
"path": "packages/hadron-demo/event-emitter/case2/index.ts",
"chars": 554,
"preview": "import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';\nimport { EventEmitter } from 'events';\n"
},
{
"path": "packages/hadron-demo/event-emitter/case3/index.ts",
"chars": 689,
"preview": "import eventManagerProvider, { IEventsConfig } from '@brainhubeu/hadron-events';\nimport { EventEmitter } from 'events';\n"
},
{
"path": "packages/hadron-demo/event-emitter/config.ts",
"chars": 614,
"preview": "import { IEventsConfig } from '@brainhubeu/hadron-events';\n\nconst emitterConfig: IEventsConfig = {\n listeners: [\n {\n"
},
{
"path": "packages/hadron-demo/express-demo/index.ts",
"chars": 3048,
"preview": "import { Container as container } from '@brainhubeu/hadron-core';\n\nconst getDate = () => {\n const d = new Date();\n ret"
},
{
"path": "packages/hadron-demo/index.ts",
"chars": 1805,
"preview": "import * as bodyParser from 'body-parser';\nimport * as express from 'express';\nimport hadron, { IContainer } from '@brai"
},
{
"path": "packages/hadron-demo/logger/adapters/winstonAdapter.ts",
"chars": 523,
"preview": "import * as winston from 'winston';\nimport { ILogger } from '@brainhubeu/hadron-logger';\n\nexport default (config: any): "
},
{
"path": "packages/hadron-demo/logger/index.ts",
"chars": 193,
"preview": "export default {\n logger: [\n {\n type: 'bunyan',\n name: 'first logger',\n },\n {\n type: 'winston',"
},
{
"path": "packages/hadron-demo/package.json",
"chars": 1366,
"preview": "{\n \"name\": \"@brainhubeu/hadron-demo\",\n \"version\": \"1.1.4\",\n \"description\": \"Hadron demo example app\",\n \"main\": \"dist"
},
{
"path": "packages/hadron-demo/performance-tests/README.md",
"chars": 1912,
"preview": "# Performance Tests\n\n## Testing toolkit\n\nArtillery - [Artillery website](https://artillery.io/)\n\n## Basic commmands\n\n* d"
},
{
"path": "packages/hadron-demo/performance-tests/teamService/getTeams.yml",
"chars": 171,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/teamService/insertTeam.yml",
"chars": 253,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/teamService/team.yml",
"chars": 509,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n - duration: 20\n arr"
},
{
"path": "packages/hadron-demo/performance-tests/teamService/updateTeam.yml",
"chars": 276,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/userService/getUsers.yml",
"chars": 171,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/userService/insertUser.yml",
"chars": 272,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/userService/updateUser.yml",
"chars": 291,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n defaults:\nscenarios:\n - f"
},
{
"path": "packages/hadron-demo/performance-tests/userService/user.yml",
"chars": 591,
"preview": "config:\n target: 'http://localhost:8080'\n phases:\n - duration: 10\n arrivalRate: 5\n - duration: 20\n arr"
},
{
"path": "packages/hadron-demo/routing/home.config.js",
"chars": 563,
"preview": "const helloWorldCallback = () => 'Hello world';\nconst versionCallback = () => `Version: ${process.env.VERSION || '0.0.1'"
},
{
"path": "packages/hadron-demo/routing/nested-routes.config.js",
"chars": 1514,
"preview": "const testMiddleware = (req, res, next) => {\n res.locals.injected = 'I was injected here!';\n next();\n};\n\nconst teamRou"
},
{
"path": "packages/hadron-demo/routing/team.config.js",
"chars": 707,
"preview": "const teamService = require('../services/teamService');\n\nconst teamRoutsConfig = () => {\n return {\n getTeams: {\n "
},
{
"path": "packages/hadron-demo/routing/user.config.js",
"chars": 712,
"preview": "const userService = require('../services/userService');\n\nconst userRoutsConfig = () => {\n return {\n getAllUsers: {\n "
},
{
"path": "packages/hadron-demo/security/loginRoute.ts",
"chars": 984,
"preview": "import * as jwt from 'jsonwebtoken';\nimport { bcrypt } from '@brainhubeu/hadron-auth';\n\nconst secret = process.env.JWT_S"
},
{
"path": "packages/hadron-demo/security/securedRoutesConfig.ts",
"chars": 328,
"preview": "const securedRoutesConfig = [\n {\n path: '/team/*',\n roles: [['Admin', 'User'], 'Manager'],\n },\n {\n path: '/u"
},
{
"path": "packages/hadron-demo/serialization/routing/index.ts",
"chars": 151,
"preview": "import unicornsRoutes from './unicorns';\nimport princessesRoutes from './princesses';\n\nexport default {\n ...unicornsRou"
},
{
"path": "packages/hadron-demo/serialization/routing/princesses.ts",
"chars": 1049,
"preview": "import { Container } from '@brainhubeu/hadron-core';\nimport { princesses } from '../unicorns-and-princesses';\nimport { I"
},
{
"path": "packages/hadron-demo/serialization/routing/unicorns.ts",
"chars": 1004,
"preview": "import { Container } from '@brainhubeu/hadron-core';\nimport { unicorns } from '../unicorns-and-princesses';\nimport { ISe"
},
{
"path": "packages/hadron-demo/serialization/schemas/princess.json",
"chars": 599,
"preview": "{\n \"name\": \"Princess\",\n \"properties\": [\n { \"name\": \"name\", \"type\": \"string\" },\n { \"name\": \"address\","
},
{
"path": "packages/hadron-demo/serialization/schemas/unicorn.json",
"chars": 697,
"preview": "{\n \"name\": \"Unicorn\",\n \"properties\": [\n { \"name\": \"name\", \"type\": \"string\" },\n { \"name\": \"hornLength"
},
{
"path": "packages/hadron-demo/serialization/serialization-demo.ts",
"chars": 1061,
"preview": "import { Container } from '@brainhubeu/hadron-core';\nimport {\n schemaProvider,\n CONTAINER_NAME,\n ISerializer,\n} from "
},
{
"path": "packages/hadron-demo/serialization/unicorns-and-princesses.ts",
"chars": 1813,
"preview": "export const unicorns = {\n arthur: {\n hornLength: '20',\n id: '10002',\n magicPower: {\n magicSchool: 'Fake'"
},
{
"path": "packages/hadron-demo/services/teamService.ts",
"chars": 1921,
"preview": "import { Team } from '../entity/Team';\nimport { User } from '../entity/User';\nimport { Repository } from 'typeorm';\nimpo"
},
{
"path": "packages/hadron-demo/services/userService.ts",
"chars": 2473,
"preview": "import { User } from '../entity/User';\nimport validate from '../entity/validation/validate';\nimport { Container } from '"
},
{
"path": "packages/hadron-demo/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-demo/typeorm-demo/index.ts",
"chars": 474,
"preview": "import { ConnectionOptions } from 'typeorm';\nimport { User } from '../entity/User';\nimport { Team } from '../entity/Team"
},
{
"path": "packages/hadron-error-handler/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-error-handler/README.md",
"chars": 76,
"preview": "# Default Error for Hadron\n\n<b>(Currently there is nothing to see here)</b>\n"
},
{
"path": "packages/hadron-error-handler/index.ts",
"chars": 82,
"preview": "import HadronError from './src/errorHandler';\n\nexport { HadronError as default };\n"
},
{
"path": "packages/hadron-error-handler/package.json",
"chars": 429,
"preview": "{\n \"name\": \"@brainhubeu/hadron-error-handler\",\n \"version\": \"1.0.0\",\n \"description\": \"Error handler for hadron\",\n \"ma"
},
{
"path": "packages/hadron-error-handler/src/errorHandler.ts",
"chars": 209,
"preview": "class HadronError extends Error {\n public error?: Error;\n constructor(message: string = 'Hadron unhandled error') {\n "
},
{
"path": "packages/hadron-error-handler/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-events/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-events/README.md",
"chars": 4701,
"preview": "## Installation\n\n```bash\nnpm install @brainhubeu/hadron-events --save\n```\n\n[More info about installation](/core/#install"
},
{
"path": "packages/hadron-events/index.ts",
"chars": 858,
"preview": "import { EventEmitter } from 'events';\nimport { Lifecycle, IContainer } from '@brainhubeu/hadron-core';\n\nimport eventMan"
},
{
"path": "packages/hadron-events/package.json",
"chars": 613,
"preview": "{\n \"name\": \"@brainhubeu/hadron-events\",\n \"version\": \"1.0.1\",\n \"description\": \"Hadron event emitter module\",\n \"main\":"
},
{
"path": "packages/hadron-events/src/__tests__/eventManagerProvider.ts",
"chars": 6373,
"preview": "import { expect } from 'chai';\nimport { EventEmitter } from 'events';\nimport * as sinon from 'sinon';\nimport { IEventLis"
},
{
"path": "packages/hadron-events/src/constants.ts",
"chars": 99,
"preview": "export enum Event {\n HANDLE_TERMINATE_APPLICATION_EVENT = 'HANDLE_TERMINATE_APPLICATION_EVENT',\n}\n"
},
{
"path": "packages/hadron-events/src/eventManagerProvider.ts",
"chars": 1574,
"preview": "import { hasFunctionArgument } from './helpers/functionHelper';\nimport {\n IEventEmitter,\n IEventListener,\n CallbackEv"
},
{
"path": "packages/hadron-events/src/helpers/functionHelper.ts",
"chars": 256,
"preview": "import { getArgs } from '@brainhubeu/hadron-utils';\n\n// tslint:disable-next-line:ban-types\nfunction hasFunctionArgument("
},
{
"path": "packages/hadron-events/src/registerProcessEvents.ts",
"chars": 237,
"preview": "import { IEventManager } from './types';\nimport { Event } from './constants';\n\nexport default (eventEmitter: IEventManag"
},
{
"path": "packages/hadron-events/src/types.ts",
"chars": 705,
"preview": "export type CallbackEvent = (...args: any[]) => any;\nexport type EventHandler = (callback: CallbackEvent, ...args: any[]"
},
{
"path": "packages/hadron-events/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-express/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-express/README.md",
"chars": 7346,
"preview": "## Installation\n\nINFO: Currently routing with hadron works only with the Express framework.\n\n```bash\nnpm install @brainh"
},
{
"path": "packages/hadron-express/index.ts",
"chars": 598,
"preview": "import hadronExpress from './src/hadronToExpress';\nexport {\n Callback,\n IRoutesConfig,\n IRoute,\n Middleware,\n ICont"
},
{
"path": "packages/hadron-express/package.json",
"chars": 1057,
"preview": "{\n \"name\": \"@brainhubeu/hadron-express\",\n \"version\": \"2.0.0\",\n \"description\": \"Hadron module implementing express ele"
},
{
"path": "packages/hadron-express/src/__tests__/__mocks__/routes/route.js",
"chars": 131,
"preview": "routes = () => ({\n testRoute: {\n callback: () => null,\n methods: ['GET'],\n path: '/',\n },\n});\n\nmodule.exports"
},
{
"path": "packages/hadron-express/src/__tests__/createContainerProxy.ts",
"chars": 767,
"preview": "import { Container } from '@brainhubeu/hadron-core';\nimport createContainerProxy from '../createContainerProxy';\nimport "
},
{
"path": "packages/hadron-express/src/__tests__/generateMiddlewares.ts",
"chars": 3303,
"preview": "import { expect } from 'chai';\nimport { isRawMiddleware, createRawMiddleware } from '../generateMiddlewares';\nimport { g"
},
{
"path": "packages/hadron-express/src/__tests__/hadronToExpress.ts",
"chars": 13710,
"preview": "import { expect } from 'chai';\n\nimport * as express from 'express';\nimport * as fs from 'fs-extra';\nimport * as HTTPStat"
},
{
"path": "packages/hadron-express/src/constants/eventNames.ts",
"chars": 140,
"preview": "export enum Event {\n HANDLE_REQUEST_CALLBACK_EVENT = 'HANDLE_REQUEST_CALLBACK_EVENT',\n HANDLE_RESPONSE_EVENT = 'HANDLE"
},
{
"path": "packages/hadron-express/src/constants/routing.ts",
"chars": 122,
"preview": "export enum HTTPRequestMethods {\n GET = 'GET',\n POST = 'POST',\n PATCH = 'PATCH',\n DELETE = 'DELETE',\n PUT = 'PUT',\n"
},
{
"path": "packages/hadron-express/src/createContainerProxy.ts",
"chars": 429,
"preview": "import { IContainer } from './types';\n\nconst createContainerProxy = (container: IContainer): any => {\n return new Proxy"
},
{
"path": "packages/hadron-express/src/declarations.d.ts",
"chars": 65,
"preview": "declare module '@hadron/events';\ndeclare module '@hadron/utils';\n"
},
{
"path": "packages/hadron-express/src/errors/CreateRouteError.ts",
"chars": 268,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class CreateRouteError extends Hadron"
},
{
"path": "packages/hadron-express/src/errors/GenerateMiddlewareError.ts",
"chars": 258,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class GenerateMiddlewareError extends"
},
{
"path": "packages/hadron-express/src/errors/InvalidRouteMethodError.ts",
"chars": 318,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class InvalidRouteMethodError extends"
},
{
"path": "packages/hadron-express/src/errors/NoRouterMethodSpecifiedError.ts",
"chars": 318,
"preview": "import HadronErrorHandler from '@brainhubeu/hadron-error-handler';\n\nexport default class NoRouterMethodSpecifiedError ex"
},
{
"path": "packages/hadron-express/src/generateMiddlewares.ts",
"chars": 2139,
"preview": "import * as express from 'express';\nimport { Container } from '@brainhubeu/hadron-core';\nimport { IRoute, Middleware, Ha"
},
{
"path": "packages/hadron-express/src/hadronToExpress.ts",
"chars": 5474,
"preview": "import * as express from 'express';\nimport * as nodePath from 'path';\nimport {\n IContainer,\n IRoute,\n Middleware,\n I"
},
{
"path": "packages/hadron-express/src/handleResponseSpec.ts",
"chars": 1250,
"preview": "import * as express from 'express';\nimport * as HTTPStatus from 'http-status';\nimport { IResponseSpec, IPartialResponseS"
},
{
"path": "packages/hadron-express/src/prepareRequest.ts",
"chars": 428,
"preview": "import * as express from 'express';\nimport { IRequest } from './types';\n\nconst prepareRequest = (\n expressRequest: expr"
},
{
"path": "packages/hadron-express/src/types.ts",
"chars": 2073,
"preview": "import * as express from 'express';\n\nexport type Middleware = (\n req: express.Request,\n res: express.Response,\n next:"
},
{
"path": "packages/hadron-express/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-file-locator/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-file-locator/README.md",
"chars": 62,
"preview": "# Hadron file locator\n\nIt's just part of hadron-json-provider\n"
},
{
"path": "packages/hadron-file-locator/index.ts",
"chars": 108,
"preview": "import locate, { configLocate } from './src/file-locator';\n\nexport default locate;\nexport { configLocate };\n"
},
{
"path": "packages/hadron-file-locator/package.json",
"chars": 588,
"preview": "{\n \"name\": \"@brainhubeu/hadron-file-locator\",\n \"version\": \"1.0.0\",\n \"description\": \"Hadron module for searching file "
},
{
"path": "packages/hadron-file-locator/src/__tests__/file-locator.ts",
"chars": 2485,
"preview": "import { expect } from 'chai';\nimport { configLocate } from '../file-locator';\n\ndescribe('locate', () => {\n const packa"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config.js",
"chars": 128,
"preview": "const x = () => {\n return {\n usernameJS: \"user-JS\",\n emailJS: \"user-JS@email.com\",\n }\n}\n\nmodule.expo"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config.json",
"chars": 136,
"preview": "{\n \"status\": \"Default\",\n \"database\": {\n \"host\": \"default\",\n \"user\": \"default\",\n \"password\": \""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config.xml",
"chars": 22,
"preview": "<status>Test</status>\n"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config_development.js",
"chars": 86,
"preview": "const x = () => {\n return {\n name: \"module - x\"\n }\n}\n\nmodule.exports = x;"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config_development.json",
"chars": 78,
"preview": "{\n \"status\": \"Development\",\n \"onlyDevelopmentProp\": \"I am developer!\"\n}\n"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/config/config_test.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/ext/config.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/ext/config.json",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/ext/config.xml",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/universal/dog.config.js",
"chars": 84,
"preview": "const x = () => {\n return {\n dogName: \"Rex\",\n }\n}\n\nmodule.exports = x;\n"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/universal/other.json",
"chars": 24,
"preview": "{\n \"fromJSON\": true\n}"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/universal/team.config.js",
"chars": 86,
"preview": "const x = () => {\n return {\n teamName: \"team1\",\n }\n}\n\nmodule.exports = x;"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/universal/test.js",
"chars": 81,
"preview": "const x = () => {\n return {\n name: \"TEST\",\n }\n}\n\nmodule.exports = x;"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/app/universal/user.config.js",
"chars": 86,
"preview": "const x = () => {\n return {\n userName: \"user1\",\n }\n}\n\nmodule.exports = x;"
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin1/config/config.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin1/config/config_development.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin1/config/config_development.ts",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin1/config/config_test.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin2/config/config.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin2/config/config_development.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin2/config/config_test.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin3/config/config.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin3/config/config_development.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/__tests__/mock/plugins/plugin3/config/config_test.js",
"chars": 0,
"preview": ""
},
{
"path": "packages/hadron-file-locator/src/declarations.d.ts",
"chars": 23,
"preview": "declare module 'glob';\n"
},
{
"path": "packages/hadron-file-locator/src/file-locator.ts",
"chars": 1919,
"preview": "import glob from './glob-promise';\n\nconst addExtension = (paths: string[], extensions: string[]): string[] =>\n paths.re"
},
{
"path": "packages/hadron-file-locator/src/glob-promise.ts",
"chars": 293,
"preview": "import * as glob from 'glob';\n\nconst promise = (pattern: string): Promise<any> =>\n new Promise(\n (resolve, reject) ="
},
{
"path": "packages/hadron-file-locator/tsconfig.json",
"chars": 514,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es6\",\n \"noImplicitAny\": true,\n \"noEmitOnError\": "
},
{
"path": "packages/hadron-json-provider/LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 Brainhub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "packages/hadron-json-provider/README.md",
"chars": 1703,
"preview": "## Installation\n\n```bash\nnpm install @brainhubeu/hadron-json-provider --save\n```\n\n[More info about installation](/core/#"
},
{
"path": "packages/hadron-json-provider/index.ts",
"chars": 208,
"preview": "import jsonProvider, {\n configJsonProvider,\n jsLoader,\n jsonLoader,\n xmlLoader,\n} from './src/json-provider';\n\nexpor"
},
{
"path": "packages/hadron-json-provider/package.json",
"chars": 617,
"preview": "{\n \"name\": \"@brainhubeu/hadron-json-provider\",\n \"version\": \"1.0.0\",\n \"description\": \"Hadron json provider module\",\n "
},
{
"path": "packages/hadron-json-provider/src/__tests__/config-json-provider.ts",
"chars": 1879,
"preview": "import { expect } from 'chai';\nimport { configJsonProvider } from '../json-provider';\n\ndescribe('configJsonProvider', ()"
},
{
"path": "packages/hadron-json-provider/src/__tests__/js-loader.ts",
"chars": 1324,
"preview": "import { expect } from 'chai';\nimport { jsLoader } from '../json-provider';\n\ndescribe('jsLoader', () => {\n const mockDi"
},
{
"path": "packages/hadron-json-provider/src/__tests__/json-loader.ts",
"chars": 853,
"preview": "import { expect } from 'chai';\nimport { jsonLoader } from '../json-provider';\n\ndescribe('jsonLoader', () => {\n const pa"
},
{
"path": "packages/hadron-json-provider/src/__tests__/json-provider.ts",
"chars": 1142,
"preview": "import { expect } from 'chai';\nimport jsonProvider from '../json-provider';\n\ndescribe('jsonProvider', () => {\n const mo"
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config.js",
"chars": 128,
"preview": "const x = () => {\n return {\n usernameJS: \"user-JS\",\n emailJS: \"user-JS@email.com\",\n }\n}\n\nmodule.expo"
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config.json",
"chars": 136,
"preview": "{\n \"status\": \"Default\",\n \"database\": {\n \"host\": \"default\",\n \"user\": \"default\",\n \"password\": \""
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config.xml",
"chars": 22,
"preview": "<status>Test</status>\n"
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config_development.js",
"chars": 86,
"preview": "const x = () => {\n return {\n name: \"module - x\"\n }\n}\n\nmodule.exports = x;"
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config_development.json",
"chars": 31,
"preview": "{\n \"status\": \"Development\"\n}"
},
{
"path": "packages/hadron-json-provider/src/__tests__/mock/app/config/config_test.js",
"chars": 0,
"preview": ""
}
]
// ... and 115 more files (download for full content)
About this extraction
This page contains the full source code of the brainhubeu/hadron GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 315 files (283.2 KB), approximately 83.1k tokens, and a symbol index with 166 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.