@nestia/migrate 11.0.0-dev.20260316 → 11.0.1

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 (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -93
  3. package/lib/NestiaMigrateApplication.js +341 -341
  4. package/lib/bundles/NEST_TEMPLATE.js +48 -48
  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/executable/migrate.js +0 -0
  9. package/lib/index.mjs +469 -469
  10. package/lib/index.mjs.map +1 -1
  11. package/package.json +10 -8
  12. package/src/NestiaMigrateApplication.ts +168 -168
  13. package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +51 -51
  14. package/src/bundles/NEST_TEMPLATE.ts +48 -48
  15. package/src/bundles/SDK_TEMPLATE.ts +21 -21
  16. package/src/executable/NestiaMigrateCommander.ts +104 -104
  17. package/src/executable/NestiaMigrateInquirer.ts +106 -106
  18. package/src/executable/bundle.js +129 -125
  19. package/src/executable/migrate.ts +0 -0
  20. package/src/factories/TypeLiteralFactory.ts +57 -57
  21. package/src/module.ts +7 -7
  22. package/src/programmers/NestiaMigrateApiFileProgrammer.ts +55 -55
  23. package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +347 -347
  24. package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +517 -517
  25. package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +308 -308
  26. package/src/programmers/NestiaMigrateApiStartProgrammer.ts +197 -197
  27. package/src/programmers/NestiaMigrateDtoProgrammer.ts +98 -98
  28. package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +153 -153
  29. package/src/programmers/NestiaMigrateE2eProgrammer.ts +48 -48
  30. package/src/programmers/NestiaMigrateImportProgrammer.ts +118 -118
  31. package/src/programmers/NestiaMigrateNestControllerProgrammer.ts +69 -69
  32. package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +409 -409
  33. package/src/programmers/NestiaMigrateSchemaProgrammer.ts +465 -465
  34. package/src/programmers/index.ts +15 -15
  35. package/src/structures/INestiaMigrateContext.ts +9 -9
  36. package/src/structures/INestiaMigrateController.ts +8 -8
  37. package/src/structures/INestiaMigrateDto.ts +8 -8
  38. package/src/structures/INestiaMigrateProgram.ts +11 -11
  39. package/src/structures/index.ts +4 -4
  40. package/src/utils/FilePrinter.ts +49 -49
  41. package/src/utils/StringUtil.ts +114 -114
@@ -1,465 +1,465 @@
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
- ];
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
+ ];