@nestia/migrate 10.0.2 → 11.0.0-dev.20260312

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -93
  3. package/lib/NestiaMigrateApplication.d.ts +3 -3
  4. package/lib/NestiaMigrateApplication.js +10086 -7707
  5. package/lib/NestiaMigrateApplication.js.map +1 -1
  6. package/lib/analyzers/NestiaMigrateControllerAnalyzer.d.ts +1 -1
  7. package/lib/analyzers/NestiaMigrateControllerAnalyzer.js.map +1 -1
  8. package/lib/bundles/NEST_TEMPLATE.js +48 -48
  9. package/lib/bundles/NEST_TEMPLATE.js.map +1 -1
  10. package/lib/bundles/SDK_TEMPLATE.js +21 -21
  11. package/lib/bundles/SDK_TEMPLATE.js.map +1 -1
  12. package/lib/executable/NestiaMigrateCommander.js +4 -4
  13. package/lib/executable/NestiaMigrateCommander.js.map +1 -1
  14. package/lib/executable/NestiaMigrateInquirer.js +13 -16
  15. package/lib/executable/NestiaMigrateInquirer.js.map +1 -1
  16. package/lib/factories/TypeLiteralFactory.js +2 -2
  17. package/lib/factories/TypeLiteralFactory.js.map +1 -1
  18. package/lib/index.mjs +9540 -7065
  19. package/lib/index.mjs.map +1 -1
  20. package/lib/module.d.ts +3 -0
  21. package/lib/module.js +3 -0
  22. package/lib/module.js.map +1 -1
  23. package/lib/programmers/NestiaMigrateApiFileProgrammer.d.ts +1 -1
  24. package/lib/programmers/NestiaMigrateApiFunctionProgrammer.d.ts +1 -1
  25. package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js +16 -15
  26. package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js.map +1 -1
  27. package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.d.ts +2 -1
  28. package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.js +12 -15
  29. package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.js.map +1 -1
  30. package/lib/programmers/NestiaMigrateApiSimulationProgrammer.d.ts +2 -1
  31. package/lib/programmers/NestiaMigrateApiSimulationProgrammer.js +11 -13
  32. package/lib/programmers/NestiaMigrateApiSimulationProgrammer.js.map +1 -1
  33. package/lib/programmers/NestiaMigrateApiStartProgrammer.js +3 -4
  34. package/lib/programmers/NestiaMigrateApiStartProgrammer.js.map +1 -1
  35. package/lib/programmers/NestiaMigrateDtoProgrammer.d.ts +1 -1
  36. package/lib/programmers/NestiaMigrateE2eFileProgrammer.d.ts +2 -1
  37. package/lib/programmers/NestiaMigrateE2eFileProgrammer.js +3 -4
  38. package/lib/programmers/NestiaMigrateE2eFileProgrammer.js.map +1 -1
  39. package/lib/programmers/NestiaMigrateImportProgrammer.js.map +1 -1
  40. package/lib/programmers/NestiaMigrateNestControllerProgrammer.d.ts +1 -1
  41. package/lib/programmers/NestiaMigrateNestMethodProgrammer.d.ts +2 -1
  42. package/lib/programmers/NestiaMigrateNestMethodProgrammer.js +10 -13
  43. package/lib/programmers/NestiaMigrateNestMethodProgrammer.js.map +1 -1
  44. package/lib/programmers/NestiaMigrateSchemaProgrammer.d.ts +1 -1
  45. package/lib/programmers/NestiaMigrateSchemaProgrammer.js +26 -28
  46. package/lib/programmers/NestiaMigrateSchemaProgrammer.js.map +1 -1
  47. package/lib/structures/INestiaMigrateContext.d.ts +1 -1
  48. package/lib/structures/INestiaMigrateController.d.ts +1 -1
  49. package/lib/structures/INestiaMigrateDto.d.ts +1 -1
  50. package/lib/structures/INestiaMigrateProgram.d.ts +1 -1
  51. package/lib/utils/StringUtil.js.map +1 -1
  52. package/package.json +32 -46
  53. package/src/NestiaMigrateApplication.ts +165 -159
  54. package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +51 -51
  55. package/src/bundles/NEST_TEMPLATE.ts +48 -48
  56. package/src/bundles/SDK_TEMPLATE.ts +21 -21
  57. package/src/executable/NestiaMigrateCommander.ts +104 -98
  58. package/src/executable/NestiaMigrateInquirer.ts +106 -106
  59. package/src/executable/bundle.js +125 -125
  60. package/src/executable/migrate.ts +0 -0
  61. package/src/factories/TypeLiteralFactory.ts +57 -57
  62. package/src/module.ts +7 -2
  63. package/src/programmers/NestiaMigrateApiFileProgrammer.ts +55 -55
  64. package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +347 -344
  65. package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +517 -514
  66. package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +308 -309
  67. package/src/programmers/NestiaMigrateApiStartProgrammer.ts +197 -198
  68. package/src/programmers/NestiaMigrateDtoProgrammer.ts +98 -98
  69. package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +153 -153
  70. package/src/programmers/NestiaMigrateE2eProgrammer.ts +48 -48
  71. package/src/programmers/NestiaMigrateImportProgrammer.ts +118 -118
  72. package/src/programmers/NestiaMigrateNestControllerProgrammer.ts +69 -69
  73. package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +409 -406
  74. package/src/programmers/NestiaMigrateSchemaProgrammer.ts +465 -467
  75. package/src/programmers/index.ts +15 -0
  76. package/src/structures/INestiaMigrateContext.ts +9 -9
  77. package/src/structures/INestiaMigrateController.ts +8 -8
  78. package/src/structures/INestiaMigrateDto.ts +8 -8
  79. package/src/structures/INestiaMigrateProgram.ts +11 -11
  80. package/src/structures/index.ts +4 -0
  81. package/src/utils/FilePrinter.ts +49 -49
  82. package/src/utils/StringUtil.ts +114 -113
@@ -1,467 +1,465 @@
1
- import { OpenApi, OpenApiTypeChecker } from "@samchon/openapi";
2
- import ts from "typescript";
3
- import typia from "typia";
4
- import { TypeFactory } from "typia/lib/factories/TypeFactory";
5
- import { FormatCheatSheet } from "typia/lib/tags/internal/FormatCheatSheet";
6
- import { Escaper } from "typia/lib/utils/Escaper";
7
-
8
- import { FilePrinter } from "../utils/FilePrinter";
9
- import { StringUtil } from "../utils/StringUtil";
10
- import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
11
-
12
- export namespace NestiaMigrateSchemaProgrammer {
13
- /* -----------------------------------------------------------
14
- FACADE
15
- ----------------------------------------------------------- */
16
- export const write = (props: {
17
- components: OpenApi.IComponents;
18
- importer: NestiaMigrateImportProgrammer;
19
- schema: OpenApi.IJsonSchema;
20
- }): ts.TypeNode => {
21
- // CONSIDER ANY TYPE CASE
22
- const union: ts.TypeNode[] = [];
23
- if (OpenApiTypeChecker.isUnknown(props.schema))
24
- return TypeFactory.keyword("any");
25
-
26
- // ITERATION
27
- const type: ts.TypeNode = (() => {
28
- // ATOMIC
29
- if (OpenApiTypeChecker.isConstant(props.schema))
30
- return writeConstant({
31
- importer: props.importer,
32
- schema: props.schema,
33
- });
34
- else if (OpenApiTypeChecker.isBoolean(props.schema))
35
- return writeBoolean();
36
- else if (OpenApiTypeChecker.isInteger(props.schema))
37
- return writeInteger({
38
- importer: props.importer,
39
- schema: props.schema,
40
- });
41
- else if (OpenApiTypeChecker.isNumber(props.schema))
42
- return writeNumber({
43
- importer: props.importer,
44
- schema: props.schema,
45
- });
46
- else if (OpenApiTypeChecker.isString(props.schema))
47
- return writeString({
48
- importer: props.importer,
49
- schema: props.schema,
50
- });
51
- // INSTANCES
52
- else if (OpenApiTypeChecker.isArray(props.schema))
53
- return writeArray({
54
- components: props.components,
55
- importer: props.importer,
56
- schema: props.schema,
57
- });
58
- else if (OpenApiTypeChecker.isTuple(props.schema))
59
- return writeTuple({
60
- components: props.components,
61
- importer: props.importer,
62
- schema: props.schema,
63
- });
64
- else if (OpenApiTypeChecker.isObject(props.schema))
65
- return writeObject({
66
- components: props.components,
67
- importer: props.importer,
68
- schema: props.schema,
69
- });
70
- else if (OpenApiTypeChecker.isReference(props.schema))
71
- return writeReference({
72
- importer: props.importer,
73
- schema: props.schema,
74
- });
75
- // UNION
76
- else if (OpenApiTypeChecker.isOneOf(props.schema))
77
- return writeUnion({
78
- components: props.components,
79
- importer: props.importer,
80
- elements: props.schema.oneOf,
81
- });
82
- else if (OpenApiTypeChecker.isNull(props.schema))
83
- return createNode("null");
84
- else return TypeFactory.keyword("any");
85
- })();
86
- union.push(type);
87
-
88
- // DETERMINE
89
- if (union.length === 0) return TypeFactory.keyword("any");
90
- else if (union.length === 1) return union[0];
91
- return ts.factory.createUnionTypeNode(union);
92
- };
93
-
94
- /* -----------------------------------------------------------
95
- ATOMICS
96
- ----------------------------------------------------------- */
97
- const writeConstant = (props: {
98
- importer: NestiaMigrateImportProgrammer;
99
- schema: OpenApi.IJsonSchema.IConstant;
100
- }): ts.TypeNode => {
101
- return ts.factory.createLiteralTypeNode(
102
- typeof props.schema.const === "boolean"
103
- ? props.schema.const === true
104
- ? ts.factory.createTrue()
105
- : ts.factory.createFalse()
106
- : typeof props.schema.const === "number"
107
- ? props.schema.const < 0
108
- ? ts.factory.createPrefixUnaryExpression(
109
- ts.SyntaxKind.MinusToken,
110
- ts.factory.createNumericLiteral(-props.schema.const),
111
- )
112
- : ts.factory.createNumericLiteral(props.schema.const)
113
- : ts.factory.createStringLiteral(props.schema.const),
114
- );
115
- };
116
-
117
- const writeBoolean = (): ts.TypeNode => TypeFactory.keyword("boolean");
118
-
119
- const writeInteger = (props: {
120
- importer: NestiaMigrateImportProgrammer;
121
- schema: OpenApi.IJsonSchema.IInteger;
122
- }): ts.TypeNode =>
123
- writeNumeric({
124
- factory: () => [
125
- TypeFactory.keyword("number"),
126
- props.importer.tag("Type", "int32"),
127
- ],
128
- importer: props.importer,
129
- schema: props.schema,
130
- });
131
-
132
- const writeNumber = (props: {
133
- importer: NestiaMigrateImportProgrammer;
134
- schema: OpenApi.IJsonSchema.INumber;
135
- }): ts.TypeNode =>
136
- writeNumeric({
137
- factory: () => [TypeFactory.keyword("number")],
138
- importer: props.importer,
139
- schema: props.schema,
140
- });
141
-
142
- const writeNumeric = (props: {
143
- factory: () => ts.TypeNode[];
144
- importer: NestiaMigrateImportProgrammer;
145
- schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
146
- }): ts.TypeNode => {
147
- const intersection: ts.TypeNode[] = props.factory();
148
- if (props.schema.default !== undefined)
149
- intersection.push(props.importer.tag("Default", props.schema.default));
150
- if (props.schema.minimum !== undefined)
151
- intersection.push(
152
- props.importer.tag(
153
- props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
154
- props.schema.minimum,
155
- ),
156
- );
157
- if (props.schema.maximum !== undefined)
158
- intersection.push(
159
- props.importer.tag(
160
- props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
161
- props.schema.maximum,
162
- ),
163
- );
164
- if (props.schema.multipleOf !== undefined)
165
- intersection.push(
166
- props.importer.tag("MultipleOf", props.schema.multipleOf),
167
- );
168
- return intersection.length === 1
169
- ? intersection[0]
170
- : ts.factory.createIntersectionTypeNode(intersection);
171
- };
172
-
173
- const writeString = (props: {
174
- importer: NestiaMigrateImportProgrammer;
175
- schema: OpenApi.IJsonSchema.IString;
176
- }): ts.TypeNode => {
177
- if (props.schema.format === "binary")
178
- return ts.factory.createTypeReferenceNode("File");
179
-
180
- const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
181
- if (props.schema.default !== undefined)
182
- intersection.push(props.importer.tag("Default", props.schema.default));
183
- if (props.schema.minLength !== undefined)
184
- intersection.push(
185
- props.importer.tag("MinLength", props.schema.minLength),
186
- );
187
- if (props.schema.maxLength !== undefined)
188
- intersection.push(
189
- props.importer.tag("MaxLength", props.schema.maxLength),
190
- );
191
- if (props.schema.pattern !== undefined)
192
- intersection.push(props.importer.tag("Pattern", props.schema.pattern));
193
- if (
194
- props.schema.format !== undefined &&
195
- (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
196
- undefined
197
- )
198
- intersection.push(props.importer.tag("Format", props.schema.format));
199
- if (props.schema.contentMediaType !== undefined)
200
- intersection.push(
201
- props.importer.tag("ContentMediaType", props.schema.contentMediaType),
202
- );
203
- return intersection.length === 1
204
- ? intersection[0]
205
- : ts.factory.createIntersectionTypeNode(intersection);
206
- };
207
-
208
- /* -----------------------------------------------------------
209
- INSTANCES
210
- ----------------------------------------------------------- */
211
- const writeArray = (props: {
212
- components: OpenApi.IComponents;
213
- importer: NestiaMigrateImportProgrammer;
214
- schema: OpenApi.IJsonSchema.IArray;
215
- }): ts.TypeNode => {
216
- const intersection: ts.TypeNode[] = [
217
- ts.factory.createArrayTypeNode(
218
- write({
219
- components: props.components,
220
- importer: props.importer,
221
- schema: props.schema.items,
222
- }),
223
- ),
224
- ];
225
- if (props.schema.minItems !== undefined)
226
- intersection.push(props.importer.tag("MinItems", props.schema.minItems));
227
- if (props.schema.maxItems !== undefined)
228
- intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
229
- if (props.schema.uniqueItems === true)
230
- intersection.push(props.importer.tag("UniqueItems"));
231
- return intersection.length === 1
232
- ? intersection[0]
233
- : ts.factory.createIntersectionTypeNode(intersection);
234
- };
235
-
236
- const writeTuple = (props: {
237
- components: OpenApi.IComponents;
238
- importer: NestiaMigrateImportProgrammer;
239
- schema: OpenApi.IJsonSchema.ITuple;
240
- }): ts.TypeNode =>
241
- ts.factory.createTupleTypeNode([
242
- ...props.schema.prefixItems.map((item) =>
243
- write({
244
- components: props.components,
245
- importer: props.importer,
246
- schema: item,
247
- }),
248
- ),
249
- ...(typeof props.schema.additionalItems === "object" &&
250
- props.schema.additionalItems !== null
251
- ? [
252
- ts.factory.createRestTypeNode(
253
- write({
254
- components: props.components,
255
- importer: props.importer,
256
- schema: props.schema.additionalItems,
257
- }),
258
- ),
259
- ]
260
- : props.schema.additionalItems === true
261
- ? [
262
- ts.factory.createRestTypeNode(
263
- ts.factory.createArrayTypeNode(
264
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
265
- ),
266
- ),
267
- ]
268
- : []),
269
- ]);
270
-
271
- const writeObject = (props: {
272
- components: OpenApi.IComponents;
273
- importer: NestiaMigrateImportProgrammer;
274
- schema: OpenApi.IJsonSchema.IObject;
275
- }): ts.TypeNode => {
276
- const regular = () =>
277
- ts.factory.createTypeLiteralNode(
278
- Object.entries(props.schema.properties ?? [])
279
- .map(([key, value], index) => [
280
- ...(index !== 0 &&
281
- (!!value.title?.length || !!value.description?.length)
282
- ? [ts.factory.createIdentifier("\n") as any]
283
- : []),
284
- writeRegularProperty({
285
- components: props.components,
286
- importer: props.importer,
287
- required: props.schema.required ?? [],
288
- key,
289
- value,
290
- }),
291
- ])
292
- .flat(),
293
- );
294
- const dynamic = () =>
295
- ts.factory.createTypeLiteralNode([
296
- writeDynamicProperty({
297
- components: props.components,
298
- importer: props.importer,
299
- schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
300
- }),
301
- ]);
302
- return !!props.schema.properties?.length &&
303
- typeof props.schema.additionalProperties === "object"
304
- ? ts.factory.createIntersectionTypeNode([regular(), dynamic()])
305
- : typeof props.schema.additionalProperties === "object"
306
- ? dynamic()
307
- : regular();
308
- };
309
-
310
- const writeRegularProperty = (props: {
311
- components: OpenApi.IComponents;
312
- importer: NestiaMigrateImportProgrammer;
313
- required: string[];
314
- key: string;
315
- value: OpenApi.IJsonSchema;
316
- }) => {
317
- const valueTypeNode: ts.TypeNode = write({
318
- components: props.components,
319
- importer: props.importer,
320
- schema: props.value,
321
- });
322
- return FilePrinter.description(
323
- ts.factory.createPropertySignature(
324
- props.value.readOnly
325
- ? [ts.factory.createToken(ts.SyntaxKind.ReadonlyKeyword)]
326
- : undefined,
327
- Escaper.variable(props.key)
328
- ? ts.factory.createIdentifier(props.key)
329
- : ts.factory.createStringLiteral(props.key),
330
- props.required.includes(props.key)
331
- ? undefined
332
- : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
333
- props.required.includes(props.key)
334
- ? valueTypeNode
335
- : ts.isUnionTypeNode(valueTypeNode)
336
- ? ts.factory.createUnionTypeNode([
337
- ...valueTypeNode.types,
338
- ts.factory.createTypeReferenceNode("undefined"),
339
- ])
340
- : ts.factory.createUnionTypeNode([
341
- valueTypeNode,
342
- ts.factory.createTypeReferenceNode("undefined"),
343
- ]),
344
- ),
345
- writeComment(props.value),
346
- );
347
- };
348
-
349
- const writeDynamicProperty = (props: {
350
- components: OpenApi.IComponents;
351
- importer: NestiaMigrateImportProgrammer;
352
- schema: OpenApi.IJsonSchema;
353
- }) =>
354
- FilePrinter.description(
355
- ts.factory.createIndexSignature(
356
- undefined,
357
- [
358
- ts.factory.createParameterDeclaration(
359
- undefined,
360
- undefined,
361
- ts.factory.createIdentifier("key"),
362
- undefined,
363
- TypeFactory.keyword("string"),
364
- ),
365
- ],
366
- write(props),
367
- ),
368
- writeComment(props.schema),
369
- );
370
-
371
- const writeReference = (props: {
372
- importer: NestiaMigrateImportProgrammer;
373
- schema: OpenApi.IJsonSchema.IReference;
374
- }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
375
- if (props.schema.$ref.startsWith("#/components/schemas") === false)
376
- return TypeFactory.keyword("any");
377
- const name: string = props.schema.$ref
378
- .split("/")
379
- .slice(3)
380
- .filter((str) => str.length !== 0)
381
- .map(StringUtil.escapeNonVariable)
382
- .join("");
383
- if (name === "") return TypeFactory.keyword("any");
384
- return props.importer.dto(name);
385
- };
386
-
387
- /* -----------------------------------------------------------
388
- UNIONS
389
- ----------------------------------------------------------- */
390
- const writeUnion = (props: {
391
- components: OpenApi.IComponents;
392
- importer: NestiaMigrateImportProgrammer;
393
- elements: OpenApi.IJsonSchema[];
394
- }): ts.UnionTypeNode =>
395
- ts.factory.createUnionTypeNode(
396
- props.elements.map((schema) =>
397
- write({
398
- components: props.components,
399
- importer: props.importer,
400
- schema,
401
- }),
402
- ),
403
- );
404
- }
405
- const createNode = (text: string) => ts.factory.createTypeReferenceNode(text);
406
-
407
- const writeComment = (schema: OpenApi.IJsonSchema): string => {
408
- interface IPlugin {
409
- key: string;
410
- value: undefined | string | number | boolean;
411
- }
412
- const plugins: IPlugin[] = [];
413
- if (schema.title !== undefined)
414
- plugins.push({
415
- key: "title",
416
- value: schema.title,
417
- });
418
- if (schema.deprecated === true)
419
- plugins.push({
420
- key: "deprecated",
421
- value: undefined,
422
- });
423
- for (const [key, value] of Object.entries(schema))
424
- if (key.startsWith("x-") && typia.is<boolean | number | string>(value))
425
- plugins.push({
426
- key,
427
- value,
428
- });
429
- return [
430
- ...(schema.description?.length
431
- ? [eraseCommentTags(schema.description)]
432
- : []),
433
- ...(schema.description?.length && plugins.length !== 0 ? [""] : []),
434
- ...plugins.map((p) =>
435
- p.value === undefined ? `@${p.key}` : `@${p.key} ${String(p.value)}`,
436
- ),
437
- ].join("\n");
438
- };
439
-
440
- const eraseCommentTags = (description: string): string => {
441
- const lines: string[] = description.split("\n");
442
- return lines
443
- .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
444
- .join("\n");
445
- };
446
-
447
- const COMMENT_TAGS = [
448
- // string
449
- "@format",
450
- "@pattern",
451
- "@length",
452
- "@minLength",
453
- "@maxLength",
454
- "@contentMediaType",
455
- // number
456
- "@type",
457
- "@minimum",
458
- "@maximum",
459
- "@exclusiveMinimum",
460
- "@exclusiveMaximum",
461
- "@multipleOf",
462
- // array
463
- "@items",
464
- "@minItems",
465
- "@maxItems",
466
- "@uniqueItems",
467
- ];
1
+ import { FormatCheatSheet, TypeFactory } from "@typia/core";
2
+ import { NamingConvention, OpenApiTypeChecker } from "@typia/utils";
3
+ import ts from "typescript";
4
+ import typia, { OpenApi } from "typia";
5
+
6
+ import { FilePrinter } from "../utils/FilePrinter";
7
+ import { StringUtil } from "../utils/StringUtil";
8
+ import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
9
+
10
+ export namespace NestiaMigrateSchemaProgrammer {
11
+ /* -----------------------------------------------------------
12
+ FACADE
13
+ ----------------------------------------------------------- */
14
+ export const write = (props: {
15
+ components: OpenApi.IComponents;
16
+ importer: NestiaMigrateImportProgrammer;
17
+ schema: OpenApi.IJsonSchema;
18
+ }): ts.TypeNode => {
19
+ // CONSIDER ANY TYPE CASE
20
+ const union: ts.TypeNode[] = [];
21
+ if (OpenApiTypeChecker.isUnknown(props.schema))
22
+ return TypeFactory.keyword("any");
23
+
24
+ // ITERATION
25
+ const type: ts.TypeNode = (() => {
26
+ // ATOMIC
27
+ if (OpenApiTypeChecker.isConstant(props.schema))
28
+ return writeConstant({
29
+ importer: props.importer,
30
+ schema: props.schema,
31
+ });
32
+ else if (OpenApiTypeChecker.isBoolean(props.schema))
33
+ return writeBoolean();
34
+ else if (OpenApiTypeChecker.isInteger(props.schema))
35
+ return writeInteger({
36
+ importer: props.importer,
37
+ schema: props.schema,
38
+ });
39
+ else if (OpenApiTypeChecker.isNumber(props.schema))
40
+ return writeNumber({
41
+ importer: props.importer,
42
+ schema: props.schema,
43
+ });
44
+ else if (OpenApiTypeChecker.isString(props.schema))
45
+ return writeString({
46
+ importer: props.importer,
47
+ schema: props.schema,
48
+ });
49
+ // INSTANCES
50
+ else if (OpenApiTypeChecker.isArray(props.schema))
51
+ return writeArray({
52
+ components: props.components,
53
+ importer: props.importer,
54
+ schema: props.schema,
55
+ });
56
+ else if (OpenApiTypeChecker.isTuple(props.schema))
57
+ return writeTuple({
58
+ components: props.components,
59
+ importer: props.importer,
60
+ schema: props.schema,
61
+ });
62
+ else if (OpenApiTypeChecker.isObject(props.schema))
63
+ return writeObject({
64
+ components: props.components,
65
+ importer: props.importer,
66
+ schema: props.schema,
67
+ });
68
+ else if (OpenApiTypeChecker.isReference(props.schema))
69
+ return writeReference({
70
+ importer: props.importer,
71
+ schema: props.schema,
72
+ });
73
+ // UNION
74
+ else if (OpenApiTypeChecker.isOneOf(props.schema))
75
+ return writeUnion({
76
+ components: props.components,
77
+ importer: props.importer,
78
+ elements: props.schema.oneOf,
79
+ });
80
+ else if (OpenApiTypeChecker.isNull(props.schema))
81
+ return createNode("null");
82
+ else return TypeFactory.keyword("any");
83
+ })();
84
+ union.push(type);
85
+
86
+ // DETERMINE
87
+ if (union.length === 0) return TypeFactory.keyword("any");
88
+ else if (union.length === 1) return union[0]!;
89
+ return ts.factory.createUnionTypeNode(union);
90
+ };
91
+
92
+ /* -----------------------------------------------------------
93
+ ATOMICS
94
+ ----------------------------------------------------------- */
95
+ const writeConstant = (props: {
96
+ importer: NestiaMigrateImportProgrammer;
97
+ schema: OpenApi.IJsonSchema.IConstant;
98
+ }): ts.TypeNode => {
99
+ return ts.factory.createLiteralTypeNode(
100
+ typeof props.schema.const === "boolean"
101
+ ? props.schema.const === true
102
+ ? ts.factory.createTrue()
103
+ : ts.factory.createFalse()
104
+ : typeof props.schema.const === "number"
105
+ ? props.schema.const < 0
106
+ ? ts.factory.createPrefixUnaryExpression(
107
+ ts.SyntaxKind.MinusToken,
108
+ ts.factory.createNumericLiteral(-props.schema.const),
109
+ )
110
+ : ts.factory.createNumericLiteral(props.schema.const)
111
+ : ts.factory.createStringLiteral(props.schema.const),
112
+ );
113
+ };
114
+
115
+ const writeBoolean = (): ts.TypeNode => TypeFactory.keyword("boolean");
116
+
117
+ const writeInteger = (props: {
118
+ importer: NestiaMigrateImportProgrammer;
119
+ schema: OpenApi.IJsonSchema.IInteger;
120
+ }): ts.TypeNode =>
121
+ writeNumeric({
122
+ factory: () => [
123
+ TypeFactory.keyword("number"),
124
+ props.importer.tag("Type", "int32"),
125
+ ],
126
+ importer: props.importer,
127
+ schema: props.schema,
128
+ });
129
+
130
+ const writeNumber = (props: {
131
+ importer: NestiaMigrateImportProgrammer;
132
+ schema: OpenApi.IJsonSchema.INumber;
133
+ }): ts.TypeNode =>
134
+ writeNumeric({
135
+ factory: () => [TypeFactory.keyword("number")],
136
+ importer: props.importer,
137
+ schema: props.schema,
138
+ });
139
+
140
+ const writeNumeric = (props: {
141
+ factory: () => ts.TypeNode[];
142
+ importer: NestiaMigrateImportProgrammer;
143
+ schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
144
+ }): ts.TypeNode => {
145
+ const intersection: ts.TypeNode[] = props.factory();
146
+ if (props.schema.default !== undefined)
147
+ intersection.push(props.importer.tag("Default", props.schema.default));
148
+ if (props.schema.minimum !== undefined)
149
+ intersection.push(
150
+ props.importer.tag(
151
+ props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
152
+ props.schema.minimum,
153
+ ),
154
+ );
155
+ if (props.schema.maximum !== undefined)
156
+ intersection.push(
157
+ props.importer.tag(
158
+ props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
159
+ props.schema.maximum,
160
+ ),
161
+ );
162
+ if (props.schema.multipleOf !== undefined)
163
+ intersection.push(
164
+ props.importer.tag("MultipleOf", props.schema.multipleOf),
165
+ );
166
+ return intersection.length === 1
167
+ ? intersection[0]!
168
+ : ts.factory.createIntersectionTypeNode(intersection);
169
+ };
170
+
171
+ const writeString = (props: {
172
+ importer: NestiaMigrateImportProgrammer;
173
+ schema: OpenApi.IJsonSchema.IString;
174
+ }): ts.TypeNode => {
175
+ if (props.schema.format === "binary")
176
+ return ts.factory.createTypeReferenceNode("File");
177
+
178
+ const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
179
+ if (props.schema.default !== undefined)
180
+ intersection.push(props.importer.tag("Default", props.schema.default));
181
+ if (props.schema.minLength !== undefined)
182
+ intersection.push(
183
+ props.importer.tag("MinLength", props.schema.minLength),
184
+ );
185
+ if (props.schema.maxLength !== undefined)
186
+ intersection.push(
187
+ props.importer.tag("MaxLength", props.schema.maxLength),
188
+ );
189
+ if (props.schema.pattern !== undefined)
190
+ intersection.push(props.importer.tag("Pattern", props.schema.pattern));
191
+ if (
192
+ props.schema.format !== undefined &&
193
+ (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
194
+ undefined
195
+ )
196
+ intersection.push(props.importer.tag("Format", props.schema.format));
197
+ if (props.schema.contentMediaType !== undefined)
198
+ intersection.push(
199
+ props.importer.tag("ContentMediaType", props.schema.contentMediaType),
200
+ );
201
+ return intersection.length === 1
202
+ ? intersection[0]!
203
+ : ts.factory.createIntersectionTypeNode(intersection);
204
+ };
205
+
206
+ /* -----------------------------------------------------------
207
+ INSTANCES
208
+ ----------------------------------------------------------- */
209
+ const writeArray = (props: {
210
+ components: OpenApi.IComponents;
211
+ importer: NestiaMigrateImportProgrammer;
212
+ schema: OpenApi.IJsonSchema.IArray;
213
+ }): ts.TypeNode => {
214
+ const intersection: ts.TypeNode[] = [
215
+ ts.factory.createArrayTypeNode(
216
+ write({
217
+ components: props.components,
218
+ importer: props.importer,
219
+ schema: props.schema.items,
220
+ }),
221
+ ),
222
+ ];
223
+ if (props.schema.minItems !== undefined)
224
+ intersection.push(props.importer.tag("MinItems", props.schema.minItems));
225
+ if (props.schema.maxItems !== undefined)
226
+ intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
227
+ if (props.schema.uniqueItems === true)
228
+ intersection.push(props.importer.tag("UniqueItems"));
229
+ return intersection.length === 1
230
+ ? intersection[0]!
231
+ : ts.factory.createIntersectionTypeNode(intersection);
232
+ };
233
+
234
+ const writeTuple = (props: {
235
+ components: OpenApi.IComponents;
236
+ importer: NestiaMigrateImportProgrammer;
237
+ schema: OpenApi.IJsonSchema.ITuple;
238
+ }): ts.TypeNode =>
239
+ ts.factory.createTupleTypeNode([
240
+ ...props.schema.prefixItems.map((item) =>
241
+ write({
242
+ components: props.components,
243
+ importer: props.importer,
244
+ schema: item,
245
+ }),
246
+ ),
247
+ ...(typeof props.schema.additionalItems === "object" &&
248
+ props.schema.additionalItems !== null
249
+ ? [
250
+ ts.factory.createRestTypeNode(
251
+ write({
252
+ components: props.components,
253
+ importer: props.importer,
254
+ schema: props.schema.additionalItems,
255
+ }),
256
+ ),
257
+ ]
258
+ : props.schema.additionalItems === true
259
+ ? [
260
+ ts.factory.createRestTypeNode(
261
+ ts.factory.createArrayTypeNode(
262
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
263
+ ),
264
+ ),
265
+ ]
266
+ : []),
267
+ ]);
268
+
269
+ const writeObject = (props: {
270
+ components: OpenApi.IComponents;
271
+ importer: NestiaMigrateImportProgrammer;
272
+ schema: OpenApi.IJsonSchema.IObject;
273
+ }): ts.TypeNode => {
274
+ const regular = () =>
275
+ ts.factory.createTypeLiteralNode(
276
+ Object.entries(props.schema.properties ?? [])
277
+ .map(([key, value], index) => [
278
+ ...(index !== 0 &&
279
+ (!!value.title?.length || !!value.description?.length)
280
+ ? [ts.factory.createIdentifier("\n") as any]
281
+ : []),
282
+ writeRegularProperty({
283
+ components: props.components,
284
+ importer: props.importer,
285
+ required: props.schema.required ?? [],
286
+ key,
287
+ value,
288
+ }),
289
+ ])
290
+ .flat(),
291
+ );
292
+ const dynamic = () =>
293
+ ts.factory.createTypeLiteralNode([
294
+ writeDynamicProperty({
295
+ components: props.components,
296
+ importer: props.importer,
297
+ schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
298
+ }),
299
+ ]);
300
+ return !!props.schema.properties?.length &&
301
+ typeof props.schema.additionalProperties === "object"
302
+ ? ts.factory.createIntersectionTypeNode([regular(), dynamic()])
303
+ : typeof props.schema.additionalProperties === "object"
304
+ ? dynamic()
305
+ : regular();
306
+ };
307
+
308
+ const writeRegularProperty = (props: {
309
+ components: OpenApi.IComponents;
310
+ importer: NestiaMigrateImportProgrammer;
311
+ required: string[];
312
+ key: string;
313
+ value: OpenApi.IJsonSchema;
314
+ }) => {
315
+ const valueTypeNode: ts.TypeNode = write({
316
+ components: props.components,
317
+ importer: props.importer,
318
+ schema: props.value,
319
+ });
320
+ return FilePrinter.description(
321
+ ts.factory.createPropertySignature(
322
+ props.value.readOnly
323
+ ? [ts.factory.createToken(ts.SyntaxKind.ReadonlyKeyword)]
324
+ : undefined,
325
+ NamingConvention.variable(props.key)
326
+ ? ts.factory.createIdentifier(props.key)
327
+ : ts.factory.createStringLiteral(props.key),
328
+ props.required.includes(props.key)
329
+ ? undefined
330
+ : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
331
+ props.required.includes(props.key)
332
+ ? valueTypeNode
333
+ : ts.isUnionTypeNode(valueTypeNode)
334
+ ? ts.factory.createUnionTypeNode([
335
+ ...valueTypeNode.types,
336
+ ts.factory.createTypeReferenceNode("undefined"),
337
+ ])
338
+ : ts.factory.createUnionTypeNode([
339
+ valueTypeNode,
340
+ ts.factory.createTypeReferenceNode("undefined"),
341
+ ]),
342
+ ),
343
+ writeComment(props.value),
344
+ );
345
+ };
346
+
347
+ const writeDynamicProperty = (props: {
348
+ components: OpenApi.IComponents;
349
+ importer: NestiaMigrateImportProgrammer;
350
+ schema: OpenApi.IJsonSchema;
351
+ }) =>
352
+ FilePrinter.description(
353
+ ts.factory.createIndexSignature(
354
+ undefined,
355
+ [
356
+ ts.factory.createParameterDeclaration(
357
+ undefined,
358
+ undefined,
359
+ ts.factory.createIdentifier("key"),
360
+ undefined,
361
+ TypeFactory.keyword("string"),
362
+ ),
363
+ ],
364
+ write(props),
365
+ ),
366
+ writeComment(props.schema),
367
+ );
368
+
369
+ const writeReference = (props: {
370
+ importer: NestiaMigrateImportProgrammer;
371
+ schema: OpenApi.IJsonSchema.IReference;
372
+ }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
373
+ if (props.schema.$ref.startsWith("#/components/schemas") === false)
374
+ return TypeFactory.keyword("any");
375
+ const name: string = props.schema.$ref
376
+ .split("/")
377
+ .slice(3)
378
+ .filter((str) => str.length !== 0)
379
+ .map(StringUtil.escapeNonVariable)
380
+ .join("");
381
+ if (name === "") return TypeFactory.keyword("any");
382
+ return props.importer.dto(name);
383
+ };
384
+
385
+ /* -----------------------------------------------------------
386
+ UNIONS
387
+ ----------------------------------------------------------- */
388
+ const writeUnion = (props: {
389
+ components: OpenApi.IComponents;
390
+ importer: NestiaMigrateImportProgrammer;
391
+ elements: OpenApi.IJsonSchema[];
392
+ }): ts.UnionTypeNode =>
393
+ ts.factory.createUnionTypeNode(
394
+ props.elements.map((schema) =>
395
+ write({
396
+ components: props.components,
397
+ importer: props.importer,
398
+ schema,
399
+ }),
400
+ ),
401
+ );
402
+ }
403
+ const createNode = (text: string) => ts.factory.createTypeReferenceNode(text);
404
+
405
+ const writeComment = (schema: OpenApi.IJsonSchema): string => {
406
+ interface IPlugin {
407
+ key: string;
408
+ value: undefined | string | number | boolean;
409
+ }
410
+ const plugins: IPlugin[] = [];
411
+ if (schema.title !== undefined)
412
+ plugins.push({
413
+ key: "title",
414
+ value: schema.title,
415
+ });
416
+ if (schema.deprecated === true)
417
+ plugins.push({
418
+ key: "deprecated",
419
+ value: undefined,
420
+ });
421
+ for (const [key, value] of Object.entries(schema))
422
+ if (key.startsWith("x-") && typia.is<boolean | number | string>(value))
423
+ plugins.push({
424
+ key,
425
+ value,
426
+ });
427
+ return [
428
+ ...(schema.description?.length
429
+ ? [eraseCommentTags(schema.description)]
430
+ : []),
431
+ ...(schema.description?.length && plugins.length !== 0 ? [""] : []),
432
+ ...plugins.map((p) =>
433
+ p.value === undefined ? `@${p.key}` : `@${p.key} ${String(p.value)}`,
434
+ ),
435
+ ].join("\n");
436
+ };
437
+
438
+ const eraseCommentTags = (description: string): string => {
439
+ const lines: string[] = description.split("\n");
440
+ return lines
441
+ .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
442
+ .join("\n");
443
+ };
444
+
445
+ const COMMENT_TAGS = [
446
+ // string
447
+ "@format",
448
+ "@pattern",
449
+ "@length",
450
+ "@minLength",
451
+ "@maxLength",
452
+ "@contentMediaType",
453
+ // number
454
+ "@type",
455
+ "@minimum",
456
+ "@maximum",
457
+ "@exclusiveMinimum",
458
+ "@exclusiveMaximum",
459
+ "@multipleOf",
460
+ // array
461
+ "@items",
462
+ "@minItems",
463
+ "@maxItems",
464
+ "@uniqueItems",
465
+ ];