@nestia/migrate 7.0.0-dev.20250608 → 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 (52) 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 +69 -69
  9. package/lib/index.mjs.map +1 -1
  10. package/lib/utils/openapi-down-convert/converter.js +2 -2
  11. package/package.json +7 -7
  12. package/src/NestiaMigrateApplication.ts +144 -144
  13. package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +51 -51
  14. package/src/archivers/NestiaMigrateFileArchiver.ts +28 -28
  15. package/src/bundles/NEST_TEMPLATE.ts +47 -47
  16. package/src/bundles/SDK_TEMPLATE.ts +21 -21
  17. package/src/executable/NestiaMigrateCommander.ts +98 -98
  18. package/src/executable/NestiaMigrateInquirer.ts +106 -106
  19. package/src/executable/bundle.js +129 -129
  20. package/src/executable/migrate.ts +7 -7
  21. package/src/factories/TypeLiteralFactory.ts +57 -57
  22. package/src/index.ts +4 -4
  23. package/src/module.ts +2 -2
  24. package/src/programmers/NestiaMigrateApiFileProgrammer.ts +55 -55
  25. package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +256 -256
  26. package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +515 -515
  27. package/src/programmers/NestiaMigrateApiProgrammer.ts +107 -107
  28. package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +340 -340
  29. package/src/programmers/NestiaMigrateApiStartProgrammer.ts +198 -198
  30. package/src/programmers/NestiaMigrateDtoProgrammer.ts +101 -101
  31. package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +153 -153
  32. package/src/programmers/NestiaMigrateE2eProgrammer.ts +46 -46
  33. package/src/programmers/NestiaMigrateImportProgrammer.ts +118 -118
  34. package/src/programmers/NestiaMigrateNestControllerProgrammer.ts +66 -66
  35. package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +406 -406
  36. package/src/programmers/NestiaMigrateNestModuleProgrammer.ts +65 -65
  37. package/src/programmers/NestiaMigrateNestProgrammer.ts +88 -88
  38. package/src/programmers/NestiaMigrateSchemaProgrammer.ts +475 -475
  39. package/src/structures/INestiaMigrateConfig.ts +10 -10
  40. package/src/structures/INestiaMigrateContext.ts +15 -15
  41. package/src/structures/INestiaMigrateController.ts +8 -8
  42. package/src/structures/INestiaMigrateDto.ts +8 -8
  43. package/src/structures/INestiaMigrateFile.ts +5 -5
  44. package/src/structures/INestiaMigrateProgram.ts +11 -11
  45. package/src/structures/INestiaMigrateSchema.ts +4 -4
  46. package/src/utils/FilePrinter.ts +38 -38
  47. package/src/utils/MapUtil.ts +13 -13
  48. package/src/utils/OpenApiTypeChecker.ts +73 -73
  49. package/src/utils/SetupWizard.ts +12 -12
  50. package/src/utils/StringUtil.ts +113 -113
  51. package/src/utils/openapi-down-convert/RefVisitor.ts +139 -139
  52. 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
+ };