@nestia/sdk 2.4.7 → 2.5.0-dev.20240129

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 (40) hide show
  1. package/lib/generates/internal/E2eFileProgrammer.js +44 -46
  2. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
  3. package/lib/generates/internal/SdkDtoGenerator.js +2 -1
  4. package/lib/generates/internal/SdkDtoGenerator.js.map +1 -1
  5. package/lib/generates/internal/SdkFileProgrammer.js +16 -10
  6. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  7. package/lib/generates/internal/SdkFunctionProgrammer.d.ts +6 -1
  8. package/lib/generates/internal/SdkFunctionProgrammer.js +73 -323
  9. package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -1
  10. package/lib/generates/internal/SdkNamespaceProgrammer.d.ts +11 -0
  11. package/lib/generates/internal/SdkNamespaceProgrammer.js +175 -0
  12. package/lib/generates/internal/SdkNamespaceProgrammer.js.map +1 -0
  13. package/lib/generates/internal/SdkRouteProgrammer.d.ts +7 -0
  14. package/lib/generates/internal/SdkRouteProgrammer.js +55 -0
  15. package/lib/generates/internal/SdkRouteProgrammer.js.map +1 -0
  16. package/lib/generates/internal/SdkSimulationProgrammer.d.ts +7 -1
  17. package/lib/generates/internal/SdkSimulationProgrammer.js +98 -88
  18. package/lib/generates/internal/SdkSimulationProgrammer.js.map +1 -1
  19. package/lib/utils/FormatUtil.d.ts +6 -0
  20. package/lib/utils/FormatUtil.js +36 -0
  21. package/lib/utils/FormatUtil.js.map +1 -0
  22. package/lib/utils/ImportDictionary.d.ts +2 -0
  23. package/lib/utils/ImportDictionary.js +45 -0
  24. package/lib/utils/ImportDictionary.js.map +1 -1
  25. package/package.json +6 -3
  26. package/src/analyses/ConfigAnalyzer.ts +147 -147
  27. package/src/analyses/ControllerAnalyzer.ts +390 -390
  28. package/src/analyses/PathAnalyzer.ts +110 -110
  29. package/src/analyses/ReflectAnalyzer.ts +464 -464
  30. package/src/generates/SwaggerGenerator.ts +376 -376
  31. package/src/generates/internal/E2eFileProgrammer.ts +148 -73
  32. package/src/generates/internal/SdkDtoGenerator.ts +6 -1
  33. package/src/generates/internal/SdkFileProgrammer.ts +40 -13
  34. package/src/generates/internal/SdkFunctionProgrammer.ts +176 -475
  35. package/src/generates/internal/SdkNamespaceProgrammer.ts +495 -0
  36. package/src/generates/internal/SdkRouteProgrammer.ts +82 -0
  37. package/src/generates/internal/SdkSimulationProgrammer.ts +359 -133
  38. package/src/generates/internal/SwaggerSchemaGenerator.ts +444 -444
  39. package/src/utils/FormatUtil.ts +30 -0
  40. package/src/utils/ImportDictionary.ts +72 -0
@@ -0,0 +1,495 @@
1
+ import ts from "typescript";
2
+ import typia from "typia";
3
+ import { IdentifierFactory } from "typia/lib/factories/IdentifierFactory";
4
+ import { LiteralFactory } from "typia/lib/factories/LiteralFactory";
5
+ import { TypeFactory } from "typia/lib/factories/TypeFactory";
6
+ import { Escaper } from "typia/lib/utils/Escaper";
7
+
8
+ import { INestiaConfig } from "../../INestiaConfig";
9
+ import { IController } from "../../structures/IController";
10
+ import { IRoute } from "../../structures/IRoute";
11
+ import { FormatUtil } from "../../utils/FormatUtil";
12
+ import { ImportDictionary } from "../../utils/ImportDictionary";
13
+ import { SdkDtoGenerator } from "./SdkDtoGenerator";
14
+ import { SdkImportWizard } from "./SdkImportWizard";
15
+ import { SdkSimulationProgrammer } from "./SdkSimulationProgrammer";
16
+ import { SdkTypeDefiner } from "./SdkTypeDefiner";
17
+
18
+ export namespace SdkNamespaceProgrammer {
19
+ export const generate =
20
+ (config: INestiaConfig) =>
21
+ (importer: ImportDictionary) =>
22
+ (
23
+ route: IRoute,
24
+ props: {
25
+ headers: IRoute.IParameter | undefined;
26
+ query: IRoute.IParameter | undefined;
27
+ input: IRoute.IParameter | undefined;
28
+ },
29
+ ): ts.ModuleDeclaration => {
30
+ const types = generate_types(config)(importer)(route, props);
31
+ return ts.factory.createModuleDeclaration(
32
+ [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
33
+ ts.factory.createIdentifier(route.name),
34
+ ts.factory.createModuleBlock([
35
+ ...types,
36
+ ...(types.length ? [FormatUtil.enter()] : []),
37
+ generate_metadata(importer)(route, props),
38
+ FormatUtil.enter(),
39
+ generate_path(config)(importer)(route, props),
40
+ ...(config.simulate
41
+ ? [
42
+ SdkSimulationProgrammer.random(config)(importer)(route),
43
+ SdkSimulationProgrammer.simulate(config)(importer)(
44
+ route,
45
+ props,
46
+ ),
47
+ ]
48
+ : []),
49
+ ...(config.json && props.input?.category === "body"
50
+ ? [generate_stringify(config)(importer)]
51
+ : []),
52
+ ]),
53
+ ts.NodeFlags.Namespace,
54
+ );
55
+ };
56
+
57
+ const generate_types =
58
+ (config: INestiaConfig) =>
59
+ (importer: ImportDictionary) =>
60
+ (
61
+ route: IRoute,
62
+ props: {
63
+ headers: IRoute.IParameter | undefined;
64
+ query: IRoute.IParameter | undefined;
65
+ input: IRoute.IParameter | undefined;
66
+ },
67
+ ): ts.TypeAliasDeclaration[] => {
68
+ const array: ts.TypeAliasDeclaration[] = [];
69
+ const declare = (name: string, type: string) =>
70
+ array.push(
71
+ ts.factory.createTypeAliasDeclaration(
72
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
73
+ name,
74
+ undefined,
75
+ ts.factory.createTypeReferenceNode(type),
76
+ ),
77
+ );
78
+ if (props.headers !== undefined)
79
+ declare(
80
+ "Headers",
81
+ SdkTypeDefiner.headers(config)(importer)(props.headers),
82
+ );
83
+ if (props.query !== undefined)
84
+ declare("Query", SdkTypeDefiner.query(config)(importer)(props.query));
85
+ if (props.input !== undefined)
86
+ declare("Input", SdkTypeDefiner.input(config)(importer)(props.input));
87
+ if (config.propagate === true || route.output.typeName !== "void")
88
+ declare("Output", SdkTypeDefiner.output(config)(importer)(route));
89
+ return array;
90
+ };
91
+
92
+ const generate_metadata =
93
+ (importer: ImportDictionary) =>
94
+ (
95
+ route: IRoute,
96
+ props: {
97
+ headers: IRoute.IParameter | undefined;
98
+ query: IRoute.IParameter | undefined;
99
+ input: IRoute.IParameter | undefined;
100
+ },
101
+ ): ts.VariableStatement =>
102
+ constant("METADATA")(
103
+ ts.factory.createAsExpression(
104
+ ts.factory.createObjectLiteralExpression(
105
+ [
106
+ ts.factory.createPropertyAssignment(
107
+ "method",
108
+ ts.factory.createStringLiteral(route.method),
109
+ ),
110
+ ts.factory.createPropertyAssignment(
111
+ "path",
112
+ ts.factory.createStringLiteral(route.path),
113
+ ),
114
+ ts.factory.createPropertyAssignment(
115
+ "request",
116
+ props.input
117
+ ? LiteralFactory.generate(
118
+ typia.is<IController.IBodyParameter>(props.input)
119
+ ? {
120
+ type: props.input.contentType,
121
+ encrypted: !!props.input.encrypted,
122
+ }
123
+ : {
124
+ type: "application/json",
125
+ encrypted: false,
126
+ },
127
+ )
128
+ : ts.factory.createNull(),
129
+ ),
130
+ ts.factory.createPropertyAssignment(
131
+ "response",
132
+ route.method !== "HEAD"
133
+ ? LiteralFactory.generate({
134
+ type: route.output.contentType,
135
+ encrypted: !!route.encrypted,
136
+ })
137
+ : ts.factory.createNull(),
138
+ ),
139
+ ts.factory.createPropertyAssignment(
140
+ "status",
141
+ route.status !== undefined
142
+ ? ts.factory.createNumericLiteral(route.status)
143
+ : ts.factory.createNull(),
144
+ ),
145
+ ...(route.output.contentType ===
146
+ "application/x-www-form-urlencoded"
147
+ ? [
148
+ ts.factory.createPropertyAssignment(
149
+ "parseQuery",
150
+ ts.factory.createCallExpression(
151
+ ts.factory.createIdentifier(
152
+ `${SdkImportWizard.typia(importer)}.http.createAssertQuery`,
153
+ ),
154
+ [
155
+ ts.factory.createTypeReferenceNode(
156
+ route.output.typeName,
157
+ ),
158
+ ],
159
+ undefined,
160
+ ),
161
+ ),
162
+ ]
163
+ : []),
164
+ ],
165
+ true,
166
+ ),
167
+ ts.factory.createTypeReferenceNode(
168
+ ts.factory.createIdentifier("const"),
169
+ ),
170
+ ),
171
+ );
172
+
173
+ const generate_path =
174
+ (config: INestiaConfig) =>
175
+ (importer: ImportDictionary) =>
176
+ (
177
+ route: IRoute,
178
+ props: {
179
+ query: IRoute.IParameter | undefined;
180
+ },
181
+ ): ts.VariableStatement => {
182
+ const g = {
183
+ total: [
184
+ ...route.parameters.filter(
185
+ (param) => param.category === "param" || param.category === "query",
186
+ ),
187
+ ],
188
+ query: route.parameters.filter(
189
+ (param) => param.category === "query" && param.field !== undefined,
190
+ ),
191
+ path: route.parameters.filter((param) => param.category === "param"),
192
+ };
193
+ const out = (body: ts.ConciseBody) =>
194
+ constant("path")(
195
+ ts.factory.createArrowFunction(
196
+ [],
197
+ [],
198
+ g.total.map((p) =>
199
+ IdentifierFactory.parameter(
200
+ p.name,
201
+ ts.factory.createTypeReferenceNode(
202
+ p === props.query
203
+ ? `${route.name}.Query`
204
+ : getType(config)(importer)(p),
205
+ ),
206
+ ),
207
+ ),
208
+ undefined,
209
+ undefined,
210
+ body,
211
+ ),
212
+ );
213
+ if (g.total.length === 0)
214
+ return out(ts.factory.createStringLiteral(route.path));
215
+
216
+ const template = () => {
217
+ const splitted: string[] = route.path.split(":");
218
+ if (splitted.length === 1)
219
+ return ts.factory.createStringLiteral(route.path);
220
+ return ts.factory.createTemplateExpression(
221
+ ts.factory.createTemplateHead(splitted[0]),
222
+ splitted.slice(1).map((s, i, arr) => {
223
+ const name: string = s.split("/")[0];
224
+ return ts.factory.createTemplateSpan(
225
+ ts.factory.createCallExpression(
226
+ ts.factory.createIdentifier("encodeURIComponent"),
227
+ undefined,
228
+ [
229
+ ts.factory.createBinaryExpression(
230
+ ts.factory.createIdentifier(
231
+ g.path.find((p) => p.field === name)!.name,
232
+ ),
233
+ ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
234
+ ts.factory.createStringLiteral("null"),
235
+ ),
236
+ ],
237
+ ),
238
+ (i !== arr.length - 1
239
+ ? ts.factory.createTemplateMiddle
240
+ : ts.factory.createTemplateTail)(s.substring(name.length)),
241
+ );
242
+ }),
243
+ );
244
+ };
245
+ if (props.query === undefined && g.query.length === 0)
246
+ return out(template());
247
+
248
+ const block = (expr: ts.Expression) => {
249
+ const computeName = (str: string): string =>
250
+ g.total
251
+ .filter((p) => p.category !== "headers")
252
+ .find((p) => p.name === str) !== undefined
253
+ ? computeName("_" + str)
254
+ : str;
255
+ const variables: string = computeName("variables");
256
+ return ts.factory.createBlock(
257
+ [
258
+ local(variables)("URLSearchParams")(
259
+ ts.factory.createNewExpression(
260
+ ts.factory.createIdentifier("URLSearchParams"),
261
+ [],
262
+ [],
263
+ ),
264
+ ),
265
+ ts.factory.createForOfStatement(
266
+ undefined,
267
+ ts.factory.createVariableDeclarationList(
268
+ [
269
+ ts.factory.createVariableDeclaration(
270
+ ts.factory.createArrayBindingPattern([
271
+ ts.factory.createBindingElement(
272
+ undefined,
273
+ undefined,
274
+ ts.factory.createIdentifier("key"),
275
+ undefined,
276
+ ),
277
+ ts.factory.createBindingElement(
278
+ undefined,
279
+ undefined,
280
+ ts.factory.createIdentifier("value"),
281
+ undefined,
282
+ ),
283
+ ]),
284
+ undefined,
285
+ undefined,
286
+ undefined,
287
+ ),
288
+ ],
289
+ ts.NodeFlags.Const,
290
+ ),
291
+ ts.factory.createCallExpression(
292
+ ts.factory.createIdentifier("Object.entries"),
293
+ undefined,
294
+ [
295
+ ts.factory.createAsExpression(
296
+ expr,
297
+ TypeFactory.keyword("any"),
298
+ ),
299
+ ],
300
+ ),
301
+ ts.factory.createIfStatement(
302
+ ts.factory.createStrictEquality(
303
+ ts.factory.createIdentifier("undefined"),
304
+ ts.factory.createIdentifier("value"),
305
+ ),
306
+ ts.factory.createContinueStatement(),
307
+ ts.factory.createIfStatement(
308
+ ts.factory.createCallExpression(
309
+ ts.factory.createIdentifier("Array.isArray"),
310
+ undefined,
311
+ [ts.factory.createIdentifier("value")],
312
+ ),
313
+ ts.factory.createExpressionStatement(
314
+ ts.factory.createCallExpression(
315
+ ts.factory.createPropertyAccessExpression(
316
+ ts.factory.createIdentifier("value"),
317
+ ts.factory.createIdentifier("forEach"),
318
+ ),
319
+ undefined,
320
+ [
321
+ ts.factory.createArrowFunction(
322
+ undefined,
323
+ undefined,
324
+ [IdentifierFactory.parameter("elem")],
325
+ undefined,
326
+ undefined,
327
+ ts.factory.createCallExpression(
328
+ IdentifierFactory.access(
329
+ ts.factory.createIdentifier(variables),
330
+ )("append"),
331
+ undefined,
332
+ [
333
+ ts.factory.createIdentifier("key"),
334
+ ts.factory.createCallExpression(
335
+ ts.factory.createIdentifier("String"),
336
+ undefined,
337
+ [ts.factory.createIdentifier("elem")],
338
+ ),
339
+ ],
340
+ ),
341
+ ),
342
+ ],
343
+ ),
344
+ ),
345
+ ts.factory.createExpressionStatement(
346
+ ts.factory.createCallExpression(
347
+ IdentifierFactory.access(
348
+ ts.factory.createIdentifier(variables),
349
+ )("set"),
350
+ undefined,
351
+ [
352
+ ts.factory.createIdentifier("key"),
353
+ ts.factory.createCallExpression(
354
+ ts.factory.createIdentifier("String"),
355
+ undefined,
356
+ [ts.factory.createIdentifier("value")],
357
+ ),
358
+ ],
359
+ ),
360
+ ),
361
+ ),
362
+ ),
363
+ ),
364
+ local("location")("string")(template()),
365
+ ts.factory.createReturnStatement(
366
+ ts.factory.createConditionalExpression(
367
+ ts.factory.createStrictEquality(
368
+ ts.factory.createNumericLiteral(0),
369
+ IdentifierFactory.access(
370
+ ts.factory.createIdentifier(variables),
371
+ )("size"),
372
+ ),
373
+ undefined,
374
+ ts.factory.createIdentifier("location"),
375
+ undefined,
376
+ ts.factory.createTemplateExpression(
377
+ ts.factory.createTemplateHead(""),
378
+ [
379
+ ts.factory.createTemplateSpan(
380
+ ts.factory.createIdentifier("location"),
381
+ ts.factory.createTemplateMiddle("?"),
382
+ ),
383
+ ts.factory.createTemplateSpan(
384
+ ts.factory.createCallExpression(
385
+ IdentifierFactory.access(
386
+ ts.factory.createIdentifier(variables),
387
+ )("toString"),
388
+ undefined,
389
+ undefined,
390
+ ),
391
+ ts.factory.createTemplateTail(""),
392
+ ),
393
+ ],
394
+ ),
395
+ ),
396
+ ),
397
+ ],
398
+ true,
399
+ );
400
+ };
401
+ if (props.query !== undefined && g.query.length === 0)
402
+ return out(block(ts.factory.createIdentifier(props.query.name)));
403
+ return out(
404
+ block(
405
+ ts.factory.createObjectLiteralExpression(
406
+ [
407
+ ...(props.query
408
+ ? [
409
+ ts.factory.createSpreadAssignment(
410
+ ts.factory.createIdentifier(props.query.name),
411
+ ),
412
+ ]
413
+ : []),
414
+ ...g.query.map((q) =>
415
+ q.name === q.field
416
+ ? ts.factory.createShorthandPropertyAssignment(q.name)
417
+ : ts.factory.createPropertyAssignment(
418
+ Escaper.variable(q.field!)
419
+ ? q.field!
420
+ : ts.factory.createStringLiteral(q.field!),
421
+ ts.factory.createIdentifier(q.name),
422
+ ),
423
+ ),
424
+ ],
425
+ true,
426
+ ),
427
+ ),
428
+ );
429
+ };
430
+
431
+ const generate_stringify =
432
+ (config: INestiaConfig) =>
433
+ (importer: ImportDictionary): ts.VariableStatement =>
434
+ constant("stringify")(
435
+ ts.factory.createArrowFunction(
436
+ [],
437
+ undefined,
438
+ [
439
+ IdentifierFactory.parameter(
440
+ "input",
441
+ ts.factory.createTypeReferenceNode("Input"),
442
+ ),
443
+ ],
444
+ undefined,
445
+ undefined,
446
+ ts.factory.createCallExpression(
447
+ IdentifierFactory.access(
448
+ IdentifierFactory.access(
449
+ ts.factory.createIdentifier(SdkImportWizard.typia(importer)),
450
+ )("json"),
451
+ )(config.assert ? "stringify" : "assertStringify"),
452
+ undefined,
453
+ [ts.factory.createIdentifier("input")],
454
+ ),
455
+ ),
456
+ );
457
+ }
458
+
459
+ const local = (name: string) => (type: string) => (expression: ts.Expression) =>
460
+ ts.factory.createVariableStatement(
461
+ [],
462
+ ts.factory.createVariableDeclarationList(
463
+ [
464
+ ts.factory.createVariableDeclaration(
465
+ name,
466
+ undefined,
467
+ ts.factory.createTypeReferenceNode(type),
468
+ expression,
469
+ ),
470
+ ],
471
+ ts.NodeFlags.Const,
472
+ ),
473
+ );
474
+ const constant = (name: string) => (expression: ts.Expression) =>
475
+ ts.factory.createVariableStatement(
476
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
477
+ ts.factory.createVariableDeclarationList(
478
+ [
479
+ ts.factory.createVariableDeclaration(
480
+ name,
481
+ undefined,
482
+ undefined,
483
+ expression,
484
+ ),
485
+ ],
486
+ ts.NodeFlags.Const,
487
+ ),
488
+ );
489
+ const getType =
490
+ (config: INestiaConfig) =>
491
+ (importer: ImportDictionary) =>
492
+ (p: IRoute.IParameter | IRoute.IOutput) =>
493
+ p.metadata
494
+ ? SdkDtoGenerator.decode(config)(importer)(p.metadata)
495
+ : p.typeName;
@@ -0,0 +1,82 @@
1
+ import ts from "typescript";
2
+ import { IJsDocTagInfo } from "typia";
3
+
4
+ import { INestiaConfig } from "../../INestiaConfig";
5
+ import { IRoute } from "../../structures/IRoute";
6
+ import { FormatUtil } from "../../utils/FormatUtil";
7
+ import { ImportDictionary } from "../../utils/ImportDictionary";
8
+ import { SdkFunctionProgrammer } from "./SdkFunctionProgrammer";
9
+ import { SdkNamespaceProgrammer } from "./SdkNamespaceProgrammer";
10
+
11
+ export namespace SdkRouteProgrammer {
12
+ export const generate =
13
+ (config: INestiaConfig) =>
14
+ (importer: ImportDictionary) =>
15
+ (route: IRoute): ts.Statement[] => {
16
+ const props = {
17
+ headers: route.parameters.find(
18
+ (p) => p.category === "headers" && p.field === undefined,
19
+ ),
20
+ query: route.parameters.find(
21
+ (p) => p.category === "query" && p.field === undefined,
22
+ ),
23
+ input: route.parameters.find((p) => p.category === "body"),
24
+ };
25
+ return [
26
+ FormatUtil.description(
27
+ SdkFunctionProgrammer.generate(config)(importer)(route, props),
28
+ describe(route),
29
+ ),
30
+ SdkNamespaceProgrammer.generate(config)(importer)(route, props),
31
+ ];
32
+ };
33
+
34
+ const describe = (route: IRoute): string => {
35
+ // MAIN DESCRIPTION
36
+ const comments: string[] = route.description
37
+ ? route.description.split("\n")
38
+ : [];
39
+
40
+ // COMMENT TAGS
41
+ const tags: IJsDocTagInfo[] = route.jsDocTags.filter(
42
+ (tag) =>
43
+ tag.name !== "param" ||
44
+ route.parameters
45
+ .filter((p) => p.category !== "headers")
46
+ .some((p) => p.name === tag.text?.[0]?.text),
47
+ );
48
+ if (tags.length !== 0) {
49
+ const content: string[] = tags.map((t) =>
50
+ t.text?.length
51
+ ? `@${t.name} ${t.text.map((e) => e.text).join("")}`
52
+ : `@${t.name}`,
53
+ );
54
+ comments.push("", ...new Set(content));
55
+ }
56
+
57
+ // EXCEPTIONS
58
+ for (const [key, value] of Object.entries(route.exceptions)) {
59
+ if (
60
+ comments.some(
61
+ (str) =>
62
+ str.startsWith(`@throw ${key}`) || str.startsWith(`@throws ${key}`),
63
+ )
64
+ )
65
+ continue;
66
+ comments.push(
67
+ value.description?.length
68
+ ? `@throws ${key} ${value.description.split("\n")[0]}`
69
+ : `@throws ${key}`,
70
+ );
71
+ }
72
+
73
+ // POSTFIX
74
+ if (!!comments.length) comments.push("");
75
+ comments.push(
76
+ `@controller ${route.symbol.class}.${route.symbol.function}`,
77
+ `@path ${route.method} ${route.path}`,
78
+ `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
79
+ );
80
+ return comments.join("\n");
81
+ };
82
+ }