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