te.js 1.0.0 → 1.0.2

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/server/ammo.js CHANGED
@@ -1,95 +1,96 @@
1
- import { statusAndData } from './ammo/dispatch-helper.js';
2
- import {
3
- isStatusCode,
4
- toStatusCode,
5
- toStatusMessage,
6
- } from '../utils/status-codes.js';
7
- import html from '../utils/tejas-entrypoint-html.js';
8
- import ammoEnhancer from './ammo/enhancer.js';
9
-
10
- class Ammo {
11
- constructor(req, res) {
12
- this.req = req;
13
- this.res = res;
14
-
15
- // Request related data
16
- this.ip = undefined;
17
- this.headers = undefined;
18
- this.payload = undefined;
19
- this.method = undefined;
20
-
21
- // URL related data
22
- this.protocol = undefined;
23
- this.hostname = undefined;
24
- this.path = undefined;
25
- this.endpoint = undefined;
26
-
27
- this.fullURL = undefined;
28
-
29
- // Response related data
30
- this.dispatchedData = undefined;
31
- }
32
-
33
- async enhance() {
34
- await ammoEnhancer(this);
35
- }
36
-
37
- dispatch() {
38
- const { statusCode, data, contentType } = statusAndData(arguments);
39
- const contentTypeHeader = { 'Content-Type': contentType };
40
-
41
- this.dispatchedData = data;
42
-
43
- this.res.writeHead(statusCode, contentTypeHeader);
44
- this.res.write(data ?? '');
45
- this.res.end();
46
- }
47
-
48
- notFound() {
49
- this.throw(new Error('Not Found'));
50
- }
51
-
52
- notAllowed() {
53
- this.throw(new Error('Method Not Allowed'));
54
- }
55
-
56
- unauthorized() {
57
- this.throw(new Error('Unauthorized'));
58
- }
59
-
60
- defaultEntry() {
61
- this.dispatch(html);
62
- }
63
-
64
- throw() {
65
- const err = arguments[0];
66
- const errCode = arguments[1];
67
-
68
- let errMsg = err instanceof Error ? err.message : err.toString();
69
-
70
- if (errCode && isStatusCode(errCode)) {
71
- if (!errMsg) errMsg = toStatusMessage(errCode);
72
- this.dispatch(errCode, errMsg);
73
- return;
74
- }
75
-
76
- if (err instanceof Error) {
77
- const errMessage = err.message;
78
-
79
- if (!isNaN(parseInt(errMessage))) {
80
- // Execute when errMessage is a number. Notice ! in front of isNan
81
- const message = toStatusMessage(errMessage) ?? toStatusMessage(500);
82
- this.dispatch(message, message);
83
- return;
84
- }
85
-
86
- const code = toStatusCode(errMsg) ?? 500;
87
- this.dispatch(code, errMsg);
88
- return;
89
- }
90
-
91
- this.dispatch(err);
92
- }
93
- }
94
-
95
- export default Ammo;
1
+ import { statusAndData } from './ammo/dispatch-helper.js';
2
+ import {
3
+ isStatusCode,
4
+ toStatusCode,
5
+ toStatusMessage,
6
+ } from '../utils/status-codes.js';
7
+ import html from '../utils/tejas-entrypoint-html.js';
8
+ import ammoEnhancer from './ammo/enhancer.js';
9
+ import TejError from './error.js';
10
+
11
+ class Ammo {
12
+ constructor(req, res) {
13
+ this.req = req;
14
+ this.res = res;
15
+
16
+ // Request related data
17
+ this.ip = undefined;
18
+ this.headers = undefined;
19
+ this.payload = undefined;
20
+ this.method = undefined;
21
+
22
+ // URL related data
23
+ this.protocol = undefined;
24
+ this.hostname = undefined;
25
+ this.path = undefined;
26
+ this.endpoint = undefined;
27
+
28
+ this.fullURL = undefined;
29
+
30
+ // Response related data
31
+ this.dispatchedData = undefined;
32
+ }
33
+
34
+ async enhance() {
35
+ await ammoEnhancer(this);
36
+ }
37
+
38
+ fire() {
39
+ const { statusCode, data, contentType } = statusAndData(arguments);
40
+ const contentTypeHeader = { 'Content-Type': contentType };
41
+
42
+ this.dispatchedData = data;
43
+
44
+ this.res.writeHead(statusCode, contentTypeHeader);
45
+ this.res.write(data ?? '');
46
+ this.res.end();
47
+ }
48
+
49
+ notFound() {
50
+ this.throw(new Error('Not Found'));
51
+ }
52
+
53
+ notAllowed() {
54
+ this.throw(new Error('Method Not Allowed'));
55
+ }
56
+
57
+ unauthorized() {
58
+ throw new TejError(401, 'Unauthorized');
59
+ }
60
+
61
+ defaultEntry() {
62
+ this.fire(html);
63
+ }
64
+
65
+ throw() {
66
+ const errCode = arguments[0];
67
+ const err = arguments[1];
68
+
69
+ let errMsg = err instanceof Error ? err.message : err.toString();
70
+
71
+ if (errCode && isStatusCode(errCode)) {
72
+ if (!errMsg) errMsg = toStatusMessage(errCode);
73
+ this.fire(errCode, errMsg);
74
+ return;
75
+ }
76
+
77
+ if (err instanceof Error) {
78
+ const errMessage = err.message;
79
+
80
+ if (!isNaN(parseInt(errMessage))) {
81
+ // Execute when errMessage is a number. Notice ! in front of isNan
82
+ const message = toStatusMessage(errMessage) ?? toStatusMessage(500);
83
+ this.fire(message, message);
84
+ return;
85
+ }
86
+
87
+ const code = toStatusCode(errMsg) ?? 500;
88
+ this.fire(code, errMsg);
89
+ return;
90
+ }
91
+
92
+ this.fire(err);
93
+ }
94
+ }
95
+
96
+ export default Ammo;
@@ -0,0 +1,9 @@
1
+ class TejError extends Error {
2
+ constructor(code, message) {
3
+ super(message);
4
+ this.name = this.constructor.name;
5
+ this.code = code;
6
+ }
7
+ }
8
+
9
+ export default TejError;
@@ -0,0 +1,33 @@
1
+ import mime from 'mime';
2
+
3
+ const paths = (destination, filename) => {
4
+ const dir = `${process.cwd()}\\${destination}`;
5
+ const path = `${dir}\\${filename}`;
6
+
7
+ const absolute = path;
8
+ const relative = path.replace(process.cwd(), '');
9
+
10
+ return { dir, absolute, relative };
11
+ };
12
+
13
+ const extAndType = (obj) => {
14
+ const contentType = obj.headers['content-type'];
15
+ const ext = mime.getExtension(contentType);
16
+ const type = mime.getType(ext);
17
+ return {
18
+ ext,
19
+ type,
20
+ };
21
+ };
22
+
23
+ const extract = (contentDisposition, key) => {
24
+ if (!contentDisposition) {
25
+ return null;
26
+ }
27
+
28
+ const parts = contentDisposition.split(';').map((part) => part.trim());
29
+ const part = parts.find((part) => part.startsWith(key));
30
+ return part ? part?.split('=')[1]?.trim()?.replace(/"/g, '') : undefined;
31
+ };
32
+
33
+ export { extAndType, extract, paths };
@@ -0,0 +1,142 @@
1
+ import { filesize } from 'filesize';
2
+ import fs from 'fs';
3
+ import TejError from './../error.js';
4
+ import { extAndType, extract, paths } from './helper.js';
5
+
6
+ class TejFileUploader {
7
+ /*
8
+ * @param {Object} options
9
+ * @param {string} options.destination - Destination to upload file to
10
+ * @param {string} options.name - Name of the file
11
+ * @param {number} options.maxFileSize - Maximum file size in bytes
12
+ */
13
+ constructor(options = {}) {
14
+ this.destination = options.destination;
15
+ this.name = options.name;
16
+ this.maxFileSize = options.maxFileSize;
17
+ }
18
+
19
+ file() {
20
+ const keys = [...arguments];
21
+ return async (ammo, next) => {
22
+ if (!ammo.headers['content-type'].startsWith('multipart/form-data'))
23
+ return next();
24
+
25
+ const payload = ammo.payload;
26
+ const updatedPayload = {};
27
+
28
+ for (const part in payload) {
29
+ const obj = payload[part];
30
+ const contentDisposition = obj.headers['content-disposition'];
31
+
32
+ const { ext, type } = extAndType(obj);
33
+ if (!ext) continue;
34
+
35
+ const key = extract(contentDisposition, 'name');
36
+ if (ext === 'txt') {
37
+ updatedPayload[key] = obj.value;
38
+ } else {
39
+ if (!keys.includes(key)) continue;
40
+
41
+ const filename = extract(contentDisposition, 'filename');
42
+ if (!filename) continue;
43
+
44
+ const { dir, absolute, relative } = paths(this.destination, filename);
45
+ const size = filesize(obj.value.length,
46
+ { output: 'object', round: 0 });
47
+ const maxSize = filesize(this.maxFileSize,
48
+ { output: 'object', round: 0 });
49
+ if (this.maxFileSize && obj.value.length > this.maxFileSize)
50
+ throw new TejError(413,
51
+ `File size exceeds ${maxSize.value} ${maxSize.symbol}`);
52
+
53
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
54
+ fs.writeFileSync(absolute, obj.value, 'binary');
55
+
56
+ updatedPayload[key] = {
57
+ filename,
58
+ extension: ext,
59
+ path: {
60
+ absolute: absolute,
61
+ relative: relative
62
+ },
63
+ mimetype: type,
64
+ size
65
+ };
66
+ }
67
+ }
68
+
69
+ ammo.payload = updatedPayload;
70
+ next();
71
+ };
72
+ }
73
+
74
+ files() {
75
+ const keys = [...arguments];
76
+ return async (ammo, next) => {
77
+ if (!ammo.headers['content-type'].startsWith('multipart/form-data'))
78
+ return next();
79
+
80
+ const payload = ammo.payload;
81
+ const updatedPayload = {};
82
+ const files = [];
83
+
84
+ for (const part in payload) {
85
+ const obj = payload[part];
86
+ const contentDisposition = obj.headers['content-disposition'];
87
+
88
+ const { ext, type } = extAndType(obj);
89
+ if (!ext) continue;
90
+
91
+ const key = extract(contentDisposition, 'name');
92
+ if (ext === 'txt') {
93
+ updatedPayload[key] = obj.value;
94
+ } else {
95
+ if (!keys.includes(key)) continue;
96
+
97
+ const filename = extract(contentDisposition, 'filename');
98
+ if (!filename) continue;
99
+
100
+ const { dir, absolute, relative } = paths(this.destination, filename);
101
+ const size = filesize(obj.value.length,
102
+ { output: 'object', round: 0 });
103
+ const maxSize = filesize(this.maxFileSize,
104
+ { output: 'object', round: 0 });
105
+ if (this.maxFileSize && obj.value.length > this.maxFileSize) {
106
+ throw new TejError(413,
107
+ `File size exceeds ${maxSize.value} ${maxSize.symbol}`);
108
+ }
109
+
110
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
111
+ fs.writeFileSync(absolute, obj.value, 'binary');
112
+
113
+ files.push({
114
+ key,
115
+ filename,
116
+ path: {
117
+ absolute: absolute,
118
+ relative: relative
119
+ },
120
+ mimetype: type,
121
+ size
122
+ });
123
+ }
124
+ }
125
+
126
+ const groupedFilesByKey = files.reduce((acc, file) => {
127
+ if (!acc[file.key]) acc[file.key] = [];
128
+ acc[file.key].push(file);
129
+ return acc;
130
+ }, {});
131
+
132
+ for (const key in groupedFilesByKey) {
133
+ updatedPayload[key] = groupedFilesByKey[key];
134
+ }
135
+
136
+ ammo.payload = updatedPayload;
137
+ next();
138
+ };
139
+ }
140
+ }
141
+
142
+ export default TejFileUploader;
package/server/handler.js CHANGED
@@ -1,70 +1,71 @@
1
- import TejLogger from 'tej-logger';
2
- import { env } from 'tej-env';
3
-
4
- import Ammo from './ammo.js';
5
- import TargetRegistry from './targets/registry.js';
6
- import logHttpRequest from '../utils/request-logger.js';
7
-
8
- const targetRegistry = new TargetRegistry();
9
- const errorLogger = new TejLogger('Tejas.Exception');
10
-
11
- const executeChain = async (target, ammo) => {
12
- let i = 0;
13
-
14
- const chain = targetRegistry.globalMiddlewares.concat(target.middlewares);
15
- chain.push(target.shoot);
16
-
17
- const next = async () => {
18
- const middleware = chain[i];
19
- i++;
20
-
21
- const args =
22
- middleware.length === 3 ? [ammo.req, ammo.res, next] : [ammo, next];
23
-
24
- try {
25
- await middleware(...args);
26
- } catch (err) {
27
- const ammo =
28
- middleware.length === 2 ? args[0] : new Ammo(args[0], args[1]);
29
- errorHandler(ammo, err);
30
- }
31
- };
32
-
33
- await next();
34
- };
35
-
36
- const errorHandler = (ammo, err, errCode) => {
37
- if (env('LOG_EXCEPTIONS')) errorLogger.error(err);
38
-
39
- ammo.throw(err, errCode);
40
- };
41
-
42
- const handler = async (req, res) => {
43
- const target = targetRegistry.aim(req.method, req.url.split('?')[0]);
44
- const ammo = new Ammo(req, res);
45
- await ammo.enhance();
46
-
47
- if (env('LOG_HTTP_REQUESTS')) logHttpRequest(ammo);
48
-
49
- try {
50
- if (target) {
51
- await executeChain(target, ammo);
52
- } else {
53
- if (req.url === '/') {
54
- ammo.defaultEntry();
55
- } else {
56
- errorHandler(
57
- ammo,
58
- new Error(
59
- `No target found for URL ${ammo.fullURL} with method ${ammo.method}`,
60
- ),
61
- 404,
62
- );
63
- }
64
- }
65
- } catch (err) {
66
- errorHandler(ammo, err);
67
- }
68
- };
69
-
70
- export default handler;
1
+ import { env } from 'tej-env';
2
+ import TejLogger from 'tej-logger';
3
+ import logHttpRequest from '../utils/request-logger.js';
4
+
5
+ import Ammo from './ammo.js';
6
+ import TejError from './error.js';
7
+ import TargetRegistry from './targets/registry.js';
8
+
9
+ const targetRegistry = new TargetRegistry();
10
+ const errorLogger = new TejLogger('Tejas.Exception');
11
+
12
+ const executeChain = async (target, ammo) => {
13
+ let i = 0;
14
+
15
+ const chain = targetRegistry.globalMiddlewares.concat(target.middlewares);
16
+ chain.push(target.shoot);
17
+
18
+ const next = async () => {
19
+ const middleware = chain[i];
20
+ i++;
21
+
22
+ const args =
23
+ middleware.length === 3 ? [ammo.req, ammo.res, next] : [ammo, next];
24
+
25
+ try {
26
+ await middleware(...args);
27
+ } catch (err) {
28
+ errorHandler(ammo, err);
29
+ }
30
+ };
31
+
32
+ await next();
33
+ };
34
+
35
+ const errorHandler = (ammo, err) => {
36
+ if (env('LOG_EXCEPTIONS')) errorLogger.error(err);
37
+
38
+ if (err instanceof TejError)
39
+ return ammo.throw(err.code, err);
40
+
41
+ ammo.throw(500, err);
42
+ };
43
+
44
+ const handler = async (req, res) => {
45
+ const target = targetRegistry.aim(req.method, req.url.split('?')[0]);
46
+ const ammo = new Ammo(req, res);
47
+ await ammo.enhance();
48
+
49
+ if (env('LOG_HTTP_REQUESTS')) logHttpRequest(ammo);
50
+
51
+ try {
52
+ if (target) {
53
+ await executeChain(target, ammo);
54
+ } else {
55
+ if (req.url === '/') {
56
+ ammo.defaultEntry();
57
+ } else {
58
+ errorHandler(
59
+ ammo,
60
+ new TejError(404,
61
+ `No target found for URL ${ammo.fullURL} with method ${ammo.method}`
62
+ )
63
+ );
64
+ }
65
+ }
66
+ } catch (err) {
67
+ errorHandler(ammo, err);
68
+ }
69
+ };
70
+
71
+ export default handler;
package/server/target.js CHANGED
@@ -1,62 +1,62 @@
1
- import isMiddlewareValid from './targets/middleware-validator.js';
2
- import TargetRegistry from './targets/registry.js';
3
-
4
- const targetRegistry = new TargetRegistry();
5
-
6
- const isEndpointValid = (endpoint) => {
7
- if (typeof endpoint !== 'string') return false;
8
- if (endpoint.length === 0) return false;
9
- return endpoint[0] === '/';
10
- };
11
-
12
- const isShootValid = (shoot) => typeof shoot === 'function';
13
-
14
- const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
15
-
16
- class Target {
17
- constructor(base = '') {
18
- this.base = base;
19
- this.targetMiddlewares = [];
20
- }
21
-
22
- base(base) {
23
- if (!base || !base.startsWith('/')) return;
24
- this.base = base;
25
- }
26
-
27
- midair() {
28
- if (!arguments) return;
29
- const middlewares = [...arguments];
30
- const validMiddlewares = middlewares.filter(isMiddlewareValid);
31
- this.targetMiddlewares = this.targetMiddlewares.concat(validMiddlewares);
32
- }
33
-
34
- register() {
35
- let allowedMethods = validMethods;
36
- let args = arguments;
37
- if (!args) return;
38
-
39
- if (validMethods.includes(args[0])) {
40
- allowedMethods = [args[0]];
41
- args = arguments[1];
42
- }
43
-
44
- const endpoint = args[0];
45
- const shoot = args[args.length - 1];
46
- const middlewares = Array.from(args).slice(1, args.length - 1);
47
-
48
- if (!isEndpointValid(endpoint)) return;
49
- if (!isShootValid(shoot)) return;
50
-
51
- const validMiddlewares = middlewares.filter(isMiddlewareValid);
52
-
53
- targetRegistry.targets.push({
54
- allowedMethods: allowedMethods.length > 0 ? allowedMethods : validMethods,
55
- endpoint: this.base + endpoint,
56
- middlewares: this.targetMiddlewares.concat(validMiddlewares),
57
- shoot,
58
- });
59
- }
60
- }
61
-
62
- export default Target;
1
+ import isMiddlewareValid from './targets/middleware-validator.js';
2
+ import TargetRegistry from './targets/registry.js';
3
+
4
+ const targetRegistry = new TargetRegistry();
5
+
6
+ const isEndpointValid = (endpoint) => {
7
+ if (typeof endpoint !== 'string') return false;
8
+ if (endpoint.length === 0) return false;
9
+ return endpoint[0] === '/';
10
+ };
11
+
12
+ const isShootValid = (shoot) => typeof shoot === 'function';
13
+
14
+ const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
15
+
16
+ class Target {
17
+ constructor(base = '') {
18
+ this.base = base;
19
+ this.targetMiddlewares = [];
20
+ }
21
+
22
+ base(base) {
23
+ if (!base || !base.startsWith('/')) return;
24
+ this.base = base;
25
+ }
26
+
27
+ midair() {
28
+ if (!arguments) return;
29
+ const middlewares = [...arguments];
30
+ const validMiddlewares = middlewares.filter(isMiddlewareValid);
31
+ this.targetMiddlewares = this.targetMiddlewares.concat(validMiddlewares);
32
+ }
33
+
34
+ register() {
35
+ let allowedMethods = validMethods;
36
+ let args = arguments;
37
+ if (!args) return;
38
+
39
+ if (validMethods.includes(args[0])) {
40
+ allowedMethods = [args[0]];
41
+ args = arguments[1];
42
+ }
43
+
44
+ const endpoint = args[0];
45
+ const shoot = args[args.length - 1];
46
+ const middlewares = Array.from(args).slice(1, args.length - 1);
47
+
48
+ if (!isEndpointValid(endpoint)) return;
49
+ if (!isShootValid(shoot)) return;
50
+
51
+ const validMiddlewares = middlewares.filter(isMiddlewareValid);
52
+
53
+ targetRegistry.targets.push({
54
+ allowedMethods: allowedMethods.length > 0 ? allowedMethods : validMethods,
55
+ endpoint: this.base + endpoint,
56
+ middlewares: this.targetMiddlewares.concat(validMiddlewares),
57
+ shoot,
58
+ });
59
+ }
60
+ }
61
+
62
+ export default Target;