├── .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 |
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 |
6 |
7 |
8 |
9 | [](https://www.npmjs.com/package/organiser) [](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta) [](https://standardjs.com/) [](https://raw.githubusercontent.com/fatec-taquaritinga/organiser/master/LICENSE) [](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 |
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 |
233 |
234 |
235 |
236 |
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 |
253 |
254 |
255 |
256 |
257 | ```javascript
258 | // '../utils/exampleLog'
259 |
260 | export default function (entity) {
261 | console.log(entity)
262 | return entity
263 | }
264 | ```
265 |
266 |
267 |
268 |
269 |
270 |
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 |
--------------------------------------------------------------------------------