@tsed/platform-router 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 +16 -15
- package/src/domain/PlatformHandlerMetadata.spec.ts +149 -0
- package/src/domain/PlatformHandlerMetadata.ts +145 -0
- package/src/domain/PlatformHandlerType.ts +10 -0
- package/src/domain/PlatformLayer.ts +89 -0
- package/src/domain/PlatformRouter.ts +131 -0
- package/src/domain/PlatformRouters.ts +196 -0
- package/src/domain/SinglePathType.ts +1 -0
- package/src/index.ts +11 -0
- package/src/utils/formatMethod.ts +5 -0
- package/src/utils/useContextHandler.ts +22 -0
package/package.json
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
"name": "@tsed/platform-router",
|
|
3
3
|
"description": "Router",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.0.
|
|
5
|
+
"version": "8.0.2",
|
|
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"
|
|
@@ -25,25 +26,25 @@
|
|
|
25
26
|
"tslib": "2.7.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@tsed/barrels": "8.0.
|
|
29
|
-
"@tsed/core": "8.0.
|
|
30
|
-
"@tsed/di": "8.0.
|
|
31
|
-
"@tsed/exceptions": "8.0.
|
|
32
|
-
"@tsed/json-mapper": "8.0.
|
|
33
|
-
"@tsed/platform-params": "8.0.
|
|
34
|
-
"@tsed/schema": "8.0.
|
|
35
|
-
"@tsed/typescript": "8.0.
|
|
29
|
+
"@tsed/barrels": "8.0.2",
|
|
30
|
+
"@tsed/core": "8.0.2",
|
|
31
|
+
"@tsed/di": "8.0.2",
|
|
32
|
+
"@tsed/exceptions": "8.0.2",
|
|
33
|
+
"@tsed/json-mapper": "8.0.2",
|
|
34
|
+
"@tsed/platform-params": "8.0.2",
|
|
35
|
+
"@tsed/schema": "8.0.2",
|
|
36
|
+
"@tsed/typescript": "8.0.2",
|
|
36
37
|
"eslint": "9.12.0",
|
|
37
38
|
"typescript": "5.4.5",
|
|
38
39
|
"vitest": "2.1.2"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
|
-
"@tsed/core": "8.0.
|
|
42
|
-
"@tsed/di": "8.0.
|
|
43
|
-
"@tsed/exceptions": "8.0.
|
|
44
|
-
"@tsed/json-mapper": "8.0.
|
|
45
|
-
"@tsed/platform-params": "8.0.
|
|
46
|
-
"@tsed/schema": "8.0.
|
|
42
|
+
"@tsed/core": "8.0.2",
|
|
43
|
+
"@tsed/di": "8.0.2",
|
|
44
|
+
"@tsed/exceptions": "8.0.2",
|
|
45
|
+
"@tsed/json-mapper": "8.0.2",
|
|
46
|
+
"@tsed/platform-params": "8.0.2",
|
|
47
|
+
"@tsed/schema": "8.0.2"
|
|
47
48
|
},
|
|
48
49
|
"peerDependenciesMeta": {
|
|
49
50
|
"@tsed/core": {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {Controller, destroyInjector, injector} from "@tsed/di";
|
|
2
|
+
import {Err, Next, Req} from "@tsed/platform-http";
|
|
3
|
+
import {Middleware} from "@tsed/platform-middlewares";
|
|
4
|
+
import {Get, JsonMethodStore} from "@tsed/schema";
|
|
5
|
+
|
|
6
|
+
import {useContextHandler} from "../utils/useContextHandler.js";
|
|
7
|
+
import {PlatformHandlerMetadata} from "./PlatformHandlerMetadata.js";
|
|
8
|
+
import {PlatformHandlerType} from "./PlatformHandlerType.js";
|
|
9
|
+
|
|
10
|
+
describe("PlatformHandlerMetadata", () => {
|
|
11
|
+
afterEach(() => destroyInjector());
|
|
12
|
+
describe("from()", () => {
|
|
13
|
+
it("should return PlatformMetadata", () => {
|
|
14
|
+
const meta = new PlatformHandlerMetadata({
|
|
15
|
+
type: PlatformHandlerType.CUSTOM,
|
|
16
|
+
handler: () => {}
|
|
17
|
+
});
|
|
18
|
+
const result = PlatformHandlerMetadata.from(meta);
|
|
19
|
+
expect(result).toEqual(meta);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("from useContextHandler", () => {
|
|
23
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
24
|
+
// GIVEN
|
|
25
|
+
const handler = useContextHandler((ctx: any) => {});
|
|
26
|
+
|
|
27
|
+
// WHEN
|
|
28
|
+
const handlerMetadata = new PlatformHandlerMetadata({
|
|
29
|
+
handler
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// THEN
|
|
33
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.CTX_FN);
|
|
34
|
+
expect(handlerMetadata.hasNextFunction).toEqual(false);
|
|
35
|
+
expect(handlerMetadata.hasErrorParam).toEqual(false);
|
|
36
|
+
expect(handlerMetadata.toString()).toEqual("");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("from function", () => {
|
|
40
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
41
|
+
// GIVEN
|
|
42
|
+
const options = {
|
|
43
|
+
handler(req: any, res: any, next: any) {}
|
|
44
|
+
};
|
|
45
|
+
// WHEN
|
|
46
|
+
const handlerMetadata = new PlatformHandlerMetadata(options);
|
|
47
|
+
|
|
48
|
+
// THEN
|
|
49
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.RAW_FN);
|
|
50
|
+
expect(handlerMetadata.hasNextFunction).toEqual(true);
|
|
51
|
+
expect(handlerMetadata.hasErrorParam).toEqual(false);
|
|
52
|
+
expect(handlerMetadata.toString()).toEqual("handler");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("from function err", () => {
|
|
56
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
57
|
+
// GIVEN
|
|
58
|
+
const options = {
|
|
59
|
+
handler(err: any, req: any, res: any, next: any) {}
|
|
60
|
+
};
|
|
61
|
+
// WHEN
|
|
62
|
+
const handlerMetadata = new PlatformHandlerMetadata(options);
|
|
63
|
+
|
|
64
|
+
// THEN
|
|
65
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.RAW_ERR_FN);
|
|
66
|
+
expect(handlerMetadata.hasNextFunction).toEqual(true);
|
|
67
|
+
expect(handlerMetadata.hasErrorParam).toEqual(true);
|
|
68
|
+
expect(handlerMetadata.propertyKey).toBeUndefined();
|
|
69
|
+
expect(handlerMetadata.toString()).toEqual("handler");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("from function without nextFn", () => {
|
|
73
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
74
|
+
// GIVEN
|
|
75
|
+
const options = {
|
|
76
|
+
handler(req: any, res: any) {}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// WHEN
|
|
80
|
+
const handlerMetadata = new PlatformHandlerMetadata(options);
|
|
81
|
+
|
|
82
|
+
// THEN
|
|
83
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.RAW_FN);
|
|
84
|
+
expect(handlerMetadata.hasNextFunction).toEqual(false);
|
|
85
|
+
expect(handlerMetadata.hasErrorParam).toEqual(false);
|
|
86
|
+
expect(handlerMetadata.propertyKey).toBeUndefined();
|
|
87
|
+
expect(handlerMetadata.toString()).toEqual("handler");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("from endpoint/middleware with injection", () => {
|
|
91
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
92
|
+
// GIVEN
|
|
93
|
+
@Controller("/")
|
|
94
|
+
class Test {
|
|
95
|
+
@Get("/")
|
|
96
|
+
test(@Req() req: Req, @Next() next: Next) {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
injector().addProvider(Test);
|
|
100
|
+
|
|
101
|
+
const options = {
|
|
102
|
+
provider: injector().getProvider(Test),
|
|
103
|
+
propertyKey: "test",
|
|
104
|
+
type: PlatformHandlerType.ENDPOINT
|
|
105
|
+
};
|
|
106
|
+
// WHEN
|
|
107
|
+
const handlerMetadata = new PlatformHandlerMetadata(options);
|
|
108
|
+
|
|
109
|
+
// THEN
|
|
110
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.ENDPOINT);
|
|
111
|
+
expect(handlerMetadata.hasNextFunction).toEqual(true);
|
|
112
|
+
expect(handlerMetadata.hasErrorParam).toEqual(false);
|
|
113
|
+
expect(handlerMetadata.propertyKey).toEqual("test");
|
|
114
|
+
expect(handlerMetadata.scope).toEqual("singleton");
|
|
115
|
+
expect(handlerMetadata.toString()).toEqual("Test.test");
|
|
116
|
+
expect(handlerMetadata.store).toBeInstanceOf(JsonMethodStore);
|
|
117
|
+
|
|
118
|
+
expect(handlerMetadata.getParams()[0].paramType).toEqual("REQUEST");
|
|
119
|
+
expect(handlerMetadata.getParams()[1].paramType).toEqual("NEXT_FN");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("from middleware with injection and error", () => {
|
|
124
|
+
it("should create a new handlerMetadata with right metadata", () => {
|
|
125
|
+
// WHEN
|
|
126
|
+
@Middleware()
|
|
127
|
+
class Test {
|
|
128
|
+
use(@Err() error: any, @Next() next: Next) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
injector().addProvider(Test);
|
|
132
|
+
|
|
133
|
+
const options = {
|
|
134
|
+
provider: injector().getProvider(Test),
|
|
135
|
+
propertyKey: "use",
|
|
136
|
+
type: PlatformHandlerType.MIDDLEWARE
|
|
137
|
+
};
|
|
138
|
+
// WHEN
|
|
139
|
+
const handlerMetadata = new PlatformHandlerMetadata(options);
|
|
140
|
+
|
|
141
|
+
// THEN
|
|
142
|
+
expect(handlerMetadata.type).toEqual(PlatformHandlerType.ERR_MIDDLEWARE);
|
|
143
|
+
expect(handlerMetadata.hasNextFunction).toEqual(true);
|
|
144
|
+
expect(handlerMetadata.hasErrorParam).toEqual(true);
|
|
145
|
+
expect(handlerMetadata.propertyKey).toEqual("use");
|
|
146
|
+
expect(handlerMetadata.toString()).toEqual("Test.use");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {nameOf} from "@tsed/core";
|
|
2
|
+
import {DIContext, injector, Provider, ProviderScope, TokenProvider} from "@tsed/di";
|
|
3
|
+
import {ParamTypes} from "@tsed/platform-params";
|
|
4
|
+
import {EndpointMetadata, JsonEntityStore, JsonParameterStore} from "@tsed/schema";
|
|
5
|
+
|
|
6
|
+
import {PlatformHandlerType} from "./PlatformHandlerType.js";
|
|
7
|
+
import {SinglePathType} from "./SinglePathType.js";
|
|
8
|
+
|
|
9
|
+
export interface PlatformHandlerMetadataOpts extends Record<string, any> {
|
|
10
|
+
token?: TokenProvider;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PlatformHandlerMetadataProps {
|
|
14
|
+
provider?: Provider;
|
|
15
|
+
handler?: any;
|
|
16
|
+
opts?: PlatformHandlerMetadataOpts;
|
|
17
|
+
propertyKey?: string | symbol;
|
|
18
|
+
type?: PlatformHandlerType;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class PlatformHandlerMetadata {
|
|
22
|
+
path: SinglePathType;
|
|
23
|
+
|
|
24
|
+
readonly provider?: Provider;
|
|
25
|
+
readonly propertyKey: string | symbol;
|
|
26
|
+
readonly type: PlatformHandlerType = PlatformHandlerType.RAW_FN;
|
|
27
|
+
readonly hasNextFunction: boolean = false;
|
|
28
|
+
readonly opts: PlatformHandlerMetadataOpts = {};
|
|
29
|
+
compiledHandler: ($ctx: DIContext) => any;
|
|
30
|
+
#handler: any;
|
|
31
|
+
|
|
32
|
+
constructor(props: PlatformHandlerMetadataProps) {
|
|
33
|
+
const {propertyKey, type, provider, handler, opts} = props;
|
|
34
|
+
|
|
35
|
+
this.provider = provider;
|
|
36
|
+
this.type = type || handler.type || PlatformHandlerType.RAW_FN;
|
|
37
|
+
this.opts = opts || {};
|
|
38
|
+
|
|
39
|
+
this.#handler = propertyKey ? this.target.prototype[propertyKey] : handler;
|
|
40
|
+
|
|
41
|
+
if (propertyKey) {
|
|
42
|
+
this.propertyKey = propertyKey;
|
|
43
|
+
this.hasNextFunction = this.hasParamType(ParamTypes.NEXT_FN);
|
|
44
|
+
|
|
45
|
+
if (this.hasParamType(ParamTypes.ERR)) {
|
|
46
|
+
this.type = PlatformHandlerType.ERR_MIDDLEWARE;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (this.#handler.length === 4) {
|
|
50
|
+
this.type = PlatformHandlerType.RAW_ERR_FN;
|
|
51
|
+
}
|
|
52
|
+
this.hasNextFunction = this.#handler.length >= 3;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get target() {
|
|
57
|
+
return this.provider?.useClass || this.#handler;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get token() {
|
|
61
|
+
return this.provider?.token || this.#handler;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get handler() {
|
|
65
|
+
return this.#handler;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get scope() {
|
|
69
|
+
return this.provider?.scope || ProviderScope.SINGLETON;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get hasErrorParam() {
|
|
73
|
+
return this.type === PlatformHandlerType.ERR_MIDDLEWARE || this.type === PlatformHandlerType.RAW_ERR_FN;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get store() {
|
|
77
|
+
return JsonEntityStore.fromMethod(this.provider!.useClass, this.propertyKey!);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static from(input: any, opts: PlatformHandlerMetadataOpts = {}): PlatformHandlerMetadata {
|
|
81
|
+
if (input instanceof PlatformHandlerMetadata) {
|
|
82
|
+
return input;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (input instanceof EndpointMetadata) {
|
|
86
|
+
const provider = injector().getProvider(opts.token)!;
|
|
87
|
+
|
|
88
|
+
return new PlatformHandlerMetadata({
|
|
89
|
+
provider,
|
|
90
|
+
type: PlatformHandlerType.ENDPOINT,
|
|
91
|
+
propertyKey: input.propertyKey,
|
|
92
|
+
opts
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const provider = injector().getProvider(input);
|
|
97
|
+
|
|
98
|
+
if (provider) {
|
|
99
|
+
return new PlatformHandlerMetadata({
|
|
100
|
+
provider,
|
|
101
|
+
type: PlatformHandlerType.MIDDLEWARE,
|
|
102
|
+
propertyKey: "use",
|
|
103
|
+
opts
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new PlatformHandlerMetadata({
|
|
108
|
+
handler: input,
|
|
109
|
+
type: input.type,
|
|
110
|
+
opts
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public getParams() {
|
|
115
|
+
return JsonParameterStore.getParams(this.target, this.propertyKey) || [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public hasParamType(paramType: any): boolean {
|
|
119
|
+
return this.getParams().findIndex((p) => p.paramType === paramType) > -1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public isInjectable() {
|
|
123
|
+
return !(this.isRawFn() || this.isResponseFn());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public isRawFn() {
|
|
127
|
+
return this.type === PlatformHandlerType.RAW_ERR_FN || this.type === PlatformHandlerType.RAW_FN;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public isEndpoint() {
|
|
131
|
+
return this.type === PlatformHandlerType.ENDPOINT;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public isCtxFn() {
|
|
135
|
+
return this.type === PlatformHandlerType.CTX_FN;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public isResponseFn() {
|
|
139
|
+
return this.type === PlatformHandlerType.RESPONSE_FN;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
toString() {
|
|
143
|
+
return [nameOf(this.target), this.propertyKey].filter(Boolean).join(".");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export enum PlatformHandlerType {
|
|
2
|
+
CUSTOM = "custom",
|
|
3
|
+
ENDPOINT = "endpoint",
|
|
4
|
+
MIDDLEWARE = "middleware",
|
|
5
|
+
ERR_MIDDLEWARE = "err:middleware",
|
|
6
|
+
CTX_FN = "context",
|
|
7
|
+
RESPONSE_FN = "response",
|
|
8
|
+
RAW_FN = "raw:middleware",
|
|
9
|
+
RAW_ERR_FN = "raw:err:middleware"
|
|
10
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {isPrimitive, nameOf, Type} from "@tsed/core";
|
|
2
|
+
import {Provider} from "@tsed/di";
|
|
3
|
+
import type {PlatformParamsCallback} from "@tsed/platform-params";
|
|
4
|
+
import {concatPath} from "@tsed/schema";
|
|
5
|
+
|
|
6
|
+
import {PlatformHandlerMetadata} from "./PlatformHandlerMetadata.js";
|
|
7
|
+
import type {PlatformRouter} from "./PlatformRouter.js";
|
|
8
|
+
import {SinglePathType} from "./SinglePathType.js";
|
|
9
|
+
|
|
10
|
+
export interface PlatformLayerOptions extends Record<string, any> {
|
|
11
|
+
token?: Type<any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PlatformLayerProps {
|
|
15
|
+
parent: PlatformLayer;
|
|
16
|
+
provider: Provider;
|
|
17
|
+
path: SinglePathType;
|
|
18
|
+
basePath: SinglePathType;
|
|
19
|
+
method: string;
|
|
20
|
+
handlers: any[];
|
|
21
|
+
router: PlatformRouter;
|
|
22
|
+
opts: PlatformLayerOptions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class PlatformLayer {
|
|
26
|
+
public provider: Provider;
|
|
27
|
+
public path: SinglePathType = "";
|
|
28
|
+
public method: string;
|
|
29
|
+
public handlers: PlatformHandlerMetadata[] = [];
|
|
30
|
+
public router?: PlatformRouter;
|
|
31
|
+
public opts: PlatformLayerOptions = {};
|
|
32
|
+
|
|
33
|
+
public layers: PlatformLayer[] = [];
|
|
34
|
+
|
|
35
|
+
public parent?: PlatformLayer;
|
|
36
|
+
basePath: SinglePathType;
|
|
37
|
+
#args: PlatformParamsCallback[];
|
|
38
|
+
|
|
39
|
+
constructor(props: Partial<PlatformLayerProps> = {}) {
|
|
40
|
+
Object.assign(this, props);
|
|
41
|
+
|
|
42
|
+
this.layers = this.layers.map((layer) => {
|
|
43
|
+
return new PlatformLayer({
|
|
44
|
+
...layer,
|
|
45
|
+
parent: this
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
set(args: PlatformParamsCallback[]) {
|
|
51
|
+
this.#args = args;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getArgs() {
|
|
55
|
+
return [this.path, ...this.#args].filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
isProvider() {
|
|
59
|
+
return !!this.provider;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
inspect() {
|
|
63
|
+
return {
|
|
64
|
+
basePath: this.getBasePath(),
|
|
65
|
+
path: this.path,
|
|
66
|
+
method: this.method,
|
|
67
|
+
handlers: this.handlers.map((item: any) => String(item)),
|
|
68
|
+
opts: Object.entries(this.opts).reduce((obj, [key, value]) => {
|
|
69
|
+
return {
|
|
70
|
+
...obj,
|
|
71
|
+
[key]: isPrimitive(value) ? value : nameOf(value)
|
|
72
|
+
};
|
|
73
|
+
}, {})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getBasePath(): string {
|
|
78
|
+
return concatPath(this.parent?.getBasePath(), this.basePath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setLayers(layers: PlatformLayer[]) {
|
|
82
|
+
this.layers = layers.map((layer) => {
|
|
83
|
+
return new PlatformLayer({
|
|
84
|
+
...layer,
|
|
85
|
+
parent: this
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {isString} from "@tsed/core";
|
|
2
|
+
import {injectable, Provider, ProviderScope} from "@tsed/di";
|
|
3
|
+
import {concatPath} from "@tsed/schema";
|
|
4
|
+
|
|
5
|
+
import {formatMethod} from "../utils/formatMethod.js";
|
|
6
|
+
import {PlatformHandlerMetadata} from "./PlatformHandlerMetadata.js";
|
|
7
|
+
import {PlatformLayer, PlatformLayerOptions} from "./PlatformLayer.js";
|
|
8
|
+
import {SinglePathType} from "./SinglePathType.js";
|
|
9
|
+
|
|
10
|
+
function printHandler(handler: any) {
|
|
11
|
+
return handler.toString().split("{")[0].trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class PlatformRouter {
|
|
15
|
+
readonly layers: PlatformLayer[] = [];
|
|
16
|
+
provider: Provider;
|
|
17
|
+
#isBuilt = false;
|
|
18
|
+
|
|
19
|
+
use(...handlers: any[]) {
|
|
20
|
+
const layer = handlers.reduce(
|
|
21
|
+
(layer: PlatformLayer, item) => {
|
|
22
|
+
if (isString(item) || item instanceof RegExp) {
|
|
23
|
+
layer.path = item;
|
|
24
|
+
layer.basePath = item;
|
|
25
|
+
} else {
|
|
26
|
+
if (item instanceof PlatformRouter) {
|
|
27
|
+
layer.router = item;
|
|
28
|
+
layer.setLayers(item.layers);
|
|
29
|
+
|
|
30
|
+
if (!this.provider && item.provider) {
|
|
31
|
+
layer.path = concatPath(layer.path, item.provider.path);
|
|
32
|
+
} else {
|
|
33
|
+
layer.path = layer.path || item.provider.path;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
item = PlatformHandlerMetadata.from(item);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
layer.handlers.push(item);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return layer;
|
|
43
|
+
},
|
|
44
|
+
new PlatformLayer({
|
|
45
|
+
method: "use",
|
|
46
|
+
basePath: this.provider?.path || "/",
|
|
47
|
+
provider: this.provider
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
this.layers.push(layer);
|
|
52
|
+
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addRoute(method: string, path: SinglePathType, handlers: any[], opts: PlatformLayerOptions = {}) {
|
|
57
|
+
const layer = new PlatformLayer({
|
|
58
|
+
provider: this.provider,
|
|
59
|
+
method: formatMethod(method),
|
|
60
|
+
path,
|
|
61
|
+
handlers: handlers.map((input) => {
|
|
62
|
+
return PlatformHandlerMetadata.from(input, opts);
|
|
63
|
+
}),
|
|
64
|
+
opts
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.layers.push(layer);
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
all(path: SinglePathType, ...handlers: any[]) {
|
|
73
|
+
return this.addRoute("all", path, handlers);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get(path: SinglePathType, ...handlers: any[]) {
|
|
77
|
+
return this.addRoute("get", path, handlers);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
post(path: SinglePathType, ...handlers: any[]) {
|
|
81
|
+
return this.addRoute("post", path, handlers);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
put(path: SinglePathType, ...handlers: any[]) {
|
|
85
|
+
return this.addRoute("put", path, handlers);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
delete(path: SinglePathType, ...handlers: any[]) {
|
|
89
|
+
return this.addRoute("delete", path, handlers);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
patch(path: SinglePathType, ...handlers: any[]) {
|
|
93
|
+
return this.addRoute("patch", path, handlers);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
head(path: SinglePathType, ...handlers: any[]) {
|
|
97
|
+
return this.addRoute("head", path, handlers);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
options(path: SinglePathType, ...handlers: any[]) {
|
|
101
|
+
return this.addRoute("options", path, handlers);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
statics(path: string, options: any) {
|
|
105
|
+
return this.addRoute("statics", path, [], options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
inspect() {
|
|
109
|
+
return this.layers.map((layer) => {
|
|
110
|
+
const obj = layer.inspect();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...obj,
|
|
114
|
+
handlers: obj.handlers.map(printHandler),
|
|
115
|
+
path: concatPath(this.provider?.path, obj.path)
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isBuilt() {
|
|
121
|
+
if (this.#isBuilt) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.#isBuilt = true;
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
injectable(PlatformRouter).scope(ProviderScope.INSTANCE);
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {getValue, Type} from "@tsed/core";
|
|
2
|
+
import {
|
|
3
|
+
constant,
|
|
4
|
+
ControllerProvider,
|
|
5
|
+
inject,
|
|
6
|
+
injectable,
|
|
7
|
+
injector,
|
|
8
|
+
Provider,
|
|
9
|
+
ProviderType,
|
|
10
|
+
ResolvedInvokeOptions,
|
|
11
|
+
TokenProvider
|
|
12
|
+
} from "@tsed/di";
|
|
13
|
+
import {$on, Hooks} from "@tsed/hooks";
|
|
14
|
+
import {PlatformParamsCallback} from "@tsed/platform-params";
|
|
15
|
+
import {concatPath, getOperationsRoutes, JsonMethodStore, OPERATION_HTTP_VERBS} from "@tsed/schema";
|
|
16
|
+
|
|
17
|
+
import {useContextHandler} from "../utils/useContextHandler.js";
|
|
18
|
+
import {PlatformHandlerMetadata} from "./PlatformHandlerMetadata.js";
|
|
19
|
+
import {PlatformLayer} from "./PlatformLayer.js";
|
|
20
|
+
import {PlatformRouter} from "./PlatformRouter.js";
|
|
21
|
+
|
|
22
|
+
let AUTO_INC = 0;
|
|
23
|
+
|
|
24
|
+
function getInjectableRouter(provider: Provider): PlatformRouter {
|
|
25
|
+
return injector().get(provider.tokenRouter)!;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createTokenRouter(provider: ControllerProvider) {
|
|
29
|
+
return (provider.tokenRouter = provider.tokenRouter || `${provider.name}_ROUTER_${AUTO_INC++}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createInjectableRouter(provider: ControllerProvider): PlatformRouter {
|
|
33
|
+
const tokenRouter = createTokenRouter(provider);
|
|
34
|
+
|
|
35
|
+
if (injector().has(tokenRouter)) {
|
|
36
|
+
return getInjectableRouter(provider);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const router = inject<PlatformRouter>(PlatformRouter);
|
|
40
|
+
router.provider = provider;
|
|
41
|
+
|
|
42
|
+
return injector()
|
|
43
|
+
.add(tokenRouter, {
|
|
44
|
+
useValue: router
|
|
45
|
+
})
|
|
46
|
+
.invoke<PlatformRouter>(tokenRouter);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AlterEndpointHandlersArg {
|
|
50
|
+
before: (Type<any> | Function)[];
|
|
51
|
+
endpoint: JsonMethodStore;
|
|
52
|
+
after: (Type<any> | Function)[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class PlatformRouters {
|
|
56
|
+
readonly hooks = new Hooks();
|
|
57
|
+
readonly allowedVerbs = OPERATION_HTTP_VERBS;
|
|
58
|
+
|
|
59
|
+
prebuild() {
|
|
60
|
+
injector()
|
|
61
|
+
.getProviders(ProviderType.CONTROLLER)
|
|
62
|
+
.forEach((provider: ControllerProvider) => {
|
|
63
|
+
createInjectableRouter(provider);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
from(token: TokenProvider, parentMiddlewares: any[] = []) {
|
|
68
|
+
const provider = injector().getProvider<ControllerProvider>(token)!;
|
|
69
|
+
|
|
70
|
+
if (!provider) {
|
|
71
|
+
throw new Error("Token not found in the provider registry");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const router = createInjectableRouter(provider);
|
|
75
|
+
|
|
76
|
+
if (router.isBuilt()) {
|
|
77
|
+
return router;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const useBefore = getValue(provider, "middlewares.useBefore", []);
|
|
81
|
+
const middlewares: any[] = [...parentMiddlewares, ...useBefore];
|
|
82
|
+
const {children} = provider;
|
|
83
|
+
|
|
84
|
+
// Set default to true in next major version
|
|
85
|
+
const appendChildrenRoutesFirst = constant<boolean>("router.appendChildrenRoutesFirst", false);
|
|
86
|
+
|
|
87
|
+
if (appendChildrenRoutesFirst) {
|
|
88
|
+
children.forEach((token: Type<any>) => {
|
|
89
|
+
const nested = this.from(token, middlewares);
|
|
90
|
+
router.use(nested);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getOperationsRoutes(provider.token, {allowedVerbs: this.allowedVerbs}).forEach((operationRoute) => {
|
|
95
|
+
const {endpoint} = operationRoute;
|
|
96
|
+
const {beforeMiddlewares, middlewares: mldwrs, afterMiddlewares} = endpoint;
|
|
97
|
+
|
|
98
|
+
const useBefore = getValue(provider, "middlewares.useBefore", []);
|
|
99
|
+
const use = getValue(provider, "middlewares.use", []);
|
|
100
|
+
const useAfter = getValue(provider, "middlewares.useAfter", []);
|
|
101
|
+
|
|
102
|
+
let handlers = this.hooks.alter<AlterEndpointHandlersArg>(
|
|
103
|
+
"alterEndpointHandlers",
|
|
104
|
+
{
|
|
105
|
+
before: [...parentMiddlewares, ...useBefore, ...beforeMiddlewares, ...use, ...mldwrs],
|
|
106
|
+
endpoint,
|
|
107
|
+
after: [...afterMiddlewares, ...useAfter]
|
|
108
|
+
},
|
|
109
|
+
[operationRoute],
|
|
110
|
+
this
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
handlers = this.sortHandlers(handlers);
|
|
114
|
+
|
|
115
|
+
router.addRoute(
|
|
116
|
+
operationRoute.method,
|
|
117
|
+
operationRoute.path || "",
|
|
118
|
+
[
|
|
119
|
+
useContextHandler(($ctx) => {
|
|
120
|
+
$ctx.endpoint = operationRoute.endpoint;
|
|
121
|
+
}),
|
|
122
|
+
...[...handlers.before, handlers.endpoint, ...handlers.after]
|
|
123
|
+
],
|
|
124
|
+
operationRoute
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!appendChildrenRoutesFirst) {
|
|
129
|
+
children.forEach((token: Type<any>) => {
|
|
130
|
+
const nested = this.from(token, middlewares);
|
|
131
|
+
router.use(nested);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return router;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getLayers(router: PlatformRouter): PlatformLayer[] {
|
|
139
|
+
return this.flatMapLayers(router.layers);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private sortHandlers(handlers: AlterEndpointHandlersArg) {
|
|
143
|
+
const get = (token: TokenProvider) => {
|
|
144
|
+
return injector().getProvider(token)?.priority || 0;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const sort = (p1: TokenProvider, p2: TokenProvider) => (get(p1) < get(p2) ? -1 : get(p1) > get(p2) ? 1 : 0);
|
|
148
|
+
|
|
149
|
+
handlers.before = handlers.before.sort(sort);
|
|
150
|
+
handlers.after = handlers.after.sort(sort);
|
|
151
|
+
|
|
152
|
+
return handlers;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private flatMapLayers(layers: PlatformLayer[]): PlatformLayer[] {
|
|
156
|
+
return layers
|
|
157
|
+
.flatMap((layer) => {
|
|
158
|
+
if (layer.router) {
|
|
159
|
+
return this.flatMapLayers(layer.layers).map((subLayer: PlatformLayer) => {
|
|
160
|
+
return new PlatformLayer({
|
|
161
|
+
...subLayer,
|
|
162
|
+
path: concatPath(layer.path, subLayer.path)
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return new PlatformLayer(layer);
|
|
168
|
+
})
|
|
169
|
+
.map((layer) => {
|
|
170
|
+
const handlers = layer.handlers.map((handlerMetadata) => {
|
|
171
|
+
// set path on handler metadata to retrieve it later in $ctx
|
|
172
|
+
handlerMetadata.path = layer.path;
|
|
173
|
+
|
|
174
|
+
if (handlerMetadata.isRawFn()) {
|
|
175
|
+
return handlerMetadata.handler;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this.hooks.alter<PlatformHandlerMetadata, PlatformParamsCallback>("alterHandler", handlerMetadata);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
layer.set(handlers);
|
|
182
|
+
|
|
183
|
+
return layer;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
injectable(PlatformRouters);
|
|
189
|
+
/**
|
|
190
|
+
* Create injectable router for the current invoked provider.
|
|
191
|
+
* @ignore
|
|
192
|
+
*/
|
|
193
|
+
$on(`$beforeInvoke:${ProviderType.CONTROLLER}`, ({provider, locals}: ResolvedInvokeOptions) => {
|
|
194
|
+
const router = createInjectableRouter(provider as ControllerProvider);
|
|
195
|
+
locals.set(PlatformRouter, router);
|
|
196
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type SinglePathType = string | RegExp;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Automatically generated by @tsed/barrels.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./domain/PlatformHandlerMetadata.js";
|
|
5
|
+
export * from "./domain/PlatformHandlerType.js";
|
|
6
|
+
export * from "./domain/PlatformLayer.js";
|
|
7
|
+
export * from "./domain/PlatformRouter.js";
|
|
8
|
+
export * from "./domain/PlatformRouters.js";
|
|
9
|
+
export * from "./domain/SinglePathType.js";
|
|
10
|
+
export * from "./utils/formatMethod.js";
|
|
11
|
+
export * from "./utils/useContextHandler.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {DIContext} from "@tsed/di";
|
|
2
|
+
|
|
3
|
+
import {PlatformHandlerType} from "../domain/PlatformHandlerType.js";
|
|
4
|
+
|
|
5
|
+
export type PlatformContextHandler<Context = DIContext> = ($ctx: Context) => any | Promise<any>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create Ts.ED context handler
|
|
9
|
+
* @param fn
|
|
10
|
+
* @ignore
|
|
11
|
+
*/
|
|
12
|
+
export function useContextHandler(fn: PlatformContextHandler & {type?: PlatformHandlerType}) {
|
|
13
|
+
fn.type = PlatformHandlerType.CTX_FN;
|
|
14
|
+
|
|
15
|
+
return fn;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useResponseHandler(fn: PlatformContextHandler & {type?: PlatformHandlerType}) {
|
|
19
|
+
fn.type = PlatformHandlerType.RESPONSE_FN;
|
|
20
|
+
|
|
21
|
+
return fn;
|
|
22
|
+
}
|