@tsed/platform-http 8.0.1 → 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.
Files changed (100) hide show
  1. package/package.json +18 -16
  2. package/src/common/builder/PlatformBuilder.spec.ts +331 -0
  3. package/src/common/builder/PlatformBuilder.ts +426 -0
  4. package/src/common/builder/__fixtures__/PlatformCustom.ts +39 -0
  5. package/src/common/config/interfaces/PlatformJsonMapperSettings.ts +11 -0
  6. package/src/common/config/interfaces/PlatformMulterSettings.ts +21 -0
  7. package/src/common/config/interfaces/PlatformRouterSettings.ts +11 -0
  8. package/src/common/config/interfaces/PlatformStaticsSettings.ts +23 -0
  9. package/src/common/config/services/PlatformConfiguration.spec.ts +181 -0
  10. package/src/common/config/services/PlatformConfiguration.ts +157 -0
  11. package/src/common/constants/routerOptions.ts +1 -0
  12. package/src/common/decorators/PlatformProvider.ts +14 -0
  13. package/src/common/decorators/multer/multerFileSize.spec.ts +22 -0
  14. package/src/common/decorators/multer/multerFileSize.ts +32 -0
  15. package/src/common/decorators/multer/multerOptions.spec.ts +21 -0
  16. package/src/common/decorators/multer/multerOptions.ts +53 -0
  17. package/src/common/decorators/multer/multipartFile.spec.ts +259 -0
  18. package/src/common/decorators/multer/multipartFile.ts +88 -0
  19. package/src/common/decorators/params/error.spec.ts +15 -0
  20. package/src/common/decorators/params/error.ts +16 -0
  21. package/src/common/decorators/params/next.spec.ts +15 -0
  22. package/src/common/decorators/params/next.ts +31 -0
  23. package/src/common/decorators/params/request.spec.ts +49 -0
  24. package/src/common/decorators/params/request.ts +85 -0
  25. package/src/common/decorators/params/response.spec.ts +36 -0
  26. package/src/common/decorators/params/response.ts +73 -0
  27. package/src/common/domain/AnyToPromiseWithCtx.spec.ts +20 -0
  28. package/src/common/domain/AnyToPromiseWithCtx.ts +30 -0
  29. package/src/common/domain/EndpointMetadata.ts +1 -0
  30. package/src/common/domain/PlatformContext.spec.ts +124 -0
  31. package/src/common/domain/PlatformContext.ts +135 -0
  32. package/src/common/domain/PlatformRouteDetails.ts +7 -0
  33. package/src/common/exports.ts +4 -0
  34. package/src/common/fn/adapter.ts +23 -0
  35. package/src/common/fn/application.ts +11 -0
  36. package/src/common/index.ts +62 -0
  37. package/src/common/interfaces/AfterInit.ts +3 -0
  38. package/src/common/interfaces/AfterListen.ts +3 -0
  39. package/src/common/interfaces/AfterRoutesInit.ts +3 -0
  40. package/src/common/interfaces/BeforeInit.ts +3 -0
  41. package/src/common/interfaces/BeforeListen.ts +3 -0
  42. package/src/common/interfaces/BeforeRoutesInit.ts +3 -0
  43. package/src/common/interfaces/HandlerType.ts +12 -0
  44. package/src/common/interfaces/IncomingEvent.ts +6 -0
  45. package/src/common/interfaces/OnReady.ts +3 -0
  46. package/src/common/interfaces/OnRequest.ts +5 -0
  47. package/src/common/interfaces/OnResponse.ts +5 -0
  48. package/src/common/interfaces/OnRoutesInit.ts +3 -0
  49. package/src/common/interfaces/PlatformRouteOptions.ts +17 -0
  50. package/src/common/interfaces/ResponseErrorObject.ts +8 -0
  51. package/src/common/interfaces/Route.ts +12 -0
  52. package/src/common/middlewares/PlatformAcceptMimesMiddleware.spec.ts +215 -0
  53. package/src/common/middlewares/PlatformAcceptMimesMiddleware.ts +24 -0
  54. package/src/common/middlewares/PlatformMulterMiddleware.spec.ts +97 -0
  55. package/src/common/middlewares/PlatformMulterMiddleware.ts +59 -0
  56. package/src/common/services/Platform.spec.ts +145 -0
  57. package/src/common/services/Platform.ts +79 -0
  58. package/src/common/services/PlatformAdapter.ts +89 -0
  59. package/src/common/services/PlatformApplication.spec.ts +101 -0
  60. package/src/common/services/PlatformApplication.ts +54 -0
  61. package/src/common/services/PlatformHandler.spec.ts +152 -0
  62. package/src/common/services/PlatformHandler.ts +140 -0
  63. package/src/common/services/PlatformMiddlewaresChain.spec.ts +96 -0
  64. package/src/common/services/PlatformMiddlewaresChain.ts +55 -0
  65. package/src/common/services/PlatformRequest.spec.ts +185 -0
  66. package/src/common/services/PlatformRequest.ts +181 -0
  67. package/src/common/services/PlatformResponse.spec.ts +344 -0
  68. package/src/common/services/PlatformResponse.ts +388 -0
  69. package/src/common/utils/closeServer.spec.ts +11 -0
  70. package/src/common/utils/closeServer.ts +7 -0
  71. package/src/common/utils/createContext.spec.ts +111 -0
  72. package/src/common/utils/createContext.ts +52 -0
  73. package/src/common/utils/createHttpServer.spec.ts +74 -0
  74. package/src/common/utils/createHttpServer.ts +15 -0
  75. package/src/common/utils/createHttpsServer.spec.ts +73 -0
  76. package/src/common/utils/createHttpsServer.ts +16 -0
  77. package/src/common/utils/createInjector.spec.ts +42 -0
  78. package/src/common/utils/createInjector.ts +55 -0
  79. package/src/common/utils/createServer.ts +51 -0
  80. package/src/common/utils/ensureContext.spec.ts +41 -0
  81. package/src/common/utils/ensureContext.ts +17 -0
  82. package/src/common/utils/getConfiguration.spec.ts +163 -0
  83. package/src/common/utils/getConfiguration.ts +34 -0
  84. package/src/common/utils/getStaticsOptions.spec.ts +27 -0
  85. package/src/common/utils/getStaticsOptions.ts +24 -0
  86. package/src/common/utils/listenServer.ts +17 -0
  87. package/src/common/utils/mapReturnedResponse.spec.ts +35 -0
  88. package/src/common/utils/mapReturnedResponse.ts +10 -0
  89. package/src/common/utils/printRoutes.spec.ts +19 -0
  90. package/src/common/utils/printRoutes.ts +40 -0
  91. package/src/common/utils/resolveControllers.spec.ts +40 -0
  92. package/src/common/utils/resolveControllers.ts +54 -0
  93. package/src/common/utils/setResponseHeaders.spec.ts +84 -0
  94. package/src/common/utils/setResponseHeaders.ts +31 -0
  95. package/src/index.ts +2 -0
  96. package/src/testing/FakeAdapter.ts +69 -0
  97. package/src/testing/FakeResponse.ts +72 -0
  98. package/src/testing/PlatformTest.spec.ts +53 -0
  99. package/src/testing/PlatformTest.ts +157 -0
  100. package/src/testing/index.ts +5 -0
@@ -0,0 +1,215 @@
1
+ import {catchError} from "@tsed/core";
2
+ import {AcceptMime, EndpointMetadata, Get} from "@tsed/schema";
3
+
4
+ import {PlatformTest} from "../../testing/index.js";
5
+ import {PlatformAcceptMimesMiddleware} from "./PlatformAcceptMimesMiddleware.js";
6
+
7
+ describe("PlatformMimesMiddleware", () => {
8
+ describe("when server has configuration", () => {
9
+ beforeEach(() =>
10
+ PlatformTest.create({
11
+ acceptMimes: ["application/json", "text"]
12
+ })
13
+ );
14
+ afterEach(() => PlatformTest.reset());
15
+ it("should accept type (application/json)", async () => {
16
+ class Test {
17
+ @Get("/")
18
+ @AcceptMime("application/json")
19
+ get() {}
20
+ }
21
+
22
+ const endpoint = EndpointMetadata.get(Test, "get");
23
+ const request = PlatformTest.createRequest({
24
+ headers: {
25
+ accept: "application/json"
26
+ }
27
+ });
28
+ const ctx = PlatformTest.createRequestContext({
29
+ event: {request},
30
+ endpoint
31
+ });
32
+
33
+ vi.spyOn(request, "accepts");
34
+
35
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
36
+
37
+ middleware.use(ctx as never);
38
+
39
+ expect(request.accepts).toHaveBeenCalledWith(["application/json", "text"]);
40
+ });
41
+ it("should accept type (text)", async () => {
42
+ class Test {
43
+ @Get("/")
44
+ @AcceptMime("text")
45
+ get() {}
46
+ }
47
+
48
+ const endpoint = EndpointMetadata.get(Test, "get");
49
+ const request: any = PlatformTest.createRequest({
50
+ headers: {
51
+ accept: "text/*, application/json"
52
+ }
53
+ });
54
+ const ctx = PlatformTest.createRequestContext({
55
+ event: {request},
56
+ endpoint
57
+ });
58
+
59
+ vi.spyOn(request, "accepts");
60
+
61
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
62
+ middleware.use(ctx as never);
63
+
64
+ expect(request.accepts).toHaveBeenCalledWith(["text", "application/json"]);
65
+ });
66
+ it("should accept type (text) without endpoint", async () => {
67
+ class Test {
68
+ @Get("/")
69
+ @AcceptMime("text")
70
+ get() {}
71
+ }
72
+
73
+ const request: any = PlatformTest.createRequest({
74
+ headers: {
75
+ accept: "text/*, application/json"
76
+ }
77
+ });
78
+ const ctx = PlatformTest.createRequestContext({
79
+ event: {request}
80
+ });
81
+ vi.spyOn(request, "accepts");
82
+
83
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
84
+ middleware.use(ctx as never);
85
+
86
+ expect(request.accepts).toHaveBeenCalledWith(["application/json", "text"]);
87
+ });
88
+ it("should refuse type", async () => {
89
+ class Test {
90
+ @Get("/")
91
+ @AcceptMime("application/json")
92
+ get() {}
93
+ }
94
+
95
+ const endpoint = EndpointMetadata.get(Test, "get");
96
+ const request: any = PlatformTest.createRequest({
97
+ headers: {
98
+ accept: "application/xml"
99
+ }
100
+ });
101
+ const ctx = PlatformTest.createRequestContext({
102
+ event: {request},
103
+ endpoint
104
+ });
105
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
106
+
107
+ const error: any = catchError(() => middleware.use(ctx as never));
108
+
109
+ expect(error.message).toEqual("You must accept content-type application/json, text");
110
+ });
111
+ });
112
+ describe("when server hasn't configuration", () => {
113
+ beforeEach(() => PlatformTest.create());
114
+ afterEach(() => PlatformTest.reset());
115
+ it("should do noting", async () => {
116
+ class Test {
117
+ @Get("/")
118
+ get() {}
119
+ }
120
+
121
+ const endpoint = EndpointMetadata.get(Test, "get");
122
+ const request: any = PlatformTest.createRequest({
123
+ headers: {
124
+ accept: "application/json"
125
+ }
126
+ });
127
+ const ctx = PlatformTest.createRequestContext({
128
+ event: {request},
129
+ endpoint
130
+ });
131
+ vi.spyOn(request, "accepts");
132
+
133
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
134
+ middleware.use(ctx as never);
135
+
136
+ return expect(request.accepts).not.toHaveBeenCalled();
137
+ });
138
+ it("should accept type (application/json)", async () => {
139
+ class Test {
140
+ @Get("/")
141
+ @AcceptMime("application/json")
142
+ get() {}
143
+ }
144
+
145
+ const endpoint = EndpointMetadata.get(Test, "get");
146
+ const request: any = PlatformTest.createRequest({
147
+ headers: {
148
+ accept: "application/json"
149
+ }
150
+ });
151
+ const ctx = PlatformTest.createRequestContext({
152
+ event: {request},
153
+ endpoint
154
+ });
155
+ vi.spyOn(request, "accepts");
156
+
157
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
158
+ middleware.use(ctx as never);
159
+
160
+ expect(request.accepts).toHaveBeenCalledWith(["application/json"]);
161
+ });
162
+ it("should accept type (text)", async () => {
163
+ class Test {
164
+ @Get("/")
165
+ @AcceptMime("text")
166
+ get() {}
167
+ }
168
+
169
+ const endpoint = EndpointMetadata.get(Test, "get");
170
+ const request: any = PlatformTest.createRequest({
171
+ headers: {
172
+ accept: "text/*, application/json"
173
+ }
174
+ });
175
+ const ctx = PlatformTest.createRequestContext({
176
+ event: {
177
+ request
178
+ },
179
+ endpoint
180
+ });
181
+ vi.spyOn(request, "accepts");
182
+
183
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
184
+ middleware.use(ctx as never);
185
+
186
+ expect(request.accepts).toHaveBeenCalledWith(["text"]);
187
+ });
188
+ it("should refuse type", async () => {
189
+ class Test {
190
+ @Get("/")
191
+ @AcceptMime("application/json")
192
+ get() {}
193
+ }
194
+
195
+ const endpoint = EndpointMetadata.get(Test, "get");
196
+ const request: any = PlatformTest.createRequest({
197
+ headers: {
198
+ accept: "application/xml"
199
+ }
200
+ });
201
+ const ctx = PlatformTest.createRequestContext({
202
+ event: {request},
203
+ endpoint
204
+ });
205
+
206
+ vi.spyOn(request, "accepts");
207
+
208
+ const middleware = await PlatformTest.invoke<PlatformAcceptMimesMiddleware>(PlatformAcceptMimesMiddleware);
209
+
210
+ const error: any = catchError(() => middleware.use(ctx as never));
211
+
212
+ expect(error.message).toEqual("You must accept content-type application/json");
213
+ });
214
+ });
215
+ });
@@ -0,0 +1,24 @@
1
+ import {uniq} from "@tsed/core";
2
+ import {constant, injectable, ProviderType} from "@tsed/di";
3
+ import {NotAcceptable} from "@tsed/exceptions";
4
+ import {MiddlewareMethods} from "@tsed/platform-middlewares";
5
+ import {Context} from "@tsed/platform-params";
6
+
7
+ /**
8
+ * @middleware
9
+ * @platform
10
+ */
11
+ export class PlatformAcceptMimesMiddleware implements MiddlewareMethods {
12
+ acceptMimes = constant<string[]>("acceptMimes", []);
13
+
14
+ public use(@Context() ctx: Context): void {
15
+ const {endpoint, request} = ctx;
16
+ const mimes = uniq((endpoint?.get("acceptMimes") || []).concat(this.acceptMimes));
17
+
18
+ if (mimes.length && !request.accepts(mimes)) {
19
+ throw new NotAcceptable(mimes.join(", "));
20
+ }
21
+ }
22
+ }
23
+
24
+ injectable(PlatformAcceptMimesMiddleware).type(ProviderType.MIDDLEWARE).priority(-10);
@@ -0,0 +1,97 @@
1
+ import {catchAsyncError} from "@tsed/core";
2
+ import {Exception} from "@tsed/exceptions";
3
+ import {MulterError} from "multer";
4
+
5
+ import {PlatformTest} from "../../testing/PlatformTest.js";
6
+ import {MulterOptions} from "../decorators/multer/multerOptions.js";
7
+ import {MultipartFile} from "../decorators/multer/multipartFile.js";
8
+ import {EndpointMetadata} from "../domain/EndpointMetadata.js";
9
+ import {PlatformApplication} from "../services/PlatformApplication.js";
10
+ import {PlatformMulterMiddleware} from "./PlatformMulterMiddleware.js";
11
+
12
+ async function build(options = {}) {
13
+ class Test {
14
+ @MulterOptions(options)
15
+ upload(@MultipartFile("file1") file1: any) {}
16
+ }
17
+
18
+ const multerMiddleware = vi.fn();
19
+ const multer = {
20
+ fields: vi.fn().mockReturnValue(multerMiddleware)
21
+ };
22
+ const app = {
23
+ multer: vi.fn().mockReturnValue(multer)
24
+ };
25
+
26
+ const middleware = await PlatformTest.invoke<PlatformMulterMiddleware>(PlatformMulterMiddleware, [
27
+ {
28
+ token: PlatformApplication,
29
+ use: app
30
+ }
31
+ ]);
32
+ const ctx: any = PlatformTest.createRequestContext();
33
+ ctx.endpoint = EndpointMetadata.get(Test, "upload");
34
+
35
+ return {middleware, ctx, multer, app, multerMiddleware};
36
+ }
37
+
38
+ describe("PlatformMulterMiddleware", () => {
39
+ beforeEach(() =>
40
+ PlatformTest.create({
41
+ multer: {
42
+ dest: "/dest"
43
+ }
44
+ })
45
+ );
46
+ afterEach(() => PlatformTest.reset());
47
+ it("should create middleware", async () => {
48
+ const {middleware, ctx, multer, app, multerMiddleware} = await build({});
49
+
50
+ await middleware.use(ctx);
51
+
52
+ expect(app.multer).toHaveBeenCalledWith({
53
+ dest: "/dest"
54
+ });
55
+ expect(multer.fields).toHaveBeenCalledWith([{maxCount: undefined, name: "file1"}]);
56
+ expect(multerMiddleware).toHaveBeenCalledWith(ctx.request.raw, ctx.response.raw);
57
+ });
58
+ it("should create middleware with storage", async () => {
59
+ const {middleware, ctx, multer, app, multerMiddleware} = await build({
60
+ storage: "storage"
61
+ });
62
+
63
+ await middleware.use(ctx);
64
+
65
+ expect(app.multer).toHaveBeenCalledWith({
66
+ storage: "storage"
67
+ });
68
+ expect(multer.fields).toHaveBeenCalledWith([{maxCount: undefined, name: "file1"}]);
69
+ expect(multerMiddleware).toHaveBeenCalledWith(ctx.request.raw, ctx.response.raw);
70
+ });
71
+ it("should catch error with code", async () => {
72
+ const {middleware, ctx, multerMiddleware} = await build();
73
+ const error = new MulterError("LIMIT_FILE_SIZE", "field");
74
+
75
+ multerMiddleware.mockRejectedValue(error);
76
+
77
+ const actualError: any | undefined = await catchAsyncError(() => middleware.use(ctx));
78
+
79
+ expect(actualError).toBeInstanceOf(Exception);
80
+ expect(actualError?.message).toEqual("File too large");
81
+ expect(actualError?.status).toEqual(400);
82
+ });
83
+ it("should throw error without code", async () => {
84
+ const {middleware, ctx, multerMiddleware} = await build();
85
+
86
+ multerMiddleware.mockRejectedValue(new Error("test"));
87
+
88
+ let actualError: any;
89
+ try {
90
+ await middleware.use(ctx);
91
+ } catch (er) {
92
+ actualError = er;
93
+ }
94
+
95
+ expect(actualError.message).toEqual("test");
96
+ });
97
+ });
@@ -0,0 +1,59 @@
1
+ import {constant, inject, injectable, ProviderType} from "@tsed/di";
2
+ import {BadRequest} from "@tsed/exceptions";
3
+ import {MiddlewareMethods} from "@tsed/platform-middlewares";
4
+ import {Context} from "@tsed/platform-params";
5
+ import type {MulterError} from "multer";
6
+
7
+ import {PlatformMulterField, PlatformMulterSettings} from "../config/interfaces/PlatformMulterSettings.js";
8
+ import {PlatformContext} from "../domain/PlatformContext.js";
9
+ import {PlatformApplication} from "../services/PlatformApplication.js";
10
+
11
+ export interface MulterInputOptions {
12
+ fields: PlatformMulterField[];
13
+ }
14
+
15
+ export class MulterException extends BadRequest {
16
+ constructor(er: MulterError) {
17
+ super(er.message);
18
+ this.origin = er;
19
+ this.name = er.code;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * @middleware
25
+ */
26
+ export class PlatformMulterMiddleware implements MiddlewareMethods {
27
+ protected app = inject(PlatformApplication);
28
+
29
+ async use(@Context() ctx: PlatformContext) {
30
+ try {
31
+ const {fields, options = {}} = ctx.endpoint.get(PlatformMulterMiddleware);
32
+ const settings: PlatformMulterSettings = {
33
+ ...constant("multer", {}),
34
+ ...options
35
+ };
36
+
37
+ /* istanbul ignore next */
38
+ if (settings.storage) {
39
+ delete settings.dest;
40
+ }
41
+
42
+ const middleware: any = this.app.multer(settings).fields(this.getFields({fields}));
43
+
44
+ return await middleware(ctx.getRequest(), ctx.getResponse());
45
+ } catch (er) {
46
+ if (er.code) {
47
+ throw new MulterException(er);
48
+ }
49
+
50
+ throw er;
51
+ }
52
+ }
53
+
54
+ protected getFields(conf: MulterInputOptions) {
55
+ return conf.fields.map(({name, maxCount}) => ({name, maxCount}));
56
+ }
57
+ }
58
+
59
+ injectable(PlatformMulterMiddleware).type(ProviderType.MIDDLEWARE).priority(-10);
@@ -0,0 +1,145 @@
1
+ import {nameOf} from "@tsed/core";
2
+ import {Controller} from "@tsed/di";
3
+ import {Get, Post} from "@tsed/schema";
4
+
5
+ import {PlatformTest} from "../../testing/PlatformTest.js";
6
+ import {application} from "../fn/application.js";
7
+ import {Platform} from "./Platform.js";
8
+
9
+ @Controller("/my-route")
10
+ class MyCtrl {
11
+ @Get("/")
12
+ get() {}
13
+ }
14
+
15
+ @Controller("/my-sub-route")
16
+ class MySubCtrl {
17
+ @Get("/")
18
+ get() {}
19
+ }
20
+
21
+ @Controller({
22
+ path: "/my-route",
23
+ children: [MySubCtrl]
24
+ })
25
+ class MyNestedCtrl {
26
+ @Get("/")
27
+ get() {}
28
+ }
29
+
30
+ @Controller("/")
31
+ export class FlaggedCommentController {
32
+ @Get("/flag")
33
+ async list() {}
34
+
35
+ @Post("/:commentID/flag")
36
+ async create() {}
37
+ }
38
+
39
+ @Controller({
40
+ path: "/comments",
41
+ children: [FlaggedCommentController]
42
+ })
43
+ export class CommentController {
44
+ @Get("/all")
45
+ get() {}
46
+ }
47
+
48
+ @Controller({
49
+ path: "/domain/:contextID",
50
+ children: [CommentController]
51
+ })
52
+ export class DomainController {
53
+ @Get("/all")
54
+ get() {}
55
+ }
56
+
57
+ @Controller({
58
+ path: "/platform/:platform",
59
+ children: [CommentController]
60
+ })
61
+ export class PlatformController {
62
+ @Get("/")
63
+ get() {}
64
+ }
65
+
66
+ describe("Platform", () => {
67
+ beforeEach(PlatformTest.create);
68
+ afterEach(PlatformTest.reset);
69
+ afterEach(() => {
70
+ vi.resetAllMocks();
71
+ });
72
+
73
+ describe("addRoute", () => {
74
+ it("should add a route", async () => {
75
+ // GIVEN
76
+ const platform = await PlatformTest.get<Platform>(Platform);
77
+
78
+ vi.spyOn(application(), "use");
79
+
80
+ // WHEN
81
+ platform.addRoutes([{route: "/test", token: MyCtrl}]);
82
+ });
83
+ it("should add nested controllers (1)", async () => {
84
+ // GIVEN
85
+ const platform = await PlatformTest.get<Platform>(Platform);
86
+
87
+ vi.spyOn(application(), "use");
88
+
89
+ // WHEN
90
+ platform.addRoutes([{route: "/rest", token: MyNestedCtrl}]);
91
+ platform.addRoutes([{route: "/rest", token: MySubCtrl}]);
92
+
93
+ platform.getLayers();
94
+ const result = platform.getMountedControllers();
95
+
96
+ // THEN
97
+ expect(result.length).toEqual(2);
98
+ expect(result[0].route).toEqual("/rest");
99
+ expect(result[1].route).toEqual("/rest/my-route");
100
+ });
101
+ it("should add nested controllers (2)", async () => {
102
+ // GIVEN
103
+ const platform = await PlatformTest.get<Platform>(Platform);
104
+
105
+ vi.spyOn(application(), "use");
106
+
107
+ // WHEN
108
+ platform.addRoutes([{route: "/rest", token: DomainController}]);
109
+ platform.addRoutes([{route: "/rest", token: PlatformController}]);
110
+
111
+ platform.getLayers();
112
+
113
+ const result = platform.getMountedControllers();
114
+
115
+ // THEN
116
+ const data = result.map((c) => ({route: c.route, name: nameOf(c.provider)}));
117
+ expect(data).toEqual([
118
+ {
119
+ name: "Token:DomainController:DomainController",
120
+ route: "/rest"
121
+ },
122
+ {
123
+ name: "Token:CommentController:CommentController",
124
+ route: "/rest/domain/:contextID"
125
+ },
126
+ {
127
+ name: "Token:FlaggedCommentController:FlaggedCommentController",
128
+ route: "/rest/domain/:contextID/comments"
129
+ },
130
+ {
131
+ name: "Token:PlatformController:PlatformController",
132
+ route: "/rest"
133
+ },
134
+ {
135
+ name: "Token:CommentController:CommentController",
136
+ route: "/rest/platform/:platform"
137
+ },
138
+ {
139
+ name: "Token:FlaggedCommentController:FlaggedCommentController",
140
+ route: "/rest/platform/:platform/comments"
141
+ }
142
+ ]);
143
+ });
144
+ });
145
+ });
@@ -0,0 +1,79 @@
1
+ import {ControllerProvider, inject, injectable, injector, ProviderScope, TokenProvider} from "@tsed/di";
2
+ import {PlatformLayer, PlatformRouters} from "@tsed/platform-router";
3
+
4
+ import {application} from "../fn/application.js";
5
+ import {Route, RouteController} from "../interfaces/Route.js";
6
+
7
+ /**
8
+ * `Platform` is used to provide all routes collected by annotation `@Controller`.
9
+ *
10
+ * @platform
11
+ */
12
+ export class Platform {
13
+ readonly platformRouters = inject(PlatformRouters);
14
+ #layers: PlatformLayer[];
15
+
16
+ constructor() {
17
+ this.platformRouters.prebuild();
18
+ }
19
+
20
+ get app() {
21
+ return application();
22
+ }
23
+
24
+ public addRoutes(routes: Route[]) {
25
+ routes.forEach((routeSettings) => {
26
+ this.addRoute(routeSettings.route, routeSettings.token);
27
+ });
28
+ }
29
+
30
+ public addRoute(route: string, token: TokenProvider) {
31
+ const app = application();
32
+ const provider = injector().getProvider(token) as ControllerProvider;
33
+
34
+ if (!provider || provider.hasParent()) {
35
+ return this;
36
+ }
37
+
38
+ const router = this.platformRouters.from(provider.token);
39
+
40
+ app.use(route, router);
41
+
42
+ return this;
43
+ }
44
+
45
+ public getLayers() {
46
+ this.#layers = this.#layers || this.platformRouters.getLayers(application());
47
+
48
+ return this.#layers;
49
+ }
50
+
51
+ /**
52
+ * Get all controllers mounted on the application.
53
+ * @returns {RouteController[]}
54
+ */
55
+ public getMountedControllers() {
56
+ const controllers = this.getLayers().reduce((controllers, layer) => {
57
+ if (layer.isProvider()) {
58
+ const route = String(layer.getBasePath());
59
+ const key = `${layer.provider.toString()}:${route}`;
60
+
61
+ if (!controllers.has(key)) {
62
+ controllers.set(key, {
63
+ route,
64
+ routes: new Set(),
65
+ provider: layer.provider
66
+ });
67
+ }
68
+
69
+ controllers.get(key)!.routes.add(String(layer.path));
70
+ }
71
+
72
+ return controllers;
73
+ }, new Map<string, RouteController>());
74
+
75
+ return [...controllers.values()];
76
+ }
77
+ }
78
+
79
+ injectable(Platform).scope(ProviderScope.SINGLETON);