@nestia/migrate 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.2

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