@nestia/migrate 7.0.0-dev.20250607 → 7.0.0

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