quantum-flow 1.3.9 → 1.4.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 +6 -8
- package/dist/app/aws/helpers.d.ts +25 -0
- package/dist/app/aws/helpers.js +46 -0
- package/dist/app/aws/lambda.d.ts +7 -10
- package/dist/app/aws/lambda.js +177 -186
- package/dist/app/aws/utils/response.d.ts +7 -0
- package/dist/app/aws/utils/response.js +13 -0
- package/dist/app/http/Application.js +10 -0
- package/dist/app/http/decorators.js +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -1
- package/dist/core/Controller.d.ts +1 -9
- package/dist/core/Controller.js +23 -57
- package/dist/core/index.d.ts +1 -1
- package/dist/core/utils/cors.d.ts +2 -0
- package/dist/core/utils/cors.js +21 -0
- package/dist/core/utils/index.d.ts +1 -0
- package/dist/core/utils/index.js +1 -0
- package/dist/examples/app.d.ts +5 -0
- package/dist/examples/app.js +40 -0
- package/dist/examples/controllers/user.js +2 -1
- package/dist/examples/controllers/userMetadata.d.ts +4 -0
- package/dist/examples/controllers/userMetadata.js +20 -1
- package/dist/examples/lambda.d.ts +1 -0
- package/dist/examples/lambda.js +6 -0
- package/dist/examples/mock/context.d.ts +14 -0
- package/dist/examples/mock/context.js +17 -0
- package/dist/examples/server.d.ts +1 -1
- package/dist/examples/server.js +2 -29
- package/dist/types/common.d.ts +7 -13
- package/dist/types/controller.d.ts +3 -0
- package/dist/types/cors.d.ts +10 -0
- package/dist/types/cors.js +2 -0
- package/dist/types/http.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/lambda.d.ts +48 -2
- package/dist/utils/controller.d.ts +9 -1
- package/dist/utils/controller.js +62 -5
- package/dist/utils/cors.d.ts +8 -0
- package/dist/utils/cors.js +126 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/lambda.d.ts +2 -0
- package/dist/utils/lambda.js +53 -0
- package/dist/utils/multipart.d.ts +1 -0
- package/dist/utils/multipart.js +98 -1
- package/dist/utils/server.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
Request,
|
|
42
42
|
Response,
|
|
43
43
|
Status,
|
|
44
|
-
USE
|
|
44
|
+
USE,
|
|
45
|
+
CORS
|
|
45
46
|
} from 'quantum-flow/core';
|
|
46
47
|
import {IsString} from 'class-validator'
|
|
47
48
|
|
|
@@ -58,10 +59,12 @@ class UserDto {
|
|
|
58
59
|
return { data, intercepted: true };
|
|
59
60
|
},
|
|
60
61
|
})
|
|
62
|
+
@CORS({ origin: '*' })
|
|
61
63
|
@Catch((err) => ({ status: 500, err }))
|
|
62
64
|
export class User {
|
|
63
65
|
@Status(201)
|
|
64
66
|
@PUT(':id',[...middlewares])
|
|
67
|
+
@CORS({ origin: '*' })
|
|
65
68
|
async createUser(
|
|
66
69
|
@Body(UserDto) body: UserDto,
|
|
67
70
|
@Query() query: any,
|
|
@@ -97,7 +100,7 @@ Use the `@Server` decorator with configuration options like port, host, controll
|
|
|
97
100
|
```typescript
|
|
98
101
|
import { Server, Port, Host, Use, Catch, HttpServer } from 'quantum-flow/http';
|
|
99
102
|
|
|
100
|
-
@Server({ controllers: [RootController] })
|
|
103
|
+
@Server({ controllers: [RootController], cors: { origin: '*' } })
|
|
101
104
|
@Port(3000)
|
|
102
105
|
@Host('localhost')
|
|
103
106
|
@Use((data) => data)
|
|
@@ -115,6 +118,7 @@ server.listen().catch(console.error);
|
|
|
115
118
|
- Use `@Use` to apply middlewares.
|
|
116
119
|
- Use `@Catch` to handle errors.
|
|
117
120
|
- Use `@Port` and `@Host` to configure server port and host.
|
|
121
|
+
- Use `@CORS` to configure cors.
|
|
118
122
|
|
|
119
123
|
## Request decorators
|
|
120
124
|
|
|
@@ -160,8 +164,6 @@ Enable WebSocket in the server configuration and register WebSocket controllers.
|
|
|
160
164
|
export class Socket {
|
|
161
165
|
@OnConnection()
|
|
162
166
|
onConnection(event: WebSocketEvent) {
|
|
163
|
-
console.log(`✅ Connected: ${event.client.id}`);
|
|
164
|
-
|
|
165
167
|
// Send greeting ONLY to this client
|
|
166
168
|
event.client.socket.send(
|
|
167
169
|
JSON.stringify({
|
|
@@ -181,8 +183,6 @@ export class Socket {
|
|
|
181
183
|
// The message is ALREADY automatically broadcast to all!
|
|
182
184
|
|
|
183
185
|
const msg = event.message?.data;
|
|
184
|
-
console.log(`💬 Message in chat: ${msg?.text}`);
|
|
185
|
-
|
|
186
186
|
// You can add logic, but no need to broadcast
|
|
187
187
|
if (msg?.text.includes('bad')) {
|
|
188
188
|
// If return empty, the message will not be sent
|
|
@@ -197,7 +197,6 @@ export class Socket {
|
|
|
197
197
|
*/
|
|
198
198
|
@Subscribe('news')
|
|
199
199
|
onNewsMessage(event: WebSocketEvent) {
|
|
200
|
-
console.log(`📰 News: ${event.message?.data.title}`);
|
|
201
200
|
// Automatic broadcast to all subscribed to 'news'
|
|
202
201
|
}
|
|
203
202
|
|
|
@@ -221,7 +220,6 @@ export class Socket {
|
|
|
221
220
|
@OnMessage('subscribe')
|
|
222
221
|
onSubscribe(event: WebSocketEvent) {
|
|
223
222
|
const topic = event.message?.data.topic;
|
|
224
|
-
console.log(`📌 Client ${event.client.id} subscribed to ${topic}`);
|
|
225
223
|
|
|
226
224
|
// Server will save the subscription automatically, no need to do anything!
|
|
227
225
|
// Just confirm
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AppRequest, HTTP_METHODS } from '../../types/index.js';
|
|
2
|
+
import { IncomingHttpHeaders } from 'http';
|
|
3
|
+
export declare class LResponse {
|
|
4
|
+
headers: IncomingHttpHeaders;
|
|
5
|
+
constructor();
|
|
6
|
+
setHeader(header: string, value: string): void;
|
|
7
|
+
}
|
|
8
|
+
export declare class LRequest {
|
|
9
|
+
headers: IncomingHttpHeaders;
|
|
10
|
+
url: URL;
|
|
11
|
+
method: HTTP_METHODS;
|
|
12
|
+
path?: string;
|
|
13
|
+
query?: any;
|
|
14
|
+
params?: any;
|
|
15
|
+
body: any;
|
|
16
|
+
cookies: Record<string, string>;
|
|
17
|
+
_startTime: number;
|
|
18
|
+
event?: any;
|
|
19
|
+
context?: any;
|
|
20
|
+
requestId?: string;
|
|
21
|
+
stage?: string;
|
|
22
|
+
sourceIp?: string;
|
|
23
|
+
userAgent?: string;
|
|
24
|
+
constructor(request: AppRequest);
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LRequest = exports.LResponse = void 0;
|
|
4
|
+
class LResponse {
|
|
5
|
+
headers = {};
|
|
6
|
+
constructor() { }
|
|
7
|
+
setHeader(header, value) {
|
|
8
|
+
this.headers[header] = value;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.LResponse = LResponse;
|
|
12
|
+
class LRequest {
|
|
13
|
+
headers;
|
|
14
|
+
url;
|
|
15
|
+
method;
|
|
16
|
+
path;
|
|
17
|
+
query;
|
|
18
|
+
params;
|
|
19
|
+
body;
|
|
20
|
+
cookies;
|
|
21
|
+
_startTime;
|
|
22
|
+
event;
|
|
23
|
+
context;
|
|
24
|
+
requestId;
|
|
25
|
+
stage;
|
|
26
|
+
sourceIp;
|
|
27
|
+
userAgent;
|
|
28
|
+
constructor(request) {
|
|
29
|
+
this.headers = { ...request.headers };
|
|
30
|
+
this.url = request.url;
|
|
31
|
+
this.method = request.method;
|
|
32
|
+
this.path = request.path;
|
|
33
|
+
this.query = request.query;
|
|
34
|
+
this.params = request.params;
|
|
35
|
+
this.body = request.body;
|
|
36
|
+
this.cookies = request.cookies;
|
|
37
|
+
this._startTime = request._startTime;
|
|
38
|
+
this.event = request.event;
|
|
39
|
+
this.context = request.context;
|
|
40
|
+
this.requestId = request.requestId;
|
|
41
|
+
this.stage = request.stage;
|
|
42
|
+
this.sourceIp = request.sourceIp;
|
|
43
|
+
this.userAgent = request.userAgent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.LRequest = LRequest;
|
package/dist/app/aws/lambda.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { LambdaApp } from '../../types/index.js';
|
|
2
|
+
import { Handler } from 'aws-lambda';
|
|
3
3
|
export declare class LambdaAdapter {
|
|
4
|
-
|
|
5
|
-
static
|
|
6
|
-
static
|
|
7
|
-
static toLambdaResponse
|
|
8
|
-
static
|
|
9
|
-
private static safeHeaders;
|
|
10
|
-
private static safeParams;
|
|
4
|
+
static createHandler(Controller: new (...args: any[]) => LambdaApp): Handler;
|
|
5
|
+
private static getEventType;
|
|
6
|
+
private static toRequest;
|
|
7
|
+
private static toLambdaResponse;
|
|
8
|
+
private static handleError;
|
|
11
9
|
private static getSourceIp;
|
|
12
|
-
private static getUserAgent;
|
|
13
10
|
}
|
package/dist/app/aws/lambda.js
CHANGED
|
@@ -3,242 +3,233 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LambdaAdapter = void 0;
|
|
4
4
|
const _constants_1 = require("../../constants.js");
|
|
5
5
|
const _utils_1 = require("../../utils/index.js");
|
|
6
|
+
const helpers_1 = require("./helpers");
|
|
6
7
|
class LambdaAdapter {
|
|
7
|
-
static
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
static createHandler(Controller) {
|
|
9
|
+
return async (event, context) => {
|
|
10
|
+
const instance = new Controller();
|
|
11
|
+
if (Object.hasOwn(instance, 'beforeStart')) {
|
|
12
|
+
await instance.beforeStart?.();
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const cors = Reflect.getMetadata(_constants_1.CORS_METADATA, instance);
|
|
16
|
+
const eventType = this.getEventType(event);
|
|
17
|
+
const normalizedEvent = (0, _utils_1.normalizeEvent)(event, eventType);
|
|
18
|
+
const request = this.toRequest(normalizedEvent, context);
|
|
19
|
+
const lambdaRequest = new helpers_1.LRequest(request);
|
|
20
|
+
const lambdaResponse = new helpers_1.LResponse();
|
|
21
|
+
let handledCors = { permitted: true, continue: true };
|
|
22
|
+
if (cors) {
|
|
23
|
+
handledCors = (0, _utils_1.handleCORS)(request, lambdaResponse, cors);
|
|
24
|
+
}
|
|
25
|
+
if (!handledCors.permitted) {
|
|
26
|
+
return this.toLambdaResponse({ status: 403, message: 'CORS: Origin not allowed' }, lambdaRequest, lambdaResponse, eventType);
|
|
27
|
+
}
|
|
28
|
+
if (!handledCors.continue && handledCors.permitted) {
|
|
29
|
+
return this.toLambdaResponse({ status: 204 }, lambdaRequest, lambdaResponse, eventType);
|
|
30
|
+
}
|
|
31
|
+
if (typeof instance.handleRequest !== 'function') {
|
|
32
|
+
throw new Error('Controller must have handleRequest method');
|
|
33
|
+
}
|
|
34
|
+
const response = await instance.handleRequest(request, lambdaRequest, lambdaResponse);
|
|
35
|
+
return this.toLambdaResponse(response, lambdaRequest, lambdaResponse, eventType);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return this.handleError(error, event, context);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static getEventType(event) {
|
|
43
|
+
if (event.httpMethod && event.resource) {
|
|
44
|
+
return 'rest';
|
|
45
|
+
}
|
|
46
|
+
if (event.version === '2.0' || event.requestContext?.http) {
|
|
47
|
+
return 'http';
|
|
48
|
+
}
|
|
49
|
+
if (event.version && event.rawPath && !event.requestContext?.http) {
|
|
50
|
+
return 'url';
|
|
13
51
|
}
|
|
14
|
-
return
|
|
52
|
+
return 'rest';
|
|
15
53
|
}
|
|
16
54
|
static toRequest(event, context) {
|
|
17
55
|
const query = {};
|
|
18
|
-
if (event.
|
|
56
|
+
if (event.multiValueQueryStringParameters) {
|
|
57
|
+
Object.entries(event.multiValueQueryStringParameters).forEach(([key, value]) => {
|
|
58
|
+
query[key] = value;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
19
62
|
Object.entries(event.queryStringParameters).forEach(([key, value]) => {
|
|
20
|
-
|
|
21
|
-
query[key] = event.multiValueQueryStringParameters[key];
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
query[key] = value || '';
|
|
25
|
-
}
|
|
63
|
+
query[key] = value;
|
|
26
64
|
});
|
|
27
65
|
}
|
|
28
66
|
const cookies = {};
|
|
29
|
-
const cookieHeader = event.headers?.Cookie || event.headers?.cookie;
|
|
67
|
+
const cookieHeader = event.headers?.Cookie || event.headers?.cookie || event.headers?.cookies;
|
|
30
68
|
if (cookieHeader) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let url;
|
|
44
|
-
try {
|
|
45
|
-
url = new URL(fullUrl);
|
|
46
|
-
if (event.queryStringParameters) {
|
|
47
|
-
Object.entries(event.queryStringParameters).forEach(([key, value]) => {
|
|
48
|
-
if (value)
|
|
49
|
-
url.searchParams.append(key, value);
|
|
69
|
+
if (Array.isArray(cookieHeader)) {
|
|
70
|
+
cookieHeader.forEach((cookie) => {
|
|
71
|
+
const [name, value] = cookie.split('=');
|
|
72
|
+
if (name && value)
|
|
73
|
+
cookies[name] = decodeURIComponent(value);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
cookieHeader.split(';').forEach((cookie) => {
|
|
78
|
+
const [name, value] = cookie.trim().split('=');
|
|
79
|
+
if (name && value)
|
|
80
|
+
cookies[name] = decodeURIComponent(value);
|
|
50
81
|
});
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
let rawBody = Buffer.from(event.body ?? '', 'base64');
|
|
85
|
+
let body = event.body || {};
|
|
86
|
+
if (event.body && event.isBase64Encoded) {
|
|
87
|
+
body = rawBody.toString('utf-8');
|
|
56
88
|
}
|
|
89
|
+
if (typeof body === 'string' && body.trim().startsWith('{')) {
|
|
90
|
+
try {
|
|
91
|
+
body = JSON.parse(body);
|
|
92
|
+
}
|
|
93
|
+
catch (e) { }
|
|
94
|
+
}
|
|
95
|
+
const xForvarded = Array.isArray(event.headers['x-forwarded-proto'])
|
|
96
|
+
? event.headers['x-forwarded-proto']?.[0]
|
|
97
|
+
: event.headers['x-forwarded-proto'];
|
|
98
|
+
const xhost = Array.isArray(event.headers['host'])
|
|
99
|
+
? event.headers['host']?.[0]
|
|
100
|
+
: event.headers['host'];
|
|
101
|
+
const protocol = xForvarded || 'https';
|
|
102
|
+
const host = xhost || 'localhost:3000';
|
|
103
|
+
const fullUrl = `${protocol}://${host}${event.path}`;
|
|
104
|
+
let url = new URL(fullUrl);
|
|
57
105
|
return {
|
|
58
|
-
method: event.httpMethod,
|
|
59
|
-
path: cleanPath,
|
|
106
|
+
method: event.httpMethod.toUpperCase(),
|
|
60
107
|
url,
|
|
61
|
-
headers:
|
|
108
|
+
headers: event.headers,
|
|
62
109
|
query,
|
|
63
110
|
body,
|
|
64
|
-
params:
|
|
111
|
+
params: event.pathParameters,
|
|
65
112
|
cookies,
|
|
66
113
|
event,
|
|
67
114
|
context,
|
|
68
|
-
|
|
115
|
+
rawBody,
|
|
116
|
+
path: event.path,
|
|
117
|
+
isBase64Encoded: event.isBase64Encoded,
|
|
69
118
|
requestId: context.awsRequestId,
|
|
70
119
|
stage: event.requestContext?.stage || '$default',
|
|
71
120
|
sourceIp: this.getSourceIp(event),
|
|
72
|
-
|
|
121
|
+
_startTime: Date.now(),
|
|
122
|
+
userAgent: typeof event.headers['user-agent'] === 'string'
|
|
123
|
+
? event.headers['user-agent']
|
|
124
|
+
: event.headers['user-agent']?.[0] || 'unknown',
|
|
73
125
|
};
|
|
74
126
|
}
|
|
75
|
-
static
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if ('data' in response) {
|
|
79
|
-
body = {
|
|
80
|
-
data: response.data,
|
|
81
|
-
timestamp: new Date().toISOString(),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
else if (response.body) {
|
|
85
|
-
return response;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
body = {
|
|
89
|
-
data: response,
|
|
90
|
-
timestamp: new Date().toISOString(),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return body;
|
|
95
|
-
}
|
|
96
|
-
static toLambdaResponse(response, request) {
|
|
97
|
-
let statusCode = response.status ?? 200;
|
|
98
|
-
let body = this.createResponseBody(response);
|
|
99
|
-
let headers = {
|
|
127
|
+
static toLambdaResponse(appResponse, request, res, eventType) {
|
|
128
|
+
const statusCode = appResponse.status || 200;
|
|
129
|
+
const headers = {
|
|
100
130
|
'Content-Type': 'application/json',
|
|
101
|
-
'X-Request-Id': request
|
|
102
|
-
|
|
131
|
+
'X-Request-Id': request.requestId,
|
|
132
|
+
...(appResponse.headers || {}),
|
|
133
|
+
...res.headers,
|
|
103
134
|
};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
data: response.data,
|
|
110
|
-
timestamp: new Date().toISOString(),
|
|
111
|
-
requestId: request?.requestId,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
else if (response.body) {
|
|
115
|
-
return response;
|
|
135
|
+
const originHeader = request.headers['origin'] || request.headers['Origin'];
|
|
136
|
+
let origin;
|
|
137
|
+
if (originHeader) {
|
|
138
|
+
if (Array.isArray(originHeader)) {
|
|
139
|
+
origin = originHeader[0];
|
|
116
140
|
}
|
|
117
141
|
else {
|
|
118
|
-
|
|
119
|
-
success: _constants_1.OK_STATUSES.includes(statusCode),
|
|
120
|
-
data: response,
|
|
121
|
-
timestamp: new Date().toISOString(),
|
|
122
|
-
requestId: request?.requestId,
|
|
123
|
-
};
|
|
142
|
+
origin = originHeader;
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (originHeader) {
|
|
129
|
-
headers['Access-Control-Allow-Origin'] = originHeader;
|
|
145
|
+
if (origin) {
|
|
146
|
+
headers['Access-Control-Allow-Origin'] = origin;
|
|
130
147
|
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
131
148
|
headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS';
|
|
132
149
|
headers['Access-Control-Allow-Headers'] =
|
|
133
150
|
'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token';
|
|
134
151
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
const
|
|
152
|
+
const body = JSON.stringify({
|
|
153
|
+
success: statusCode < 400,
|
|
154
|
+
data: appResponse.data,
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
});
|
|
157
|
+
const commonResponse = {
|
|
141
158
|
statusCode,
|
|
142
159
|
headers,
|
|
143
|
-
body:
|
|
144
|
-
|
|
160
|
+
body: appResponse.data,
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
145
162
|
};
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
static createHandler(Contoller) {
|
|
157
|
-
const handler = async (event, context) => {
|
|
158
|
-
const instance = new Contoller();
|
|
159
|
-
if (Object.hasOwn(instance, 'beforeStart')) {
|
|
160
|
-
await instance.beforeStart?.();
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const request = LambdaAdapter.toRequest(event, context);
|
|
164
|
-
if (typeof instance.handleRequest === 'function') {
|
|
165
|
-
const response = await instance.handleRequest(request);
|
|
166
|
-
return LambdaAdapter.toLambdaResponse(response, request);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
throw new Error('Controller must have handleRequest method');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
console.error('❌ Lambda error:', error);
|
|
174
|
-
const errorResponse = {
|
|
175
|
-
statusCode: error.status || 500,
|
|
176
|
-
headers: {
|
|
177
|
-
'Content-Type': 'application/json',
|
|
178
|
-
'X-Request-Id': context.awsRequestId,
|
|
179
|
-
'Access-Control-Allow-Origin': event.headers?.origin || event.headers?.Origin || '*',
|
|
180
|
-
'Access-Control-Allow-Credentials': 'true',
|
|
181
|
-
},
|
|
182
|
-
body: JSON.stringify({
|
|
183
|
-
success: false,
|
|
184
|
-
message: error.message || 'Internal Server Error',
|
|
185
|
-
requestId: context.awsRequestId,
|
|
186
|
-
}),
|
|
163
|
+
switch (eventType) {
|
|
164
|
+
case 'rest':
|
|
165
|
+
return { ...commonResponse, isBase64Encoded: false };
|
|
166
|
+
case 'http':
|
|
167
|
+
return {
|
|
168
|
+
...commonResponse,
|
|
169
|
+
cookies: request.cookies
|
|
170
|
+
? Object.entries(request.cookies).map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
171
|
+
: undefined,
|
|
187
172
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
result[key] = value;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
return result;
|
|
173
|
+
case 'url':
|
|
174
|
+
return {
|
|
175
|
+
...commonResponse,
|
|
176
|
+
cookies: request.cookies
|
|
177
|
+
? Object.entries(request.cookies).map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
178
|
+
: undefined,
|
|
179
|
+
};
|
|
180
|
+
default:
|
|
181
|
+
return {
|
|
182
|
+
statusCode,
|
|
183
|
+
headers,
|
|
184
|
+
body,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
206
187
|
}
|
|
207
|
-
static
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
188
|
+
static handleError(error, event, context) {
|
|
189
|
+
const eventType = this.getEventType(event);
|
|
190
|
+
const statusCode = error.status || 500;
|
|
191
|
+
const body = JSON.stringify({
|
|
192
|
+
success: false,
|
|
193
|
+
message: error.message || 'Internal Server Error',
|
|
194
|
+
requestId: context.awsRequestId,
|
|
215
195
|
});
|
|
216
|
-
|
|
196
|
+
const headers = {
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
'X-Request-Id': context.awsRequestId,
|
|
199
|
+
'Access-Control-Allow-Origin': '*',
|
|
200
|
+
'Access-Control-Allow-Credentials': 'true',
|
|
201
|
+
};
|
|
202
|
+
switch (eventType) {
|
|
203
|
+
case 'rest':
|
|
204
|
+
return {
|
|
205
|
+
statusCode,
|
|
206
|
+
headers,
|
|
207
|
+
body,
|
|
208
|
+
isBase64Encoded: false,
|
|
209
|
+
};
|
|
210
|
+
default:
|
|
211
|
+
return {
|
|
212
|
+
statusCode,
|
|
213
|
+
headers,
|
|
214
|
+
body,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
217
|
}
|
|
218
218
|
static getSourceIp(event) {
|
|
219
|
-
|
|
220
|
-
return event.requestContext.identity.sourceIp;
|
|
221
|
-
}
|
|
222
|
-
const forwardedFor = event.headers?.['x-forwarded-for'];
|
|
219
|
+
const forwardedFor = event.headers['x-forwarded-for'];
|
|
223
220
|
if (forwardedFor) {
|
|
224
|
-
if (
|
|
225
|
-
return forwardedFor.split(',')[0].trim();
|
|
226
|
-
}
|
|
227
|
-
if (Array.isArray(forwardedFor) && forwardedFor.length > 0) {
|
|
221
|
+
if (Array.isArray(forwardedFor)) {
|
|
228
222
|
return forwardedFor[0].split(',')[0].trim();
|
|
229
223
|
}
|
|
224
|
+
return forwardedFor.split(',')[0].trim();
|
|
225
|
+
}
|
|
226
|
+
if (event.requestContext?.identity?.sourceIp) {
|
|
227
|
+
return event.requestContext.identity.sourceIp;
|
|
228
|
+
}
|
|
229
|
+
if (event.requestContext?.http?.sourceIp) {
|
|
230
|
+
return event.requestContext.http.sourceIp;
|
|
230
231
|
}
|
|
231
232
|
return '0.0.0.0';
|
|
232
233
|
}
|
|
233
|
-
static getUserAgent(event) {
|
|
234
|
-
const ua = event.headers?.['user-agent'];
|
|
235
|
-
if (!ua)
|
|
236
|
-
return 'unknown';
|
|
237
|
-
if (typeof ua === 'string')
|
|
238
|
-
return ua;
|
|
239
|
-
if (Array.isArray(ua) && ua.length > 0)
|
|
240
|
-
return ua[0];
|
|
241
|
-
return 'unknown';
|
|
242
|
-
}
|
|
243
234
|
}
|
|
244
235
|
exports.LambdaAdapter = LambdaAdapter;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Response = void 0;
|
|
4
|
+
class Response {
|
|
5
|
+
headers;
|
|
6
|
+
constructor(request) {
|
|
7
|
+
this.headers = { ...request.headers };
|
|
8
|
+
}
|
|
9
|
+
setHeader(header, value) {
|
|
10
|
+
this.headers[header] = value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.Response = Response;
|
|
@@ -101,6 +101,16 @@ class HttpServer extends Socket_1.Socket {
|
|
|
101
101
|
const startTime = Date.now();
|
|
102
102
|
try {
|
|
103
103
|
const request = await this.createRequest(req);
|
|
104
|
+
let handledCors = { permitted: true, continue: true };
|
|
105
|
+
if (this.config.cors) {
|
|
106
|
+
handledCors = (0, _utils_1.handleCORS)(request, res, this.config.cors);
|
|
107
|
+
}
|
|
108
|
+
if (!handledCors.permitted) {
|
|
109
|
+
return this.sendResponse(res, { status: 403, message: 'CORS: Origin not allowed' }, startTime);
|
|
110
|
+
}
|
|
111
|
+
if (!handledCors.continue && handledCors.permitted) {
|
|
112
|
+
return this.sendResponse(res, { status: 204 }, startTime);
|
|
113
|
+
}
|
|
104
114
|
let appRequest = await this.applyMiddlewares(request, req, res);
|
|
105
115
|
let data = await this.findController(appRequest, req, res);
|
|
106
116
|
const isError = !_constants_1.OK_STATUSES.includes(data.status);
|
|
@@ -12,6 +12,7 @@ function Server(config = {}) {
|
|
|
12
12
|
...config,
|
|
13
13
|
controllers: [...(existingConfig.controllers || []), ...(config.controllers || [])],
|
|
14
14
|
middlewares: [...(existingConfig.middlewares ?? []), ...(config.middlewares ?? [])],
|
|
15
|
+
cors: config.cors,
|
|
15
16
|
interceptors: existingConfig.interceptor ?? config.interceptor,
|
|
16
17
|
};
|
|
17
18
|
Reflect.defineMetadata(_constants_1.SERVER_CONFIG_KEY, mergedConfig, target);
|
package/dist/constants.d.ts
CHANGED
|
@@ -20,3 +20,4 @@ export declare const OK_STATUSES: number[];
|
|
|
20
20
|
export declare const TO_VALIDATE: string[];
|
|
21
21
|
export declare const STATISTIC: Record<'controllers' | 'routes', number>;
|
|
22
22
|
export declare const INCREMENT_STATISTIC: (prop: "controllers" | "routes") => void;
|
|
23
|
+
export declare const CORS_METADATA = "cors:config";
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INCREMENT_STATISTIC = exports.STATISTIC = exports.TO_VALIDATE = exports.OK_STATUSES = exports.STOPPED = exports.CATCH = exports.INTECEPT = exports.WS_SERVICE_KEY = exports.WS_TOPIC_KEY = exports.WS_METADATA_KEY = exports.USE_MIDDLEWARE = exports.SERVER_MODULES_KEY = exports.SERVER_CONFIG_KEY = exports.OK_METADATA_KEY = exports.ENDPOINT = exports.INTERCEPTOR = exports.CONTROLLERS = exports.MIDDLEWARES = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PREFIX = exports.APP_METADATA_KEY = exports.PARAM_METADATA_KEY = void 0;
|
|
3
|
+
exports.CORS_METADATA = exports.INCREMENT_STATISTIC = exports.STATISTIC = exports.TO_VALIDATE = exports.OK_STATUSES = exports.STOPPED = exports.CATCH = exports.INTECEPT = exports.WS_SERVICE_KEY = exports.WS_TOPIC_KEY = exports.WS_METADATA_KEY = exports.USE_MIDDLEWARE = exports.SERVER_MODULES_KEY = exports.SERVER_CONFIG_KEY = exports.OK_METADATA_KEY = exports.ENDPOINT = exports.INTERCEPTOR = exports.CONTROLLERS = exports.MIDDLEWARES = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PREFIX = exports.APP_METADATA_KEY = exports.PARAM_METADATA_KEY = void 0;
|
|
4
4
|
exports.PARAM_METADATA_KEY = 'design:parameters';
|
|
5
5
|
exports.APP_METADATA_KEY = 'app:configuration';
|
|
6
6
|
exports.ROUTE_PREFIX = 'route:prefix';
|
|
@@ -34,3 +34,4 @@ const INCREMENT_STATISTIC = (prop) => {
|
|
|
34
34
|
exports.STATISTIC[prop] = exports.STATISTIC[prop] + 1;
|
|
35
35
|
};
|
|
36
36
|
exports.INCREMENT_STATISTIC = INCREMENT_STATISTIC;
|
|
37
|
+
exports.CORS_METADATA = 'cors:config';
|