@nestia/sdk 1.0.5 → 1.0.7

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