@nestia/sdk 2.3.0-dev.20231019 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import path from "path";
2
2
  import ts from "typescript";
3
3
 
4
4
  import { IController } from "../structures/IController";
5
+ import { INestiaProject } from "../structures/INestiaProject";
5
6
  import { IRoute } from "../structures/IRoute";
6
7
  import { ITypeTuple } from "../structures/ITypeTuple";
7
8
  import { GenericAnalyzer } from "./GenericAnalyzer";
@@ -9,12 +10,12 @@ import { ImportAnalyzer } from "./ImportAnalyzer";
9
10
 
10
11
  export namespace ExceptionAnalyzer {
11
12
  export const analyze =
12
- (checker: ts.TypeChecker) =>
13
+ (project: INestiaProject) =>
13
14
  (
14
15
  genericDict: GenericAnalyzer.Dictionary,
15
16
  importDict: ImportAnalyzer.Dictionary,
16
17
  ) =>
17
- (func: IController.IFunction) =>
18
+ (controller: IController, func: IController.IFunction) =>
18
19
  (
19
20
  declaration: ts.MethodDeclaration,
20
21
  ): Record<number | "2XX" | "3XX" | "4XX" | "5XX", IRoute.IOutput> => {
@@ -24,19 +25,20 @@ export namespace ExceptionAnalyzer {
24
25
  > = {} as any;
25
26
  for (const decorator of declaration.modifiers ?? [])
26
27
  if (ts.isDecorator(decorator))
27
- analyzeTyped(checker)(genericDict, importDict)(func)(
28
- output,
29
- )(decorator);
28
+ analyzeTyped(project)(genericDict, importDict)(
29
+ controller,
30
+ func,
31
+ )(output)(decorator);
30
32
  return output;
31
33
  };
32
34
 
33
35
  const analyzeTyped =
34
- (checker: ts.TypeChecker) =>
36
+ (project: INestiaProject) =>
35
37
  (
36
38
  genericDict: GenericAnalyzer.Dictionary,
37
39
  importDict: ImportAnalyzer.Dictionary,
38
40
  ) =>
39
- (func: IController.IFunction) =>
41
+ (controller: IController, func: IController.IFunction) =>
40
42
  (
41
43
  output: Record<
42
44
  number | "2XX" | "3XX" | "4XX" | "5XX",
@@ -51,7 +53,7 @@ export namespace ExceptionAnalyzer {
51
53
 
52
54
  // CHECK SIGNATURE
53
55
  const signature: ts.Signature | undefined =
54
- checker.getResolvedSignature(decorator.expression);
56
+ project.checker.getResolvedSignature(decorator.expression);
55
57
  if (!signature || !signature.declaration) return false;
56
58
  else if (
57
59
  path
@@ -62,19 +64,33 @@ export namespace ExceptionAnalyzer {
62
64
 
63
65
  // GET TYPE INFO
64
66
  const node: ts.TypeNode = decorator.expression.typeArguments![0];
65
- const type: ts.Type = checker.getTypeFromTypeNode(node);
66
- if (type.isTypeParameter())
67
- throw new Error(
68
- "Error on @nestia.core.TypedException(): non-specified generic argument.",
69
- );
67
+ const type: ts.Type = project.checker.getTypeFromTypeNode(node);
68
+ if (type.isTypeParameter()) {
69
+ project.errors.push({
70
+ file: controller.file,
71
+ controller: controller.name,
72
+ function: func.name,
73
+ message:
74
+ "TypedException() without generic argument specification.",
75
+ });
76
+ return false;
77
+ }
70
78
 
71
79
  const tuple: ITypeTuple | null = ImportAnalyzer.analyze(
72
- checker,
80
+ project.checker,
73
81
  genericDict,
74
82
  importDict,
75
83
  type,
76
84
  );
77
- if (tuple === null) return false;
85
+ if (tuple === null || tuple.typeName === "__type") {
86
+ project.errors.push({
87
+ file: controller.file,
88
+ controller: controller.name,
89
+ function: func.name,
90
+ message: "TypeException() with implicit (unnamed) type.",
91
+ });
92
+ return false;
93
+ }
78
94
 
79
95
  // DO ASSIGN
80
96
  const matched: IController.IException[] = Object.entries(
@@ -4,6 +4,8 @@ import "reflect-metadata";
4
4
  import { equal } from "tstl/ranges/module";
5
5
 
6
6
  import { IController } from "../structures/IController";
7
+ import { IErrorReport } from "../structures/IErrorReport";
8
+ import { INestiaProject } from "../structures/INestiaProject";
7
9
  import { ParamCategory } from "../structures/ParamCategory";
8
10
  import { ArrayUtil } from "../utils/ArrayUtil";
9
11
  import { PathAnalyzer } from "./PathAnalyzer";
@@ -14,105 +16,107 @@ type IModule = {
14
16
  };
15
17
 
16
18
  export namespace ReflectAnalyzer {
17
- export async function analyze(
18
- unique: WeakSet<any>,
19
- file: string,
20
- prefixes: string[],
21
- target?: Function,
22
- ): Promise<IController[]> {
23
- const module: IModule = await (async () => {
24
- try {
25
- return await import(file);
26
- } catch (exp) {
27
- console.log(
28
- ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
29
- );
30
- console.log(`Error on "${file}" file. Check your code.`);
31
- console.log(exp);
32
- console.log(
33
- ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
19
+ export const analyze =
20
+ (project: INestiaProject) =>
21
+ async (
22
+ unique: WeakSet<any>,
23
+ file: string,
24
+ prefixes: string[],
25
+ target?: Function,
26
+ ): Promise<IController[]> => {
27
+ const module: IModule = await (async () => {
28
+ try {
29
+ return await import(file);
30
+ } catch (exp) {
31
+ console.log(
32
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
33
+ );
34
+ console.log(`Error on "${file}" file. Check your code.`);
35
+ console.log(exp);
36
+ console.log(
37
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
38
+ );
39
+ process.exit(-1);
40
+ }
41
+ })();
42
+ const ret: IController[] = [];
43
+
44
+ for (const [key, value] of Object.entries(module)) {
45
+ if (typeof value !== "function" || unique.has(value)) continue;
46
+ else if ((target ?? value) !== value) continue;
47
+ else unique.add(value);
48
+
49
+ const result: IController | null = _Analyze_controller(project)(
50
+ file,
51
+ key,
52
+ value,
53
+ prefixes,
34
54
  );
35
- process.exit(-1);
55
+ if (result !== null) ret.push(result);
36
56
  }
37
- })();
38
- const ret: IController[] = [];
39
-
40
- for (const [key, value] of Object.entries(module)) {
41
- if (typeof value !== "function" || unique.has(value)) continue;
42
- else if ((target ?? value) !== value) continue;
43
- else unique.add(value);
44
-
45
- const result: IController | null = _Analyze_controller(
46
- file,
47
- key,
48
- value,
49
- prefixes,
50
- );
51
- if (result !== null) ret.push(result);
52
- }
53
- return ret;
54
- }
57
+ return ret;
58
+ };
55
59
 
56
60
  /* ---------------------------------------------------------
57
61
  CONTROLLER
58
62
  --------------------------------------------------------- */
59
- function _Analyze_controller(
60
- file: string,
61
- name: string,
62
- creator: any,
63
- prefixes: string[],
64
- ): IController | null {
65
- //----
66
- // VALIDATIONS
67
- //----
68
- // MUST BE TYPE OF A CREATOR WHO HAS THE CONSTRUCTOR
69
- if (
70
- !(
71
- creator instanceof Function &&
72
- creator.constructor instanceof Function
63
+ const _Analyze_controller =
64
+ (project: INestiaProject) =>
65
+ (
66
+ file: string,
67
+ name: string,
68
+ creator: any,
69
+ prefixes: string[],
70
+ ): IController | null => {
71
+ //----
72
+ // VALIDATIONS
73
+ //----
74
+ // MUST BE TYPE OF A CREATOR WHO HAS THE CONSTRUCTOR
75
+ if (
76
+ !(
77
+ creator instanceof Function &&
78
+ creator.constructor instanceof Function
79
+ )
73
80
  )
74
- )
75
- return null;
76
- // MUST HAVE THOSE MATADATA
77
- else if (
78
- ArrayUtil.has(
79
- Reflect.getMetadataKeys(creator),
80
- Constants.PATH_METADATA,
81
- Constants.HOST_METADATA,
82
- Constants.SCOPE_OPTIONS_METADATA,
83
- ) === false
84
- )
85
- return null;
86
-
87
- //----
88
- // CONSTRUCTION
89
- //----
90
- // BASIC INFO
91
- const meta: IController = {
92
- file,
93
- name,
94
- functions: [],
95
- prefixes,
96
- paths: _Get_paths(creator),
97
- versions: _Get_versions(creator),
98
- security: _Get_securities(creator),
99
- swaggerTgas:
100
- Reflect.getMetadata("swagger/apiUseTags", creator) ?? [],
101
- };
81
+ return null;
82
+ // MUST HAVE THOSE MATADATA
83
+ else if (
84
+ ArrayUtil.has(
85
+ Reflect.getMetadataKeys(creator),
86
+ Constants.PATH_METADATA,
87
+ Constants.HOST_METADATA,
88
+ Constants.SCOPE_OPTIONS_METADATA,
89
+ ) === false
90
+ )
91
+ return null;
102
92
 
103
- // PARSE CHILDREN DATA
104
- for (const tuple of _Get_prototype_entries(creator)) {
105
- const child: IController.IFunction | null = _Analyze_function(
106
- creator.prototype,
107
- meta,
108
- ...tuple,
109
- );
110
- if (child !== null) meta.functions.push(child);
111
- }
93
+ //----
94
+ // CONSTRUCTION
95
+ //----
96
+ // BASIC INFO
97
+ const meta: IController = {
98
+ file,
99
+ name,
100
+ functions: [],
101
+ prefixes,
102
+ paths: _Get_paths(creator),
103
+ versions: _Get_versions(creator),
104
+ security: _Get_securities(creator),
105
+ swaggerTgas:
106
+ Reflect.getMetadata("swagger/apiUseTags", creator) ?? [],
107
+ };
112
108
 
113
- // RETURNS
114
- return meta;
115
- }
109
+ // PARSE CHILDREN DATA
110
+ for (const tuple of _Get_prototype_entries(creator)) {
111
+ const child: IController.IFunction | null = _Analyze_function(
112
+ project,
113
+ )(creator.prototype, meta, ...tuple);
114
+ if (child !== null) meta.functions.push(child);
115
+ }
116
+
117
+ // RETURNS
118
+ return meta;
119
+ };
116
120
 
117
121
  function _Get_prototype_entries(creator: any): Array<[string, unknown]> {
118
122
  const keyList = Object.getOwnPropertyNames(creator.prototype);
@@ -172,132 +176,152 @@ export namespace ReflectAnalyzer {
172
176
  /* ---------------------------------------------------------
173
177
  FUNCTION
174
178
  --------------------------------------------------------- */
175
- function _Analyze_function(
176
- classProto: any,
177
- controller: IController,
178
- name: string,
179
- proto: any,
180
- ): IController.IFunction | null {
181
- //----
182
- // VALIDATIONS
183
- //----
184
- // MUST BE TYPE OF A FUNCTION
185
- if (!(proto instanceof Function)) return null;
186
- // MUST HAVE THOSE METADATE
187
- else if (
188
- ArrayUtil.has(
189
- Reflect.getMetadataKeys(proto),
190
- Constants.PATH_METADATA,
191
- Constants.METHOD_METADATA,
192
- ) === false
193
- )
194
- return null;
195
-
196
- //----
197
- // CONSTRUCTION
198
- //----
199
- // BASIC INFO
200
- const encrypted: boolean =
201
- Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
202
- ?.constructor?.name === "EncryptedRouteInterceptor";
203
- const query: boolean =
204
- Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
205
- ?.constructor?.name === "TypedQueryRouteInterceptor";
206
- const method: string =
207
- METHODS[Reflect.getMetadata(Constants.METHOD_METADATA, proto)];
208
- if (method === undefined || method === "OPTIONS") return null;
209
-
210
- const parameters: IController.IParameter[] = (() => {
211
- const nestParameters: NestParameters | undefined =
212
- Reflect.getMetadata(
213
- Constants.ROUTE_ARGS_METADATA,
214
- classProto.constructor,
215
- name,
216
- );
217
- if (nestParameters === undefined) return [];
218
-
219
- const output: IController.IParameter[] = [];
220
- for (const tuple of Object.entries(nestParameters)) {
221
- const child: IController.IParameter | null = _Analyze_parameter(
222
- ...tuple,
223
- );
224
- if (child !== null) output.push(child);
225
- }
226
- return output.sort((x, y) => x.index - y.index);
227
- })();
228
-
229
- // VALIDATE BODY
230
- const body: IController.IParameter | undefined = parameters.find(
231
- (param) => param.category === "body",
232
- );
233
- if (body !== undefined && (method === "GET" || method === "HEAD"))
234
- throw new Error(
235
- `Error on ${controller.name}.${name}(): "body" parameter cannot be used in the "GET" or "HEAD" method`,
179
+ const _Analyze_function =
180
+ (project: INestiaProject) =>
181
+ (
182
+ classProto: any,
183
+ controller: IController,
184
+ name: string,
185
+ proto: any,
186
+ ): IController.IFunction | null => {
187
+ //----
188
+ // VALIDATIONS
189
+ //----
190
+ // MUST BE TYPE OF A FUNCTION
191
+ if (!(proto instanceof Function)) return null;
192
+ // MUST HAVE THOSE METADATE
193
+ else if (
194
+ ArrayUtil.has(
195
+ Reflect.getMetadataKeys(proto),
196
+ Constants.PATH_METADATA,
197
+ Constants.METHOD_METADATA,
198
+ ) === false
199
+ )
200
+ return null;
201
+
202
+ const errors: IErrorReport[] = [];
203
+
204
+ //----
205
+ // CONSTRUCTION
206
+ //----
207
+ // BASIC INFO
208
+ const encrypted: boolean =
209
+ Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
210
+ ?.constructor?.name === "EncryptedRouteInterceptor";
211
+ const query: boolean =
212
+ Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
213
+ ?.constructor?.name === "TypedQueryRouteInterceptor";
214
+ const method: string =
215
+ METHODS[Reflect.getMetadata(Constants.METHOD_METADATA, proto)];
216
+ if (method === undefined || method === "OPTIONS") return null;
217
+
218
+ const parameters: IController.IParameter[] = (() => {
219
+ const nestParameters: NestParameters | undefined =
220
+ Reflect.getMetadata(
221
+ Constants.ROUTE_ARGS_METADATA,
222
+ classProto.constructor,
223
+ name,
224
+ );
225
+ if (nestParameters === undefined) return [];
226
+
227
+ const output: IController.IParameter[] = [];
228
+ for (const tuple of Object.entries(nestParameters)) {
229
+ const child: IController.IParameter | null =
230
+ _Analyze_parameter(...tuple);
231
+ if (child !== null) output.push(child);
232
+ }
233
+ return output.sort((x, y) => x.index - y.index);
234
+ })();
235
+
236
+ // VALIDATE BODY
237
+ const body: IController.IParameter | undefined = parameters.find(
238
+ (param) => param.category === "body",
236
239
  );
240
+ if (body !== undefined && (method === "GET" || method === "HEAD")) {
241
+ errors.push({
242
+ file: controller.file,
243
+ controller: controller.name,
244
+ function: name,
245
+ message: `"body" parameter cannot be used in the "${method}" method.`,
246
+ });
247
+ return null;
248
+ }
237
249
 
238
- // DO CONSTRUCT
239
- const meta: IController.IFunction = {
240
- name,
241
- method: method === "ALL" ? "POST" : method,
242
- paths: _Get_paths(proto),
243
- versions: _Get_versions(proto),
244
- parameters,
245
- status: Reflect.getMetadata(Constants.HTTP_CODE_METADATA, proto),
246
- encrypted,
247
- contentType: encrypted
248
- ? "text/plain"
249
- : query
250
- ? "application/x-www-form-urlencoded"
251
- : Reflect.getMetadata(Constants.HEADERS_METADATA, proto)?.find(
252
- (h: Record<string, string>) =>
253
- typeof h?.name === "string" &&
254
- typeof h?.value === "string" &&
255
- h.name.toLowerCase() === "content-type",
256
- )?.value ?? "application/json",
257
- security: _Get_securities(proto),
258
- exceptions: _Get_exceptions(proto),
259
- swaggerTags: [
260
- ...new Set([
261
- ...controller.swaggerTgas,
262
- ...(Reflect.getMetadata("swagger/apiUseTags", proto) ?? []),
263
- ]),
264
- ],
265
- };
266
-
267
- // VALIDATE PATH ARGUMENTS
268
- for (const controllerLocation of controller.paths)
269
- for (const metaLocation of meta.paths) {
270
- // NORMALIZE LOCATION
271
- const location: string = PathAnalyzer.join(
272
- controllerLocation,
273
- metaLocation,
274
- );
250
+ // DO CONSTRUCT
251
+ const meta: IController.IFunction = {
252
+ name,
253
+ method: method === "ALL" ? "POST" : method,
254
+ paths: _Get_paths(proto),
255
+ versions: _Get_versions(proto),
256
+ parameters,
257
+ status: Reflect.getMetadata(
258
+ Constants.HTTP_CODE_METADATA,
259
+ proto,
260
+ ),
261
+ encrypted,
262
+ contentType: encrypted
263
+ ? "text/plain"
264
+ : query
265
+ ? "application/x-www-form-urlencoded"
266
+ : Reflect.getMetadata(
267
+ Constants.HEADERS_METADATA,
268
+ proto,
269
+ )?.find(
270
+ (h: Record<string, string>) =>
271
+ typeof h?.name === "string" &&
272
+ typeof h?.value === "string" &&
273
+ h.name.toLowerCase() === "content-type",
274
+ )?.value ?? "application/json",
275
+ security: _Get_securities(proto),
276
+ exceptions: _Get_exceptions(proto),
277
+ swaggerTags: [
278
+ ...new Set([
279
+ ...controller.swaggerTgas,
280
+ ...(Reflect.getMetadata("swagger/apiUseTags", proto) ??
281
+ []),
282
+ ]),
283
+ ],
284
+ };
275
285
 
276
- // LIST UP PARAMETERS
277
- const binded: string[] = PathAnalyzer.parameters(
278
- location,
279
- () => `${controller.name}.${name}()`,
280
- ).sort();
281
-
282
- const parameters: string[] = meta.parameters
283
- .filter((param) => param.category === "param")
284
- .map((param) => param.field!)
285
- .sort();
286
-
287
- // DO VALIDATE
288
- if (equal(binded, parameters) === false)
289
- throw new Error(
290
- `Error on ${
291
- controller.name
292
- }.${name}(): binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
293
- ", ",
294
- )}], parameters: [${parameters.join(", ")}])`,
286
+ // VALIDATE PATH ARGUMENTS
287
+ for (const controllerLocation of controller.paths)
288
+ for (const metaLocation of meta.paths) {
289
+ // NORMALIZE LOCATION
290
+ const location: string = PathAnalyzer.join(
291
+ controllerLocation,
292
+ metaLocation,
295
293
  );
296
- }
297
294
 
298
- // RETURNS
299
- return meta;
300
- }
295
+ // LIST UP PARAMETERS
296
+ const binded: string[] = PathAnalyzer.parameters(
297
+ location,
298
+ () => `${controller.name}.${name}()`,
299
+ ).sort();
300
+
301
+ const parameters: string[] = meta.parameters
302
+ .filter((param) => param.category === "param")
303
+ .map((param) => param.field!)
304
+ .sort();
305
+
306
+ // DO VALIDATE
307
+ if (equal(binded, parameters) === false)
308
+ errors.push({
309
+ file: controller.file,
310
+ controller: controller.name,
311
+ function: name,
312
+ message: `binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
313
+ ", ",
314
+ )}], parameters: [${parameters.join(", ")}]).`,
315
+ });
316
+ }
317
+
318
+ // RETURNS
319
+ if (errors.length) {
320
+ project.errors.push(...errors);
321
+ return null;
322
+ }
323
+ return meta;
324
+ };
301
325
 
302
326
  /* ---------------------------------------------------------
303
327
  PARAMETER
@@ -31,6 +31,7 @@ function dependencies(argv: string[]): void {
31
31
 
32
32
  for (const lib of ["@nestia/fetcher", "typia"]) {
33
33
  const command: string = `${prefix} ${lib}`;
34
+ console.log(`\n$ ${command}`);
34
35
  cp.execSync(command, { stdio: "inherit" });
35
36
  }
36
37
  }
@@ -0,0 +1,6 @@
1
+ export interface IErrorReport {
2
+ file: string;
3
+ controller: string;
4
+ function: string;
5
+ message: string;
6
+ }
@@ -0,0 +1,12 @@
1
+ import ts from "typescript";
2
+
3
+ import { INestiaConfig } from "../INestiaConfig";
4
+ import { IErrorReport } from "./IErrorReport";
5
+ import { INormalizedInput } from "./INormalizedInput";
6
+
7
+ export interface INestiaProject {
8
+ config: INestiaConfig;
9
+ input: INormalizedInput;
10
+ checker: ts.TypeChecker;
11
+ errors: IErrorReport[];
12
+ }