icore 0.1.38 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md DELETED
@@ -1,267 +0,0 @@
1
- # Iсore
2
-
3
- [![License](https://img.shields.io/npm/l/express.svg)](https://github.com/woodger/icore/blob/master/LICENSE)
4
- [![Build Status](https://travis-ci.com/woodger/icore.svg?branch=master)](https://travis-ci.com/woodger/icore)
5
- [![Coverage Status](https://coveralls.io/repos/github/woodger/icore/badge.svg?branch=master)](https://coveralls.io/github/woodger/icore?branch=master)
6
- [![Known Vulnerabilities](https://snyk.io/test/github/woodger/icore/badge.svg?targetFile=package.json)](https://snyk.io/test/github/woodger/icore?targetFile=package.json)
7
-
8
- `Icore` is a framework a high-level to build strict web application. Allows the developer to focus on the final handler.
9
-
10
- ## Features
11
-
12
- This module for [Node.js®](https://nodejs.org) implemented by following the [ECMAScript® 2018 Language Specification Standard](https://www.ecma-international.org/ecma-262/9.0/index.html)
13
-
14
- * strict interface
15
- * not immutable context
16
- * routing implements a binary tree
17
- * all asynchronous functions on promises
18
- * error handling
19
-
20
- ## Install
21
-
22
- To use `iсore` in your project, run:
23
-
24
- ```bash
25
- npm i icore
26
- ```
27
-
28
- ## Table of Contents
29
-
30
- [class Application](#class-application)
31
-
32
- * [constructor: new Application(options)](#constructor-new-Applicationoptions)
33
- * [app.listenHttp(router)](#applistenhttprouter)
34
- * [app.close()](#appclose)
35
- * [app.server](#appserver)
36
- * [app.context](#appcontext)
37
-
38
- [class Route](#class-route)
39
-
40
- * [Event: 'error'](#event-error)
41
- * [constructor: new Route(options)](#constructor-new-routeoptions)
42
- * [route.route(options)](#routerouteoptions)
43
- * [route.find(paths[, index])](#routefindpaths-index)
44
- * [route.method](#routemethod)
45
- * [route.handler](#routehandler)
46
- * [route.finish](#routefinish)
47
- * [route.childs](#routechilds)
48
-
49
- [class Inquiry](#class-inquiry)
50
-
51
- * [constructor: new Inquiry(req, cookie)](#constructor-new-inquiryreq-cookie)
52
- * [inq.read(options)](#inqreadoptions)
53
- * [inq.pipe(stream[, options])](#inqpipestream-options)
54
- * [inq.queries](#inqqueries)
55
- * [inq.method](#inqmethod)
56
- * [inq.headers](#inqheaders)
57
- * [inq.context](#inqcontext)
58
-
59
- ### class Application
60
-
61
- #### constructor: new Application(options)
62
-
63
- * `options` <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>
64
-
65
- * `host` <[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> To start the development server using a different default `hostname` or `IP address`. This will start a TCP server listening for connections on the provided host. **Default:** `'localhost'`.
66
-
67
- * `port` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> TCP port of remote server. Is required parameter.
68
-
69
- * `timeout` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> The number of milliseconds of inactivity before a socket is presumed to have timed out. A value of `0` will disable the timeout behavior on incoming connections. **Default:** `12e4` (2 minutes).
70
-
71
- * `maxHeadersCount` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> Limits maximum incoming headers count. If set to `0`, no limit will be applied. **Default:** `2e3`.
72
-
73
- For example:
74
-
75
- ```js
76
- const icore = require('icore');
77
-
78
- const app = new icore.Application({
79
- port: 3000
80
- });
81
- ```
82
-
83
- #### app.listenHttp(router)
84
-
85
- * `router` <[Router](#class-route)> Is instance class Route this module.
86
- * returns: <[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)> Following successful listen, the Promise is resolved with an value with a `undefined`.
87
-
88
- Starts the HTTP server listening for connections. These arguments are documented on [nodejs.org](https://nodejs.org/api/http.html#http_server_listen).
89
-
90
- **index.js**
91
-
92
- ```js
93
- const icore = require('icore');
94
- const router = require('./router');
95
-
96
- const app = new icore.Application({
97
- port: 3000
98
- });
99
-
100
- app.listenHttp(router);
101
- ```
102
-
103
- Start script in terminal:
104
-
105
- ```console
106
- $ node index.js
107
- Server started on http://localhost:3000
108
- ```
109
-
110
- #### app.close()
111
-
112
- * returns: <[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)> Following successful listen, the Promise is resolved with an value with a `undefined`.
113
-
114
- Stops the server from accepting new connections.
115
-
116
- #### app.server
117
-
118
- In this variable, an instance of the server will be assigned after a successful listen installation.
119
-
120
- #### app.context
121
-
122
- This empty <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>. You may add additional properties to [inq.context](#inqcontext) by editing `app.context`.
123
-
124
- ```js
125
- app.context.db = db();
126
- ```
127
-
128
- #### class Route
129
-
130
- The router is made up of instances class `Route`.
131
-
132
- ![router](http://yuml.me/woodger/diagram/scruffy;dir:LR/class/['']%2F-get[%2Flogs_\d%2F{bg:violet}],[''{bg:yellowgreen}]%2F-get['catalog'{bg:yellowgreen}],['catalog']%2F-post['books'{bg:yellowgreen}].svg)
133
-
134
- Routing will be done:
135
-
136
- | Method | URL |
137
- |--------|----------------------------------|
138
- | GET | http://example.com |
139
- | GET | http://example.com/catalog |
140
- | GET | http://example.com/logs_0 |
141
- | GET | http://example.com/logs_1 |
142
- | POST | http://example.com/catalog/books |
143
-
144
- Extends the Node.js [events](https://nodejs.org/api/events.html) module.
145
-
146
- ##### Event: 'error'
147
-
148
- The `'error'` event is emitted if throw an exception. The listener callback is passed a single `Error` argument when called.
149
- The server is not closed when the `'error'` event is emitted.
150
-
151
- ##### constructor: new Route(options)
152
-
153
- When defining a route in `icore` you need one basic element - the `handler`.
154
-
155
- * `options` <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>
156
- * `handler` <[AsyncFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)> takes an instance of a [class Inquiry](#class-inquiry) as context. The handler option must return a object, a promise, or throw an error.
157
- * `path` <[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> The path option must be a string, though it can contain regular expression. **Default:** `''`.
158
- * `method` <[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> The property of the method can be any valid HTTP method in lower case letters. **Default:** `'get'`.
159
- * `finish` <[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)> If set to `true`, the find returns the current route. **Default:** `false`.
160
-
161
- Simple example use Route class:
162
-
163
- ```js
164
- const homepage = new Route({
165
- async handler(inq) {
166
- return {
167
- status: 200,
168
- header: {
169
- 'Content-Type': 'text/html; charset=utf-8'
170
- },
171
- body: 'Hello World!'
172
- };
173
- }
174
- });
175
- ```
176
-
177
- #### route.route(options)
178
-
179
- Uses the same interface as the class constructor.
180
-
181
- ```js
182
- const catalog = homepage.route({
183
- method: 'catalog',
184
- async handler(inq) {
185
- return {};
186
- }
187
- });
188
- ```
189
-
190
- #### route.find(paths[, index])
191
-
192
- This method is intended for [class Application](#class-application) use.
193
-
194
- * `paths` <[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)> The result of the transform of the URL.
195
- * `index` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> Is cursor for next call find. **Default:** `0`.
196
- * returns: <this> Returns `null` if the route is not found.
197
-
198
- #### route.method
199
-
200
- * <[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> The property of the method can be any valid HTTP method in lower case letters. **Default:** `'get'`.
201
-
202
- #### route.handler
203
-
204
- * <[AsyncFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)> takes an instance of a [class Inquiry](#class-inquiry) as context. The handler option must return a object, a promise, or throw an error.
205
-
206
- #### route.finish
207
-
208
- * <[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)> If set to `true`, the find returns the current route. **Default:** `false`.
209
-
210
- #### route.childs
211
-
212
- * <[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)> Nested route list.
213
-
214
- #### class Inquiry
215
-
216
- A [class Application](#class-application) context encapsulates node's request and response objects into a single object which provides many helpful methods for writing web applications and APIs.
217
-
218
- #### constructor: new Inquiry(req, cookie)
219
-
220
- The constructor will be auto called whenever a new `request` is established.
221
-
222
- * `req` <[http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)> Node's request object.
223
- * `cookie` <[coookie.entries](https://www.npmjs.com/package/cookie-httponly#cookieentries)> The values of the incoming cookie.
224
-
225
- #### inq.read(options)
226
-
227
- * `options` <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>
228
- * `maxSize` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> Limits the amount of memory all fields together (except files) can allocate in bytes. If this value is exceeded, an 'error' event is emitted. **Default:** `Infinity`.
229
- * returns: <[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)> Following successful listen, the Promise is resolved with an value with a `string` type.
230
-
231
- To start reading the body of the incoming message.
232
-
233
- ```js
234
- async handler(inq) {
235
- const data = await inq.read();
236
- }
237
- ```
238
-
239
- #### inq.pipe(stream[, options])
240
-
241
- * `stream` <[stream.Writable](https://nodejs.org/api/stream.html#stream_class_stream_writable)>
242
- * `options` <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>
243
- * `maxSize` <[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)> Limits the amount of memory all fields together (except files) can allocate in bytes. If this value is exceeded, an 'error' event is emitted. **Default:** `Infinity`.
244
-
245
- To start reading the body of an incoming message as a stream
246
-
247
- ```js
248
- async handler(inq) {
249
- inq.pipe(writeStream);
250
- }
251
- ```
252
-
253
- #### inq.queries
254
-
255
- * <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> Get raw query string void of `?`.
256
-
257
- #### inq.method
258
-
259
- * <[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> The property of the method can be any valid HTTP method in lower case letters. **Default:** `'get'`.
260
-
261
- #### inq.headers
262
-
263
- * <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> Response header object.
264
-
265
- #### inq.context
266
-
267
- * <[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> Inherit [app.context](#appcontext)
package/index.js DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Icore is a framework a high-level to build strict web application
3
- *
4
- * This module for Node.js® implemented by following the ECMAScript® 2018
5
- * Language Specification Standard
6
- *
7
- * https://www.ecma-international.org/ecma-262/9.0/index.html
8
- */
9
-
10
- const Application = require('./src/application');
11
- const Inquiry = require('./src/inquiry');
12
- const Route = require('./src/route');
13
-
14
- module.exports = {
15
- Application,
16
- Inquiry,
17
- Route
18
- };
@@ -1,219 +0,0 @@
1
- const http = require('http');
2
- const stream = require('stream');
3
- const mime = require('mime-types');
4
- const CookieHttpOnly = require('cookie-httponly');
5
- const TypeEnforcement = require('type-enforcement');
6
- const AsyncFunction = require('./async-function');
7
- const Inquiry = require('./inquiry');
8
- const Route = require('./route');
9
-
10
- const te = new TypeEnforcement({
11
- '#constructor()': {
12
- port: Number,
13
- host: String,
14
- timeout: Number,
15
- maxHeadersCount: Number
16
- },
17
- '#listenHttp()': {
18
- router: Route
19
- },
20
- '#handler()': {
21
- status: Number,
22
- cookie: Array,
23
- headers: Object
24
- }
25
- });
26
-
27
- class Application {
28
- constructor({
29
- port, host = 'localhost', timeout = 12e4, maxHeadersCount = 2e3
30
- }) {
31
- const err = te.validate('#constructor()', {
32
- port,
33
- host,
34
- timeout,
35
- maxHeadersCount
36
- });
37
-
38
- if (err) {
39
- throw err;
40
- }
41
-
42
- if (Number.isInteger(port) === false) {
43
- throw new Error('TCP port value must be an integer');
44
- }
45
-
46
- if (port < 1 || port > 65535) {
47
- throw new Error('TCP port must be in use allowed range');
48
- }
49
-
50
- this.port = port;
51
- this.host = host;
52
- this.timeout = timeout;
53
- this.maxHeadersCount = maxHeadersCount;
54
- this.server = null;
55
- this.context = {};
56
- }
57
-
58
- listenHttp(router) {
59
- const err = te.validate('#listenHttp()', {
60
- router
61
- });
62
-
63
- if (err) {
64
- throw err;
65
- }
66
-
67
- const incomingHandler = async (inq, coookieHttpOnly) => {
68
- Object.assign(inq.context, this.context);
69
-
70
- const paths = inq.url.pathname === '/' ?
71
- [''] : inq.url.pathname.split('/');
72
-
73
- const route = router.find(paths);
74
-
75
- if (route === null) {
76
- return {
77
- status: 404
78
- };
79
- }
80
-
81
- if (route.method !== inq.method && inq.method !== 'head') {
82
- return {
83
- status: 405
84
- };
85
- }
86
-
87
- const {
88
- status = 200,
89
- cookie = [],
90
- headers = {},
91
- body = ''
92
- } = await route.handler(inq);
93
-
94
- const err = te.validate('#handler()', {
95
- status,
96
- cookie,
97
- headers
98
- });
99
-
100
- if (err) {
101
- throw err;
102
- }
103
-
104
- if (Number.isInteger(status) === false || status < 100 || status > 599) {
105
- throw new Error('Invalid HTTP status code');
106
- }
107
-
108
- const isStream = body instanceof stream.Readable;
109
-
110
- if (isStream === false && typeof body !== 'string') {
111
- throw new Error(
112
- `Invalid value 'body'. Expected 'string' or stream.Readable object`
113
- );
114
- }
115
-
116
- if (isStream === true) {
117
- headers['Content-Type'] = mime.lookup(body.path) || 'application/octet-stream';
118
- }
119
-
120
- if (cookie.length > 0) {
121
- for (let {name, value, domain, path, expires} of cookie) {
122
- coookieHttpOnly.set(name, value, {
123
- domain,
124
- path,
125
- expires
126
- });
127
- }
128
- }
129
-
130
- for (let i in headers) {
131
- if (headers.hasOwnProperty(i)) {
132
- headers[i] = Buffer.from(headers[i]).toString('binary');
133
- }
134
- }
135
-
136
- return {
137
- status,
138
- headers,
139
- body
140
- };
141
- };
142
-
143
- return new Promise((resolve, reject) => {
144
- this.server = http.createServer();
145
-
146
- this.server.on('request', (req, res) => {
147
- const coookie = new CookieHttpOnly(req, res);
148
- const inq = new Inquiry(req, coookie.entries);
149
-
150
- incomingHandler(inq, coookie).catch((err) => {
151
- router.emit('error', err);
152
-
153
- return {
154
- status: 500
155
- };
156
- })
157
- .then(({status, headers = {}, body = ''}) => {
158
- const bodyless = inq.method === 'head';
159
-
160
- if (body instanceof stream.Readable) {
161
- res.writeHead(status, headers);
162
-
163
- if (bodyless === true) {
164
- if (body.finished === false) {
165
- body.destroy();
166
- }
167
-
168
- res.end();
169
- }
170
- else {
171
- body.pipe(res);
172
- }
173
- }
174
- else {
175
- res.writeHead(status, headers);
176
-
177
- if (bodyless === false) {
178
- res.write(body);
179
- }
180
-
181
- res.end();
182
- }
183
- });
184
- });
185
-
186
- this.server.once('listening', () => {
187
- console.log(`Server started on http://${this.host}:${this.port}`);
188
- resolve();
189
- });
190
-
191
- this.server.once('error', (err) => {
192
- reject(err);
193
- });
194
-
195
- this.server.maxHeadersCount = this.maxHeadersCount;
196
- this.server.timeout = this.timeout;
197
-
198
- this.server.listen({
199
- host: this.host,
200
- port: this.port
201
- });
202
- });
203
- }
204
-
205
- close() {
206
- return new Promise((resolve, reject) => {
207
- this.server.close((err) => {
208
- if (err) {
209
- reject(err);
210
- }
211
-
212
- console.log(`Server stoped on http://${this.host}:${this.port}`);
213
- resolve();
214
- });
215
- });
216
- }
217
- }
218
-
219
- module.exports = Application;
@@ -1,3 +0,0 @@
1
- module.exports = Object.freeze(
2
- Object.getPrototypeOf(async () => {}).constructor
3
- );
package/src/inquiry.js DELETED
@@ -1,141 +0,0 @@
1
- const url = require('url');
2
- const stream = require('stream');
3
- const querystring = require('querystring');
4
- const EventEmitter = require('events');
5
- const TypeEnforcement = require('type-enforcement');
6
-
7
- const te = new TypeEnforcement({
8
- '#pipe()': {
9
- maxSize: Number
10
- },
11
- '#read()': {
12
- maxSize: Number
13
- }
14
- });
15
-
16
- class Inquiry extends EventEmitter {
17
- constructor(req, cookie) {
18
- super();
19
-
20
- this.request = req;
21
-
22
- req.on('aborted', (err) => {
23
- this.emit('aborted', err);
24
- });
25
-
26
- this.cookie = cookie;
27
- this.type = 0;
28
-
29
- let [domain, port = 80] = req.headers.host.split(':');
30
- port = Number(port);
31
-
32
- const protocol = port === 443 ?
33
- 'https:' : 'http:';
34
-
35
- this.url = url.parse(`${protocol}//${domain}:${port}${req.url}`);
36
- this.paths = [];
37
-
38
- if (this.url.pathname !== '/') {
39
- let paths = this.url.pathname.substr(1).split('/');
40
-
41
- for (let i of paths) {
42
- this.paths.push(decodeURI(i));
43
- }
44
- }
45
-
46
- this.queries = querystring.parse(this.url.query);
47
- this.method = req.method.toLowerCase();
48
- this.headers = req.headers;
49
- this.context = {};
50
- }
51
-
52
- async read({maxSize = Infinity} = {}) {
53
- const err = te.validate('#read()', {
54
- maxSize
55
- });
56
-
57
- if (err) {
58
- throw err;
59
- }
60
-
61
- return new Promise((resolve, reject) => {
62
- let data = '';
63
-
64
- this.request.on('data', (chunk) => {
65
- if (data.length + chunk.length > maxSize) {
66
- const err = new Error(`Exceeding maximum body size`);
67
-
68
- this.request.destroy(err);
69
- }
70
- else {
71
- data += chunk;
72
- }
73
- });
74
-
75
- this.request.on('end', () => {
76
- resolve(data);
77
- });
78
-
79
- this.request.on('aborted', () => {
80
- const err = new Error(`Socket aborted`);
81
- reject(err);
82
- });
83
-
84
- this.request.on('error', reject);
85
- });
86
- }
87
-
88
- pipe(dest, {maxSize = Infinity} = {}) {
89
- const err = te.validate('#pipe()', {
90
- maxSize
91
- });
92
-
93
- if (err) {
94
- throw err;
95
- }
96
-
97
- if (dest instanceof stream.Writable === false) {
98
- throw new Error('Expected stream.Writable object');
99
- }
100
-
101
- const pipe = () => {
102
- this.request.on('data', (chunk) => {
103
- if (dest.bytesWritten + chunk.byteLength > maxSize) {
104
- const err = new Error('Exceeding maximum body size');
105
-
106
- /**
107
- * this.request.on('aborted') -> this.request.on('close')
108
- *
109
- */
110
-
111
- dest.destroy(err);
112
- }
113
- else if (dest.write(chunk) === false) {
114
- this.request.pause();
115
-
116
- dest.once('drain', () => {
117
- this.request.resume();
118
- });
119
- }
120
- });
121
- };
122
-
123
- dest.on('ready', pipe);
124
-
125
- dest.on('error', (err) => {
126
- this.request.destroy();
127
- });
128
-
129
- this.request.on('end', () => {
130
- dest.end(); // -> dest.on('close')
131
- });
132
-
133
- this.request.on('aborted', () => {
134
- dest.end();
135
- });
136
-
137
- return dest;
138
- }
139
- }
140
-
141
- module.exports = Inquiry;