@nestia/sdk 1.0.0 → 1.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 (51) hide show
  1. package/assets/config/nestia.config.ts +70 -70
  2. package/lib/INestiaConfig.d.ts +28 -9
  3. package/lib/analyses/ControllerAnalyzer.js +21 -1
  4. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  5. package/lib/executable/internal/NestiaSdkConfig.js +168 -14
  6. package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
  7. package/lib/executable/sdk.js +16 -16
  8. package/lib/generates/FunctionGenerator.js +35 -15
  9. package/lib/generates/FunctionGenerator.js.map +1 -1
  10. package/lib/generates/SwaggerGenerator.d.ts +1 -1
  11. package/lib/generates/SwaggerGenerator.js +36 -16
  12. package/lib/generates/SwaggerGenerator.js.map +1 -1
  13. package/lib/structures/IRoute.d.ts +8 -0
  14. package/lib/structures/ISwaggerDocument.d.ts +95 -0
  15. package/lib/structures/{ISwagger.js → ISwaggerDocument.js} +1 -1
  16. package/lib/structures/ISwaggerDocument.js.map +1 -0
  17. package/package.json +3 -3
  18. package/src/INestiaConfig.ts +147 -120
  19. package/src/NestiaSdkApplication.ts +183 -183
  20. package/src/analyses/ControllerAnalyzer.ts +223 -203
  21. package/src/analyses/GenericAnalyzer.ts +53 -53
  22. package/src/analyses/ImportAnalyzer.ts +143 -143
  23. package/src/analyses/PathAnalyzer.ts +58 -58
  24. package/src/analyses/ReflectAnalyzer.ts +279 -279
  25. package/src/analyses/SourceFinder.ts +59 -59
  26. package/src/executable/internal/CommandParser.ts +15 -15
  27. package/src/executable/internal/NestiaConfigCompilerOptions.ts +18 -18
  28. package/src/executable/internal/NestiaSdkCommand.ts +174 -174
  29. package/src/executable/internal/NestiaSdkConfig.ts +35 -35
  30. package/src/executable/internal/nestia.config.getter.ts +12 -12
  31. package/src/executable/sdk.ts +74 -74
  32. package/src/generates/FileGenerator.ts +156 -156
  33. package/src/generates/FunctionGenerator.ts +322 -287
  34. package/src/generates/SdkGenerator.ts +50 -50
  35. package/src/generates/SwaggerGenerator.ts +422 -393
  36. package/src/index.ts +3 -3
  37. package/src/module.ts +2 -2
  38. package/src/structures/IController.ts +27 -27
  39. package/src/structures/IRoute.ts +33 -29
  40. package/src/structures/ISwaggerDocument.ts +117 -0
  41. package/src/structures/ITypeTuple.ts +6 -6
  42. package/src/structures/MethodType.ts +11 -11
  43. package/src/structures/ParamCategory.ts +1 -1
  44. package/src/structures/TypeEntry.ts +22 -22
  45. package/src/utils/ArrayUtil.ts +26 -26
  46. package/src/utils/ImportDictionary.ts +56 -56
  47. package/src/utils/MapUtil.ts +14 -14
  48. package/src/utils/StripEnums.ts +10 -10
  49. package/lib/structures/ISwagger.d.ts +0 -48
  50. package/lib/structures/ISwagger.js.map +0 -1
  51. package/src/structures/ISwagger.ts +0 -55
@@ -1,287 +1,322 @@
1
- import { Vector } from "tstl/container/Vector";
2
- import { Pair } from "tstl/utility/Pair";
3
- import ts from "typescript";
4
- import { Escaper } from "typia/lib/utils/Escaper";
5
-
6
- import { INestiaConfig } from "../INestiaConfig";
7
- import { IRoute } from "../structures/IRoute";
8
-
9
- export namespace FunctionGenerator {
10
- export function generate(config: INestiaConfig, route: IRoute): string {
11
- const query: IRoute.IParameter | undefined = route.parameters.find(
12
- (param) => param.category === "query" && param.field === undefined,
13
- );
14
- const input: IRoute.IParameter | undefined = route.parameters.find(
15
- (param) => param.category === "body",
16
- );
17
-
18
- return [head, body, tail]
19
- .map((closure) => closure(route, query, input, config))
20
- .filter((str) => !!str)
21
- .join("\n");
22
- }
23
-
24
- /* ---------------------------------------------------------
25
- BODY
26
- --------------------------------------------------------- */
27
- function body(
28
- route: IRoute,
29
- query: IRoute.IParameter | undefined,
30
- input: IRoute.IParameter | undefined,
31
- config: INestiaConfig,
32
- ): string {
33
- // FETCH ARGUMENTS WITH REQUST BODY
34
- const parameters = filter_parameters(route, query);
35
- const fetchArguments: string[] = [
36
- "connection",
37
- `${route.name}.ENCRYPTED`,
38
- `${route.name}.METHOD`,
39
- `${route.name}.path(${parameters.map((p) => p.name).join(", ")})`,
40
- ];
41
- if (input !== undefined) {
42
- fetchArguments.push(input.name);
43
- if (config.json === true)
44
- fetchArguments.push(`${route.name}.stringify`);
45
- }
46
-
47
- const assertions: string =
48
- config.assert === true && route.parameters.length !== 0
49
- ? route.parameters
50
- .map(
51
- (param) =>
52
- ` typia.assert<typeof ${param.name}>(${param.name});`,
53
- )
54
- .join("\n") + "\n\n"
55
- : "";
56
-
57
- // RETURNS WITH FINALIZATION
58
- return (
59
- "{\n" +
60
- assertions +
61
- " return Fetcher.fetch\n" +
62
- " (\n" +
63
- fetchArguments.map((param) => ` ${param}`).join(",\n") +
64
- "\n" +
65
- " );\n" +
66
- "}"
67
- );
68
- }
69
-
70
- function filter_parameters(
71
- route: IRoute,
72
- query: IRoute.IParameter | undefined,
73
- ): IRoute.IParameter[] {
74
- const parameters: IRoute.IParameter[] = route.parameters.filter(
75
- (param) =>
76
- param.category === "param" ||
77
- (param.category === "query" && param.field !== undefined),
78
- );
79
- if (query) parameters.push(query);
80
- return parameters;
81
- }
82
-
83
- /* ---------------------------------------------------------
84
- HEAD & TAIL
85
- --------------------------------------------------------- */
86
- function head(
87
- route: IRoute,
88
- query: IRoute.IParameter | undefined,
89
- input: IRoute.IParameter | undefined,
90
- config: INestiaConfig,
91
- ): string {
92
- //----
93
- // CONSTRUCT COMMENT
94
- //----
95
- // MAIN DESCRIPTION
96
- const comments: string[] = route.comments
97
- .map((part) => `${part.kind === "linkText" ? " " : ""}${part.text}`)
98
- .map((str) => str.split("\r\n").join("\n"))
99
- .join("")
100
- .split("\n")
101
- .filter((str, i, array) => str !== "" || i !== array.length - 1);
102
- if (comments.length) comments.push("");
103
-
104
- // FILTER TAGS (VULNERABLE PARAMETERS WOULD BE REMOVED)
105
- const tagList: ts.JSDocTagInfo[] = route.tags.filter(
106
- (tag) => tag.text !== undefined,
107
- );
108
- if (tagList.length !== 0) {
109
- const index: number = tagList.findIndex((t) => t.name === "param");
110
- if (index !== -1) {
111
- const capsule: Vector<ts.JSDocTagInfo> = Vector.wrap(tagList);
112
- capsule.insert(capsule.nth(index), {
113
- name: "param",
114
- text: [
115
- {
116
- kind: "parameterName",
117
- text: "connection",
118
- },
119
- {
120
- kind: "space",
121
- text: " ",
122
- },
123
- {
124
- kind: "text",
125
- text: "connection Information of the remote HTTP(s) server with headers (+encryption password)",
126
- },
127
- ],
128
- });
129
- }
130
- comments.push(
131
- ...tagList.map(
132
- (tag) =>
133
- `@${tag.name} ${tag
134
- .text!.map((elem) => elem.text)
135
- .join("")}`,
136
- ),
137
- "",
138
- );
139
- }
140
-
141
- // COMPLETE THE COMMENT
142
- comments.push(
143
- `@controller ${route.symbol}`,
144
- `@path ${route.method} ${route.path}`,
145
- `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
146
- );
147
-
148
- //----
149
- // FINALIZATION
150
- //----
151
- // REFORM PARAMETERS TEXT
152
- const parameters: string[] = [
153
- "connection: IConnection",
154
- ...route.parameters.map((param) => {
155
- const type: string =
156
- config.primitive !== false &&
157
- (param === query || param === input)
158
- ? `Primitive<${route.name}.${
159
- param === query ? "Query" : "Input"
160
- }>`
161
- : param.type.name;
162
- return `${param.name}: ${type}`;
163
- }),
164
- ];
165
-
166
- // OUTPUT TYPE
167
- const output: string =
168
- route.output.name === "void" ? "void" : `${route.name}.Output`;
169
-
170
- // RETURNS WITH CONSTRUCTION
171
- return (
172
- "" +
173
- "/**\n" +
174
- comments.map((str) => ` * ${str}`).join("\n") +
175
- "\n" +
176
- " */\n" +
177
- `export function ${route.name}\n` +
178
- ` (\n` +
179
- `${parameters.map((str) => ` ${str}`).join(",\n")}\n` +
180
- ` ): Promise<${output}>`
181
- );
182
- }
183
-
184
- function tail(
185
- route: IRoute,
186
- query: IRoute.IParameter | undefined,
187
- input: IRoute.IParameter | undefined,
188
- config: INestiaConfig,
189
- ): string | null {
190
- // LIST UP TYPES
191
- const types: Pair<string, string>[] = [];
192
- if (query !== undefined) types.push(new Pair("Query", query.type.name));
193
- if (input !== undefined) types.push(new Pair("Input", input.type.name));
194
- if (route.output.name !== "void")
195
- types.push(new Pair("Output", route.output.name));
196
-
197
- // PATH WITH PARAMETERS
198
- const parameters: IRoute.IParameter[] = filter_parameters(route, query);
199
- const path: string = compute_path(query, parameters, route.path);
200
-
201
- return (
202
- `export namespace ${route.name}\n` +
203
- "{\n" +
204
- (types.length !== 0
205
- ? types
206
- .map(
207
- (tuple) =>
208
- ` export type ${tuple.first} = ${
209
- config.primitive !== false
210
- ? `Primitive<${tuple.second}>`
211
- : tuple.second
212
- };`,
213
- )
214
- .join("\n") + "\n"
215
- : "") +
216
- "\n" +
217
- ` export const METHOD = "${route.method}" as const;\n` +
218
- ` export const PATH: string = "${route.path}";\n` +
219
- ` export const ENCRYPTED: Fetcher.IEncrypted = {\n` +
220
- ` request: ${input !== undefined && input.encrypted},\n` +
221
- ` response: ${route.encrypted},\n` +
222
- ` };\n` +
223
- "\n" +
224
- ` export function path(${parameters
225
- .map((param) => `${param.name}: ${param.type.name}`)
226
- .join(", ")}): string\n` +
227
- ` {\n` +
228
- ` return ${path};\n` +
229
- ` }\n` +
230
- (config.json === true &&
231
- (route.method === "POST" ||
232
- route.method === "PUT" ||
233
- route.method === "PATCH")
234
- ? ` export const stringify = (input: Input) => typia.stringify(input);\n`
235
- : "") +
236
- "}"
237
- );
238
- }
239
-
240
- function compute_path(
241
- query: IRoute.IParameter | undefined,
242
- parameters: IRoute.IParameter[],
243
- path: string,
244
- ): string {
245
- for (const param of parameters)
246
- if (param.category === "param")
247
- path = path.replace(
248
- `:${param.field}`,
249
- `\${encodeURIComponent(${param.name})}`,
250
- );
251
- const queryParams: IRoute.IParameter[] = parameters.filter(
252
- (param) => param.category === "query" && param.field !== undefined,
253
- );
254
- if (query === undefined && queryParams.length === 0)
255
- return `\`${path}\``;
256
-
257
- const wrapper = (str: string) =>
258
- `\`${path}?\${new URLSearchParams(${str}).toString()}\``;
259
- if (query !== undefined && queryParams.length === 0)
260
- return wrapper(`${query.name} as any`);
261
- else if (query === undefined)
262
- return wrapper(`
263
- {
264
- ${rest_query_parameters(queryParams)}
265
- } as any`);
266
-
267
- return wrapper(`
268
- {
269
- ...${query.name},
270
- ${rest_query_parameters(queryParams)},
271
- } as any`);
272
- }
273
-
274
- function rest_query_parameters(parameters: IRoute.IParameter[]): string {
275
- return parameters
276
- .map((param) =>
277
- param.name === param.field
278
- ? param.name
279
- : `${
280
- Escaper.variable(param.field!)
281
- ? param.field
282
- : JSON.stringify(param.field)
283
- }: ${param.name}`,
284
- )
285
- .join(`,\n${" ".repeat(12)}`);
286
- }
287
- }
1
+ import { Vector } from "tstl/container/Vector";
2
+ import { Pair } from "tstl/utility/Pair";
3
+ import ts from "typescript";
4
+ import { Escaper } from "typia/lib/utils/Escaper";
5
+
6
+ import { INestiaConfig } from "../INestiaConfig";
7
+ import { IRoute } from "../structures/IRoute";
8
+
9
+ export namespace FunctionGenerator {
10
+ export function generate(config: INestiaConfig, route: IRoute): string {
11
+ const query: IRoute.IParameter | undefined = route.parameters.find(
12
+ (param) => param.category === "query" && param.field === undefined,
13
+ );
14
+ const input: IRoute.IParameter | undefined = route.parameters.find(
15
+ (param) => param.category === "body",
16
+ );
17
+
18
+ return [head, body, tail]
19
+ .map((closure) => closure(route, query, input, config))
20
+ .filter((str) => !!str)
21
+ .join("\n");
22
+ }
23
+
24
+ /* ---------------------------------------------------------
25
+ BODY
26
+ --------------------------------------------------------- */
27
+ function body(
28
+ route: IRoute,
29
+ query: IRoute.IParameter | undefined,
30
+ input: IRoute.IParameter | undefined,
31
+ config: INestiaConfig,
32
+ ): string {
33
+ // FETCH ARGUMENTS WITH REQUST BODY
34
+ const parameters = filter_parameters(route, query);
35
+ const fetchArguments: string[] = [
36
+ "connection",
37
+ `${route.name}.ENCRYPTED`,
38
+ `${route.name}.METHOD`,
39
+ `${route.name}.path(${parameters.map((p) => p.name).join(", ")})`,
40
+ ];
41
+ if (input !== undefined) {
42
+ fetchArguments.push(input.name);
43
+ if (config.json === true)
44
+ fetchArguments.push(`${route.name}.stringify`);
45
+ }
46
+
47
+ const assertions: string =
48
+ config.assert === true && route.parameters.length !== 0
49
+ ? route.parameters
50
+ .map(
51
+ (param) =>
52
+ ` typia.assert<typeof ${param.name}>(${param.name});`,
53
+ )
54
+ .join("\n") + "\n\n"
55
+ : "";
56
+
57
+ // FUNCTION CALL STATEMENT
58
+ const caller: string =
59
+ "Fetcher.fetch\n" +
60
+ " (\n" +
61
+ fetchArguments.map((param) => ` ${param}`).join(",\n") +
62
+ "\n" +
63
+ " )";
64
+ if (route.setHeaders.length === 0)
65
+ return `{\n${assertions} return ${caller};\n}`;
66
+
67
+ // SET HEADERS
68
+ const content: string[] = [
69
+ `{\n`,
70
+ assertions,
71
+ ` const output: ${route.name}.Output = await ${caller};\n`,
72
+ "\n",
73
+ ` // configure header(s)\n`,
74
+ ` connection.headers ??= {};\n`,
75
+ ];
76
+
77
+ for (const header of route.setHeaders) {
78
+ if (header.type === "assigner")
79
+ content.push(
80
+ " ",
81
+ `Object.assign(connection.headers, ${access(
82
+ "output",
83
+ header.source,
84
+ )});\n`,
85
+ );
86
+ else
87
+ content.push(
88
+ " ",
89
+ `${access(
90
+ "connection.headers",
91
+ header.target ?? header.source,
92
+ )} = ${access("output", header.source)};\n`,
93
+ );
94
+ }
95
+ content.push("\n", " return output;\n", "}");
96
+ return content.join("");
97
+ }
98
+
99
+ function filter_parameters(
100
+ route: IRoute,
101
+ query: IRoute.IParameter | undefined,
102
+ ): IRoute.IParameter[] {
103
+ const parameters: IRoute.IParameter[] = route.parameters.filter(
104
+ (param) =>
105
+ param.category === "param" ||
106
+ (param.category === "query" && param.field !== undefined),
107
+ );
108
+ if (query) parameters.push(query);
109
+ return parameters;
110
+ }
111
+
112
+ function access(x: string, y: string): string {
113
+ return y[0] === "[" ? `${x}${y}` : `${x}.${y}`;
114
+ }
115
+
116
+ /* ---------------------------------------------------------
117
+ HEAD & TAIL
118
+ --------------------------------------------------------- */
119
+ function head(
120
+ route: IRoute,
121
+ query: IRoute.IParameter | undefined,
122
+ input: IRoute.IParameter | undefined,
123
+ config: INestiaConfig,
124
+ ): string {
125
+ //----
126
+ // CONSTRUCT COMMENT
127
+ //----
128
+ // MAIN DESCRIPTION
129
+ const comments: string[] = route.comments
130
+ .map((part) => `${part.kind === "linkText" ? " " : ""}${part.text}`)
131
+ .map((str) => str.split("\r\n").join("\n"))
132
+ .join("")
133
+ .split("\n")
134
+ .filter((str, i, array) => str !== "" || i !== array.length - 1);
135
+ if (comments.length) comments.push("");
136
+
137
+ // FILTER TAGS (VULNERABLE PARAMETERS WOULD BE REMOVED)
138
+ const tagList: ts.JSDocTagInfo[] = route.tags.filter(
139
+ (tag) => tag.text !== undefined,
140
+ );
141
+ if (tagList.length !== 0) {
142
+ const index: number = tagList.findIndex((t) => t.name === "param");
143
+ if (index !== -1) {
144
+ const capsule: Vector<ts.JSDocTagInfo> = Vector.wrap(tagList);
145
+ capsule.insert(capsule.nth(index), {
146
+ name: "param",
147
+ text: [
148
+ {
149
+ kind: "parameterName",
150
+ text: "connection",
151
+ },
152
+ {
153
+ kind: "space",
154
+ text: " ",
155
+ },
156
+ {
157
+ kind: "text",
158
+ text: "connection Information of the remote HTTP(s) server with headers (+encryption password)",
159
+ },
160
+ ],
161
+ });
162
+ }
163
+ comments.push(
164
+ ...tagList.map(
165
+ (tag) =>
166
+ `@${tag.name} ${tag
167
+ .text!.map((elem) => elem.text)
168
+ .join("")}`,
169
+ ),
170
+ "",
171
+ );
172
+ }
173
+
174
+ // COMPLETE THE COMMENT
175
+ comments.push(
176
+ `@controller ${route.symbol}`,
177
+ `@path ${route.method} ${route.path}`,
178
+ `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
179
+ );
180
+
181
+ //----
182
+ // FINALIZATION
183
+ //----
184
+ // REFORM PARAMETERS TEXT
185
+ const parameters: string[] = [
186
+ "connection: IConnection",
187
+ ...route.parameters.map((param) => {
188
+ const type: string =
189
+ config.primitive !== false &&
190
+ (param === query || param === input)
191
+ ? `Primitive<${route.name}.${
192
+ param === query ? "Query" : "Input"
193
+ }>`
194
+ : param.type.name;
195
+ return `${param.name}: ${type}`;
196
+ }),
197
+ ];
198
+
199
+ // OUTPUT TYPE
200
+ const output: string =
201
+ route.output.name === "void" ? "void" : `${route.name}.Output`;
202
+
203
+ // RETURNS WITH CONSTRUCTION
204
+ return (
205
+ "" +
206
+ "/**\n" +
207
+ comments.map((str) => ` * ${str}`).join("\n") +
208
+ "\n" +
209
+ " */\n" +
210
+ `export${route.setHeaders ? " async" : ""} function ${
211
+ route.name
212
+ }\n` +
213
+ ` (\n` +
214
+ `${parameters.map((str) => ` ${str}`).join(",\n")}\n` +
215
+ ` ): Promise<${output}>`
216
+ );
217
+ }
218
+
219
+ function tail(
220
+ route: IRoute,
221
+ query: IRoute.IParameter | undefined,
222
+ input: IRoute.IParameter | undefined,
223
+ config: INestiaConfig,
224
+ ): string | null {
225
+ // LIST UP TYPES
226
+ const types: Pair<string, string>[] = [];
227
+ if (query !== undefined) types.push(new Pair("Query", query.type.name));
228
+ if (input !== undefined) types.push(new Pair("Input", input.type.name));
229
+ if (route.output.name !== "void")
230
+ types.push(new Pair("Output", route.output.name));
231
+
232
+ // PATH WITH PARAMETERS
233
+ const parameters: IRoute.IParameter[] = filter_parameters(route, query);
234
+ const path: string = compute_path(query, parameters, route.path);
235
+
236
+ return (
237
+ `export namespace ${route.name}\n` +
238
+ "{\n" +
239
+ (types.length !== 0
240
+ ? types
241
+ .map(
242
+ (tuple) =>
243
+ ` export type ${tuple.first} = ${
244
+ config.primitive !== false
245
+ ? `Primitive<${tuple.second}>`
246
+ : tuple.second
247
+ };`,
248
+ )
249
+ .join("\n") + "\n"
250
+ : "") +
251
+ "\n" +
252
+ ` export const METHOD = "${route.method}" as const;\n` +
253
+ ` export const PATH: string = "${route.path}";\n` +
254
+ ` export const ENCRYPTED: Fetcher.IEncrypted = {\n` +
255
+ ` request: ${input !== undefined && input.encrypted},\n` +
256
+ ` response: ${route.encrypted},\n` +
257
+ ` };\n` +
258
+ "\n" +
259
+ ` export function path(${parameters
260
+ .map((param) => `${param.name}: ${param.type.name}`)
261
+ .join(", ")}): string\n` +
262
+ ` {\n` +
263
+ ` return ${path};\n` +
264
+ ` }\n` +
265
+ (config.json === true &&
266
+ (route.method === "POST" ||
267
+ route.method === "PUT" ||
268
+ route.method === "PATCH")
269
+ ? ` export const stringify = (input: Input) => typia.assertStringify(input);\n`
270
+ : "") +
271
+ "}"
272
+ );
273
+ }
274
+
275
+ function compute_path(
276
+ query: IRoute.IParameter | undefined,
277
+ parameters: IRoute.IParameter[],
278
+ path: string,
279
+ ): string {
280
+ for (const param of parameters)
281
+ if (param.category === "param")
282
+ path = path.replace(
283
+ `:${param.field}`,
284
+ `\${encodeURIComponent(${param.name})}`,
285
+ );
286
+ const queryParams: IRoute.IParameter[] = parameters.filter(
287
+ (param) => param.category === "query" && param.field !== undefined,
288
+ );
289
+ if (query === undefined && queryParams.length === 0)
290
+ return `\`${path}\``;
291
+
292
+ const wrapper = (str: string) =>
293
+ `\`${path}?\${new URLSearchParams(${str}).toString()}\``;
294
+ if (query !== undefined && queryParams.length === 0)
295
+ return wrapper(`${query.name} as any`);
296
+ else if (query === undefined)
297
+ return wrapper(`
298
+ {
299
+ ${rest_query_parameters(queryParams)}
300
+ } as any`);
301
+
302
+ return wrapper(`
303
+ {
304
+ ...${query.name},
305
+ ${rest_query_parameters(queryParams)},
306
+ } as any`);
307
+ }
308
+
309
+ function rest_query_parameters(parameters: IRoute.IParameter[]): string {
310
+ return parameters
311
+ .map((param) =>
312
+ param.name === param.field
313
+ ? param.name
314
+ : `${
315
+ Escaper.variable(param.field!)
316
+ ? param.field
317
+ : JSON.stringify(param.field)
318
+ }: ${param.name}`,
319
+ )
320
+ .join(`,\n${" ".repeat(12)}`);
321
+ }
322
+ }