@tsed/platform-serverless 8.0.0 → 8.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/package.json +14 -13
- package/src/builder/PlatformServerless.spec.ts +219 -0
- package/src/builder/PlatformServerless.ts +202 -0
- package/src/builder/PlatformServerlessHandler.spec.ts +145 -0
- package/src/builder/PlatformServerlessHandler.ts +121 -0
- package/src/domain/ServerlessContext.spec.ts +106 -0
- package/src/domain/ServerlessContext.ts +60 -0
- package/src/domain/ServerlessEvent.ts +24 -0
- package/src/domain/ServerlessRequest.spec.ts +130 -0
- package/src/domain/ServerlessRequest.ts +106 -0
- package/src/domain/ServerlessResponse.spec.ts +139 -0
- package/src/domain/ServerlessResponse.ts +226 -0
- package/src/domain/ServerlessResponseStream.spec.ts +63 -0
- package/src/domain/ServerlessResponseStream.ts +113 -0
- package/src/exports.ts +1 -0
- package/src/index.ts +14 -0
- package/src/utils/getRequestId.spec.ts +43 -0
- package/src/utils/getRequestId.ts +16 -0
- package/src/utils/isInAWS.ts +8 -0
- package/src/utils/setResponseHeaders.spec.ts +156 -0
- package/src/utils/setResponseHeaders.ts +58 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {pipeline} from "node:stream/promises";
|
|
2
|
+
|
|
3
|
+
import {AnyPromiseResult, AnyToPromise, isSerializable, isStream} from "@tsed/core";
|
|
4
|
+
import {BaseContext, inject, injectable, lazyInject, ProviderScope, runInContext, TokenProvider} from "@tsed/di";
|
|
5
|
+
import {$asyncEmit} from "@tsed/hooks";
|
|
6
|
+
import {serialize} from "@tsed/json-mapper";
|
|
7
|
+
import {DeserializerPipe, PlatformParams, ValidationPipe} from "@tsed/platform-params";
|
|
8
|
+
|
|
9
|
+
import {ServerlessContext} from "../domain/ServerlessContext.js";
|
|
10
|
+
import type {ServerlessEvent} from "../domain/ServerlessEvent.js";
|
|
11
|
+
import {ServerlessResponseStream} from "../domain/ServerlessResponseStream.js";
|
|
12
|
+
import {setResponseHeaders} from "../utils/setResponseHeaders.js";
|
|
13
|
+
|
|
14
|
+
export class PlatformServerlessHandler {
|
|
15
|
+
protected params = inject(PlatformParams);
|
|
16
|
+
|
|
17
|
+
createHandler(token: TokenProvider, propertyKey: string | symbol) {
|
|
18
|
+
const promisedHandler = this.params.compileHandler({token, propertyKey});
|
|
19
|
+
|
|
20
|
+
return ($ctx: ServerlessContext<ServerlessEvent>) => {
|
|
21
|
+
return runInContext($ctx, async () => {
|
|
22
|
+
await $asyncEmit("$onRequest", $ctx);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const resolver = new AnyToPromise();
|
|
26
|
+
const handler = await promisedHandler;
|
|
27
|
+
const result = await resolver.call(() => handler({$ctx}));
|
|
28
|
+
|
|
29
|
+
this.processResult(result, $ctx);
|
|
30
|
+
} catch (er) {
|
|
31
|
+
$ctx.response.status(500).body(er);
|
|
32
|
+
const exceptions = await lazyInject(() => import("@tsed/platform-exceptions"));
|
|
33
|
+
|
|
34
|
+
await exceptions.catch(er, $ctx as unknown as BaseContext);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this.flush($ctx);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async flush($ctx: ServerlessContext<ServerlessEvent>) {
|
|
43
|
+
const body: unknown = $ctx.isHttpEvent() && !$ctx.isAuthorizerEvent() ? await this.makeHttpResponse($ctx) : $ctx.response.getBody();
|
|
44
|
+
|
|
45
|
+
await $asyncEmit("$onResponse", $ctx);
|
|
46
|
+
|
|
47
|
+
$ctx.logger.flush();
|
|
48
|
+
$ctx.destroy();
|
|
49
|
+
|
|
50
|
+
if (!$ctx.isHttpEvent() && $ctx.response.statusCode >= 400 && body) {
|
|
51
|
+
throw new Error((body as Error).message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return body;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async makeHttpResponse($ctx: ServerlessContext<ServerlessEvent>) {
|
|
58
|
+
setResponseHeaders($ctx);
|
|
59
|
+
|
|
60
|
+
let body = $ctx.response.getBody();
|
|
61
|
+
|
|
62
|
+
if (isSerializable(body)) {
|
|
63
|
+
$ctx.response.set("content-type", "application/json");
|
|
64
|
+
body = JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const meta = {
|
|
68
|
+
statusCode: $ctx.response.getStatus(),
|
|
69
|
+
headers: {
|
|
70
|
+
...$ctx.response.getHeaders(),
|
|
71
|
+
"x-request-id": $ctx.id
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (isStream(body) && $ctx.responseStream) {
|
|
76
|
+
const resStream = ServerlessResponseStream.setMeta($ctx.responseStream, {
|
|
77
|
+
...meta,
|
|
78
|
+
headers: {
|
|
79
|
+
"content-type": "application/octet-stream",
|
|
80
|
+
...meta.headers
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await pipeline(body, resStream);
|
|
85
|
+
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
...meta,
|
|
91
|
+
body: body === undefined ? "" : body,
|
|
92
|
+
isBase64Encoded: false
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private processResult({status, headers, data}: AnyPromiseResult, $ctx: ServerlessContext<ServerlessEvent>) {
|
|
97
|
+
if (status) {
|
|
98
|
+
$ctx.response.status(status);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (headers) {
|
|
102
|
+
$ctx.response.setHeaders(headers);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (data !== undefined) {
|
|
106
|
+
data = $ctx.response.getStatus() !== 204 ? data : "";
|
|
107
|
+
|
|
108
|
+
if (isSerializable(data)) {
|
|
109
|
+
data = serialize(data, {
|
|
110
|
+
...$ctx.endpoint.getResponseOptions($ctx.response.getStatus()),
|
|
111
|
+
useAlias: true,
|
|
112
|
+
endpoint: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
$ctx.response.body(data);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
injectable(PlatformServerlessHandler).scope(ProviderScope.SINGLETON).imports([DeserializerPipe, ValidationPipe]);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {PlatformServerlessTest} from "@tsed/platform-serverless-testing";
|
|
2
|
+
|
|
3
|
+
import {createServerlessContext} from "../../test/utils/createServerlessContext.js";
|
|
4
|
+
|
|
5
|
+
describe("ServerlessContext", () => {
|
|
6
|
+
beforeEach(() => PlatformServerlessTest.create());
|
|
7
|
+
afterEach(() => PlatformServerlessTest.reset());
|
|
8
|
+
|
|
9
|
+
describe("constructor", () => {
|
|
10
|
+
it("should push callback", () => {
|
|
11
|
+
const ctx = createServerlessContext({
|
|
12
|
+
endpoint: {} as any
|
|
13
|
+
});
|
|
14
|
+
expect(ctx.event).toEqual({
|
|
15
|
+
body: "",
|
|
16
|
+
headers: {},
|
|
17
|
+
httpMethod: "",
|
|
18
|
+
isBase64Encoded: false,
|
|
19
|
+
multiValueHeaders: {},
|
|
20
|
+
multiValueQueryStringParameters: {},
|
|
21
|
+
path: "/",
|
|
22
|
+
pathParameters: {},
|
|
23
|
+
queryStringParameters: {},
|
|
24
|
+
requestContext: {
|
|
25
|
+
accountId: "",
|
|
26
|
+
apiId: "",
|
|
27
|
+
httpMethod: "",
|
|
28
|
+
identity: {},
|
|
29
|
+
path: "/",
|
|
30
|
+
protocol: "https",
|
|
31
|
+
requestId: "requestId",
|
|
32
|
+
requestTimeEpoch: 0,
|
|
33
|
+
resourceId: 1,
|
|
34
|
+
resourcePath: "/",
|
|
35
|
+
stage: ""
|
|
36
|
+
},
|
|
37
|
+
resource: "",
|
|
38
|
+
stageVariables: {}
|
|
39
|
+
});
|
|
40
|
+
expect(ctx.response.raw).toEqual(ctx.request.raw);
|
|
41
|
+
expect(ctx.request.event).toEqual(ctx.response.event);
|
|
42
|
+
expect(ctx.request.response).toEqual(ctx.response);
|
|
43
|
+
expect(ctx.response.request).toEqual(ctx.request);
|
|
44
|
+
expect(ctx.isHttpEvent()).toBeTruthy();
|
|
45
|
+
expect(ctx.isAuthorizerEvent()).toBeFalsy();
|
|
46
|
+
});
|
|
47
|
+
it("should push callback with event headers", () => {
|
|
48
|
+
const ctx = createServerlessContext({
|
|
49
|
+
endpoint: {} as any,
|
|
50
|
+
event: {
|
|
51
|
+
headers: undefined
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
expect(ctx.event).toEqual({
|
|
55
|
+
body: "",
|
|
56
|
+
headers: {},
|
|
57
|
+
httpMethod: "",
|
|
58
|
+
isBase64Encoded: false,
|
|
59
|
+
multiValueHeaders: {},
|
|
60
|
+
multiValueQueryStringParameters: {},
|
|
61
|
+
path: "/",
|
|
62
|
+
pathParameters: {},
|
|
63
|
+
queryStringParameters: {},
|
|
64
|
+
requestContext: {
|
|
65
|
+
accountId: "",
|
|
66
|
+
apiId: "",
|
|
67
|
+
httpMethod: "",
|
|
68
|
+
identity: {},
|
|
69
|
+
path: "/",
|
|
70
|
+
protocol: "https",
|
|
71
|
+
requestId: "requestId",
|
|
72
|
+
requestTimeEpoch: 0,
|
|
73
|
+
resourceId: 1,
|
|
74
|
+
resourcePath: "/",
|
|
75
|
+
stage: ""
|
|
76
|
+
},
|
|
77
|
+
resource: "",
|
|
78
|
+
stageVariables: {}
|
|
79
|
+
});
|
|
80
|
+
expect(ctx.response.raw).toEqual(ctx.request.raw);
|
|
81
|
+
expect(ctx.request.event).toEqual(ctx.response.event);
|
|
82
|
+
expect(ctx.request.response).toEqual(ctx.response);
|
|
83
|
+
expect(ctx.response.request).toEqual(ctx.request);
|
|
84
|
+
});
|
|
85
|
+
it("should accept auth event (request)", () => {
|
|
86
|
+
const ctx = createServerlessContext({
|
|
87
|
+
event: {
|
|
88
|
+
type: "REQUEST",
|
|
89
|
+
httpMethod: "GET"
|
|
90
|
+
} as never,
|
|
91
|
+
endpoint: {} as any
|
|
92
|
+
});
|
|
93
|
+
expect(ctx.isHttpEvent()).toBeTruthy();
|
|
94
|
+
expect(ctx.isAuthorizerEvent()).toBeTruthy();
|
|
95
|
+
});
|
|
96
|
+
it("should accept auth event (token)", () => {
|
|
97
|
+
const ctx = createServerlessContext({
|
|
98
|
+
event: {
|
|
99
|
+
type: "TOKEN"
|
|
100
|
+
} as never,
|
|
101
|
+
endpoint: {} as any
|
|
102
|
+
});
|
|
103
|
+
expect(ctx.isAuthorizerEvent()).toBeTruthy();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {DIContext, DIContextOptions} from "@tsed/di";
|
|
2
|
+
import {JsonEntityStore} from "@tsed/schema";
|
|
3
|
+
import type {APIGatewayProxyEvent, Context} from "aws-lambda";
|
|
4
|
+
|
|
5
|
+
import {ServerlessRequest} from "./ServerlessRequest.js";
|
|
6
|
+
import {ServerlessResponse} from "./ServerlessResponse.js";
|
|
7
|
+
import type {ServerlessResponseStream} from "./ServerlessResponseStream.js";
|
|
8
|
+
|
|
9
|
+
export interface ServerlessContextOptions<Event = APIGatewayProxyEvent> extends DIContextOptions {
|
|
10
|
+
event: Event;
|
|
11
|
+
context: Context;
|
|
12
|
+
responseStream?: ServerlessResponseStream;
|
|
13
|
+
endpoint: JsonEntityStore;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ServerlessContext<Event extends object = APIGatewayProxyEvent> extends DIContext {
|
|
17
|
+
readonly response: ServerlessResponse<Event>;
|
|
18
|
+
readonly request: ServerlessRequest<Event>;
|
|
19
|
+
readonly context: Context;
|
|
20
|
+
readonly event: Event;
|
|
21
|
+
readonly responseStream: ServerlessResponseStream | undefined;
|
|
22
|
+
readonly endpoint: JsonEntityStore;
|
|
23
|
+
readonly PLATFORM = "SERVERLESS";
|
|
24
|
+
|
|
25
|
+
constructor({event, context, endpoint, responseStream, ...options}: ServerlessContextOptions<Event>) {
|
|
26
|
+
super({
|
|
27
|
+
...options,
|
|
28
|
+
maxStackSize: 0
|
|
29
|
+
});
|
|
30
|
+
this.context = context;
|
|
31
|
+
this.responseStream = responseStream;
|
|
32
|
+
this.event = {
|
|
33
|
+
...event,
|
|
34
|
+
headers:
|
|
35
|
+
"headers" in event && event.headers
|
|
36
|
+
? Object.fromEntries(Object.entries(event.headers).map(([key, value]) => [key.toLowerCase(), value]))
|
|
37
|
+
: {}
|
|
38
|
+
};
|
|
39
|
+
this.request = new ServerlessRequest<Event>(this);
|
|
40
|
+
this.response = new ServerlessResponse<Event>(this);
|
|
41
|
+
this.endpoint = endpoint;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isHttpEvent() {
|
|
45
|
+
return "httpMethod" in this.event;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
isAuthorizerEvent() {
|
|
49
|
+
return "type" in this.event && ["TOKEN", "REQUEST"].includes(this.event.type as string);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async destroy() {
|
|
53
|
+
await super.destroy();
|
|
54
|
+
this.response.destroy();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isDone() {
|
|
58
|
+
return this.response.isDone();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
APIGatewayProxyEvent,
|
|
3
|
+
APIGatewayProxyEventV2,
|
|
4
|
+
APIGatewayTokenAuthorizerEvent,
|
|
5
|
+
CloudWatchLogsEvent,
|
|
6
|
+
DynamoDBStreamEvent,
|
|
7
|
+
KinesisStreamEvent,
|
|
8
|
+
S3Event,
|
|
9
|
+
ScheduledEvent,
|
|
10
|
+
SNSEvent,
|
|
11
|
+
SQSEvent
|
|
12
|
+
} from "aws-lambda";
|
|
13
|
+
|
|
14
|
+
export type ServerlessEvent =
|
|
15
|
+
| APIGatewayProxyEvent
|
|
16
|
+
| APIGatewayProxyEventV2
|
|
17
|
+
| S3Event
|
|
18
|
+
| SQSEvent
|
|
19
|
+
| SNSEvent
|
|
20
|
+
| DynamoDBStreamEvent
|
|
21
|
+
| KinesisStreamEvent
|
|
22
|
+
| CloudWatchLogsEvent
|
|
23
|
+
| ScheduledEvent
|
|
24
|
+
| APIGatewayTokenAuthorizerEvent;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {PlatformServerlessTest} from "@tsed/platform-serverless-testing";
|
|
2
|
+
|
|
3
|
+
import {createServerlessContext} from "../../test/utils/createServerlessContext.js";
|
|
4
|
+
|
|
5
|
+
describe("ServerlessRequest", () => {
|
|
6
|
+
beforeEach(() => PlatformServerlessTest.create());
|
|
7
|
+
afterEach(() => PlatformServerlessTest.reset());
|
|
8
|
+
describe("secure()", () => {
|
|
9
|
+
it("should return expected value", () => {
|
|
10
|
+
const context = createServerlessContext({
|
|
11
|
+
endpoint: {} as any
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(context.request.secure).toEqual(true);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("host()", () => {
|
|
19
|
+
it("should return expected value", () => {
|
|
20
|
+
const context = createServerlessContext({
|
|
21
|
+
endpoint: {} as any
|
|
22
|
+
});
|
|
23
|
+
context.event.headers["host"] = "host";
|
|
24
|
+
|
|
25
|
+
expect(context.request.host).toEqual("host");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("protocol()", () => {
|
|
29
|
+
it("should return expected value", () => {
|
|
30
|
+
const context = createServerlessContext({
|
|
31
|
+
endpoint: {} as any
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(context.request.protocol).toEqual("https");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("url()", () => {
|
|
38
|
+
it("should return expected value", () => {
|
|
39
|
+
const context = createServerlessContext({
|
|
40
|
+
endpoint: {} as any
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(context.request.url).toEqual("/");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("headers()", () => {
|
|
47
|
+
it("should return expected value", () => {
|
|
48
|
+
const context = createServerlessContext({
|
|
49
|
+
endpoint: {} as any
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(context.request.headers).toEqual({});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("method()", () => {
|
|
56
|
+
it("should return expected value", () => {
|
|
57
|
+
const context = createServerlessContext({
|
|
58
|
+
endpoint: {} as any
|
|
59
|
+
});
|
|
60
|
+
context.request.raw.httpMethod = "GET";
|
|
61
|
+
expect(context.request.method).toEqual("GET");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("body()", () => {
|
|
66
|
+
it("should return expected value", () => {
|
|
67
|
+
const context = createServerlessContext({
|
|
68
|
+
endpoint: {} as any
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(context.request.body).toEqual({});
|
|
72
|
+
});
|
|
73
|
+
it("should return catch error on body", () => {
|
|
74
|
+
const context = createServerlessContext({
|
|
75
|
+
endpoint: {} as any
|
|
76
|
+
});
|
|
77
|
+
context.request.raw.body = "{";
|
|
78
|
+
|
|
79
|
+
expect(context.request.body).toEqual("{");
|
|
80
|
+
});
|
|
81
|
+
it("should return parse body", () => {
|
|
82
|
+
const context = createServerlessContext({
|
|
83
|
+
endpoint: {} as any
|
|
84
|
+
});
|
|
85
|
+
context.request.raw.body = "{}";
|
|
86
|
+
|
|
87
|
+
expect(context.request.body).toEqual({});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("rawBody()", () => {
|
|
92
|
+
it("should return expected value", () => {
|
|
93
|
+
const context = createServerlessContext({
|
|
94
|
+
endpoint: {} as any
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(context.request.rawBody).toEqual("");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("params()", () => {
|
|
102
|
+
it("should return expected value", () => {
|
|
103
|
+
const context = createServerlessContext({
|
|
104
|
+
endpoint: {} as any
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(context.request.params).toEqual({});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should return expected value (undefined params)", () => {
|
|
111
|
+
const context = createServerlessContext({
|
|
112
|
+
endpoint: {} as any
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
delete context.event.pathParameters;
|
|
116
|
+
|
|
117
|
+
expect(context.request.params).toEqual({});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("query()", () => {
|
|
122
|
+
it("should return expected value", () => {
|
|
123
|
+
const context = createServerlessContext({
|
|
124
|
+
endpoint: {} as any
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(context.request.query).toEqual({});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {getValue} from "@tsed/core";
|
|
2
|
+
import type {APIGatewayProxyEvent} from "aws-lambda";
|
|
3
|
+
|
|
4
|
+
import {ServerlessContext} from "./ServerlessContext.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @platform
|
|
8
|
+
*/
|
|
9
|
+
export class ServerlessRequest<Event extends object = APIGatewayProxyEvent> {
|
|
10
|
+
constructor(protected $ctx: ServerlessContext<Event>) {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* APIGatewayProxyEvent to no break the current code
|
|
14
|
+
* @note use getEvent() instead to use generic type
|
|
15
|
+
*/
|
|
16
|
+
get event(): APIGatewayProxyEvent {
|
|
17
|
+
return this.getEvent();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get raw(): Event {
|
|
21
|
+
return this.event as unknown as Event;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get response() {
|
|
25
|
+
return this.$ctx.response;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get secure(): boolean {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get host(): string {
|
|
33
|
+
return this.get("host");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get protocol(): string {
|
|
37
|
+
return "https";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the url of the request.
|
|
42
|
+
*
|
|
43
|
+
* Is equivalent of `express.response.originalUrl || express.response.url`.
|
|
44
|
+
*/
|
|
45
|
+
get url(): string {
|
|
46
|
+
return this.event.path;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get headers() {
|
|
50
|
+
return this.event.headers;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get method(): string {
|
|
54
|
+
return this.event.httpMethod;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Contains key-value pairs of data submitted in the request body. By default, it is `undefined`, and is populated when you use
|
|
59
|
+
* `body-parsing` middleware such as `express.json()` or `express.urlencoded()`.
|
|
60
|
+
*/
|
|
61
|
+
get body(): any {
|
|
62
|
+
try {
|
|
63
|
+
return this.event.body ? JSON.parse(this.event.body!) : {};
|
|
64
|
+
} catch (er) {
|
|
65
|
+
return this.event.body;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get rawBody(): any {
|
|
70
|
+
return this.event.body;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This property is an object containing properties mapped to the named route `parameters`.
|
|
75
|
+
* For example, if you have the route `/user/:name`, then the `name` property is available as `req.params.name`.
|
|
76
|
+
* This object defaults to `{}`.
|
|
77
|
+
*/
|
|
78
|
+
get params(): {[key: string]: any} {
|
|
79
|
+
return this.event.pathParameters || {};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* This property is an object containing a property for each query string parameter in the route.
|
|
84
|
+
* When query parser is set to disabled, it is an empty object `{}`, otherwise it is the result of the configured query parser.
|
|
85
|
+
*/
|
|
86
|
+
get query(): {[key: string]: any} {
|
|
87
|
+
return this.event.queryStringParameters || {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getEvent<E = Event>(): E {
|
|
91
|
+
return this.$ctx.event as unknown as E;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the HTTP request header specified by field. The match is case-insensitive.
|
|
96
|
+
*
|
|
97
|
+
* ```typescript
|
|
98
|
+
* request.get('Content-Type') // => "text/plain"
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @param name
|
|
102
|
+
*/
|
|
103
|
+
get(name: string) {
|
|
104
|
+
return getValue(this.event.headers, name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {PlatformServerlessTest} from "@tsed/platform-serverless-testing";
|
|
2
|
+
|
|
3
|
+
import {createServerlessContext} from "../../test/utils/createServerlessContext.js";
|
|
4
|
+
|
|
5
|
+
describe("ServerlessResponse", () => {
|
|
6
|
+
beforeEach(() => PlatformServerlessTest.create());
|
|
7
|
+
afterEach(() => PlatformServerlessTest.reset());
|
|
8
|
+
|
|
9
|
+
describe("isHeadersSent", () => {
|
|
10
|
+
it("should push callback", () => {
|
|
11
|
+
const context = createServerlessContext({
|
|
12
|
+
endpoint: {} as any
|
|
13
|
+
});
|
|
14
|
+
expect(context.response.isHeadersSent).toEqual(false);
|
|
15
|
+
|
|
16
|
+
context.response.destroy();
|
|
17
|
+
|
|
18
|
+
expect(context.response.isHeadersSent).toEqual(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("statusCode", () => {
|
|
22
|
+
it("should return the statusCode", () => {
|
|
23
|
+
const context = createServerlessContext({
|
|
24
|
+
endpoint: {} as any
|
|
25
|
+
});
|
|
26
|
+
context.response.statusCode = 201;
|
|
27
|
+
expect(context.response.statusCode).toEqual(201);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("locals()", () => {
|
|
31
|
+
it("should return the locals", () => {
|
|
32
|
+
const context = createServerlessContext({
|
|
33
|
+
endpoint: {} as any
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(context.response.locals).toEqual({});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("contentLength()", () => {
|
|
40
|
+
it("should return the content length", () => {
|
|
41
|
+
const context = createServerlessContext({
|
|
42
|
+
endpoint: {} as any
|
|
43
|
+
});
|
|
44
|
+
context.response.contentLength(300);
|
|
45
|
+
expect(context.response.getContentLength()).toEqual(300);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should return the default content length", () => {
|
|
49
|
+
const context = createServerlessContext({
|
|
50
|
+
endpoint: {} as any
|
|
51
|
+
});
|
|
52
|
+
context.response.contentLength(" " as any);
|
|
53
|
+
expect(context.response.getContentLength()).toEqual(0);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("location()", () => {
|
|
57
|
+
it("should set location", () => {
|
|
58
|
+
const context = createServerlessContext({
|
|
59
|
+
endpoint: {} as any
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
context.response.location("/path/to");
|
|
63
|
+
|
|
64
|
+
expect(context.response.getHeaders()).toEqual({
|
|
65
|
+
location: "/path/to"
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it("should set location (back)", () => {
|
|
69
|
+
const context = createServerlessContext({
|
|
70
|
+
endpoint: {} as any
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
context.request.headers["Referrer"] = "https://referrer.com";
|
|
74
|
+
context.response.location("back");
|
|
75
|
+
|
|
76
|
+
expect(context.response.getHeaders()).toEqual({
|
|
77
|
+
location: "https://referrer.com"
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("redirect()", () => {
|
|
82
|
+
it("should set location", () => {
|
|
83
|
+
const context = createServerlessContext({
|
|
84
|
+
endpoint: {} as any
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
context.response.redirect(301, "/path/to");
|
|
88
|
+
|
|
89
|
+
expect(context.response.getHeaders()).toEqual({
|
|
90
|
+
"content-length": 42,
|
|
91
|
+
location: "/path/to"
|
|
92
|
+
});
|
|
93
|
+
expect(context.response.statusCode).toEqual(301);
|
|
94
|
+
expect(context.response.getBody()).toEqual("Moved Permanently. Redirecting to /path/to");
|
|
95
|
+
});
|
|
96
|
+
it("should set location (back)", () => {
|
|
97
|
+
const context = createServerlessContext({
|
|
98
|
+
endpoint: {} as any
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
context.request.headers["Referrer"] = "https://referrer.com";
|
|
102
|
+
context.response.redirect(301, "back");
|
|
103
|
+
|
|
104
|
+
expect(context.response.getHeaders()).toEqual({
|
|
105
|
+
"content-length": 54,
|
|
106
|
+
location: "https://referrer.com"
|
|
107
|
+
});
|
|
108
|
+
expect(context.response.statusCode).toEqual(301);
|
|
109
|
+
expect(context.response.getStatus()).toEqual(301);
|
|
110
|
+
expect(context.response.getContentLength()).toEqual(54);
|
|
111
|
+
expect(context.response.getContentType()).toEqual("");
|
|
112
|
+
expect(context.response.getBody()).toEqual("Moved Permanently. Redirecting to https://referrer.com");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe("setHeader()", () => {
|
|
116
|
+
it("should set location", () => {
|
|
117
|
+
const context = createServerlessContext({
|
|
118
|
+
endpoint: {} as any
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
context.event.headers["Referrer"] = "https://referrer.com";
|
|
122
|
+
context.response.set("location", "back");
|
|
123
|
+
expect(context.response.getHeaders()).toEqual({
|
|
124
|
+
location: "https://referrer.com"
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
it("should set location back with default value", () => {
|
|
128
|
+
const context = createServerlessContext({
|
|
129
|
+
endpoint: {} as any
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
context.event.headers["Referrer"] = "";
|
|
133
|
+
context.response.set("location", "back");
|
|
134
|
+
expect(context.response.getHeaders()).toEqual({
|
|
135
|
+
location: "/"
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|