@trayio/express 0.0.1-beta
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/dist/http/ExpressHttpController.d.ts +16 -0
- package/dist/http/ExpressHttpController.d.ts.map +1 -0
- package/dist/http/ExpressHttpController.js +182 -0
- package/dist/http/ExpressHttpServer.d.ts +9 -0
- package/dist/http/ExpressHttpServer.d.ts.map +1 -0
- package/dist/http/ExpressHttpServer.js +28 -0
- package/package.json +37 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router } from 'express-serve-static-core';
|
|
2
|
+
import { HttpController } from '@trayio/commons/http/HttpController';
|
|
3
|
+
export declare class ExpressHttpController {
|
|
4
|
+
protected controller: HttpController;
|
|
5
|
+
protected baseTmpPathForUploadedFiles: string;
|
|
6
|
+
constructor(controller: HttpController, baseTmpPathForUploadedFiles?: string);
|
|
7
|
+
private addRoute;
|
|
8
|
+
addRoutes: (router: Router) => Router;
|
|
9
|
+
private parseRequestBody;
|
|
10
|
+
private parseGeneralRequestBody;
|
|
11
|
+
private parseMultipartFormData;
|
|
12
|
+
private flattenFields;
|
|
13
|
+
private flattenFiles;
|
|
14
|
+
private convertFormidableFileToTrayFile;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ExpressHttpController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpressHttpController.d.ts","sourceRoot":"","sources":["../../src/http/ExpressHttpController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAQnD,OAAO,EACN,cAAc,EAEd,MAAM,qCAAqC,CAAC;AAa7C,qBAAa,qBAAqB;IAEhC,SAAS,CAAC,UAAU,EAAE,cAAc;IACpC,SAAS,CAAC,2BAA2B,EAAE,MAAM;gBADnC,UAAU,EAAE,cAAc,EAC1B,2BAA2B,GAAE,MAAe;IAGvD,OAAO,CAAC,QAAQ,CAsDd;IAEF,SAAS,WAAY,MAAM,YAKzB;IAEF,OAAO,CAAC,gBAAgB,CAgBtB;IAGF,OAAO,CAAC,uBAAuB,CAI7B;IAEF,OAAO,CAAC,sBAAsB,CAiC3B;IAEH,OAAO,CAAC,aAAa,CAenB;IAEF,OAAO,CAAC,YAAY,CAkBlB;IAEF,OAAO,CAAC,+BAA+B,CAQpC;CACH"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.ExpressHttpController = void 0;
|
|
39
|
+
const TE = __importStar(require("fp-ts/TaskEither"));
|
|
40
|
+
const E = __importStar(require("fp-ts/Either"));
|
|
41
|
+
const Http_1 = require("@trayio/commons/http/Http");
|
|
42
|
+
const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
|
|
43
|
+
const formidable_1 = __importDefault(require("formidable"));
|
|
44
|
+
const stream_1 = require("stream");
|
|
45
|
+
class ExpressHttpController {
|
|
46
|
+
constructor(controller, baseTmpPathForUploadedFiles = '/tmp') {
|
|
47
|
+
this.controller = controller;
|
|
48
|
+
this.baseTmpPathForUploadedFiles = baseTmpPathForUploadedFiles;
|
|
49
|
+
this.addRoute = (endpoint) => {
|
|
50
|
+
const route = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const headers = Object.entries(req.headers).reduce((acc, [key, value]) => {
|
|
52
|
+
const newValue = typeof value === 'undefined' ? '' : value;
|
|
53
|
+
return Object.assign(Object.assign({}, acc), { [key]: newValue });
|
|
54
|
+
}, {});
|
|
55
|
+
const requestBody = yield this.parseRequestBody(req);
|
|
56
|
+
const httpResponse = yield endpoint.execute({
|
|
57
|
+
headers,
|
|
58
|
+
pathParams: req.params,
|
|
59
|
+
queryString: req.query,
|
|
60
|
+
body: requestBody,
|
|
61
|
+
})();
|
|
62
|
+
const response = Object.entries(httpResponse.headers)
|
|
63
|
+
.reduce((acc, [key, value]) => acc.setHeader(key, value), res)
|
|
64
|
+
.status(httpResponse.statusCode);
|
|
65
|
+
httpResponse.body.pipe(response);
|
|
66
|
+
});
|
|
67
|
+
let method;
|
|
68
|
+
switch (endpoint.method) {
|
|
69
|
+
case Http_1.HttpMethod.Get:
|
|
70
|
+
method = 'get';
|
|
71
|
+
break;
|
|
72
|
+
case Http_1.HttpMethod.Post:
|
|
73
|
+
method = 'post';
|
|
74
|
+
break;
|
|
75
|
+
case Http_1.HttpMethod.Put:
|
|
76
|
+
method = 'put';
|
|
77
|
+
break;
|
|
78
|
+
case Http_1.HttpMethod.Delete:
|
|
79
|
+
method = 'delete';
|
|
80
|
+
break;
|
|
81
|
+
case Http_1.HttpMethod.Patch:
|
|
82
|
+
method = 'patch';
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
method = 'get';
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return (router) => router[method](endpoint.path, route);
|
|
89
|
+
};
|
|
90
|
+
this.addRoutes = (router) => {
|
|
91
|
+
this.controller
|
|
92
|
+
.getEndpoints()
|
|
93
|
+
.forEach((endpoint) => this.addRoute(endpoint)(router));
|
|
94
|
+
return router;
|
|
95
|
+
};
|
|
96
|
+
this.parseRequestBody = (req) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
const contentType = req.headers['content-type'];
|
|
98
|
+
if (contentType && contentType.startsWith('multipart/form-data')) {
|
|
99
|
+
const multiPartResponse = yield this.parseMultipartFormData(req)();
|
|
100
|
+
const multiPartBody = E.getOrElse((error) => {
|
|
101
|
+
throw new Error(error.message);
|
|
102
|
+
})(multiPartResponse);
|
|
103
|
+
return multiPartBody;
|
|
104
|
+
}
|
|
105
|
+
return this.parseGeneralRequestBody(req);
|
|
106
|
+
});
|
|
107
|
+
// express defaults empty request body to an empty object, so we need to transform to an array buffer
|
|
108
|
+
this.parseGeneralRequestBody = (req) => {
|
|
109
|
+
const body = Object.keys(req.body).length === 0 ? new ArrayBuffer(0) : req.body;
|
|
110
|
+
return BufferExtensions_1.BufferExtensions.arrayBufferToReadable(body);
|
|
111
|
+
};
|
|
112
|
+
this.parseMultipartFormData = (req) => TE.tryCatch(() => new Promise((resolve, reject) => {
|
|
113
|
+
/*
|
|
114
|
+
* NOTE: inorder to use any other underlying file storage other than node fs, we would have to use
|
|
115
|
+
* formidable's fileWriteStreamHandler option that enables formidable to write a file to a stream.
|
|
116
|
+
* When used the fileWriteStreamHandler option with passThrough stream, we ran into errors that we didn't have
|
|
117
|
+
* time to solve, but ideally, we would be using the FileStorage interface instead of letting formidable
|
|
118
|
+
* write to the file system directly.
|
|
119
|
+
*/
|
|
120
|
+
const form = (0, formidable_1.default)({
|
|
121
|
+
maxFiles: 1,
|
|
122
|
+
maxFileSize: 50 * 1024 * 1024,
|
|
123
|
+
uploadDir: this.baseTmpPathForUploadedFiles,
|
|
124
|
+
});
|
|
125
|
+
form.parse(req, (err, fields, files) => {
|
|
126
|
+
if (err) {
|
|
127
|
+
reject(err);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const body = {
|
|
131
|
+
fields: this.flattenFields(fields),
|
|
132
|
+
files: this.flattenFiles(files),
|
|
133
|
+
};
|
|
134
|
+
resolve(body);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}), (error) => new Error(error.message));
|
|
138
|
+
this.flattenFields = (fields) => {
|
|
139
|
+
const flattenedFields = {};
|
|
140
|
+
Object.entries(fields).forEach(([key, value]) => {
|
|
141
|
+
if (Array.isArray(value) && value.length > 1) {
|
|
142
|
+
throw new Error(`Field ${key} has more than one value and is current not supported`);
|
|
143
|
+
}
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
flattenedFields[key] = value.join(',');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
flattenedFields[key] = value !== null && value !== void 0 ? value : '';
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return flattenedFields;
|
|
152
|
+
};
|
|
153
|
+
this.flattenFiles = (files) => {
|
|
154
|
+
const flattenedFiles = {};
|
|
155
|
+
Object.entries(files)
|
|
156
|
+
.filter(([key, file]) => file !== null)
|
|
157
|
+
.forEach(([key, file]) => {
|
|
158
|
+
if (Array.isArray(file) && file.length > 1) {
|
|
159
|
+
throw new Error(`Field ${key} has more than one file and is currently not supported`);
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(file)) {
|
|
162
|
+
const fileContent = this.convertFormidableFileToTrayFile(file[0]);
|
|
163
|
+
flattenedFiles[key] = fileContent;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return flattenedFiles;
|
|
167
|
+
};
|
|
168
|
+
this.convertFormidableFileToTrayFile = (file) => {
|
|
169
|
+
var _a;
|
|
170
|
+
return ({
|
|
171
|
+
key: file.newFilename,
|
|
172
|
+
metadata: {
|
|
173
|
+
name: file.newFilename,
|
|
174
|
+
contentType: (_a = file.mimetype) !== null && _a !== void 0 ? _a : undefined,
|
|
175
|
+
size: file.size,
|
|
176
|
+
},
|
|
177
|
+
content: stream_1.Readable.from('0'),
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.ExpressHttpController = ExpressHttpController;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import http = require('http');
|
|
3
|
+
import { ExpressHttpController } from './ExpressHttpController';
|
|
4
|
+
interface ExpressServerOptions {
|
|
5
|
+
port?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const startServer: (controllers: ExpressHttpController[], options: ExpressServerOptions) => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ExpressHttpServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpressHttpServer.d.ts","sourceRoot":"","sources":["../../src/http/ExpressHttpServer.ts"],"names":[],"mappings":";AAEA,OAAO,IAAI,GAAG,QAAQ,MAAM,CAAC,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,UAAU,oBAAoB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,WAAW,gBACV,qBAAqB,EAAE,WAC3B,oBAAoB,yEAkC7B,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startServer = void 0;
|
|
4
|
+
const express = require("express");
|
|
5
|
+
const cors = require("cors");
|
|
6
|
+
const startServer = (controllers, options) => {
|
|
7
|
+
var _a;
|
|
8
|
+
const port = (_a = options.port) !== null && _a !== void 0 ? _a : 3000;
|
|
9
|
+
const app = express();
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
app.use(cors());
|
|
12
|
+
const newRouter = controllers.map((controller) => controller.addRoutes(router));
|
|
13
|
+
app.disable('etag');
|
|
14
|
+
app.disable('x-powered-by');
|
|
15
|
+
const skipBodyParserForMultipartFormData = (req) => {
|
|
16
|
+
const contentType = req.headers['content-type'];
|
|
17
|
+
return !(contentType && contentType.startsWith('multipart/form-data'));
|
|
18
|
+
};
|
|
19
|
+
app.use(express.raw({
|
|
20
|
+
limit: '50mb',
|
|
21
|
+
type: (req) => skipBodyParserForMultipartFormData(req),
|
|
22
|
+
}));
|
|
23
|
+
app.use('/', newRouter);
|
|
24
|
+
return app.listen(port, () => {
|
|
25
|
+
console.log(`Web server listening on port ${port}`);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
exports.startServer = startServer;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trayio/express",
|
|
3
|
+
"version": "0.0.1-beta",
|
|
4
|
+
"description": "Express extensions and implementations",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./*": "./dist/*.js"
|
|
7
|
+
},
|
|
8
|
+
"author": "Tray.io",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.x"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@trayio/commons": "0.0.1-beta",
|
|
18
|
+
"cors": "2.8.5",
|
|
19
|
+
"express": "4.18.2",
|
|
20
|
+
"formidable": "3.5.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/cors": "*",
|
|
24
|
+
"@types/express": "*",
|
|
25
|
+
"@types/formidable": "*"
|
|
26
|
+
},
|
|
27
|
+
"typesVersions": {
|
|
28
|
+
"*": {
|
|
29
|
+
"*": [
|
|
30
|
+
"dist/*"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"/dist"
|
|
36
|
+
]
|
|
37
|
+
}
|