├── .babelrc ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── media ├── logo.png └── logo.svg ├── package.json ├── src ├── debug │ ├── assert.js │ ├── index.js │ └── log.js ├── decorators │ └── index.js ├── enums │ ├── index.js │ ├── paramTypes.js │ └── types │ │ ├── boolean.js │ │ ├── clientRequest.js │ │ ├── index.js │ │ ├── integer.js │ │ ├── serverResponse.js │ │ ├── string.js │ │ └── uuid.js ├── events │ ├── decorator.js │ ├── index.js │ └── types │ │ ├── ExceptionThrownEvent.js │ │ ├── ServerStatusUpdateEvent.js │ │ └── index.js ├── exceptions │ ├── index.js │ ├── missingArgument.js │ ├── notInArrayException.js │ ├── notInObjectException.js │ ├── numberFormatException.js │ ├── operationNotAllowedException.js │ ├── strictMode.js │ ├── typeAlreadyExistsException.js │ ├── unexpectedValueException.js │ └── valueNotDefinedException.js ├── index.js ├── injections │ └── index.js ├── modules │ ├── bodyParser │ │ ├── index.js │ │ ├── parsers │ │ │ ├── index.js │ │ │ ├── json.js │ │ │ └── urlencoded.js │ │ └── raw.js │ └── index.js ├── persistence │ └── index.js ├── response │ ├── index.js │ ├── mediaType.js │ ├── static.js │ └── status.js ├── server │ ├── defaultOptions.js │ ├── index.js │ ├── modules.js │ ├── router │ │ ├── index.js │ │ └── storage.js │ └── utils.js └── utils │ ├── functions │ ├── isRequired.js │ └── iterate.js │ └── index.js ├── tsconfig.json └── typings └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ], 5 | "plugins": [ 6 | "transform-decorators-legacy", 7 | "syntax-async-functions", 8 | "transform-regenerator" 9 | ], 10 | "sourceMaps": true, 11 | "retainLines": true, 12 | "comments": false 13 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "babel-eslint", 4 | rules: { 5 | strict: 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | .vscode/ 5 | npm-debug.log* 6 | package-lock.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 |
5 | OrganiserJS Beta
6 |
7 | 8 | --- 9 | 10 | # Changelog 11 | 12 | ###### 1.0.1 (Beta) 13 | 14 | - Fixed serving static files as response. 15 | - Improved router response time. 16 | 17 | ###### 1.0.0 (Beta) 18 | 19 | - Initial release. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 26 | * Trolling, insulting/derogatory comments, and personal or political attacks 27 | * Public or private harassment 28 | * Publishing others' private information, such as a physical or electronic 29 | address, without explicit permission 30 | * Other conduct which could reasonably be considered inappropriate in a professional setting 31 | 32 | ## Our Responsibilities 33 | 34 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 35 | 36 | Project maintainers have the right and responsibility to remove, edit, or 37 | reject comments, commits, code, wiki edits, issues, and other contributions 38 | that are not aligned to this Code of Conduct, or to ban temporarily or 39 | permanently any contributor for other behaviors that they deem inappropriate, 40 | threatening, offensive, or harmful. 41 | 42 | ## Scope 43 | 44 | This Code of Conduct applies both within project spaces and in public spaces 45 | when an individual is representing the project or its community. Examples of 46 | representing a project or community include using an official project e-mail 47 | address, posting via an official social media account, or acting as an appointed 48 | representative at an online or offline event. Representation of a project may be 49 | further defined and clarified by project maintainers. 50 | 51 | ## Enforcement 52 | 53 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 54 | reported by contacting the project team at `arthurbergmz@gmail.com`. All 55 | complaints will be reviewed and investigated and will result in a response that 56 | is deemed necessary and appropriate to the circumstances. The project team is 57 | obligated to maintain confidentiality with regard to the reporter of an incident. 58 | Further details of specific enforcement policies may be posted separately. 59 | 60 | Project maintainers who do not follow or enforce the Code of Conduct in good 61 | faith may face temporary or permanent repercussions as determined by other 62 | members of the project's leadership. 63 | 64 | ## Attribution 65 | 66 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 67 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 68 | 69 | [homepage]: https://www.contributor-covenant.org 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Arthur Arioli Bergamaschi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 |
5 | OrganiserJS Beta
6 |
7 |
8 | 9 | [![v1.0.0 - Beta](https://img.shields.io/badge/release-1.0.0-lightgrey.svg?style=flat)](https://www.npmjs.com/package/organiser) [![Beta stage - Not safe for production](https://img.shields.io/badge/stage-beta-orange.svg?style=flat)](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta) [![StandardJS](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/fatec-taquaritinga/organiser/master/LICENSE) [![Stars on Github](https://img.shields.io/github/stars/fatec-taquaritinga/organiser.svg?style=social)](https://github.com/fatec-taquaritinga/organiser) 10 |
11 |
12 | 13 | _An organic web framework for organized web servers._ 14 | 15 | [upcoming features](https://github.com/fatec-taquaritinga/organiser/projects/3) - [known issues](https://github.com/fatec-taquaritinga/organiser/projects/1#card-5237208) - [send suggestion](https://github.com/fatec-taquaritinga/organiser/issues) 16 |
17 | 18 | --- 19 | 20 | _**Organi**ser_ is a next-generation web framework, focused on provinding the best developing and maintenance experience, benefiting from Ecmascript's new definitions, with support from [Babel](https://babeljs.io/). 21 | 22 | Our goal is having an **organi**zed and **organi**c **ser**ver. But what does that mean? 23 | 24 | It means that you can bring complex highly scalable systems to life, with easy maintaining, without having to learn hard syntaxes. It is organized because of its well-known syntax, used in Spring Boot, for example, and it is organic because Organiser makes sense: it is just like telling the server what it should do, in almost natural-like language. 25 | 26 | Organiser works with _inversion of control_ principles, creating one of the most powerful environments for MVC development, for example, in Node.js. Just tell what you want through its _decorators_ and let the magic happen. 27 | 28 | --- 29 | 30 |
31 | 32 | ⚠️ 33 | 34 | 35 | **Organiser is in beta stage.** 36 | It's not recommended for production usage yet. 37 |
38 | 39 | --- 40 | 41 | 1. [Install](https://github.com/fatec-taquaritinga/organiser#install) 42 | 2. [Examples](https://github.com/fatec-taquaritinga/organiser#examples) 43 | 3. [Events](https://github.com/fatec-taquaritinga/organiser#events) 44 | 4. [Modules](https://github.com/fatec-taquaritinga/organiser#modules) 45 | 5. [@Arguments - Inversion of Control](https://github.com/fatec-taquaritinga/organiser#arguments---inversion-of-control) 46 | 6. [Documentation](https://github.com/fatec-taquaritinga/organiser#documentation) 47 | 7. [Team](https://github.com/fatec-taquaritinga/organiser#team) 48 | 8. [License](https://github.com/fatec-taquaritinga/organiser#license) 49 | 50 | --- 51 | 52 | ### Install 53 | 54 | This is a [Node.js](https://nodejs.org/en/) module. Therefore, beforehand, you need to [download and install Node.js](https://nodejs.org/en/download/). Node.js 6.0.0 or higher is required. 55 | 56 | Assuming that you have already used the `npm init` command, run: 57 | 58 | ```bash 59 | $ npm install organiser --save 60 | ``` 61 | 62 | ### Examples 63 | - [organiser-helloworld](https://github.com/arthurbergmz/organiser-helloworld) 64 | 65 | A server with only a GET endpoint at `localhost:3000` (default) that shows `Hello, world!` as plain text. 66 | 67 | ```javascript 68 | import { Server, GET, Response, MediaType } from 'organiser' 69 | 70 | class HelloWorld { 71 | @GET 72 | async foo () { 73 | return Response.ok('Hello, world!', MediaType.TEXT_PLAIN).build() 74 | } 75 | } 76 | 77 | const server = new Server() // creates a new instance of Organise 78 | server.routes(HelloWorld) // register controllers, passing their classes by reference 79 | server.boot() // start server 80 | ``` 81 | 82 | - [organiser-agenda](https://github.com/arthurbergmz/organiser-agenda) 83 | 84 | Virtual personal agenda, with notes and contacts, using NeDB. 85 | 86 | ```javascript 87 | import { Server, Modules } from 'organiser' 88 | import { NotesController } from './controllers/notes' 89 | import { ContactsController } from './controllers/contacts' 90 | 91 | const server = new Server({ 92 | name: 'Agenda', 93 | internal: { 94 | debug: true 95 | } 96 | }) 97 | 98 | server.modules(Modules.bodyParser()) 99 | server.routes(NotesController, ContactsController) 100 | server.boot() 101 | ``` 102 | 103 | - [organiser-static](https://github.com/arthurbergmz/organiser-static) 104 | 105 | Serving static files with Organiser. 106 | 107 | ```javascript 108 | import { Server, GET, Response } from 'organiser' 109 | import path from 'path' 110 | 111 | class LandingPage { 112 | @GET 113 | async index () { 114 | return Response.static(path.join(__dirname, '../static/index.html')).build() 115 | } 116 | } 117 | 118 | const server = new Server() 119 | server.routes(LandingPage) 120 | server.boot() 121 | ``` 122 | 123 | ### Events 124 | 125 | _Work in progress..._ 126 | 127 | ### Modules 128 | 129 | You can use how many modules, before and/or after a request, as you wish. We support `context` and `connect` middlewares/modules styles. 130 | 131 | Modules defined in `server.modules(module1, module2, module3, ...)` will be executed **before** every controller, in a sequence order (module1 → module2 → module3 → ... → controller). When calling `server.modules()` with parameters, it returns an object containing a function called `after(...)`, that lets you register modules the same way, but they will run **after** every controler. Calling it without parameters will return an object with `before(...)` and `after(...)`. 132 | 133 | When you register routes through `server.routes(ControllerClass1, ControllerClass2, ControllerClass3, ...)`, it also returns an object with `before(...)` and `after`, letting you register modules only for the routes passed as parameters. 134 | 135 | Using `@ModuleBefore(module1, module2, module3, ...)` and `@ModuleAfter(module4, module5, module6, ...)` above a class or function reproduces the same behavior. 136 | 137 | ###### Built-in modules 138 | 139 | - ✔️ [Body Parser](#) `Modules.bodyParser(options)` 140 | - ✔️ [Raw Body Parser](#) `Modules.rawBodyParser(options)` 141 | 142 | ###### Example 143 | 144 | ```javascript 145 | import { Server, GET, Response, Modules, ModulesBefore, ModulesAfter } from 'organiser' 146 | 147 | function hello (context) { 148 | return new Promise((resolve) => { 149 | console.log('hello executed!') 150 | console.log('You can add properties to the request context and use their values in other modules!') 151 | context.bye = 'See you!' 152 | context.luckNumber = Math.floor(Math.random() * 10) + 1 153 | resolve() 154 | }) 155 | } 156 | 157 | function bye (context) { 158 | return new Promise((resolve) => { 159 | console.log('bye executed!') 160 | context.expectedResponse = Response.ok({ bye: context.bye, luckNumber: context.luckNumber }).build() 161 | resolve() 162 | }) 163 | } 164 | 165 | @ModulesBefore(hello) 166 | class Foo { 167 | @GET 168 | @ModulesAfter(bye) 169 | async bar () { 170 | return Response.ok({ foobar: true }).build() 171 | } 172 | } 173 | 174 | const server = new Server() 175 | server.modules(Modules.bodyParser()) 176 | server.routes(Foo).before( 177 | () => console.log('First module executed!'), 178 | () => console.log(`Keep going...`) 179 | ).after( 180 | () => console.log('last module executed!') 181 | ) 182 | server.boot() 183 | ``` 184 | 185 | _Work in progress..._ 186 | 187 | ### @Arguments - Inversion of Control 188 | 189 | Through the **Arguments** decorator, you can inject dependencies anywhere, anytime. Just use the class of the desired instance and Organised will take care of the rest. 190 | 191 | When used above classes, the respective class' constructor will be called with the parameters passed through the Arguments decorator. 192 | 193 | When used above functions, it only supports one parameters: the data model (object containing properties that Organiser should retrieve). The data model supports inner models (functions returning objects). 194 | 195 | ###### Example 196 | 197 | 198 | 199 | 233 | 269 | 270 |
200 | 201 | ```javascript 202 | import { Arguments, Path, PUT, Types } from 'organiser' 203 | import ContactsService from '../services/ContactsService' 204 | import Contact from '../models/Contact' 205 | import logEntity from '../utils/exampleLog' 206 | 207 | @Arguments(ContactsService, logEntity) 208 | @Path('contacts') 209 | export class ContactsController { 210 | 211 | constructor (service, entityLogger) { 212 | this.service = service 213 | this.logger = entityLogger 214 | } 215 | 216 | @POST 217 | @Path('{userId}') // => "/contacts/123" 218 | @Arguments({ 219 | userId: Types.INTEGER, 220 | contact: Contact 221 | }) 222 | async create ({ userId, contact }) { // userId = 123, contact = { ... } 223 | contact.ownerId = userId 224 | return Response 225 | .status(201) // Created 226 | .entity(this.logger(await this.service.create(contact))) 227 | .build() 228 | } 229 | 230 | } 231 | ``` 232 | 234 | 235 | 236 | 253 | 254 | 255 | 266 | 267 |
237 | 238 | ```javascript 239 | // '../models/Contact' 240 | 241 | import { Types } from 'organiser' 242 | 243 | export default function Contact () { 244 | return { 245 | id: Types.INTEGER, 246 | name: Types.STRING, 247 | email: Types.STRING, 248 | ownerId: Types.INTEGER 249 | } 250 | } 251 | ``` 252 |
256 | 257 | ```javascript 258 | // '../utils/exampleLog' 259 | 260 | export default function (entity) { 261 | console.log(entity) 262 | return entity 263 | } 264 | ``` 265 |
268 |
271 | 272 | - **Contact** is a function that returns an object. This is how you define a model in Organise. 273 | - **ContactsService** is just a regular class, used as a service. You can also use **Arguments** to inject parameters in its constructor (calling other services instances, for example). 274 | 275 | Read more about how the **Arguments** decorator works with functions [here](#). 276 | 277 | ### Documentation 278 | 279 | ###### Available decorators 280 | 281 | - ✔️ [`Arguments`](#) (accept parameters) 282 | - ✔️ [`Path`](#) (accept parameters) 283 | - ✔️ [`ModulesAfter`](#) (accept parameters) 284 | - ✔️ [`ModulesBefore`](#) (accept parameters) 285 | - ✔️ [`GET`](#) (functions only) 286 | - ✔️ [`HEAD`](#) (functions only) 287 | - ✔️ [`POST`](#) (functions only) 288 | - ✔️ [`PUT`](#) (functions only) 289 | - ✔️ [`DELETE`](#) (functions only) 290 | - ✔️ [`OPTIONS`](#) (functions only) 291 | - ✔️ [`TRACE`](#) (functions only) 292 | - ✔️ [`PATCH`](#) (functions only) 293 | 294 | ###### Model property types 295 | 296 | - ️️✔️ `Types.UUID`: `'uuid'` 297 | - ✔️ `Types.STRING`: `'string'` 298 | - ✔️ `Types.BOOLEAN`: `'boolean'` 299 | - ✔️ `Types.INTEGER`: `'integer'` 300 | - 🚧 `Types.DOUBLE`: `'double'` 301 | - 🚧 `Types.FLOAT`: `'float'` 302 | - 🚧 `Types.DATE`: `'date'` 303 | - 🚧 `Types.FILE`: `'file'` 304 | - ✔️ `Types.CLIENT_REQUEST`: `'clientRequest'` 305 | - ✔️ `Types.SERVER_RESPONSE`: `'serverResponse'` 306 | 307 | It is encouraged the usage of `Types.*` instead of their respective string version, for versioning purposes. 308 | 309 | ###### Data models 310 | 311 | _Work in progress..._ 312 | 313 | --- 314 | 315 | ### Team 316 | 317 | Created and developed by [Arthur Arioli Bergamaschi](https://github.com/arthurbergmz), supervised by the JavaScript Advanced Core (NAJaS - Núcleo Avançado de JavaScript) at [Fatec Taquaritinga](https://github.com/fatec-taquaritinga). 318 | 319 | --- 320 | 321 | ### License 322 | 323 | Licensed under [MIT](https://github.com/fatec-taquaritinga/organiser/blob/master/LICENSE). 324 | 325 | --- 326 | 327 | > _**Disclaimer:** Organiser is still a work in progress. Methods and behavior can change along the way._ -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for webpack-pwa-manifest 2.0.4 2 | // Project: https://github.com/arthurbergmz/webpack-pwa-manifest 3 | // Definitions by: Arthur A. Bergamaschi 4 | 5 | import * as declaration from "./typings"; 6 | export = declaration; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index.js') 2 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatec-taquaritinga/organiser/115b0adf95bae9a47218f2d3afc1b729189eeb61/media/logo.png -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "organiser", 3 | "version": "1.0.1", 4 | "description": "An organic web framework for organized web servers.", 5 | "keywords": [ 6 | "server", 7 | "mvc", 8 | "framework", 9 | "web", 10 | "rest", 11 | "api", 12 | "restful", 13 | "static", 14 | "serve", 15 | "app", 16 | "router", 17 | "application", 18 | "spring", 19 | "boot", 20 | "organiser", 21 | "organizer", 22 | "organic", 23 | "organized", 24 | "promise", 25 | "async", 26 | "await", 27 | "express" 28 | ], 29 | "main": "index.js", 30 | "types": "index.d.ts", 31 | "scripts": { 32 | "build": "babel src --out-dir dist", 33 | "test": "echo \"Error: no tests specified yet. (WIP)\" && exit 1" 34 | }, 35 | "author": "Arthur A. Bergamaschi ", 36 | "license": "MIT", 37 | "standard": { 38 | "parser": "babel-eslint" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/fatec-taquaritinga/organiser.git" 43 | }, 44 | "devDependencies": { 45 | "babel-cli": "^6.24.1", 46 | "babel-eslint": "^7.2.3", 47 | "babel-plugin-syntax-async-functions": "^6.13.0", 48 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 49 | "babel-plugin-transform-regenerator": "^6.24.1", 50 | "babel-preset-env": "^1.5.1", 51 | "eslint": "^4.4.0", 52 | "standard": "^10.0.3" 53 | }, 54 | "dependencies": { 55 | "babel-polyfill": "^6.23.0", 56 | "bytes": "^2.5.0", 57 | "colors": "^1.1.2", 58 | "mime": "^1.3.6", 59 | "urlencode": "^1.1.0", 60 | "uuid": "^3.1.0" 61 | }, 62 | "engines": { 63 | "node": ">= 6.0.0" 64 | }, 65 | "files": [ 66 | "dist", 67 | "typings", 68 | "README.md", 69 | "CHANGELOG.md", 70 | "LICENSE", 71 | "index.js", 72 | "index.d.ts", 73 | "package.json" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /src/debug/assert.js: -------------------------------------------------------------------------------- 1 | import Exceptions from '../exceptions' 2 | 3 | export default function (expression, fallbackValue, errorMessage) { 4 | if (!expression) { 5 | if (fallbackValue !== undefined) { 6 | return fallbackValue 7 | } else { 8 | throw new Exceptions.UNEXPECTED_VALUE(errorMessage) 9 | } 10 | } 11 | return true 12 | } 13 | 14 | export function isDefined (name, value, fallbackValue, errorMessage) { 15 | if (value === undefined) { 16 | throw new Exceptions.VALUE_NOT_DEFINED(errorMessage || `"${name}" must have a defined value!`) 17 | } else if (value || value === null) { 18 | return true 19 | } else if (fallbackValue !== undefined) { 20 | return fallbackValue 21 | } 22 | return false 23 | } 24 | 25 | export function isInteger (name, value, fallbackValue, errorMessage) { 26 | const response = !isNaN(+value || parseInt(value)) 27 | if (!response) { 28 | if (fallbackValue !== undefined) { 29 | return fallbackValue 30 | } else { 31 | throw new Exceptions.NUMBER_FORMAT(errorMessage || `"${name}" must be an integer number!`) 32 | } 33 | } 34 | return response 35 | } 36 | 37 | export function isInArray (arrayName, value, array, fallbackValue, errorMessage) { 38 | const response = array.indexOf(value) !== -1 39 | if (!response) { 40 | if (fallbackValue !== undefined) { 41 | return fallbackValue 42 | } else { 43 | throw new Exceptions.NOT_IN_ARRAY(errorMessage || `Value not found in "${arrayName}": ${value}`) 44 | } 45 | } 46 | return response 47 | } 48 | 49 | export function hasProperty (propertyName, property, objectName, object, fallbackValue, errorMessage) { 50 | const response = object[property] 51 | if (!response) { 52 | if (fallbackValue !== undefined) { 53 | return fallbackValue 54 | } else { 55 | throw new Exceptions.NOT_IN_OBJECT(errorMessage || `Property not found in "${objectName}": ${propertyName}`) 56 | } 57 | } 58 | return response 59 | } 60 | 61 | export function equals (operation, expected, errorMessage) { 62 | if (operation !== expected) throw new Exceptions.OPERATION_NOT_ALLOWED(errorMessage) 63 | } 64 | 65 | export function hasOperation (operation, errorMessage) { 66 | if (operation === undefined || operation === null) throw new Exceptions.OPERATION_NOT_ALLOWED(errorMessage) 67 | } 68 | 69 | export function exists (entity, entityName, errorMessage) { 70 | if (entity === undefined) throw new Exceptions.UNEXPECTED_VALUE(errorMessage || `${entityName} was not found.`) 71 | } 72 | 73 | export function isValid (entity, entityName, errorMessage) { 74 | if (entity === undefined || entity === null) throw new Exceptions.UNEXPECTED_VALUE(errorMessage || `${entityName} is not valid.`) 75 | } 76 | 77 | export function is (a, b, errorMessage) { 78 | if (a !== b) throw new Exceptions.UNEXPECTED_VALUE(errorMessage || `${a} is not ${b}.`) 79 | } 80 | -------------------------------------------------------------------------------- /src/debug/index.js: -------------------------------------------------------------------------------- 1 | import * as log from './log' 2 | import * as assert from './assert' 3 | 4 | export default Object.freeze({ 5 | assert, 6 | log 7 | }) 8 | -------------------------------------------------------------------------------- /src/debug/log.js: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os' 2 | import colors from 'colors/safe' 3 | 4 | const successPrefix = '\u2713 ' 5 | const informationPrefix = '\uD83D\uDEC8 ' 6 | const warningPrefix = '\u26A0 ' 7 | const errorPrefix = '\u2716 ' 8 | 9 | function filter (message) { 10 | return typeof message === 'string' ? message : JSON.stringify(message) 11 | } 12 | 13 | export function success (message) { 14 | process.stdout.write(colors.green(`${successPrefix} ${filter(message)}${EOL}`)) 15 | } 16 | 17 | export function info (message) { 18 | process.stdout.write(colors.cyan(`${informationPrefix} ${filter(message)}${EOL}`)) 19 | } 20 | 21 | export function warning (message) { 22 | process.stdout.write(colors.red(`${warningPrefix} ${filter(message)}${EOL}`)) 23 | } 24 | 25 | export function print (message) { 26 | process.stdout.write(colors.grey(`${informationPrefix} ${filter(message)}${EOL}`)) 27 | } 28 | 29 | export function error (message) { 30 | process.stdout.write(colors.bgRed.white(`${errorPrefix} ${filter(message)}${EOL}`)) 31 | } 32 | -------------------------------------------------------------------------------- /src/decorators/index.js: -------------------------------------------------------------------------------- 1 | import { handleArguments, handlePath, handleHttpMethod, handleModules } from '../persistence' 2 | 3 | export function Arguments (...args) { 4 | return (target, property, descriptor) => handleArguments(target, property, descriptor, args) 5 | } 6 | 7 | export function Path (path) { 8 | return (target, property, descriptor) => handlePath(target, property, descriptor, path) 9 | } 10 | 11 | export function ModulesAfter (...modules) { 12 | return (target, property, descriptor) => handleModules(target, property, descriptor, modules, false) 13 | } 14 | 15 | export function ModulesBefore (...modules) { 16 | return (target, property, descriptor) => handleModules(target, property, descriptor, modules, true) 17 | } 18 | 19 | export function GET (target, property, descriptor) { 20 | handleHttpMethod(target, property, descriptor, 'GET') 21 | } 22 | 23 | export function HEAD (target, property, descriptor) { 24 | handleHttpMethod(target, property, descriptor, 'HEAD') 25 | } 26 | 27 | export function POST (target, property, descriptor) { 28 | handleHttpMethod(target, property, descriptor, 'POST') 29 | } 30 | 31 | export function PUT (target, property, descriptor) { 32 | handleHttpMethod(target, property, descriptor, 'PUT') 33 | } 34 | 35 | export function DELETE (target, property, descriptor) { 36 | handleHttpMethod(target, property, descriptor, 'DELETE') 37 | } 38 | 39 | export function OPTIONS (target, property, descriptor) { 40 | handleHttpMethod(target, property, descriptor, 'OPTIONS') 41 | } 42 | 43 | export function TRACE (target, property, descriptor) { 44 | handleHttpMethod(target, property, descriptor, 'TRACE') 45 | } 46 | 47 | export function PATCH (target, property, descriptor) { 48 | handleHttpMethod(target, property, descriptor, 'PATCH') 49 | } 50 | -------------------------------------------------------------------------------- /src/enums/index.js: -------------------------------------------------------------------------------- 1 | export { Types } from './types' 2 | 3 | export { ParamTypes } from './paramTypes' 4 | -------------------------------------------------------------------------------- /src/enums/paramTypes.js: -------------------------------------------------------------------------------- 1 | export const ParamTypes = { 2 | QUERY: 'query', 3 | PATH: 'path', 4 | PAYLOAD: 'payload' 5 | } 6 | 7 | // path > query > payload 8 | -------------------------------------------------------------------------------- /src/enums/types/boolean.js: -------------------------------------------------------------------------------- 1 | function test (value) { 2 | return value === 0 || value === 1 || value === true || value === false || 3 | value === '0' || value === '1' || value === 'true' || value === 'false' || 4 | (typeof value === 'function' && test(value())) 5 | } 6 | 7 | function exec (value) { 8 | const type = typeof value 9 | if (type === 'boolean') { 10 | return value 11 | } else if (type === 'string') { 12 | if (value === '0') { 13 | return false 14 | } else if (value === '1') { 15 | return true 16 | } else if (value === 'false') { 17 | return false 18 | } else if (value === 'true') { 19 | return true 20 | } 21 | } else if (type === 'number') { 22 | return value === 0 ? false : (value === 1 ? true : null) 23 | } else if (type === 'function') { 24 | return exec(value()) 25 | } 26 | return null 27 | } 28 | 29 | export default { 30 | test, 31 | exec 32 | } 33 | -------------------------------------------------------------------------------- /src/enums/types/clientRequest.js: -------------------------------------------------------------------------------- 1 | function test (value) { 2 | return typeof value === 'object' && ((value.context && value.context.request) || value.request) 3 | } 4 | 5 | function exec (value) { 6 | return value.context ? value.context.request : value.request 7 | } 8 | 9 | export default { 10 | test, 11 | exec 12 | } 13 | -------------------------------------------------------------------------------- /src/enums/types/index.js: -------------------------------------------------------------------------------- 1 | import Exceptions from '../../exceptions' 2 | import uuid from './uuid' 3 | import string from './string' 4 | import boolean from './boolean' 5 | import integer from './integer' 6 | import clientRequest from './clientRequest' 7 | import serverResponse from './serverResponse' 8 | 9 | export const Types = Object.create({ 10 | UUID: 'uuid', 11 | STRING: 'string', 12 | BOOLEAN: 'boolean', 13 | INTEGER: 'integer', 14 | DOUBLE: 'double', 15 | FLOAT: 'float', 16 | DATE: 'date', 17 | FILE: 'file', // TODO multipartFile 18 | CLIENT_REQUEST: 'clientRequest', 19 | SERVER_RESPONSE: 'serverResponse', 20 | registerCustomType, 21 | unregisterCustomType, 22 | exists 23 | }) 24 | 25 | const types = Object.create({ 26 | uuid, 27 | string, 28 | boolean, 29 | integer, 30 | clientRequest, 31 | serverResponse 32 | }) 33 | 34 | function registerCustomType (propertyName, typeName, typeConverter) { 35 | if (!typeConverter && typeof typeName === 'object') return registerCustomType(propertyName, propertyName, typeName) 36 | if (propertyName && typeName && typeConverter && typeConverter.test && typeConverter.test.length === 1 && typeConverter.exec && typeConverter.exec.length === 1) { 37 | propertyName = propertyName.toUpperCase() 38 | if (Types[propertyName]) throw new Exceptions.TYPE_ALREADY_EXISTS(`${propertyName} is already registered.`) 39 | Types[propertyName] = typeName 40 | types[typeName] = typeConverter 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | // Deprecated 47 | function unregisterCustomType (name) { 48 | if (!name) return false 49 | const upperCase = name.toUpperCase() 50 | if (Types[upperCase] && types[name]) { 51 | delete Types[name.toUpperCase()] 52 | delete types[name] 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | function exists (type) { 59 | return types[type] !== undefined 60 | } 61 | 62 | export function transform (value, to) { 63 | const type = types[to] 64 | return type && type.test(value) ? type.exec(value) : null 65 | } 66 | -------------------------------------------------------------------------------- /src/enums/types/integer.js: -------------------------------------------------------------------------------- 1 | function test (value) { 2 | return !isNaN(+value || parseInt(value)) 3 | } 4 | 5 | function exec (value) { 6 | return +value || parseInt(value) 7 | } 8 | 9 | export default { 10 | test, 11 | exec 12 | } 13 | -------------------------------------------------------------------------------- /src/enums/types/serverResponse.js: -------------------------------------------------------------------------------- 1 | function test (value) { 2 | return typeof value === 'object' && ((value.context && value.context.response) || value.response) 3 | } 4 | 5 | function exec (value) { 6 | return value.context ? value.context.response : value.response 7 | } 8 | 9 | export default { 10 | test, 11 | exec 12 | } 13 | -------------------------------------------------------------------------------- /src/enums/types/string.js: -------------------------------------------------------------------------------- 1 | function exec (value) { 2 | const type = typeof value 3 | if (type === 'string') { 4 | return value 5 | } else if (type === 'number') { 6 | return isNaN(value) ? null : `${value}` 7 | } else if (type === 'object') { 8 | return JSON.stringify(value) 9 | } else if (type === 'function') { 10 | return exec(value()) 11 | } 12 | return value && value.toString ? value.toString() : null 13 | } 14 | 15 | export default { 16 | test: (value) => true, 17 | exec 18 | } 19 | -------------------------------------------------------------------------------- /src/enums/types/uuid.js: -------------------------------------------------------------------------------- 1 | import generate from 'uuid/v4' 2 | 3 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i 4 | 5 | export default { 6 | test: (value) => true, 7 | exec: (value) => { 8 | if (!value) return generate() 9 | let str = (typeof value === 'string' ? value : (value.toString ? value.toString() : null)) 10 | if (!str) return null 11 | let len = str.length 12 | if (len === 32) { 13 | len = 36 14 | str = `${str.substr(0, 8)}-${str.substr(8, 4)}-${str.substr(12, 4)}-${str.substr(16, 4)}-${str.substr(20, 12)}` 15 | } 16 | return str && len === 36 && uuidRegex.test(str) ? str : null 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/events/decorator.js: -------------------------------------------------------------------------------- 1 | export function EventHandler (eventType) { 2 | return (target, property, descriptor) => { 3 | if (!descriptor) return 4 | if (!target.eventListeners) target.eventListeners = {} 5 | if (!target.eventListeners[property]) target.eventListeners[property] = {} 6 | const type = typeof eventType 7 | let eventHandler 8 | if (type === 'string') { 9 | eventHandler = { type: eventType } 10 | } else if (type === 'object') { 11 | eventHandler = { type: eventType._eventType } 12 | } else if (type === 'function') { 13 | eventHandler = { type: eventType.name } 14 | } else { 15 | throw new Error('eventType must be a string!') 16 | } 17 | target.eventListeners[property] = eventHandler 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/events/index.js: -------------------------------------------------------------------------------- 1 | import EventType from './types' 2 | import { resolveInstance } from '../injections' 3 | import utils from '../utils' 4 | 5 | export { EventType } 6 | export { EventHandler } from './decorator' 7 | 8 | export class EventBus { 9 | static get Types () { 10 | return EventType 11 | } 12 | 13 | registerListener (eventType, listener) { 14 | const type = typeof eventType 15 | const listenerType = typeof listener 16 | if (listener && type === 'string' && listenerType === 'function') { 17 | (this[eventType] || (this[eventType] = [])).push(listener) 18 | } else if (listener && type === 'function' && listenerType === 'function') { 19 | this.registerListener(eventType.name, listener) 20 | } else if (type === 'function' && eventType.eventHandler && eventType.eventHandler.type && !listener) { 21 | this.registerListener(eventType.eventHandler.type, eventType) 22 | } else if (type === 'object' && !listener) { 23 | const eventListeners = eventType.eventListeners 24 | for (let property in eventListeners) { 25 | const eventHandler = eventType.eventListeners[property] 26 | if (!eventHandler) continue 27 | const func = eventType[property] 28 | if (typeof func === 'function' && eventHandler.type) this.registerListener(eventHandler.type, func) 29 | } 30 | } else if (type === 'function' && !listener) { 31 | this.registerListener(resolveInstance(eventType)) 32 | } else { 33 | throw new Error('Invalid event type: eventType must be a string, an object or a function.') 34 | } 35 | } 36 | 37 | removeListener (eventType, listener) { 38 | const type = typeof eventType 39 | if (type === 'string' && listener && typeof listener === 'function') { 40 | const arr = this[eventType] 41 | const len = arr.length 42 | if (arr && len > 0) { 43 | arr.slice(arr.indexOf(listener), 1) 44 | if (len === 1) delete listener[type] 45 | } 46 | } else if (type === 'function' && eventType.eventHandler && eventType.eventHandler.type && !listener) { 47 | this.removeListener(eventType.eventHandler.type, eventType) 48 | } else if (type === 'object' && !listener) { 49 | for (let property in eventType) { 50 | const func = eventType[property] 51 | if (typeof func === 'function' && func.eventHandler && eventType.eventHandler.type) this.removeListener(func.eventHandler.type, func) 52 | } 53 | } 54 | } 55 | 56 | amountOf (eventType) { 57 | const type = typeof eventType 58 | if (type === 'string') { 59 | const listeners = this[eventType] 60 | if (listeners) return listeners.length 61 | } else if (type === 'function' && eventType.eventHandler && eventType.eventHandler.type) { 62 | return this.listenersOf(eventType.eventHandler.type) 63 | } 64 | return 0 65 | } 66 | 67 | emit (eventType, e) { 68 | const type = typeof eventType 69 | if (type === 'string') { 70 | const arr = this[eventType] 71 | if (arr) utils.iterate(arr, listener => e ? listener(e) : listener()) 72 | } else if (type === 'object' && eventType._eventType) { 73 | this.emit(eventType._eventType, eventType) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/events/types/ExceptionThrownEvent.js: -------------------------------------------------------------------------------- 1 | export default function ExceptionThrownEvent (exception, isCancelled = false) { 2 | return Object.seal({ 3 | get _eventType () { 4 | return 'ExceptionThrownEvent' 5 | }, 6 | exception, 7 | isCancelled () { 8 | return isCancelled 9 | }, 10 | setCancelled (cancelled) { 11 | isCancelled = cancelled 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/events/types/ServerStatusUpdateEvent.js: -------------------------------------------------------------------------------- 1 | import debug from '../../debug' 2 | 3 | export default function ServerStatusUpdateEvent (status, message, isCancelled = false) { 4 | debug.assert.isDefined('status', status) 5 | return Object.seal({ 6 | get _eventType () { 7 | return 'ServerStatusUpdateEvent' 8 | }, 9 | get status () { 10 | return status 11 | }, 12 | message, 13 | isCancelled () { 14 | return isCancelled 15 | }, 16 | setCancelled (cancelled) { 17 | isCancelled = cancelled 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/events/types/index.js: -------------------------------------------------------------------------------- 1 | import ExceptionThrownEvent from './ExceptionThrownEvent' 2 | import ServerStatusUpdateEvent from './ServerStatusUpdateEvent' 3 | 4 | export default Object.freeze({ 5 | EXCEPTION_THROWN_EVENT: ExceptionThrownEvent, 6 | SERVER_STATUS_UPDATE_EVENT: ServerStatusUpdateEvent 7 | }) 8 | -------------------------------------------------------------------------------- /src/exceptions/index.js: -------------------------------------------------------------------------------- 1 | import valueNotDefinedException from './valueNotDefinedException' 2 | import numberFormatException from './numberFormatException' 3 | import notInArrayException from './notInArrayException' 4 | import notInObjectException from './notInObjectException' 5 | import unexpectedValueException from './unexpectedValueException' 6 | import typeAlreadyExistsException from './typeAlreadyExistsException' 7 | import missingArgumentException from './missingArgument' 8 | import strictModeException from './strictMode' 9 | import operationNotAllowedException from './operationNotAllowedException' 10 | 11 | export default { 12 | VALUE_NOT_DEFINED: valueNotDefinedException, 13 | NUMBER_FORMAT: numberFormatException, 14 | NOT_IN_ARRAY: notInArrayException, 15 | NOT_IN_OBJECT: notInObjectException, 16 | UNEXPECTED_VALUE: unexpectedValueException, 17 | TYPE_ALREADY_EXISTS: typeAlreadyExistsException, 18 | MISSING_ARGUMENT: missingArgumentException, 19 | STRICT_MODE: strictModeException, 20 | OPERATION_NOT_ALLOWED: operationNotAllowedException 21 | } 22 | -------------------------------------------------------------------------------- /src/exceptions/missingArgument.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'MissingArgumentException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/notInArrayException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'NotInArrayException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/notInObjectException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'NotInObjectException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/numberFormatException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'NumberFormatException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/operationNotAllowedException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'OperationNotAllowedException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/strictMode.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'StrictModeException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/typeAlreadyExistsException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'TypeAlreadyExistsException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/unexpectedValueException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'UnexpectedValueException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/exceptions/valueNotDefinedException.js: -------------------------------------------------------------------------------- 1 | export default function (message) { 2 | this.name = 'ValueNotDefinedException' 3 | this.message = message 4 | } 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { Server, ServerStatus } from './server' 2 | 3 | export { Modules } from './modules' 4 | 5 | export * from './decorators' 6 | 7 | export * from './enums' 8 | 9 | export { Response, MediaType, HttpStatus } from './response' 10 | 11 | export * from './events' 12 | -------------------------------------------------------------------------------- /src/injections/index.js: -------------------------------------------------------------------------------- 1 | import { Types, transform } from '../enums/types' 2 | import { ParamTypes } from '../enums/paramTypes' 3 | import { extractPathParams } from '../server/utils' 4 | import { handleArguments } from '../persistence' 5 | 6 | export function resolveRequestArguments (context) { 7 | return new Promise((resolve) => { 8 | const route = context.route 9 | const resolver = route.resolver 10 | const method = resolver.persistence[resolver.method] 11 | const requestedArgs = method.args 12 | if (requestedArgs && Object.keys(requestedArgs).length > 0) { 13 | resolveModel(requestedArgs, method.strict, { 14 | path: extractPathParams(route.path, context.url.input, route.matcher), 15 | query: context.url.parsed.query, 16 | body: context.body || {}, 17 | context 18 | }, null, resolve) 19 | } else { 20 | resolve(null) 21 | } 22 | }) 23 | } 24 | 25 | function resolveModel (model, strictMode, data, parentWhere = null, resolve = null) { 26 | // TODO strict mode 27 | // + needs to check inner models 28 | const response = {} 29 | for (let property in model) { 30 | const propertyValue = model[property] 31 | const parameter = retrieveParameter(property, propertyValue, data, parentWhere) 32 | let value = retrieveParameterAt(property, parameter.where, data, parameter.defaultValue) 33 | if (parameter.isModel && value) { 34 | const model = parameter.type() 35 | if (typeof model === 'object') { 36 | let modelData = data 37 | if (parameter.where === ParamTypes.PATH) { 38 | modelData = { 39 | path: value, 40 | query: data.query, 41 | body: data.body 42 | } 43 | } else if (parameter.where === ParamTypes.QUERY) { 44 | modelData = { 45 | path: data.path, 46 | query: value, 47 | body: data.body 48 | } 49 | } else if (parameter.where === ParamTypes.PAYLOAD) { 50 | modelData = modelData = { 51 | path: data.path, 52 | query: data.query, 53 | body: value 54 | } 55 | } 56 | value = resolveModel(model, strictMode, modelData, parameter.where) 57 | } else { 58 | value = model 59 | } 60 | } else if (parameter.isModel && !value && !isValidValue(value)) { // if there is no value, then this property is probably a general model 61 | value = parameter.type() 62 | if (typeof value === 'object') value = resolveModel(value, strictMode, data, retrieveDefaultParamType(propertyValue)) 63 | // TODO array implementation 64 | } else if (parameter.type && value) { 65 | value = transform(value, parameter.type) 66 | } else if ((parameter.type === Types.CLIENT_REQUEST || parameter.type === Types.SERVER_RESPONSE) && !value) { 67 | value = transform(data.context, parameter.type) 68 | } 69 | const propertyResponse = isValidValue(value) ? value : (value || null) // if value is undefined or null and not optional, we keep it as null 70 | if (propertyResponse !== null && !parameter.isOptinal) { 71 | response[property] = propertyResponse 72 | } 73 | } 74 | if (resolve) { 75 | resolve(response) 76 | } else { 77 | return response 78 | } 79 | } 80 | 81 | function isValidValue (value) { 82 | const type = typeof value 83 | return value ? true : (!!(type === 'boolean' || type === 'number')) 84 | } 85 | 86 | function retrieveParameter (property, value, data, parentWhere) { 87 | const type = extractTypeFromArgument(value, false) 88 | const isModel = typeof type === 'function' 89 | return { 90 | name: property, 91 | type, 92 | where: retrieveParameterType(property, value, isModel, data, parentWhere), 93 | defaultValue: retrieveDefaultValue(value), 94 | isOptinal: value.optional || false, 95 | isModel 96 | } 97 | } 98 | 99 | function retrieveDefaultValue (value) { 100 | const defaultValue = value.defaultValue 101 | if (isValidValue(defaultValue)) return defaultValue 102 | const val = value.value 103 | if (isValidValue(val)) return val 104 | const fallback = value.fallback 105 | if (isValidValue(fallback)) return fallback 106 | return null 107 | } 108 | 109 | function retrieveDefaultParamType (paramInfo) { 110 | return paramInfo.parameterType || paramInfo.paramType || paramInfo.paramTypes || paramInfo.parameter || 111 | paramInfo.param || null 112 | } 113 | 114 | function retrieveParameterType (paramName, paramInfo, isModel, data, parentWhere) { 115 | return retrieveDefaultParamType(paramInfo) || parentWhere || (isModel ? ParamTypes.PAYLOAD 116 | : (data.path[paramName] ? ParamTypes.PATH 117 | : (data.query[paramName] ? ParamTypes.QUERY : ParamTypes.PAYLOAD))) 118 | } 119 | 120 | function retrieveParameterAt (paramName, where, data, defaultValue) { 121 | if (where === ParamTypes.PATH) { 122 | return data.path[paramName] || defaultValue 123 | } else if (where === ParamTypes.QUERY) { 124 | return data.query[paramName] || defaultValue 125 | } else if (where === ParamTypes.PAYLOAD) { 126 | return data.body[paramName] || defaultValue 127 | } 128 | return defaultValue 129 | } 130 | 131 | function extractTypeFromArgument (arg, inside) { 132 | const type = typeof arg 133 | if (type === 'string') { 134 | return Types.exists(arg) ? arg : null 135 | } else if (type === 'object') { 136 | return !inside && arg.type ? extractTypeFromArgument(arg.type, true) : arg 137 | } else if (type === 'function') { 138 | return arg 139 | } 140 | return null 141 | } 142 | -------------------------------------------------------------------------------- /src/modules/bodyParser/index.js: -------------------------------------------------------------------------------- 1 | import raw from './raw' 2 | import parsers from './parsers' 3 | 4 | export default function (options = {}) { 5 | const rawParser = raw(options) 6 | return function (context) { 7 | return new Promise((resolve, reject) => { 8 | rawParser(context) 9 | .then(content => { 10 | const contentType = context.headers['content-type'] 11 | context.body = contentType ? parsers.parseFromContentType(contentType, content.rawBody, options) : parsers.parse(content.rawBody, options) 12 | resolve() 13 | }) 14 | .catch(err => { 15 | reject(err) 16 | }) 17 | }) 18 | } 19 | } 20 | 21 | // check Content-Type header and check if there is converter available (raw?) 22 | // content-type -> json -> x-www-form-urlencoded -> form-data -> binary 23 | -------------------------------------------------------------------------------- /src/modules/bodyParser/parsers/index.js: -------------------------------------------------------------------------------- 1 | import contentTypes from '../../../response/mediaType' 2 | import json from './json' 3 | import urlencoded from './urlencoded' 4 | 5 | export const parsers = Object.create({ 6 | [contentTypes.APPLICATION_JSON]: json, 7 | [contentTypes.APPLICATION_FORM_URLENCODED]: urlencoded 8 | }) 9 | 10 | export default Object.freeze({ 11 | parseFromContentType: (contentType, content, options) => { 12 | const parser = parsers[contentType] 13 | return parser ? parser(content, options) : null 14 | }, 15 | parse: (content, options) => { 16 | for (let parser in parsers) { 17 | const response = parsers[parser](content, options) 18 | if (response) return response 19 | } 20 | return content // no compatible parser available 21 | } 22 | }) 23 | 24 | // check Content-Type header and check if there is converter available (raw?) 25 | // content-type -> json -> x-www-form-urlencoded -> form-data -> binary 26 | -------------------------------------------------------------------------------- /src/modules/bodyParser/parsers/json.js: -------------------------------------------------------------------------------- 1 | export default function (content, options) { 2 | // assert charset? (RFC 7159, 8.1) 3 | try { 4 | return content && content[0] ? JSON.parse(content) : null 5 | } catch (exception) { 6 | return null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/bodyParser/parsers/urlencoded.js: -------------------------------------------------------------------------------- 1 | import urlencode from 'urlencode' 2 | 3 | export default function (content, options) { 4 | try { 5 | urlencode.parse(content, { charset: options.encoding || 'utf8' }) 6 | } catch (exception) { 7 | return null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/bodyParser/raw.js: -------------------------------------------------------------------------------- 1 | import { Response } from '../../response' 2 | import bytes from 'bytes' 3 | 4 | // TODO encoding 5 | 6 | export default function (options = {}) { 7 | options = Object.assign({ 8 | length: null, 9 | limit: '1mb', 10 | encoding: 'utf8' // TODO 11 | }, options) 12 | const length = bytes.parse(options.length) 13 | const limit = bytes.parse(options.limit) 14 | return (context) => { 15 | const request = context.request 16 | return new Promise((resolve, reject) => { 17 | let done = false 18 | let size = 0 19 | const raw = [] 20 | function onError (err) { 21 | reject(err) 22 | } 23 | function onAborted () { 24 | if (done) return 25 | resolve(Response.status(400).build()) 26 | } 27 | function onData (chunk) { 28 | if (done) return 29 | size += chunk.length 30 | if (limit !== null && size > limit) { 31 | resolve(Response.status(413).build()) 32 | } else if (length !== null && size > length) { 33 | resolve(Response.status(413).build()) 34 | } else { 35 | raw.push(chunk) 36 | } 37 | } 38 | function onEnd () { 39 | if (done) return 40 | done = true 41 | onClose() 42 | if (length !== null && length !== size) { 43 | resolve(Response.status(400).build()) 44 | } else { 45 | context.rawBody = Buffer.concat(raw).toString() 46 | } 47 | } 48 | function onClose () { 49 | request.removeListener('error', onError) 50 | request.removeListener('aborted', onAborted) 51 | request.removeListener('data', onData) 52 | request.removeListener('end', onEnd) 53 | request.removeListener('close', onClose) 54 | } 55 | request.on('error', onError).on('aborted', onAborted).on('close', onClose).on('data', onData).on('end', onEnd) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/modules/index.js: -------------------------------------------------------------------------------- 1 | import bodyParser from './bodyParser' 2 | import rawBodyParser from './bodyParser/raw' 3 | 4 | export const Modules = Object.create({ 5 | bodyParser, 6 | rawBodyParser 7 | }) 8 | -------------------------------------------------------------------------------- /src/persistence/index.js: -------------------------------------------------------------------------------- 1 | import debug from '../debug' 2 | import utils from '../utils' 3 | import ModulesChain from '../server/modules' 4 | 5 | const instances = {} 6 | 7 | function persist (target, persistObjects = true) { 8 | if (target !== undefined || target !== null) { 9 | const type = typeof target 10 | if (type === 'function') { 11 | return target._ || (target._ = Object.create({ 12 | constructor: null, 13 | path: null, 14 | modules: null 15 | })) 16 | } else if (persistObjects && type === 'object') { 17 | return target._ || (target._ = {}) 18 | } 19 | } 20 | return null 21 | } 22 | 23 | function persistMethods (persistence, property) { 24 | return (persistence[property] || (persistence[property] = Object.create({ 25 | types: [], 26 | path: null, 27 | args: null, 28 | strict: false, 29 | modules: null 30 | }))) 31 | } 32 | 33 | function persistModules (persistence) { 34 | return persistence.modules || (persistence.modules = { after: new ModulesChain(), before: new ModulesChain() }) 35 | } 36 | 37 | export function retrieve (target) { 38 | return target ? target._ : undefined 39 | } 40 | 41 | export function handleHttpMethod (target, property, descriptor, method) { 42 | debug.assert.exists(target, target.name || target) 43 | debug.assert.hasOperation(descriptor, `You can't use ${method} with ${target.name || target}.`) 44 | const persistence = persist(target) 45 | // debug.assert.hasOperation(persistence.httpMethods, `You can't use ${method} with ${target.name || target}.`) 46 | // persistence.httpMethods[method] = property 47 | const types = persistMethods(persistence, property).types 48 | if (types.indexOf(method) === -1) types.push(method) 49 | } 50 | 51 | export function handlePath (target, property, descriptor, path) { 52 | debug.assert.exists(target, target.name || target) 53 | debug.assert.is(typeof path, 'string', 'Path must be a string.') 54 | if (descriptor) { // class functions 55 | const methodPersistence = persistMethods(persist(target), property) 56 | methodPersistence.path = path 57 | } else { // class 58 | persist(target).path = path 59 | } 60 | } 61 | 62 | export function handleArguments (target, property, descriptor, args) { 63 | debug.assert.exists(target, target.name || target) 64 | if (descriptor) { // class functions - model definition 65 | const persistence = persistMethods(persist(target), property) 66 | const strict = args[1] 67 | persistence.args = args[0] // model 68 | persistence.strict = (strict === true || strict === false) ? strict : false 69 | } else { // class - constructor injection 70 | persist(target).constructor = args 71 | } 72 | } 73 | 74 | export function handleModules (target, property, descriptor, modules, before) { 75 | debug.assert.exists(target, target.name || target) 76 | const mods = persistModules(descriptor ? persistMethods(persist(target), property) : persist(target)) 77 | const chain = before ? mods.before : mods.after 78 | utils.iterate(modules, (mod) => { 79 | chain.register(mod) 80 | }) 81 | } 82 | 83 | export function resolveArgumentsInjection (object) { 84 | const persistence = persist(object, false) 85 | const injections = persistence ? persistence.constructor : null 86 | const constructorArguments = [] 87 | if (injections && Array.isArray(injections)) { 88 | utils.iterate(injections, (arg) => { 89 | const args = resolveArgumentsInjection(arg) 90 | if (args.length === 0) { // end of injections 91 | constructorArguments.push(typeof arg === 'function' ? resolveInstance(arg) : arg) // try to resolve function or class, or use raw value 92 | } else if (typeof arg === 'function') { 93 | constructorArguments.push(resolveInstance(arg, args)) // initialize and cache / retrieve instance 94 | } else { 95 | constructorArguments.push(arg) // raw value 96 | } 97 | }) 98 | } 99 | return constructorArguments 100 | } 101 | 102 | export function resolveInstance (instance, args) { 103 | let arg = instances[instance] // retrieve cached instance 104 | if (!arg) { 105 | try { 106 | arg = args ? instance(args) : instance() 107 | } catch (exception) { 108 | const msg = exception.message 109 | if (msg === 'Cannot call a class as a function' || msg.substring(msg.length - 31) === 'cannot be invoked without \'new\'') { 110 | arg = (instances[instance] = new (instance.bind.apply(instance, args ? [instance, ...args] : [instance]))())// eslint-disable-line no-useless-call 111 | } else if (typeof instance === 'function') { 112 | arg = instance 113 | } else { 114 | arg = null 115 | debug.log.error(`Error while injecting "${instance.name || instance.toString()}" as an argument: ${msg}`) 116 | } 117 | } 118 | } 119 | return arg 120 | } 121 | -------------------------------------------------------------------------------- /src/response/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import HttpStatus from './status' 3 | import MediaType from './mediaType' 4 | import serveStatic from './static' 5 | 6 | export { MediaType, HttpStatus } 7 | 8 | export class ResponseBuilder { 9 | constructor (statusCode, mediaType, entity) { 10 | this._status = statusCode 11 | this._entity = entity 12 | this._encoding = 'utf8' 13 | this._headers = { 14 | 'Content-Type': mediaType 15 | } 16 | } 17 | 18 | build () { 19 | return new Response(this) 20 | } 21 | 22 | status (status) { 23 | this._status = status 24 | return this 25 | } 26 | 27 | entity (entity) { 28 | this._entity = entity 29 | return this 30 | } 31 | 32 | type (mediaType) { 33 | this._headers['Content-Type'] = mediaType 34 | return this 35 | } 36 | 37 | cookie (...cookies) { 38 | const arr = this._headers['Set-Cookie'] || (this._headers['Set-Cookie'] = []) 39 | arr.push(...cookies) 40 | return this 41 | } 42 | 43 | expires (date = new Date()) { 44 | this._headers['Expires'] = date.toUTCString() 45 | return this 46 | } 47 | 48 | lastModified (date = new Date()) { 49 | this._headers['Last-Modified'] = date.toUTCString() 50 | return this 51 | } 52 | 53 | encoding (encoding) { 54 | this._encoding = encoding 55 | return this 56 | } 57 | 58 | header (key, value) { 59 | this._headers[key] = value 60 | return this 61 | } 62 | } 63 | 64 | export class Response { 65 | constructor (responseBuilder) { 66 | this.body = { 67 | status: responseBuilder._status, 68 | entity: responseBuilder._entity, 69 | encoding: responseBuilder._encoding 70 | } 71 | this.header = responseBuilder._headers 72 | } 73 | 74 | edit () { 75 | return Response.fromResponse(this) 76 | } 77 | 78 | static fromResponse (response) { 79 | const entity = response.body.entity 80 | const builder = new ResponseBuilder(response.body.status, MediaType.APPLICATION_JSON, typeof entity === 'object' ? JSON.parse(JSON.stringify(entity)) : entity) // is deep clone really necessary? 81 | builder._headers = JSON.parse(JSON.stringify(response.header)) // is deep clone really necessary? 82 | builder._encoding = response._encoding 83 | return builder 84 | } 85 | 86 | static accepted (entity = null, mediaType = MediaType.APPLICATION_JSON) { 87 | return new ResponseBuilder(HttpStatus.ACCEPTED, mediaType, entity) 88 | } 89 | 90 | static badRequest (entity = null, mediaType = MediaType) { 91 | return new ResponseBuilder(HttpStatus.BAD_REQUEST, mediaType, entity) 92 | } 93 | 94 | static noContent (mediaType = MediaType.APPLICATION_JSON) { 95 | return new ResponseBuilder(HttpStatus.NO_CONTENT, mediaType, null) 96 | } 97 | 98 | static notFound (entity = null, mediaType = MediaType.APPLICATION_JSON) { 99 | return new ResponseBuilder(HttpStatus.NOT_FOUND, mediaType, entity) 100 | } 101 | 102 | static ok (entity = null, mediaType = MediaType.APPLICATION_JSON) { 103 | return new ResponseBuilder(HttpStatus.OK, mediaType, entity) 104 | } 105 | 106 | static redirect (uri) { 107 | return new ResponseBuilder(HttpStatus.MOVED_PERMANENTLY, MediaType.TEXT_HTML, null).header('Location', uri) 108 | } 109 | 110 | static serverError (entity = null, mediaType = MediaType.APPLICATION_JSON) { 111 | return new ResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR, mediaType, entity) 112 | } 113 | 114 | static status (status) { 115 | return new ResponseBuilder(status, MediaType.APPLICATION_JSON, null) 116 | } 117 | 118 | static static (file, options) { 119 | const response = serveStatic(file, options) 120 | return new ResponseBuilder(response.statusCode, response.mediaType, response.content) 121 | } 122 | 123 | static get Status () { 124 | return HttpStatus 125 | } 126 | 127 | static get MediaType () { 128 | return MediaType 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/response/mediaType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | APPLICATION_ATOM_XML: 'application/atom+xml', 3 | APPLICATION_JSON: 'application/json', 4 | APPLICATION_FORM_URLENCODED: 'application/x-www-form-urlencoded', 5 | APPLICATION_OCTET_STREAM: 'application/octet-stream', 6 | APPLICATION_PDF: 'application/pdf', 7 | APPLICATION_RSS_XML: 'application/rss+xml', 8 | APPLICATION_XHTML_XML: 'application/xhtml+xml', 9 | APPLICATION_XML: 'application/xml', 10 | IMAGE_GIF: 'image/gif', 11 | IMAGE_JPEG: 'image/jpeg', 12 | IMAGE_PNG: 'image/png', 13 | MULTIPART_FORM_DATA: 'multipart/form-data', 14 | TEXT_EVENT_STREAM: 'text/event-stream', 15 | TEXT_HTML: 'text/html', 16 | TEXT_MARKDOWN: 'text/markdown', 17 | TEXT_PLAIN: 'text/plain', 18 | TEXT_XML: 'text/xml' 19 | } 20 | -------------------------------------------------------------------------------- /src/response/static.js: -------------------------------------------------------------------------------- 1 | import mime from 'mime' 2 | import fs from 'fs' 3 | import HttpStatus from './status' 4 | import MediaType from './mediaType' 5 | 6 | const cache = {} 7 | 8 | export default function (file, options) { 9 | const mediaType = mime.lookup(file) 10 | return { 11 | statusCode: HttpStatus.OK, 12 | mediaType, 13 | content: (response) => new Promise((resolve) => { 14 | const cached = cache[file] 15 | if (cached) { 16 | resolve(response.edit().entity(cached).build()) 17 | } else { 18 | fs.readFile(file, 'utf8', (err, data) => { 19 | if (err) { 20 | // TODO improve error handling 21 | resolve(response.edit().status(HttpStatus.NO_CONTENT).type(MediaType.APPLICATION_JSON).entity(null).build()) 22 | } else { 23 | resolve(response.edit().entity((cache[file] = data)).build()) 24 | } 25 | }) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/response/status.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CONTINUE: 100, 3 | SWITCHING_PROTOCOLS: 101, 4 | PROCESSING: 102, 5 | OK: 200, 6 | CREATED: 201, 7 | ACCEPTED: 202, 8 | NON_AUTHORITATIVE_INFORMATION: 203, 9 | NO_CONTENT: 204, 10 | RESET_CONTENT: 205, 11 | PARTIAL_CONTENT: 206, 12 | MULTI_STATUS: 207, 13 | ALREADY_REPORTED: 208, 14 | IM_USED: 226, 15 | MULTIPLE_CHOICES: 300, 16 | MOVED_PERMANENTLY: 301, 17 | FOUND: 302, 18 | SEE_OTHER: 303, 19 | NOT_MODIFIED: 304, 20 | USE_PROXY: 305, 21 | TEMPORARY_REDIRECT: 307, 22 | PERMANENT_REDIRECT: 308, 23 | BAD_REQUEST: 400, 24 | UNAUTHORIZED: 401, 25 | PAYMENT_REQUIRED: 402, 26 | FORBIDDEN: 403, 27 | NOT_FOUND: 404, 28 | METHOD_NOT_ALLOWED: 405, 29 | NOT_ACCEPTABLE: 406, 30 | PROXY_AUTHENTICATION_REQUIRED: 407, 31 | REQUEST_TIMEOUT: 408, 32 | CONFLICT: 409, 33 | GONE: 410, 34 | LENGTH_REQUIRED: 411, 35 | PRECONDITION_FAILED: 412, 36 | PAYLOAD_TOO_LARGE: 413, 37 | REQUEST_URI_TOO_LONG: 414, 38 | UNSUPPORTED_MEDIA_TYPE: 415, 39 | REQUESTED_RANGE_NOT_SATISFIABLE: 416, 40 | EXPECTATION_FAILED: 417, 41 | IM_A_TEAPOT: 418, // lol 42 | MISDIRECTED_REQUEST: 421, 43 | UNPROCESSABLE_ENTITY: 422, 44 | LOCKED: 423, 45 | FAILED_DEPENDENCY: 424, 46 | UPGRADE_REQUIRED: 426, 47 | PRECONDITION_REQUIRED: 428, 48 | TOO_MANY_REQUESTS: 429, 49 | REQUEST_HEADER_FIELDS_TOO_LARGE: 431, 50 | CONNECTION_CLOSED_WITHOUT_RESPONSE: 444, 51 | UNAVAILABLE_FOR_LEGAL_REASONS: 451, 52 | CLIENT_CLOSED_REQUEST: 499, 53 | INTERNAL_SERVER_ERROR: 500, 54 | NOT_IMPLEMENTED: 501, 55 | BAD_GATEWAY: 502, 56 | SERVICE_UNAVAILABLE: 503, 57 | GATEWAY_TIMEOUT: 504, 58 | HTTP_VERSION_NOT_SUPPORTED: 505, 59 | VARIANT_ALSO_NEGOTIATES: 506, 60 | INSUFFICIENTE_STORAGE: 507, 61 | LOOP_DETECTED: 508, 62 | NOT_EXTENDED: 510, 63 | NETWORK_AUTHENTICATION_REQUIRED: 511, 64 | NETWORK_CONNECT_TIMEOUT_ERROR: 599 65 | } 66 | -------------------------------------------------------------------------------- /src/server/defaultOptions.js: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | 3 | const clustering = { 4 | activate: false, 5 | amountOfWorkers: os.cpus().length, 6 | workersRespawnDelay: 1000, 7 | workersLifeTime: 30000 8 | } 9 | 10 | export default { 11 | name: 'Server', 12 | host: process.env.HOST || 'localhost', 13 | port: process.env.PORT || 3000, 14 | clustering, 15 | internal: { 16 | debug: process.env.DEBUG || false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import https from 'https' 3 | 4 | import debug from '../debug' 5 | import defaultOptions from './defaultOptions' 6 | import Router from './router' 7 | import { EventBus, EventType } from '../events' 8 | import Exceptions from '../exceptions' 9 | 10 | export const ServerStatus = { 11 | SERVER_CLOSE: 'SERVER_CLOSE', 12 | SERVER_OPEN: 'SERVER_OPEN', 13 | SERVER_BOOT: 'SERVER_BOOT', 14 | SERVER_SHUTDOWN: 'SERVER_SHUTDOWN' 15 | } 16 | 17 | export class Server { 18 | constructor (options) { 19 | this._start = process.hrtime() 20 | this.options = isNaN(options) ? options : { port: options } 21 | this._eventBus = new EventBus(this) 22 | this._router = new Router(this) 23 | this._status = ServerStatus.SERVER_CLOSE 24 | this._nodeServer = this._options.https ? https.createServer(this._options.https) : http.createServer() 25 | this._nodeServer.on('request', (req, res) => { 26 | const a = this 27 | const b = process.hrtime() 28 | this._router.resolveRoute({ 29 | set instance (instance) { 30 | throw new Exceptions.OPERATION_NOT_ALLOWED('You can\'t change the server instance of a request.') 31 | }, 32 | get instance () { 33 | return a 34 | }, 35 | set request (request) { 36 | throw new Exceptions.OPERATION_NOT_ALLOWED('You can\'t change the request object.') 37 | }, 38 | get request () { 39 | return req 40 | }, 41 | set response (response) { 42 | throw new Exceptions.OPERATION_NOT_ALLOWED('You can\'t change the response object.') 43 | }, 44 | get response () { 45 | return res 46 | }, 47 | set timing (timing) { 48 | throw new Exceptions.OPERATION_NOT_ALLOWED('This is not a DeLorean, Doc...') 49 | }, 50 | get timing () { 51 | return b 52 | } 53 | }) 54 | }) 55 | } 56 | 57 | get status () { 58 | return this._status 59 | } 60 | 61 | get options () { 62 | return this._options 63 | } 64 | 65 | set options (options) { 66 | this._options = Object.assign(defaultOptions, options || {}) 67 | } 68 | 69 | modules (...modules) { 70 | return this._router.modules(modules) 71 | } 72 | 73 | routes (...routes) { 74 | routes = routes.length > 0 ? this._router.register(routes) : null 75 | const router = this._router 76 | return { 77 | after (...modules) { 78 | const len = modules.length 79 | if (routes) { 80 | for (let route of routes) { 81 | if (len === 2 && !isNaN(routes[0])) { 82 | route.modules.after.register(routes[0], routes[1]) 83 | } else { 84 | for (let mod of modules) route.modules.after.register(mod) 85 | } 86 | } 87 | } else { 88 | if (len === 2 && !isNaN(routes[0])) { 89 | router._modules.after.register(routes[0], routes[1]) 90 | } else { 91 | for (let mod of modules) router._modules.after.register(mod) 92 | } 93 | } 94 | return this 95 | }, 96 | before (...modules) { 97 | const len = modules.length 98 | if (routes) { 99 | for (let route of routes) { 100 | if (len === 2 && !isNaN(routes[0])) { 101 | route.modules.before.register(routes[0], routes[1]) 102 | } else { 103 | let index = len 104 | while (--index >= 0) { 105 | route.modules.before.register(0, modules[index]) 106 | } 107 | } 108 | } 109 | } else { 110 | if (len === 2 && !isNaN(routes[0])) { 111 | router._modules.before.register(routes[0], routes[1]) 112 | } else { 113 | let index = len 114 | while (--index >= 0) { 115 | router._modules.before.register(0, modules[index]) 116 | } 117 | } 118 | } 119 | return this 120 | } 121 | } 122 | } 123 | 124 | set isRunning (isRunning) { 125 | if (isRunning) { 126 | if (this.isRunning) { 127 | this.reboot() 128 | } else { 129 | this.boot() 130 | } 131 | } else { 132 | this.close() 133 | } 134 | } 135 | 136 | get isRunning () { 137 | return (this._nodeServer && this._nodeServer.listening) || false 138 | } 139 | 140 | registerListener (eventType, listener) { 141 | this._eventBus.registerListener(eventType, listener) 142 | } 143 | 144 | removeListener (eventType, listener) { 145 | this._eventBus.removeListener(eventType, listener) 146 | } 147 | 148 | emitEvent (eventType, e) { 149 | this._eventBus.emit(eventType, e) 150 | } 151 | 152 | boot () { 153 | return new Promise((resolve, reject) => { 154 | const bootEvent = EventType.SERVER_STATUS_UPDATE_EVENT(ServerStatus.SERVER_BOOT) 155 | this.emitEvent(bootEvent) 156 | if (bootEvent.isCancelled()) { 157 | resolve(this._status) 158 | process.exit() 159 | } else { 160 | this._nodeServer.listen(this._options.port, this._options.host, (err) => { 161 | if (err) { 162 | reject(err) 163 | return 164 | } 165 | const diff = process.hrtime(this._start) 166 | const e = EventType.SERVER_STATUS_UPDATE_EVENT(ServerStatus.SERVER_OPEN, `(${((diff[0] * 1000000000) + diff[1]) / 1000000000}s) ${this._options.name} is up and running at ${this._options.host}:${this._options.port}.`) 167 | this.emitEvent(e) 168 | if (e.isCancelled()) { 169 | this.close().then((serverStatus) => resolve(serverStatus)) 170 | } else { 171 | if (e.message) debug.log.success(e.message) 172 | resolve((this._status = e.status)) 173 | } 174 | }) 175 | } 176 | }) 177 | } 178 | 179 | close () { 180 | return new Promise((resolve, reject) => { 181 | const shutdownEvent = EventType.SERVER_STATUS_UPDATE_EVENT(ServerStatus.SERVER_SHUTDOWN) 182 | this.emitEvent(shutdownEvent) 183 | if (shutdownEvent.isCancelled()) { 184 | resolve(this._status) 185 | } else { 186 | this._nodeServer.close((err) => { 187 | if (err) { 188 | reject(err) 189 | return 190 | } 191 | const e = EventType.SERVER_STATUS_UPDATE_EVENT(ServerStatus.SERVER_SHUTDOWN, `${this._options.name} is now closed.`) 192 | this.emitEvent(e) 193 | if (e.isCancelled()) { 194 | resolve(this._status) 195 | } else { 196 | if (e.message) debug.log.warning(e.message) 197 | resolve((this._status = e.status)) 198 | } 199 | }) 200 | } 201 | }) 202 | } 203 | 204 | reboot () { 205 | this.close().then(this.boot) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/server/modules.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import { Response } from '../response' 3 | import utils from '../utils' 4 | import Exceptions from '../exceptions' 5 | import debug from '../debug' 6 | 7 | export default class ModulesChain { 8 | constructor (...modulesChains) { 9 | this.length = 0 10 | let i = -1 11 | let modulesChain 12 | while ((modulesChain = modulesChains[++i]) !== undefined) { 13 | if (modulesChain) { 14 | if (modulesChain instanceof ModulesChain) { 15 | let j = -1 16 | let k 17 | while ((k = modulesChain[++j]) !== undefined) { 18 | if (k) this.register(k.resolve || k.original) 19 | } 20 | } else { 21 | const type = typeof modulesChain 22 | if (type === 'function') { 23 | this.register(modulesChain) 24 | } else if (type === 'object') { 25 | const mod = modulesChain.resolve || modulesChain.original 26 | debug.assert.is(typeof mod, 'function', 'Module must be a function.') 27 | this.register(mod) 28 | } else { 29 | throw new Exceptions.UNEXPECTED_VALUE('A module must be a function returning a promise or another function!') 30 | } 31 | } 32 | } 33 | } 34 | } 35 | register (index, mod, replace = false) { 36 | if (!mod) { 37 | this.register(-1, index, replace) 38 | return 39 | } 40 | if (typeof mod !== 'function') throw new Exceptions.UNEXPECTED_VALUE('A module must be a function returning a promise or another function!') 41 | const len = mod.length 42 | let obj = mod 43 | if (len === 2) { // connect style (request, next) 44 | obj = (context) => { 45 | return new Promise((resolve) => { 46 | mod(context.request, resolve) 47 | }) 48 | } 49 | } else if (len === 3) { // connect style (request, response, next) 50 | obj = (context) => { 51 | return new Promise((resolve) => { 52 | mod(context.request, context.response, resolve) 53 | }) 54 | } 55 | } else if (len > 3) { // connect style (err, request, response, next) 56 | obj = (context) => { 57 | return new Promise((resolve, reject) => { 58 | mod(err => reject(err), context.request, context.response, resolve) 59 | }) 60 | } 61 | } 62 | index = index === -1 ? this.length++ : index 63 | if (!replace && this[index] !== undefined) this._allocate(index, index + 1) 64 | this[index] = obj === mod ? { original: obj } : { original: mod, resolve: obj } 65 | } 66 | 67 | _allocate (i, j) { 68 | if (this[j] !== undefined) this._allocate(j, j + 1) 69 | this[j] = this[i] 70 | } 71 | 72 | indexOf (mod) { 73 | let i = -1 74 | let j 75 | while ((j = this[++i]) !== undefined) { 76 | if (j && j.original === mod) break 77 | } 78 | return j ? i : -1 79 | } 80 | 81 | remove (mod) { 82 | let i = -1 83 | let j 84 | while ((j = this[++i]) !== undefined) { 85 | if (j && j.original === mod) { 86 | this[i] = null 87 | break 88 | } 89 | } 90 | return !!j 91 | } 92 | 93 | async execute (context) { 94 | if (this.length === 0) return 95 | let res 96 | let i = -1 97 | let j 98 | while (!res && (j = this[++i]) !== undefined) { 99 | if (j) { 100 | const response = await (j.resolve || j.original)(context) 101 | if (response && response instanceof Response) res = response 102 | } 103 | } 104 | return res 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/server/router/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import url from 'url' 3 | import debug from '../../debug' 4 | import { resolveRequestArguments } from '../../injections' 5 | import { generateRouteMatcher, extractHeaders } from '../utils' 6 | import { Response } from '../../response' 7 | import ModulesChain from '../modules' 8 | import utils from '../../utils' 9 | import Exceptions from '../../exceptions' 10 | import { retrieve as retrievePersistence, resolveArgumentsInjection } from '../../persistence' 11 | 12 | export default class Router { 13 | constructor (server) { 14 | this._server = server 15 | this._modules = Object.seal({ 16 | before: new ModulesChain(), 17 | after: new ModulesChain() 18 | }) 19 | this._onEndpointNotFound = (input, request) => Response.notFound().build() 20 | this._onException = (exception) => Response.serverError(exception).build() 21 | this._onStrictException = (property) => Response.badRequest({ exception: Exceptions.STRICT_MODE.name, message: `${property} is missing.` }).build() 22 | } 23 | 24 | modules (...modules) { 25 | const that = this 26 | const len = modules.length 27 | if (len === 0) { 28 | return { 29 | after (...modules) { 30 | for (let mod of modules) that._modules.after.register(mod) 31 | }, 32 | before (...modules) { 33 | for (let mod of modules) that._modules.before.register(mod) 34 | } 35 | } 36 | } else if (len === 1 && Array.isArray(modules[0])) { 37 | modules = modules[0] 38 | } 39 | for (let mod of modules) that._modules.before.register(mod) 40 | return { 41 | after (...modules) { 42 | for (let mod of modules) that._modules.after.register(mod) 43 | } 44 | } 45 | } 46 | 47 | async retrieveRoute (httpMethod, url) { 48 | return new Promise((resolve) => { 49 | let response = null 50 | const routes = this[httpMethod] 51 | if (routes) { 52 | utils.iterate(routes, (route) => { 53 | if (route.matcher.test(url)) { 54 | response = route 55 | return true 56 | } 57 | }) 58 | } 59 | resolve(response) 60 | }) 61 | } 62 | 63 | async resolveRoute (context) { 64 | const ctxRequest = context.request 65 | const ctxResponse = context.response 66 | const parsed = url.parse(ctxRequest.url, true) 67 | let input = parsed.pathname.replace(/\/{2,}/g, '/') 68 | context.headers = await extractHeaders(ctxRequest) 69 | context.url = { 70 | parsed, 71 | input 72 | } 73 | const serverBefore = await this._modules.before.execute(context) 74 | if (serverBefore === undefined) { 75 | const route = await this.retrieveRoute(ctxRequest.method, input) 76 | if (route) { 77 | context.route = route 78 | const controllerBefore = await route.modules.before.execute(context) 79 | if (controllerBefore === undefined) { 80 | const args = await resolveRequestArguments(context) 81 | const resolver = route.resolver 82 | context.expectedResponse = await resolver.controller[resolver.method](args) 83 | // from now on, returning a response on a module doesn't make it final 84 | const controllerAfter = await route.modules.after.execute(context) 85 | if (controllerAfter) context.expectedResponse = controllerAfter 86 | const serverAfter = await this._modules.after.execute(context) 87 | if (serverAfter) context.expectedResponse = serverAfter 88 | await this._end(ctxResponse, context.expectedResponse) 89 | if (!this._server.options.internal.debug) return 90 | const diff = process.hrtime(context.timing) 91 | debug.log.info(`(${ctxRequest.method}): ${context.url.input}`) 92 | debug.log.info(`Request processed in ${diff[0]}s ${diff[1] / 1000000}ms.`) 93 | } else { 94 | await this._end(ctxResponse, controllerBefore) 95 | if (!this._server.options.internal.debug) return 96 | const diff = process.hrtime(context.timing) 97 | debug.log.info(`(${ctxRequest.method}): ${context.url.input}`) 98 | debug.log.info(`Request processed in ${diff[0]}s ${diff[1] / 1000000}ms.`) 99 | } 100 | } else { 101 | await this._end(ctxResponse, this._onEndpointNotFound()) 102 | if (!this._server.options.internal.debug) return 103 | const diff = process.hrtime(context.timing) 104 | debug.log.info(`(${context.request.method}): ${context.url.input}`) 105 | debug.log.info(`Request processed in ${diff[0]}s ${diff[1] / 1000000}ms.`) 106 | } 107 | } else { 108 | await this._end(ctxResponse, serverBefore) 109 | if (!this._server.options.internal.debug) return 110 | const diff = process.hrtime(context.timing) 111 | debug.log.info(`(${context.request.method}): ${context.url.input}`) 112 | debug.log.info(`Request processed in ${diff[0]}s ${diff[1] / 1000000}ms.`) 113 | } 114 | } 115 | 116 | _end (connection, response) { 117 | return new Promise(async (resolve) => { 118 | if (!(response instanceof Response)) throw new Error('Response call must return a Response object!') 119 | const entity = response.body.entity 120 | if (typeof entity === 'function') { 121 | response = await entity(response) 122 | if (!(response instanceof Response)) throw new Error('Response call must return a Response object!') 123 | } 124 | const body = response.body.entity 125 | connection.writeHead(response.body.status, response.header) 126 | if (body) { 127 | connection.end(typeof body === 'object' ? JSON.stringify(body) : body, response.body.encoding, resolve) 128 | } else { 129 | connection.end(null, 'utf8', resolve) 130 | } 131 | }) 132 | } 133 | 134 | register (...endpoints) { 135 | if (endpoints.length === 1 && Array.isArray(endpoints[0])) endpoints = endpoints[0] 136 | let response = [] 137 | for (let controller of endpoints) { 138 | if (Array.isArray(controller)) { 139 | for (let child of controller) response.push(...this._register(child)) 140 | } else { 141 | response.push(...this._register(controller)) 142 | } 143 | } 144 | return response 145 | } 146 | 147 | _register (endpoint) { 148 | const endpointType = typeof endpoint 149 | let instance 150 | if (endpointType === 'function') { 151 | try { 152 | instance = new (endpoint.bind.apply(endpoint, [endpoint, ...resolveArgumentsInjection(endpoint)]))() // eslint-disable-line no-useless-call 153 | } catch (exception) { 154 | instance = endpoint() 155 | } 156 | } else if (endpoint && endpointType === 'object') { 157 | instance = endpoint 158 | } else { 159 | throw new Exceptions.UNEXPECTED_VALUE(`Endpoint must be a class, function or object. Found: ${endpoint}`) 160 | } 161 | const endpointPersistence = retrievePersistence(endpoint) 162 | const persistence = retrievePersistence(instance) 163 | const response = [] 164 | utils.iterate(Object.keys(persistence), (functionName) => { 165 | const method = persistence[functionName] 166 | const endpointMods = endpointPersistence.modules 167 | const methodMods = method.modules 168 | utils.iterate(method.types, (httpMethod) => { 169 | const routes = this[httpMethod] || (this[httpMethod] = []) 170 | const {path, matcher} = generateRouteMatcher(endpointPersistence.path || '/', method.path || '/') 171 | const route = Object.freeze({ 172 | path, 173 | matcher, 174 | modules: Object.freeze({ 175 | after: new ModulesChain(endpointMods ? endpointMods.after : null, methodMods ? methodMods.after : null), 176 | before: new ModulesChain(endpointMods ? endpointMods.before : null, methodMods ? methodMods.before : null) 177 | }), 178 | resolver: Object.freeze({ 179 | controller: instance, 180 | persistence, 181 | method: functionName 182 | }) 183 | }) 184 | routes.push(route) 185 | response.push(route) 186 | }) 187 | }) 188 | return response 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/server/router/storage.js: -------------------------------------------------------------------------------- 1 | // TODO radix tree (router) 2 | -------------------------------------------------------------------------------- /src/server/utils.js: -------------------------------------------------------------------------------- 1 | import utils from '../utils' 2 | 3 | const parametersRegex = /\{(\S+?)\}/g 4 | const parameterMatchRegex = '([^\\s\\/]+)' 5 | 6 | function extractPathParamsIndexes (path, complex) { 7 | const response = [] 8 | let match = null 9 | while ((match = parametersRegex.exec(path))) { 10 | let param 11 | const args = match[1].split('=', 2) 12 | if (args.length === 1) { 13 | param = args[0] 14 | } else { 15 | if (complex) { 16 | param = { 17 | name: args[0], 18 | content: args[1], 19 | search: match[1] 20 | } 21 | } else { 22 | param = args[0] 23 | } 24 | } 25 | response.push(param) 26 | } 27 | return response 28 | } 29 | 30 | export function extractPathParams (path, input, pathMatcher) { 31 | const response = {} 32 | const paramsNames = extractPathParamsIndexes(path, false) 33 | if (paramsNames.length === 0) return response 34 | let params = pathMatcher.exec(input) 35 | for (let i = 1, j = params.length; i < j; ++i) response[paramsNames[i - 1]] = params[i] 36 | return response 37 | } 38 | 39 | export function extractHeaders (request) { 40 | return new Promise((resolve) => { 41 | const headers = request.headers 42 | const response = {} 43 | utils.iterate(Object.keys(headers), (header) => { 44 | response[header.toLowerCase()] = headers[header] 45 | }) 46 | resolve(response) 47 | }) 48 | } 49 | 50 | export function generateRouteMatcher (prefix, path) { 51 | const origin = (path === '/' ? `/${prefix}` : `/${prefix}/${path}`).replace(/\/+/g, '/') 52 | let pathName = `${origin}` 53 | let matcher = `${origin}` 54 | const params = extractPathParamsIndexes(pathName, true) 55 | utils.iterate(params, (i) => { 56 | if (typeof i !== 'object') return 57 | pathName = pathName.replace(i.search, i.name) 58 | let first = i.content[0] 59 | matcher = matcher.replace(`{${i.search}}`, first && first === '(' ? i.content : `(${i.content})`) 60 | }) 61 | matcher = matcher.replace(parametersRegex, parameterMatchRegex).replace('*', parameterMatchRegex) 62 | return { 63 | path: pathName, 64 | matcher: new RegExp(`^${matcher}\\/*$`) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/functions/isRequired.js: -------------------------------------------------------------------------------- 1 | import Exceptions from '../../exceptions' 2 | 3 | export default function (argumentName) { 4 | throw new Exceptions.MISSING_ARGUMENT(`"${argumentName}" is required.`) 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/functions/iterate.js: -------------------------------------------------------------------------------- 1 | import isRequired from './isRequired' 2 | 3 | export default function (arr = isRequired('arr'), onData = isRequired('onData'), onEnd) { 4 | let response 5 | let index = -1 6 | const len = arr ? arr.length : 0 7 | while (++index < len) { 8 | let value = onData(arr[index]) 9 | if (value !== undefined) { 10 | response = value 11 | break 12 | } 13 | } 14 | if (onEnd) { 15 | if (response !== undefined) { 16 | onEnd(response) 17 | } else { 18 | onEnd() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import iterate from './functions/iterate' 2 | import isRequired from './functions/isRequired' 3 | 4 | export default { 5 | iterate, 6 | isRequired 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "allowJs": true, 5 | "checkJs": true 6 | }, 7 | "exclude": [ 8 | "node_modules" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'organiser' { 2 | 3 | export function GET (); 4 | 5 | export function HEAD (); 6 | 7 | export function POST (); 8 | 9 | export function PUT (); 10 | 11 | export function DELETE (); 12 | 13 | export function OPTIONS (); 14 | 15 | export function TRACE (); 16 | 17 | export function PATCH (); 18 | 19 | export function Arguments (...args: any[]); 20 | 21 | export function Path (path: string) 22 | 23 | export function EventHandler (eventType: string | (() => void) | object); 24 | 25 | export function ModulesAfter (modules: ((context) => void)[]) 26 | 27 | export function ModulesBefore (modules: ((context) => void)[]) 28 | 29 | export namespace Modules { 30 | export function bodyParser(): (context: object) => void; 31 | export function rawBodyParser(): (context: object) => void; 32 | } 33 | 34 | export class ResponseBuilder { 35 | constructor(statusCode: number, mediaType: string, entity: object | string); 36 | build(): Response; 37 | status(status: number); 38 | entity(entity: object | string); 39 | type(mediaType: string); 40 | cookie(...cookies: any[]); 41 | expires(date: Date); 42 | lastModified(date: Date); 43 | encoding(encoding: string); 44 | header(key: string, value: string); 45 | } 46 | 47 | export class Response { 48 | constructor(builder: ResponseBuilder); 49 | edit(): ResponseBuilder; 50 | static fromResponse: (response: Response) => ResponseBuilder; 51 | static accepted: (entity?: object | string, mediaType?: string) => ResponseBuilder; 52 | static badRequest: (entity?: object | string, mediaType?: string) => ResponseBuilder; 53 | static noContent: (mediaType?: string) => ResponseBuilder; 54 | static notFound: (entity?: object | string, mediaType?: string) => ResponseBuilder; 55 | static ok: (entity?: object | string, mediaType?: string) => ResponseBuilder; 56 | static redirect: (uri: string) => ResponseBuilder; 57 | static serverError: (mediaType?: string) => ResponseBuilder; 58 | static status: (status: number) => ResponseBuilder; 59 | static static: (file: | string, options?: Response.StaticOptions) => ResponseBuilder; 60 | static Status: HttpStatus; 61 | static MediaType: MediaType; 62 | } 63 | 64 | export enum ServerStatus { 65 | SERVER_CLOSE = 'SERVER_CLOSE', 66 | SERVER_OPEN = 'SERVER_OPEN', 67 | SERVER_BOOT = 'SERVER_BOOT', 68 | SERVER_SHUTDOWN = 'SERVER_SHUTDOWN' 69 | } 70 | 71 | export enum MediaType { 72 | APPLICATION_ATOM_XML = 'application/atom+xml', 73 | APPLICATION_JSON = 'application/json', 74 | APPLICATION_FORM_URLENCODED = 'application/x-www-form-urlencoded', 75 | APPLICATION_OCTET_STREAM = 'application/octet-stream', 76 | APPLICATION_PDF = 'application/pdf', 77 | APPLICATION_RSS_XML = 'application/rss+xml', 78 | APPLICATION_XHTML_XML = 'application/xhtml+xml', 79 | APPLICATION_XML = 'application/xml', 80 | IMAGE_GIF = 'image/gif', 81 | IMAGE_JPEG = 'image/jpeg', 82 | IMAGE_PNG = 'image/png', 83 | MULTIPART_FORM_DATA = 'multipart/form-data', 84 | TEXT_EVENT_STREAM = 'text/event-stream', 85 | TEXT_HTML = 'text/html', 86 | TEXT_MARKDOWN = 'text/markdown', 87 | TEXT_PLAIN = 'text/plain', 88 | TEXT_XML = 'text/xml' 89 | } 90 | 91 | export enum HttpStatus { 92 | CONTINUE = 100, 93 | SWITCHING_PROTOCOLS = 101, 94 | PROCESSING = 102, 95 | OK = 200, 96 | CREATED = 201, 97 | ACCEPTED = 202, 98 | NON_AUTHORITATIVE_INFORMATION = 203, 99 | NO_CONTENT = 204, 100 | RESET_CONTENT = 205, 101 | PARTIAL_CONTENT = 206, 102 | MULTI_STATUS = 207, 103 | ALREADY_REPORTED = 208, 104 | IM_USED = 226, 105 | MULTIPLE_CHOICES = 300, 106 | MOVED_PERMANENTLY = 301, 107 | FOUND = 302, 108 | SEE_OTHER = 303, 109 | NOT_MODIFIED = 304, 110 | USE_PROXY = 305, 111 | TEMPORARY_REDIRECT = 307, 112 | PERMANENT_REDIRECT = 308, 113 | BAD_REQUEST = 400, 114 | UNAUTHORIZED = 401, 115 | PAYMENT_REQUIRED = 402, 116 | FORBIDDEN = 403, 117 | NOT_FOUND = 404, 118 | METHOD_NOT_ALLOWED = 405, 119 | NOT_ACCEPTABLE = 406, 120 | PROXY_AUTHENTICATION_REQUIRED = 407, 121 | REQUEST_TIMEOUT = 408, 122 | CONFLICT = 409, 123 | GONE = 410, 124 | LENGTH_REQUIRED = 411, 125 | PRECONDITION_FAILED = 412, 126 | PAYLOAD_TOO_LARGE = 413, 127 | REQUEST_URI_TOO_LONG = 414, 128 | UNSUPPORTED_MEDIA_TYPE = 415, 129 | REQUESTED_RANGE_NOT_SATISFIABLE = 416, 130 | EXPECTATION_FAILED = 417, 131 | IM_A_TEAPOT = 418, // lol 132 | MISDIRECTED_REQUEST = 421, 133 | UNPROCESSABLE_ENTITY = 422, 134 | LOCKED = 423, 135 | FAILED_DEPENDENCY = 424, 136 | UPGRADE_REQUIRED = 426, 137 | PRECONDITION_REQUIRED = 428, 138 | TOO_MANY_REQUESTS = 429, 139 | REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 140 | CONNECTION_CLOSED_WITHOUT_RESPONSE = 444, 141 | UNAVAILABLE_FOR_LEGAL_REASONS = 451, 142 | CLIENT_CLOSED_REQUEST = 499, 143 | INTERNAL_SERVER_ERROR = 500, 144 | NOT_IMPLEMENTED = 501, 145 | BAD_GATEWAY = 502, 146 | SERVICE_UNAVAILABLE = 503, 147 | GATEWAY_TIMEOUT = 504, 148 | HTTP_VERSION_NOT_SUPPORTED = 505, 149 | VARIANT_ALSO_NEGOTIATES = 506, 150 | INSUFFICIENTE_STORAGE = 507, 151 | LOOP_DETECTED = 508, 152 | NOT_EXTENDED = 510, 153 | NETWORK_AUTHENTICATION_REQUIRED = 511, 154 | NETWORK_CONNECT_TIMEOUT_ERROR = 599 155 | } 156 | 157 | export enum Types { 158 | UUID = 'uuid', 159 | STRING = 'string', 160 | BOOLEAN = 'boolean', 161 | INTEGER = 'integer', 162 | DOUBLE = 'double', 163 | FLOAT = 'float', 164 | DATE = 'date', 165 | CLIENT_REQUEST = 'clientRequest', 166 | SERVER_RESPONSE = 'serverResponse', 167 | } 168 | 169 | export class Server { 170 | constructor(options?: Server.ServerOptions); 171 | status: string; 172 | isRunning: boolean; 173 | options(options?: Server.ServerOptions): void | Server.ServerOptions; 174 | routes(...routes: any[]): Router.RouterModules; 175 | modules(...modules: ((context) => void)[]): Router.RouterModules; 176 | registerListener(eventType: string, listener: () => void | object); 177 | removeListener(eventType: string, listener: () => void | object); 178 | emitEvent(eventType: string, event: object); 179 | boot(): Promise; 180 | close(): Promise; 181 | reboot(): void; 182 | } 183 | 184 | namespace Router { 185 | 186 | interface RouterModules { 187 | after(...modules: ((context) => void)[]): RouterModules; 188 | before(...modules: ((context) => void)[]): RouterModules; 189 | } 190 | 191 | } 192 | 193 | namespace Response { 194 | 195 | interface StaticOptions { 196 | timeout?: number; 197 | mediaType?: MediaType; 198 | } 199 | 200 | } 201 | 202 | namespace Server { 203 | 204 | interface ServerOptions { 205 | name?: string; 206 | host?: string; 207 | port?: number; 208 | internal?: InternalServerOptions; 209 | } 210 | 211 | interface InternalServerOptions { 212 | debug?: boolean; 213 | } 214 | 215 | } 216 | 217 | } 218 | --------------------------------------------------------------------------------