@tsed/platform-koa 8.0.1 → 8.0.3

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 CHANGED
@@ -2,13 +2,14 @@
2
2
  "name": "@tsed/platform-koa",
3
3
  "description": "Koa package for Ts.ED framework",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.3",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
9
9
  "typings": "./lib/types/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
+ "@tsed/source": "./src/index.ts",
12
13
  "types": "./lib/types/index.d.ts",
13
14
  "import": "./lib/esm/index.js",
14
15
  "default": "./lib/esm/index.js"
@@ -53,12 +54,12 @@
53
54
  "repository": "https://github.com/tsedio/tsed",
54
55
  "devDependencies": {
55
56
  "@koa/cors": "5.0.0",
56
- "@tsed/barrels": "8.0.1",
57
- "@tsed/core": "8.0.1",
58
- "@tsed/di": "8.0.1",
59
- "@tsed/platform-http": "8.0.1",
60
- "@tsed/platform-test-sdk": "8.0.1",
61
- "@tsed/typescript": "8.0.1",
57
+ "@tsed/barrels": "8.0.3",
58
+ "@tsed/core": "8.0.3",
59
+ "@tsed/di": "8.0.3",
60
+ "@tsed/platform-http": "8.0.3",
61
+ "@tsed/platform-test-sdk": "8.0.3",
62
+ "@tsed/typescript": "8.0.3",
62
63
  "@types/koa": "2.15.0",
63
64
  "@types/koa-bodyparser": "4.3.12",
64
65
  "@types/koa-compose": "3.2.8",
@@ -86,13 +87,13 @@
86
87
  "tslib": "2.7.0"
87
88
  },
88
89
  "peerDependencies": {
89
- "@tsed/core": "8.0.1",
90
- "@tsed/di": "8.0.1",
91
- "@tsed/json-mapper": "8.0.1",
90
+ "@tsed/core": "8.0.3",
91
+ "@tsed/di": "8.0.3",
92
+ "@tsed/json-mapper": "8.0.3",
92
93
  "@tsed/logger": ">=6.7.5",
93
- "@tsed/openspec": "8.0.1",
94
- "@tsed/platform-http": "8.0.1",
95
- "@tsed/schema": "8.0.1",
94
+ "@tsed/openspec": "8.0.3",
95
+ "@tsed/platform-http": "8.0.3",
96
+ "@tsed/schema": "8.0.3",
96
97
  "cross-env": "7.0.3",
97
98
  "koa": ">=2.13.0",
98
99
  "koa-bodyparser": ">=4.3.0",
@@ -0,0 +1,64 @@
1
+ import {PlatformKoa} from "./PlatformKoa.js";
2
+
3
+ class Server {}
4
+
5
+ describe("PlatformKoa", () => {
6
+ describe("create()", () => {
7
+ it("should create platform", () => {
8
+ const platform = PlatformKoa.create(Server, {});
9
+
10
+ expect(platform.adapter).toBeInstanceOf(PlatformKoa);
11
+ });
12
+ });
13
+ describe("bootstrap()", () => {
14
+ it("should create platform", async () => {
15
+ const platform = await PlatformKoa.bootstrap(Server, {});
16
+
17
+ expect(platform.adapter).toBeInstanceOf(PlatformKoa);
18
+ });
19
+ });
20
+ describe("bodyParser()", () => {
21
+ it("should return the body parser (json)", () => {
22
+ const stub = vi.fn().mockReturnValue("body");
23
+
24
+ const platform = PlatformKoa.create(Server, {
25
+ koa: {
26
+ bodyParser: stub
27
+ }
28
+ });
29
+
30
+ const result = platform.adapter.bodyParser("json", {strict: true});
31
+
32
+ expect(result).toEqual("body");
33
+ expect(stub).toHaveBeenCalledWith({strict: true});
34
+ });
35
+ it("should return the body parser (raw)", () => {
36
+ const stub = vi.fn().mockReturnValue("body");
37
+
38
+ const platform = PlatformKoa.create(Server, {
39
+ koa: {
40
+ bodyParser: stub
41
+ }
42
+ });
43
+
44
+ const result = platform.adapter.bodyParser("raw", {strict: true});
45
+
46
+ expect(result).toEqual("body");
47
+ expect(stub).toHaveBeenCalledWith({strict: true});
48
+ });
49
+ it("should return the body parser (urlencoded)", () => {
50
+ const stub = vi.fn().mockReturnValue("body");
51
+
52
+ const platform = PlatformKoa.create(Server, {
53
+ koa: {
54
+ bodyParser: stub
55
+ }
56
+ });
57
+
58
+ const result = platform.adapter.bodyParser("urlencoded", {strict: true});
59
+
60
+ expect(result).toEqual("body");
61
+ expect(stub).toHaveBeenCalledWith({strict: true});
62
+ });
63
+ });
64
+ });
@@ -0,0 +1,209 @@
1
+ import KoaRouter from "@koa/router";
2
+ import {catchAsyncError, isFunction, Type} from "@tsed/core";
3
+ import {constant, inject, runInContext} from "@tsed/di";
4
+ import {PlatformExceptions} from "@tsed/platform-exceptions";
5
+ import {
6
+ adapter,
7
+ application,
8
+ createContext,
9
+ Platform,
10
+ PlatformAdapter,
11
+ PlatformApplication,
12
+ PlatformBuilder,
13
+ PlatformHandler,
14
+ PlatformMulter,
15
+ PlatformMulterSettings,
16
+ PlatformRequest,
17
+ PlatformResponse,
18
+ PlatformStaticsOptions
19
+ } from "@tsed/platform-http";
20
+ import {PlatformHandlerMetadata, PlatformHandlerType, PlatformLayer} from "@tsed/platform-router";
21
+ import Koa, {Context, Next} from "koa";
22
+ import koaBodyParser, {Options} from "koa-bodyparser";
23
+ // @ts-ignore
24
+ import koaQs from "koa-qs";
25
+ import send from "koa-send";
26
+
27
+ import {staticsMiddleware} from "../middlewares/staticsMiddleware.js";
28
+ import {PlatformKoaHandler} from "../services/PlatformKoaHandler.js";
29
+ import {PlatformKoaRequest} from "../services/PlatformKoaRequest.js";
30
+ import {PlatformKoaResponse} from "../services/PlatformKoaResponse.js";
31
+ import {getMulter} from "../utils/multer.js";
32
+
33
+ declare global {
34
+ namespace TsED {
35
+ export interface Application extends Koa {}
36
+ }
37
+
38
+ namespace TsED {
39
+ export interface StaticsOptions extends send.SendOptions {}
40
+ }
41
+ }
42
+
43
+ // @ts-ignore
44
+ KoaRouter.prototype.$$match = KoaRouter.prototype.match;
45
+ KoaRouter.prototype.match = function match(...args: any[]) {
46
+ const matched = this.$$match(...args);
47
+ if (matched) {
48
+ if (matched.path.length) {
49
+ matched.route = true;
50
+ }
51
+ }
52
+
53
+ return matched;
54
+ };
55
+
56
+ /**
57
+ * @platform
58
+ * @koa
59
+ */
60
+ export class PlatformKoa extends PlatformAdapter<Koa> {
61
+ readonly NAME = "koa";
62
+ readonly providers = [
63
+ {
64
+ token: PlatformResponse,
65
+ useClass: PlatformKoaResponse
66
+ },
67
+ {
68
+ token: PlatformRequest,
69
+ useClass: PlatformKoaRequest
70
+ },
71
+ {
72
+ token: PlatformHandler,
73
+ useClass: PlatformKoaHandler
74
+ },
75
+ {token: PlatformApplication},
76
+ {token: Platform}
77
+ ];
78
+
79
+ /**
80
+ * Create new serverless application. In this mode, the component scan are disabled.
81
+ * @param module
82
+ * @param settings
83
+ */
84
+ static create(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
85
+ return PlatformBuilder.create<Koa>(module, {
86
+ ...settings,
87
+ adapter: PlatformKoa
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Bootstrap a server application
93
+ * @param module
94
+ * @param settings
95
+ */
96
+ static bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
97
+ return PlatformBuilder.bootstrap<Koa>(module, {
98
+ ...settings,
99
+ adapter: PlatformKoa
100
+ });
101
+ }
102
+
103
+ onInit() {
104
+ this.app.getApp().silent = true;
105
+ }
106
+
107
+ mapLayers(layers: PlatformLayer[]) {
108
+ const options = constant("koa.router", {});
109
+ const rawRouter = new KoaRouter(options) as any;
110
+
111
+ layers.forEach((layer) => {
112
+ switch (layer.method) {
113
+ case "statics":
114
+ rawRouter.use(layer.path, this.statics(layer.path as string, layer.opts as any));
115
+ break;
116
+
117
+ default:
118
+ rawRouter[layer.method](...layer.getArgs());
119
+ }
120
+ });
121
+
122
+ application().getApp().use(rawRouter.routes()).use(rawRouter.allowedMethods());
123
+ }
124
+
125
+ mapHandler(handler: Function, metadata: PlatformHandlerMetadata) {
126
+ return async (koaContext: Koa.Context, next: Koa.Next) => {
127
+ const {$ctx} = koaContext.request;
128
+
129
+ $ctx.next = next;
130
+
131
+ const error = await catchAsyncError(() => handler($ctx));
132
+
133
+ if (error) {
134
+ $ctx.error = error;
135
+ }
136
+
137
+ if (metadata.type !== PlatformHandlerType.RESPONSE_FN) {
138
+ return $ctx.next && $ctx.error ? $ctx.next($ctx.error) : $ctx.next();
139
+ }
140
+ };
141
+ }
142
+
143
+ useContext(): this {
144
+ const invoke = createContext();
145
+ const platformExceptions = inject(PlatformExceptions);
146
+
147
+ application().use((koaContext: Context, next: Next) => {
148
+ const $ctx = invoke({
149
+ request: koaContext.request as any,
150
+ response: koaContext.response as any,
151
+ koaContext
152
+ });
153
+
154
+ return runInContext($ctx, async () => {
155
+ try {
156
+ await $ctx.start();
157
+ await next();
158
+ const status = koaContext.status || 404;
159
+
160
+ if (status === 404 && !$ctx.isDone()) {
161
+ platformExceptions?.resourceNotFound($ctx);
162
+ }
163
+ } catch (error) {
164
+ platformExceptions?.catch(error, $ctx);
165
+ } finally {
166
+ await $ctx.finish();
167
+ }
168
+ });
169
+ });
170
+
171
+ return this;
172
+ }
173
+
174
+ createApp() {
175
+ const app = constant<Koa | undefined>("koa.app") || new Koa();
176
+ koaQs(app, "extended");
177
+
178
+ return {
179
+ app,
180
+ callback() {
181
+ return app.callback();
182
+ }
183
+ };
184
+ }
185
+
186
+ multipart(options: PlatformMulterSettings): PlatformMulter {
187
+ return getMulter(options);
188
+ }
189
+
190
+ statics(endpoint: string, options: PlatformStaticsOptions) {
191
+ return staticsMiddleware(options);
192
+ }
193
+
194
+ bodyParser(_: "json" | "urlencoded" | "raw" | "text", additionalOptions: any = {}): any {
195
+ const opts = constant(`koa.bodyParser`);
196
+ let parser: any = koaBodyParser;
197
+
198
+ let options: Options = {};
199
+
200
+ if (isFunction(opts)) {
201
+ parser = opts;
202
+ options = {};
203
+ }
204
+
205
+ return parser({...options, ...additionalOptions});
206
+ }
207
+ }
208
+
209
+ adapter(PlatformKoa);
@@ -0,0 +1,14 @@
1
+ import {JsonParameterStore} from "@tsed/schema";
2
+
3
+ import {Ctx} from "./ctx.js";
4
+
5
+ describe("@Ctx", () => {
6
+ it("should call store the right configuration", () => {
7
+ class Ctrl {
8
+ test(@Ctx() ctx: Ctx) {}
9
+ }
10
+
11
+ const param = JsonParameterStore.get(Ctrl, "test", 0);
12
+ expect(param.paramType).toEqual("KOA_CTX");
13
+ });
14
+ });
@@ -0,0 +1,32 @@
1
+ import {UseParam} from "@tsed/platform-params";
2
+ import type {Context} from "koa";
3
+
4
+ /**
5
+ * Return the original Koa context.
6
+ * @decorator
7
+ * @operation
8
+ * @input
9
+ * @koa
10
+ */
11
+ export function KoaCtx(): ParameterDecorator {
12
+ return UseParam({
13
+ paramType: "KOA_CTX",
14
+ dataPath: "$ctx.request.ctx",
15
+ useMapper: false,
16
+ useValidation: false
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Return the original Koa context.
22
+ * @decorator
23
+ * @operation
24
+ * @input
25
+ * @koa
26
+ */
27
+ export function Ctx(): ParameterDecorator {
28
+ return KoaCtx();
29
+ }
30
+
31
+ export type Ctx = Context;
32
+ export type KoaCtx = Context;
@@ -0,0 +1,16 @@
1
+ import {ParamTypes} from "@tsed/platform-params";
2
+ import {JsonParameterStore} from "@tsed/schema";
3
+
4
+ import {State} from "./state.js";
5
+
6
+ describe("@State", () => {
7
+ it("should call ParamFilter.useParam method with the correct parameters", () => {
8
+ class Ctrl {
9
+ test(@State("expression") test: any) {}
10
+ }
11
+
12
+ const param = JsonParameterStore.get(Ctrl, "test", 0);
13
+ expect(param.expression).toEqual("expression");
14
+ expect(param.paramType).toEqual(ParamTypes.LOCALS);
15
+ });
16
+ });
@@ -0,0 +1,43 @@
1
+ import {Locals} from "@tsed/platform-params";
2
+
3
+ /**
4
+ * State return the value from koa.ctx.state object.
5
+ *
6
+ * #### Example
7
+ *
8
+ * ```typescript
9
+ * * @Middleware()
10
+ * class StateMiddleware {
11
+ * use(@State() state: any) {
12
+ * // set some on locals
13
+ * state.user = "user"
14
+ * }
15
+ * }
16
+ *
17
+ * @Controller('/')
18
+ * @UseBefore(StateMiddleware)
19
+ * class MyCtrl {
20
+ * @Get('/')
21
+ * @View('home.ejs') // will use locals and returned data to render the page
22
+ * get(@State('user') user: any) {
23
+ * console.log('user', user);
24
+ *
25
+ * return {
26
+ * description: 'Hello world'
27
+ * }
28
+ * }
29
+ * }
30
+ * ```
31
+ *
32
+ * @param expression The path of the property to get.
33
+ * @decorator
34
+ * @operation
35
+ * @input
36
+ * @response
37
+ * @koa
38
+ */
39
+ export function State(expression: string): ParameterDecorator;
40
+ export function State(): ParameterDecorator;
41
+ export function State(...args: any[]): ParameterDecorator {
42
+ return (Locals as any)(...args);
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./components/PlatformKoa.js";
5
+ export * from "./decorators/ctx.js";
6
+ export * from "./decorators/state.js";
7
+ export * from "./interfaces/interfaces.js";
8
+ export * from "./interfaces/PlatformKoaSettings.js";
9
+ export * from "./middlewares/staticsMiddleware.js";
10
+ export * from "./services/PlatformKoaHandler.js";
11
+ export * from "./services/PlatformKoaRequest.js";
12
+ export * from "./services/PlatformKoaResponse.js";
13
+ export * from "./utils/multer.js";
@@ -0,0 +1,15 @@
1
+ import type {RouterOptions} from "@koa/router";
2
+ import type {Middleware} from "koa";
3
+ import type {Options} from "koa-bodyparser";
4
+
5
+ export interface PlatformKoaSettings {
6
+ /**
7
+ * Koa router options
8
+ */
9
+ router?: RouterOptions;
10
+ /**
11
+ * Body parser options
12
+ * @param opts
13
+ */
14
+ bodyParser?: ((opts?: Options) => Middleware) | Options;
15
+ }
@@ -0,0 +1,18 @@
1
+ import Koa from "koa";
2
+
3
+ import {PlatformKoaSettings} from "./PlatformKoaSettings.js";
4
+
5
+ export * from "./PlatformKoaSettings.js";
6
+
7
+ declare global {
8
+ namespace TsED {
9
+ export interface Configuration {
10
+ /**
11
+ * Configuration related to Koa platform application.
12
+ */
13
+ koa: PlatformKoaSettings;
14
+ }
15
+
16
+ export interface NextFunction extends Koa.Next {}
17
+ }
18
+ }
@@ -0,0 +1,34 @@
1
+ import {PlatformStaticsOptions} from "@tsed/platform-http";
2
+ import type {Context} from "koa";
3
+ import send from "koa-send";
4
+
5
+ /**
6
+ * @ignore
7
+ * @param options
8
+ */
9
+ export function staticsMiddleware(options: PlatformStaticsOptions): any {
10
+ if (options.index !== false) options.index = options.index || "index.html";
11
+
12
+ return async (ctx: Context, next: any) => {
13
+ if (["GET", "HEAD"].includes(ctx.method) && !ctx.body) {
14
+ let path = ctx.path;
15
+
16
+ if (ctx._matchedRoute && ctx._matchedRoute !== "/") {
17
+ path = path.replace(String(ctx._matchedRoute).replace("/(.*)", ""), "");
18
+ }
19
+ try {
20
+ const done = await send(ctx as any, path, options as send.SendOptions);
21
+
22
+ if (done) {
23
+ return;
24
+ }
25
+ } catch (err) {
26
+ if (err.status !== 404) {
27
+ throw err;
28
+ }
29
+ }
30
+ }
31
+
32
+ return next();
33
+ };
34
+ }
@@ -0,0 +1,22 @@
1
+ import "./PlatformKoaRequest.js";
2
+
3
+ import {PlatformContext, PlatformHandler} from "@tsed/platform-http";
4
+ import {PlatformParamsCallback} from "@tsed/platform-params";
5
+
6
+ export class PlatformKoaHandler extends PlatformHandler {
7
+ onRequest(handler: PlatformParamsCallback, $ctx: PlatformContext) {
8
+ if (!(($ctx.error instanceof Error && !$ctx.handlerMetadata.hasErrorParam) || ($ctx.handlerMetadata.hasErrorParam && !$ctx.error))) {
9
+ return super.onRequest(handler, $ctx);
10
+ }
11
+
12
+ return Promise.resolve();
13
+ }
14
+
15
+ public flush($ctx: PlatformContext) {
16
+ if (!$ctx.error && $ctx.data === undefined && $ctx.getResponse().body) {
17
+ $ctx.data = $ctx.getResponse().body;
18
+ }
19
+
20
+ return super.flush($ctx);
21
+ }
22
+ }
@@ -0,0 +1,112 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+
3
+ import {PlatformKoaRequest} from "./PlatformKoaRequest.js";
4
+
5
+ function createRequest() {
6
+ const req = PlatformTest.createRequest();
7
+ const koaRequest = {
8
+ protocol: "http",
9
+ req,
10
+ get ctx() {
11
+ return koaCtx;
12
+ }
13
+ };
14
+
15
+ const koaCtx: any = {
16
+ request: koaRequest,
17
+ cookie: {
18
+ test: "test"
19
+ },
20
+ cookies: {
21
+ test: "test"
22
+ },
23
+ session: {
24
+ test: "test"
25
+ }
26
+ };
27
+
28
+ const ctx = PlatformTest.createRequestContext({
29
+ event: {
30
+ request: koaRequest,
31
+ koaContext: koaCtx
32
+ },
33
+ RequestKlass: PlatformKoaRequest
34
+ });
35
+
36
+ return {req, request: ctx.request, koaCtx, koaRequest};
37
+ }
38
+
39
+ describe("PlatformKoaRequest", () => {
40
+ beforeEach(() => PlatformTest.create());
41
+ afterEach(() => PlatformTest.reset());
42
+ it("should create a PlatformRequest instance", () => {
43
+ const {koaRequest, request} = createRequest();
44
+
45
+ expect(request.raw).toEqual(koaRequest);
46
+ });
47
+
48
+ describe("secure", () => {
49
+ it("should get cookies from cookie", () => {
50
+ const {koaCtx, request} = createRequest();
51
+ koaCtx.request.secure = true;
52
+
53
+ expect(request.secure).toEqual(true);
54
+ });
55
+ });
56
+
57
+ describe("protocol()", () => {
58
+ it("should return the protocol request state (http)", () => {
59
+ const {koaCtx, request} = createRequest();
60
+
61
+ koaCtx.request.req.protocol = "http";
62
+
63
+ expect(request.protocol).toEqual("http");
64
+ });
65
+ it("should return the protocol request state (https)", () => {
66
+ const {koaCtx, request} = createRequest();
67
+
68
+ koaCtx.request.protocol = "https";
69
+
70
+ expect(request.protocol).toEqual("https");
71
+ });
72
+ });
73
+
74
+ describe("host()", () => {
75
+ it("should return the host", () => {
76
+ const {koaCtx, request} = createRequest();
77
+
78
+ koaCtx.request.host = "host";
79
+
80
+ expect(request.host).toEqual("host");
81
+ });
82
+ });
83
+
84
+ describe("cookies", () => {
85
+ it("should get cookies from cookies", () => {
86
+ const {koaCtx, request} = createRequest();
87
+
88
+ koaCtx.cookie = null;
89
+
90
+ expect(request.cookies).toEqual({test: "test"});
91
+ });
92
+ it("should get cookies from cookie", () => {
93
+ const {request} = createRequest();
94
+
95
+ expect(request.cookies).toEqual({test: "test"});
96
+ });
97
+ });
98
+ describe("session", () => {
99
+ it("should get session", () => {
100
+ const {request} = createRequest();
101
+
102
+ expect(request.session).toEqual({test: "test"});
103
+ });
104
+ });
105
+ describe("getReq()", () => {
106
+ it("should return nodejs request", () => {
107
+ const {req, request} = createRequest();
108
+
109
+ expect(request.getReq()).toEqual(req);
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,51 @@
1
+ import "@koa/router";
2
+
3
+ import {PlatformContext, PlatformRequest} from "@tsed/platform-http";
4
+ import Koa from "koa";
5
+
6
+ declare module "koa" {
7
+ export interface Request {
8
+ id: string;
9
+ $ctx: PlatformContext;
10
+ }
11
+ }
12
+
13
+ declare global {
14
+ namespace TsED {
15
+ export interface Request extends Koa.Request {}
16
+ }
17
+ }
18
+
19
+ /**
20
+ * @platform
21
+ * @koa
22
+ */
23
+ export class PlatformKoaRequest extends PlatformRequest<Koa.Request> {
24
+ get ctx(): Koa.Context {
25
+ return this.$ctx.event.koaContext as Koa.Context;
26
+ }
27
+
28
+ get protocol(): string {
29
+ return this.ctx.request.protocol;
30
+ }
31
+
32
+ get host(): string {
33
+ return this.ctx.request.host;
34
+ }
35
+
36
+ get secure(): boolean {
37
+ return this.ctx.request.secure;
38
+ }
39
+
40
+ get cookies(): {[p: string]: any} {
41
+ return this.ctx.cookie || this.ctx.cookies;
42
+ }
43
+
44
+ get session(): any {
45
+ return this.ctx.session;
46
+ }
47
+
48
+ getReq() {
49
+ return this.ctx.request.req;
50
+ }
51
+ }
@@ -0,0 +1,224 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+
3
+ import {PlatformKoaRequest} from "./PlatformKoaRequest.js";
4
+ import {PlatformKoaResponse} from "./PlatformKoaResponse.js";
5
+
6
+ function createResponse() {
7
+ const res = PlatformTest.createResponse();
8
+ const req = PlatformTest.createRequest();
9
+
10
+ const koaContext: any = {
11
+ response: {}
12
+ };
13
+
14
+ const koaResponse: any = Object.assign(res, {
15
+ res,
16
+ get ctx() {
17
+ return koaContext;
18
+ }
19
+ });
20
+
21
+ const koaRequest: any = {
22
+ ...req,
23
+ req,
24
+ get ctx() {
25
+ return koaContext;
26
+ }
27
+ };
28
+
29
+ koaContext.response = koaResponse;
30
+
31
+ const ctx = PlatformTest.createRequestContext({
32
+ event: {
33
+ response: koaResponse,
34
+ request: koaRequest
35
+ },
36
+ ResponseKlass: PlatformKoaResponse,
37
+ RequestKlass: PlatformKoaRequest
38
+ });
39
+
40
+ return {res, response: ctx.response as PlatformKoaResponse, ctx, koaResponse, koaRequest, koaContext};
41
+ }
42
+
43
+ describe("PlatformKoaResponse", () => {
44
+ beforeEach(() => PlatformTest.create());
45
+ afterEach(() => PlatformTest.reset());
46
+ it("should create a PlatformResponse instance", () => {
47
+ const {koaResponse, response} = createResponse();
48
+
49
+ expect(response.raw).toEqual(koaResponse);
50
+ });
51
+
52
+ describe("getRes()", () => {
53
+ it("return res", async () => {
54
+ const {res, response} = createResponse();
55
+
56
+ const result = await response.getRes();
57
+
58
+ expect(result).toEqual(res);
59
+ });
60
+ });
61
+ describe("statusCode", () => {
62
+ it("return statusCode", () => {
63
+ const {response} = createResponse();
64
+
65
+ response.status(302);
66
+
67
+ expect(response.statusCode).toEqual(302);
68
+ });
69
+ });
70
+ describe("hasStatus", () => {
71
+ it("return hasStatus", () => {
72
+ const {response} = createResponse();
73
+
74
+ response.status(404);
75
+ expect(response.statusCode).toEqual(404);
76
+ expect(response.hasStatus()).toEqual(false);
77
+
78
+ response.status(201);
79
+ expect(response.hasStatus()).toEqual(true);
80
+ });
81
+ });
82
+ describe("contentType()", () => {
83
+ it("should set contentType", () => {
84
+ const {ctx, koaResponse} = createResponse();
85
+
86
+ ctx.response.contentType("text/html");
87
+
88
+ expect(koaResponse.type).toEqual("text/html");
89
+ });
90
+ });
91
+ describe("body()", () => {
92
+ it("should set body", () => {
93
+ const {response, koaResponse} = createResponse();
94
+
95
+ response.body("body");
96
+
97
+ expect(koaResponse.body).toEqual("body");
98
+ expect(response.getBody()).toEqual("body");
99
+ });
100
+ });
101
+ describe("location", () => {
102
+ it("should set header location", () => {
103
+ const {res, response} = createResponse();
104
+
105
+ response.location("https://location");
106
+
107
+ expect(res.headers).toEqual({
108
+ location: "https://location",
109
+ "x-request-id": "id"
110
+ });
111
+ });
112
+
113
+ it("should go back based on Referrer", async () => {
114
+ const {res, response} = createResponse();
115
+
116
+ response.request.raw.headers["referrer"] = "https://location/back";
117
+
118
+ await response.location("back");
119
+
120
+ expect(res.headers).toEqual({
121
+ location: "https://location/back",
122
+ "x-request-id": "id"
123
+ });
124
+ });
125
+
126
+ it("should go back based on default path", async () => {
127
+ const {res, response} = createResponse();
128
+
129
+ await response.location("back");
130
+
131
+ expect(res.headers).toEqual({
132
+ location: "/",
133
+ "x-request-id": "id"
134
+ });
135
+ });
136
+ });
137
+ describe("redirect", () => {
138
+ it("should set header location (HEAD)", async () => {
139
+ const {res, response, koaRequest, koaResponse} = createResponse();
140
+
141
+ res.headers["location"] = "https://location";
142
+ koaRequest.method = "HEAD";
143
+
144
+ await response.redirect(301, "https://location");
145
+
146
+ // expect(koaResponse.body).toEqual("Moved Permanently. Redirecting to https://location");
147
+ expect(response.statusCode).toEqual(301);
148
+ expect(res.headers).toEqual({
149
+ location: "https://location",
150
+ "x-request-id": "id"
151
+ });
152
+ expect(res.data).toEqual(undefined);
153
+ });
154
+ it("should set header location (POST)", async () => {
155
+ const {res, response, koaRequest, koaResponse, ctx} = createResponse();
156
+
157
+ res.headers["location"] = "https://location";
158
+ koaRequest.method = "POST";
159
+
160
+ await response.redirect(301, "https://location");
161
+
162
+ // expect(koaResponse.body).toEqual("Moved Permanently. Redirecting to https://location");
163
+ expect(response.statusCode).toEqual(301);
164
+ expect(res.headers).toEqual({
165
+ "content-length": 50,
166
+ location: "https://location",
167
+ "x-request-id": "id"
168
+ });
169
+ });
170
+ });
171
+ describe("getHeaders()", () => {
172
+ it("should get headers", () => {
173
+ const {ctx} = createResponse();
174
+
175
+ ctx.response.setHeader("contentType", "application/json");
176
+
177
+ const result = ctx.response.getHeaders();
178
+
179
+ expect(result).toEqual({
180
+ contenttype: "application/json",
181
+ "x-request-id": "id"
182
+ });
183
+ });
184
+ });
185
+ describe("cookie()", () => {
186
+ it("should manipulate cookies", () => {
187
+ const {res, response, koaContext} = createResponse();
188
+ koaContext.cookies = {};
189
+ koaContext.cookies.set = (...args: any[]) => {
190
+ if (!args[1]) {
191
+ res.headers["set-cookie"] = res.headers["set-cookie"].filter((cookie: string) => cookie.startsWith(args[0] + "="));
192
+ return;
193
+ }
194
+
195
+ let value = `${args[0]}=${args[1]}`;
196
+
197
+ if (!res.headers["set-cookie"]) {
198
+ res.headers["set-cookie"] = [value];
199
+ } else {
200
+ res.headers["set-cookie"] = [].concat(res.headers["set-cookie"], value as any);
201
+ }
202
+ };
203
+
204
+ response.cookie("locale", "fr-FR");
205
+ expect(res.headers).toEqual({
206
+ "set-cookie": ["locale=fr-FR"],
207
+ "x-request-id": "id"
208
+ });
209
+
210
+ response.cookie("filename", "test");
211
+ expect(res.headers).toEqual({
212
+ "set-cookie": ["locale=fr-FR", "filename=test"],
213
+ "x-request-id": "id"
214
+ });
215
+
216
+ response.cookie("filename", null);
217
+
218
+ expect(res.headers).toEqual({
219
+ "set-cookie": ["filename=test"],
220
+ "x-request-id": "id"
221
+ });
222
+ });
223
+ });
224
+ });
@@ -0,0 +1,112 @@
1
+ import {PlatformResponse} from "@tsed/platform-http";
2
+ import {ServerResponse} from "http";
3
+ import Koa from "koa";
4
+
5
+ declare global {
6
+ namespace TsED {
7
+ export interface Response extends Koa.Response {}
8
+ }
9
+ }
10
+
11
+ /**
12
+ * @platform
13
+ * @koa
14
+ */
15
+ export class PlatformKoaResponse extends PlatformResponse<Koa.Response> {
16
+ get ctx() {
17
+ return this.raw.ctx;
18
+ }
19
+
20
+ get statusCode() {
21
+ return this.raw.status;
22
+ }
23
+
24
+ get locals() {
25
+ return this.ctx.state;
26
+ }
27
+
28
+ /**
29
+ * Return the Node.js response object
30
+ */
31
+ getRes(): ServerResponse {
32
+ return this.raw.res;
33
+ }
34
+
35
+ hasStatus() {
36
+ // KOA set 404 by default
37
+ return this.statusCode !== 404;
38
+ }
39
+
40
+ /**
41
+ * Sets the HTTP status for the response.
42
+ *
43
+ * @param status
44
+ */
45
+ status(status: number) {
46
+ this.raw.status = status;
47
+
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * Set `Content-Type` response header with `type` through `mime.lookup()`
53
+ * when it does not contain "/", or set the Content-Type to `type` otherwise.
54
+ *
55
+ * Examples:
56
+ *
57
+ * res.type('.html');
58
+ * res.type('html');
59
+ * res.type('json');
60
+ * res.type('application/json');
61
+ * res.type('png');
62
+ */
63
+ contentType(contentType: string) {
64
+ this.raw.type = contentType;
65
+
66
+ return this;
67
+ }
68
+
69
+ getHeaders() {
70
+ return this.raw.headers;
71
+ }
72
+
73
+ stream(data: any) {
74
+ this.raw.body = data;
75
+ return this;
76
+ }
77
+
78
+ getBody(): any {
79
+ return this.raw.body;
80
+ }
81
+
82
+ cookie(name: string, value: string | null, opts?: TsED.SetCookieOpts) {
83
+ if (value === null) {
84
+ this.ctx.cookies.set(name);
85
+ } else {
86
+ this.ctx.cookies.set(name, value, opts);
87
+ }
88
+ return this;
89
+ }
90
+
91
+ protected json(data: any) {
92
+ this.end(data);
93
+ return this;
94
+ }
95
+
96
+ protected buffer(data: Buffer) {
97
+ this.end(data);
98
+ return this;
99
+ }
100
+
101
+ protected end(data?: string | Buffer) {
102
+ if ([301, 302, 303, 307, 308].includes(this.statusCode)) {
103
+ if (this.request.method === "HEAD") {
104
+ this.getRes().end();
105
+ } else {
106
+ this.getRes().end(data);
107
+ }
108
+ } else {
109
+ this.raw.body = data;
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,65 @@
1
+ import Koa from "koa";
2
+ import {promisify} from "util";
3
+
4
+ let multer: any;
5
+ import("multer").then(({default: m}) => (multer = m));
6
+
7
+ /**
8
+ * @ignore
9
+ */
10
+ function createRawMiddleware(middleware: any) {
11
+ return async (request: Koa.Request) => {
12
+ const ctx: any = request.ctx;
13
+
14
+ await middleware(ctx.req, ctx.res);
15
+
16
+ const forwardKey = (key: string) => {
17
+ if (ctx.req[key]) {
18
+ ctx.request[key] = ctx.req[key];
19
+ ctx[key] = ctx.req[key];
20
+ delete ctx.req[key];
21
+ }
22
+ };
23
+
24
+ if ("request" in ctx) {
25
+ if (ctx.req.body) {
26
+ ctx.request.body = ctx.req.body;
27
+ delete ctx.req.body;
28
+ }
29
+
30
+ forwardKey("file");
31
+ forwardKey("files");
32
+ }
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @ignore
38
+ */
39
+ function makePromise(multer: any, name: string) {
40
+ // istanbul ignore next
41
+ if (!multer[name]) return;
42
+
43
+ const fn = multer[name];
44
+
45
+ multer[name] = function apply(...args: any[]) {
46
+ const middleware: any = Reflect.apply(fn, this, args);
47
+
48
+ return createRawMiddleware(promisify(middleware));
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @ignore
54
+ */
55
+ export function getMulter(options: any) {
56
+ const m = multer(options);
57
+
58
+ makePromise(m, "any");
59
+ makePromise(m, "array");
60
+ makePromise(m, "fields");
61
+ makePromise(m, "none");
62
+ makePromise(m, "single");
63
+
64
+ return m;
65
+ }