@nestia/sdk 2.4.5 → 2.4.6

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 (66) hide show
  1. package/lib/analyses/ConfigAnalyzer.js +1 -1
  2. package/lib/analyses/ConfigAnalyzer.js.map +1 -1
  3. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  4. package/lib/analyses/PathAnalyzer.js.map +1 -1
  5. package/lib/analyses/ReflectAnalyzer.js.map +1 -1
  6. package/lib/executable/sdk.js +11 -11
  7. package/lib/generates/SwaggerGenerator.js.map +1 -1
  8. package/lib/generates/internal/SdkFunctionProgrammer.js +7 -7
  9. package/lib/generates/internal/SdkSimulationProgrammer.js.map +1 -1
  10. package/lib/generates/internal/SwaggerSchemaGenerator.js.map +1 -1
  11. package/package.json +3 -3
  12. package/src/INestiaConfig.ts +248 -248
  13. package/src/NestiaSdkApplication.ts +253 -253
  14. package/src/analyses/AccessorAnalyzer.ts +60 -60
  15. package/src/analyses/ConfigAnalyzer.ts +3 -3
  16. package/src/analyses/ControllerAnalyzer.ts +2 -2
  17. package/src/analyses/ExceptionAnalyzer.ts +115 -115
  18. package/src/analyses/GenericAnalyzer.ts +51 -51
  19. package/src/analyses/ImportAnalyzer.ts +138 -138
  20. package/src/analyses/PathAnalyzer.ts +14 -14
  21. package/src/analyses/ReflectAnalyzer.ts +9 -9
  22. package/src/analyses/SecurityAnalyzer.ts +20 -20
  23. package/src/executable/internal/CommandParser.ts +15 -15
  24. package/src/executable/internal/NestiaConfigLoader.ts +67 -67
  25. package/src/executable/internal/NestiaSdkCommand.ts +60 -60
  26. package/src/executable/sdk.ts +73 -73
  27. package/src/generates/E2eGenerator.ts +64 -64
  28. package/src/generates/SdkGenerator.ts +96 -96
  29. package/src/generates/SwaggerGenerator.ts +5 -5
  30. package/src/generates/internal/E2eFileProgrammer.ts +123 -123
  31. package/src/generates/internal/SdkDistributionComposer.ts +91 -91
  32. package/src/generates/internal/SdkDtoGenerator.ts +424 -424
  33. package/src/generates/internal/SdkFileProgrammer.ts +106 -106
  34. package/src/generates/internal/SdkFunctionProgrammer.ts +518 -518
  35. package/src/generates/internal/SdkImportWizard.ts +55 -55
  36. package/src/generates/internal/SdkRouteDirectory.ts +17 -17
  37. package/src/generates/internal/SdkSimulationProgrammer.ts +4 -4
  38. package/src/generates/internal/SdkTypeDefiner.ts +119 -119
  39. package/src/generates/internal/SwaggerSchemaGenerator.ts +16 -16
  40. package/src/generates/internal/SwaggerSchemaValidator.ts +198 -198
  41. package/src/index.ts +4 -4
  42. package/src/module.ts +2 -2
  43. package/src/structures/IController.ts +91 -91
  44. package/src/structures/IErrorReport.ts +6 -6
  45. package/src/structures/INestiaProject.ts +13 -13
  46. package/src/structures/INormalizedInput.ts +20 -20
  47. package/src/structures/IRoute.ts +52 -52
  48. package/src/structures/ISwagger.ts +91 -91
  49. package/src/structures/ISwaggerComponents.ts +29 -29
  50. package/src/structures/ISwaggerError.ts +8 -8
  51. package/src/structures/ISwaggerInfo.ts +80 -80
  52. package/src/structures/ISwaggerLazyProperty.ts +7 -7
  53. package/src/structures/ISwaggerLazySchema.ts +7 -7
  54. package/src/structures/ISwaggerRoute.ts +51 -51
  55. package/src/structures/ISwaggerSecurityScheme.ts +65 -65
  56. package/src/structures/ITypeTuple.ts +6 -6
  57. package/src/structures/MethodType.ts +5 -5
  58. package/src/structures/ParamCategory.ts +1 -1
  59. package/src/structures/TypeEntry.ts +22 -22
  60. package/src/utils/ArrayUtil.ts +26 -26
  61. package/src/utils/FileRetriever.ts +22 -22
  62. package/src/utils/ImportDictionary.ts +125 -125
  63. package/src/utils/MapUtil.ts +14 -14
  64. package/src/utils/PathUtil.ts +10 -10
  65. package/src/utils/SourceFinder.ts +66 -66
  66. package/src/utils/StripEnums.ts +5 -5
@@ -1,424 +1,424 @@
1
- import fs from "fs";
2
- import ts from "typescript";
3
- import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
4
- import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
5
- import { IJsDocTagInfo } from "typia/lib/schemas/metadata/IJsDocTagInfo";
6
- import { IMetadataTypeTag } from "typia/lib/schemas/metadata/IMetadataTypeTag";
7
- import { Metadata } from "typia/lib/schemas/metadata/Metadata";
8
- import { MetadataAlias } from "typia/lib/schemas/metadata/MetadataAlias";
9
- import { MetadataArray } from "typia/lib/schemas/metadata/MetadataArray";
10
- import { MetadataAtomic } from "typia/lib/schemas/metadata/MetadataAtomic";
11
- import { MetadataConstant } from "typia/lib/schemas/metadata/MetadataConstant";
12
- import { MetadataEscaped } from "typia/lib/schemas/metadata/MetadataEscaped";
13
- import { MetadataObject } from "typia/lib/schemas/metadata/MetadataObject";
14
- import { MetadataProperty } from "typia/lib/schemas/metadata/MetadataProperty";
15
- import { MetadataTuple } from "typia/lib/schemas/metadata/MetadataTuple";
16
- import { Escaper } from "typia/lib/utils/Escaper";
17
-
18
- import { INestiaConfig } from "../../INestiaConfig";
19
- import { IRoute } from "../../structures/IRoute";
20
- import { ImportDictionary } from "../../utils/ImportDictionary";
21
- import { MapUtil } from "../../utils/MapUtil";
22
-
23
- export namespace SdkDtoGenerator {
24
- export const generate =
25
- (checker: ts.TypeChecker) =>
26
- (config: INestiaConfig) =>
27
- async (routes: IRoute[]): Promise<void> => {
28
- try {
29
- await fs.promises.mkdir(`${config.output}/structures`);
30
- } catch {}
31
-
32
- const collection = new MetadataCollection({
33
- replace: MetadataCollection.replace,
34
- });
35
- for (const r of routes) {
36
- for (const p of r.parameters) {
37
- const res = MetadataFactory.analyze(checker)({
38
- escape: false,
39
- constant: true,
40
- absorb: false,
41
- })(collection)(p.type);
42
- if (res.success) p.metadata = res.data;
43
- }
44
- for (const e of Object.values(r.exceptions)) {
45
- const res = MetadataFactory.analyze(checker)({
46
- escape: true,
47
- constant: true,
48
- absorb: false,
49
- })(collection)(e.type);
50
- if (res.success) e.metadata = res.data;
51
- }
52
- const res = MetadataFactory.analyze(checker)({
53
- escape: true,
54
- constant: true,
55
- absorb: false,
56
- })(collection)(r.output.type);
57
- if (res.success) r.output.metadata = res.data;
58
- }
59
-
60
- const modules: Map<string, IModule> = new Map();
61
- for (const alias of collection.aliases())
62
- prepare(modules)(alias.name)((importer) =>
63
- defineAlias(config)(importer)(alias),
64
- );
65
- for (const object of collection.objects())
66
- prepare(modules)(object.name)((importer) =>
67
- defineObject(config)(importer)(object),
68
- );
69
-
70
- for (const module of modules.values()) await generateFile(config)(module);
71
- };
72
-
73
- const prepare =
74
- (dict: Map<string, IModule>) =>
75
- (name: string) =>
76
- (programmer: (importer: ImportDictionary) => string) => {
77
- const accessors: string[] = name.split(".");
78
- let module: IModule;
79
-
80
- accessors.forEach((acc, i) => {
81
- module = MapUtil.take(dict, acc, () => ({
82
- name: accessors.slice(0, i + 1).join("."),
83
- children: new Map(),
84
- }));
85
- if (i === accessors.length - 1) module.programmer = programmer;
86
- dict = module.children;
87
- });
88
- return module!;
89
- };
90
-
91
- const generateFile =
92
- (config: INestiaConfig) =>
93
- async (module: IModule): Promise<void> => {
94
- const importer: ImportDictionary = new ImportDictionary(
95
- `${config.output}/structures/${module.name}.ts`,
96
- );
97
-
98
- const body: string = writeModule(importer)(module);
99
- const content: string[] = [];
100
- if (!importer.empty())
101
- content.push(importer.toScript(`${config.output}/structures`), "");
102
- content.push(body);
103
-
104
- await fs.promises.writeFile(importer.file, content.join("\n"), "utf8");
105
- };
106
-
107
- const writeModule =
108
- (importer: ImportDictionary) =>
109
- (module: IModule): string => {
110
- const content: string[] = [];
111
- if (module.programmer) content.push(module.programmer(importer));
112
- if (module.children.size) {
113
- content.push(`export namespace ${module.name.split(".").at(-1)} {`);
114
- for (const child of module.children.values())
115
- content.push(
116
- writeModule(importer)(child)
117
- .split("\n")
118
- .map((l) => ` ${l}`)
119
- .join("\n"),
120
- );
121
- content.push("}");
122
- }
123
- return content.join("\n");
124
- };
125
-
126
- const defineAlias =
127
- (config: INestiaConfig) =>
128
- (importer: ImportDictionary) =>
129
- (alias: MetadataAlias) =>
130
- [
131
- ...writeComment(alias.value.atomics)(
132
- alias.description,
133
- alias.jsDocTags,
134
- ),
135
- `export type ${alias.name.split(".").at(-1)} = ${decode(config)(
136
- importer,
137
- )(alias.value)};`,
138
- ].join("\n");
139
-
140
- const defineObject =
141
- (config: INestiaConfig) =>
142
- (importer: ImportDictionary) =>
143
- (object: MetadataObject) => {
144
- const top: string = [
145
- ...writeComment([])(object.description ?? null, object.jsDocTags),
146
- `export type ${object.name.split(".").at(-1)} = `,
147
- ].join("\n");
148
- if (object.properties.length === 0) return top + "{};";
149
-
150
- const regular: MetadataProperty[] = object.properties.filter((p) =>
151
- p.key.isSoleLiteral(),
152
- );
153
- const dynamic: MetadataProperty[] = object.properties.filter(
154
- (p) => !p.key.isSoleLiteral(),
155
- );
156
-
157
- const brackets: string[][] = [];
158
- if (regular.length) {
159
- const row: string[] = ["{"];
160
- for (const p of regular) {
161
- const key: string = p.key.constants[0].values[0] as string;
162
- const identifier: string = Escaper.variable(key)
163
- ? key
164
- : JSON.stringify(key);
165
- row.push(
166
- ...writeComment(p.value.atomics)(p.description, p.jsDocTags).map(
167
- (l) => ` ${l}`,
168
- ),
169
- ` ${identifier}${
170
- p.value.isRequired() === false ? "?" : ""
171
- }: ${decode(config)(importer)(p.value)};`,
172
- );
173
- }
174
- row.push("}");
175
- brackets.push(row);
176
- }
177
- for (const p of dynamic) {
178
- const row: string[] = ["{"];
179
- row.push(
180
- ...writeComment(p.value.atomics)(p.description, p.jsDocTags).map(
181
- (l) => ` ${l}`,
182
- ),
183
- ` [key: ${decode(config)(importer)(p.key)}]: ${decode(config)(
184
- importer,
185
- )(p.value)};`,
186
- );
187
- row.push("}");
188
- brackets.push(row);
189
- }
190
- return top + brackets.map((row) => row.join("\n")).join(" & ");
191
- };
192
-
193
- const writeComment =
194
- (atomics: MetadataAtomic[]) =>
195
- (description: string | null, jsDocTags: IJsDocTagInfo[]): string[] => {
196
- const lines: string[] = [];
197
- if (description?.length)
198
- lines.push(...description.split("\n").map((s) => `${s}`));
199
-
200
- const filtered: IJsDocTagInfo[] =
201
- !!atomics.length && !!jsDocTags?.length
202
- ? jsDocTags.filter(
203
- (tag) =>
204
- !atomics.some((a) =>
205
- a.tags.some((r) => r.some((t) => t.kind === tag.name)),
206
- ),
207
- )
208
- : jsDocTags ?? [];
209
-
210
- if (description?.length && filtered.length) lines.push("");
211
- if (filtered.length)
212
- lines.push(
213
- ...filtered.map((t) =>
214
- t.text?.length
215
- ? `@${t.name} ${t.text.map((e) => e.text).join("")}`
216
- : `@${t.name}`,
217
- ),
218
- );
219
- if (lines.length === 0) return [];
220
- return ["/**", ...lines.map((s) => ` * ${s}`), " */"];
221
- };
222
-
223
- export const decode =
224
- (config: INestiaConfig) =>
225
- (importer: ImportDictionary) =>
226
- (meta: Metadata, parentEscaped: boolean = false): string => {
227
- const union: string[] = [];
228
-
229
- // COALESCES
230
- if (meta.any) union.push("any");
231
- if (meta.nullable) union.push("null");
232
- if (meta.isRequired() === false) union.push("undefined");
233
- if (parentEscaped === false && meta.escaped)
234
- union.push(decodeEscaped(config)(importer)(meta.escaped));
235
-
236
- // ATOMICS
237
- for (const atomic of meta.atomics)
238
- union.push(decodeAtomic(importer)(atomic));
239
- for (const constant of meta.constants)
240
- union.push(decodeConstant(constant));
241
- for (const tpl of meta.templates)
242
- union.push(decodeTemplate(config)(importer)(tpl));
243
-
244
- // ARRAYS
245
- for (const array of meta.arrays)
246
- union.push(decodeArray(config)(importer)(array));
247
- for (const tuple of meta.tuples)
248
- union.push(decodeTuple(config)(importer)(tuple));
249
-
250
- // OBJECTS
251
- for (const obj of meta.objects)
252
- union.push(decodeObject(config)(importer)(obj));
253
- for (const alias of meta.aliases)
254
- union.push(decodeAlias(config)(importer)(alias));
255
-
256
- // NATIVES
257
- for (const native of meta.natives) union.push(native);
258
- for (const set of meta.sets)
259
- union.push(`Set<${decode(config)(importer)(set)}>`);
260
- for (const map of meta.maps)
261
- union.push(
262
- `Map<${decode(config)(importer)(map.key)}, ${decode(config)(importer)(
263
- map.value,
264
- )}>`,
265
- );
266
- return union.join(" | ");
267
- };
268
-
269
- const decodeEscaped =
270
- (config: INestiaConfig) =>
271
- (importer: ImportDictionary) =>
272
- (escaped: MetadataEscaped) => {
273
- if (
274
- escaped.original.size() === 1 &&
275
- escaped.original.natives.length === 1 &&
276
- escaped.original.natives[0] === "Date"
277
- )
278
- return `(string & ${importer.external({
279
- type: true,
280
- library: `typia/lib/tags/Format`,
281
- instance: "Format",
282
- })}<"date-time">)`;
283
- return `(${decode(config)(importer)(escaped.returns, true)})`;
284
- };
285
-
286
- const decodeTypeTag =
287
- (importer: ImportDictionary) =>
288
- (tag: IMetadataTypeTag): string => {
289
- const front: string = tag.name.split("<")[0];
290
- if (NATIVE_TYPE_TAGS.has(front)) {
291
- importer.external({
292
- type: true,
293
- library: `typia/lib/tags/${front}`,
294
- instance: front,
295
- });
296
- return tag.name;
297
- }
298
- importer.external({
299
- type: true,
300
- library: `typia/lib/tags/TagBase`,
301
- instance: "TagBase",
302
- });
303
- return `TagBase<${JSON.stringify(tag)}>`;
304
- };
305
-
306
- const decodeTypeTagMatrix =
307
- (importer: ImportDictionary) =>
308
- (base: string, tags: IMetadataTypeTag[][]): string => {
309
- if (tags.length === 0) return base;
310
- else if (tags.length === 1)
311
- return `(${base} & ${tags[0]
312
- .map((t) => decodeTypeTag(importer)(t))
313
- .join(" & ")})`;
314
- return (
315
- "(" +
316
- [
317
- base,
318
- ...tags.map(
319
- (row) =>
320
- `(${row.map((t) => decodeTypeTag(importer)(t)).join(" & ")})`,
321
- ),
322
- ] +
323
- ")"
324
- );
325
- };
326
-
327
- const decodeAtomic =
328
- (importer: ImportDictionary) =>
329
- (atomic: MetadataAtomic): string =>
330
- decodeTypeTagMatrix(importer)(atomic.type, atomic.tags);
331
-
332
- const decodeTemplate =
333
- (config: INestiaConfig) =>
334
- (importer: ImportDictionary) =>
335
- (template: Metadata[]): string =>
336
- "`" +
337
- template
338
- .map((meta) =>
339
- meta.size() === 1 &&
340
- meta.isRequired() &&
341
- meta.nullable === false &&
342
- meta.constants.length === 1
343
- ? String(meta.constants[0].values[0]).split("`").join("\\`")
344
- : `\${${decode(config)(importer)(meta)}}`,
345
- )
346
- .join("") +
347
- "`";
348
-
349
- const decodeConstant = (constant: MetadataConstant): string => {
350
- if (constant.values.length === 0) return JSON.stringify(constant.values[0]);
351
- return `(${constant.values.map((val) => JSON.stringify(val)).join(" | ")})`;
352
- };
353
-
354
- const decodeArray =
355
- (config: INestiaConfig) =>
356
- (importer: ImportDictionary) =>
357
- (array: MetadataArray): string =>
358
- decodeTypeTagMatrix(importer)(
359
- `Array<${decode(config)(importer)(array.type.value)}>`,
360
- array.tags,
361
- );
362
-
363
- const decodeTuple =
364
- (config: INestiaConfig) =>
365
- (importer: ImportDictionary) =>
366
- (tuple: MetadataTuple): string =>
367
- "[" +
368
- tuple.type.elements.map((e) =>
369
- e.rest
370
- ? `...${decode(config)(importer)(e.rest)}[]`
371
- : decode(config)(importer)(e),
372
- ) +
373
- "]";
374
-
375
- const decodeAlias =
376
- (config: INestiaConfig) =>
377
- (importer: ImportDictionary) =>
378
- (alias: MetadataAlias) => {
379
- importInternalFile(config)(importer)(alias.name);
380
- return alias.name;
381
- };
382
-
383
- const decodeObject =
384
- (config: INestiaConfig) =>
385
- (importer: ImportDictionary) =>
386
- (object: MetadataObject) => {
387
- importInternalFile(config)(importer)(object.name);
388
- return object.name;
389
- };
390
-
391
- const importInternalFile =
392
- (config: INestiaConfig) =>
393
- (importer: ImportDictionary) =>
394
- (name: string) => {
395
- const top = name.split(".")[0];
396
- if (importer.file === `${config.output}/structures/${top}.ts`) return;
397
- importer.internal({
398
- type: true,
399
- file: `${config.output}/structures/${name.split(".")[0]}`,
400
- instance: top,
401
- });
402
- };
403
- }
404
-
405
- const NATIVE_TYPE_TAGS = new Set([
406
- "ExclusiveMinimum",
407
- "ExclusiveMaximum",
408
- "Format",
409
- "Maximum",
410
- "MaxItems",
411
- "MaxLength",
412
- "Minimum",
413
- "MinItems",
414
- "MinLength",
415
- "MultipleOf",
416
- "Pattern",
417
- "Type",
418
- ]);
419
-
420
- interface IModule {
421
- name: string;
422
- children: Map<string, IModule>;
423
- programmer?: (importer: ImportDictionary) => string;
424
- }
1
+ import fs from "fs";
2
+ import ts from "typescript";
3
+ import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
4
+ import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
5
+ import { IJsDocTagInfo } from "typia/lib/schemas/metadata/IJsDocTagInfo";
6
+ import { IMetadataTypeTag } from "typia/lib/schemas/metadata/IMetadataTypeTag";
7
+ import { Metadata } from "typia/lib/schemas/metadata/Metadata";
8
+ import { MetadataAlias } from "typia/lib/schemas/metadata/MetadataAlias";
9
+ import { MetadataArray } from "typia/lib/schemas/metadata/MetadataArray";
10
+ import { MetadataAtomic } from "typia/lib/schemas/metadata/MetadataAtomic";
11
+ import { MetadataConstant } from "typia/lib/schemas/metadata/MetadataConstant";
12
+ import { MetadataEscaped } from "typia/lib/schemas/metadata/MetadataEscaped";
13
+ import { MetadataObject } from "typia/lib/schemas/metadata/MetadataObject";
14
+ import { MetadataProperty } from "typia/lib/schemas/metadata/MetadataProperty";
15
+ import { MetadataTuple } from "typia/lib/schemas/metadata/MetadataTuple";
16
+ import { Escaper } from "typia/lib/utils/Escaper";
17
+
18
+ import { INestiaConfig } from "../../INestiaConfig";
19
+ import { IRoute } from "../../structures/IRoute";
20
+ import { ImportDictionary } from "../../utils/ImportDictionary";
21
+ import { MapUtil } from "../../utils/MapUtil";
22
+
23
+ export namespace SdkDtoGenerator {
24
+ export const generate =
25
+ (checker: ts.TypeChecker) =>
26
+ (config: INestiaConfig) =>
27
+ async (routes: IRoute[]): Promise<void> => {
28
+ try {
29
+ await fs.promises.mkdir(`${config.output}/structures`);
30
+ } catch {}
31
+
32
+ const collection = new MetadataCollection({
33
+ replace: MetadataCollection.replace,
34
+ });
35
+ for (const r of routes) {
36
+ for (const p of r.parameters) {
37
+ const res = MetadataFactory.analyze(checker)({
38
+ escape: false,
39
+ constant: true,
40
+ absorb: false,
41
+ })(collection)(p.type);
42
+ if (res.success) p.metadata = res.data;
43
+ }
44
+ for (const e of Object.values(r.exceptions)) {
45
+ const res = MetadataFactory.analyze(checker)({
46
+ escape: true,
47
+ constant: true,
48
+ absorb: false,
49
+ })(collection)(e.type);
50
+ if (res.success) e.metadata = res.data;
51
+ }
52
+ const res = MetadataFactory.analyze(checker)({
53
+ escape: true,
54
+ constant: true,
55
+ absorb: false,
56
+ })(collection)(r.output.type);
57
+ if (res.success) r.output.metadata = res.data;
58
+ }
59
+
60
+ const modules: Map<string, IModule> = new Map();
61
+ for (const alias of collection.aliases())
62
+ prepare(modules)(alias.name)((importer) =>
63
+ defineAlias(config)(importer)(alias),
64
+ );
65
+ for (const object of collection.objects())
66
+ prepare(modules)(object.name)((importer) =>
67
+ defineObject(config)(importer)(object),
68
+ );
69
+
70
+ for (const module of modules.values()) await generateFile(config)(module);
71
+ };
72
+
73
+ const prepare =
74
+ (dict: Map<string, IModule>) =>
75
+ (name: string) =>
76
+ (programmer: (importer: ImportDictionary) => string) => {
77
+ const accessors: string[] = name.split(".");
78
+ let module: IModule;
79
+
80
+ accessors.forEach((acc, i) => {
81
+ module = MapUtil.take(dict, acc, () => ({
82
+ name: accessors.slice(0, i + 1).join("."),
83
+ children: new Map(),
84
+ }));
85
+ if (i === accessors.length - 1) module.programmer = programmer;
86
+ dict = module.children;
87
+ });
88
+ return module!;
89
+ };
90
+
91
+ const generateFile =
92
+ (config: INestiaConfig) =>
93
+ async (module: IModule): Promise<void> => {
94
+ const importer: ImportDictionary = new ImportDictionary(
95
+ `${config.output}/structures/${module.name}.ts`,
96
+ );
97
+
98
+ const body: string = writeModule(importer)(module);
99
+ const content: string[] = [];
100
+ if (!importer.empty())
101
+ content.push(importer.toScript(`${config.output}/structures`), "");
102
+ content.push(body);
103
+
104
+ await fs.promises.writeFile(importer.file, content.join("\n"), "utf8");
105
+ };
106
+
107
+ const writeModule =
108
+ (importer: ImportDictionary) =>
109
+ (module: IModule): string => {
110
+ const content: string[] = [];
111
+ if (module.programmer) content.push(module.programmer(importer));
112
+ if (module.children.size) {
113
+ content.push(`export namespace ${module.name.split(".").at(-1)} {`);
114
+ for (const child of module.children.values())
115
+ content.push(
116
+ writeModule(importer)(child)
117
+ .split("\n")
118
+ .map((l) => ` ${l}`)
119
+ .join("\n"),
120
+ );
121
+ content.push("}");
122
+ }
123
+ return content.join("\n");
124
+ };
125
+
126
+ const defineAlias =
127
+ (config: INestiaConfig) =>
128
+ (importer: ImportDictionary) =>
129
+ (alias: MetadataAlias) =>
130
+ [
131
+ ...writeComment(alias.value.atomics)(
132
+ alias.description,
133
+ alias.jsDocTags,
134
+ ),
135
+ `export type ${alias.name.split(".").at(-1)} = ${decode(config)(
136
+ importer,
137
+ )(alias.value)};`,
138
+ ].join("\n");
139
+
140
+ const defineObject =
141
+ (config: INestiaConfig) =>
142
+ (importer: ImportDictionary) =>
143
+ (object: MetadataObject) => {
144
+ const top: string = [
145
+ ...writeComment([])(object.description ?? null, object.jsDocTags),
146
+ `export type ${object.name.split(".").at(-1)} = `,
147
+ ].join("\n");
148
+ if (object.properties.length === 0) return top + "{};";
149
+
150
+ const regular: MetadataProperty[] = object.properties.filter((p) =>
151
+ p.key.isSoleLiteral(),
152
+ );
153
+ const dynamic: MetadataProperty[] = object.properties.filter(
154
+ (p) => !p.key.isSoleLiteral(),
155
+ );
156
+
157
+ const brackets: string[][] = [];
158
+ if (regular.length) {
159
+ const row: string[] = ["{"];
160
+ for (const p of regular) {
161
+ const key: string = p.key.constants[0].values[0] as string;
162
+ const identifier: string = Escaper.variable(key)
163
+ ? key
164
+ : JSON.stringify(key);
165
+ row.push(
166
+ ...writeComment(p.value.atomics)(p.description, p.jsDocTags).map(
167
+ (l) => ` ${l}`,
168
+ ),
169
+ ` ${identifier}${
170
+ p.value.isRequired() === false ? "?" : ""
171
+ }: ${decode(config)(importer)(p.value)};`,
172
+ );
173
+ }
174
+ row.push("}");
175
+ brackets.push(row);
176
+ }
177
+ for (const p of dynamic) {
178
+ const row: string[] = ["{"];
179
+ row.push(
180
+ ...writeComment(p.value.atomics)(p.description, p.jsDocTags).map(
181
+ (l) => ` ${l}`,
182
+ ),
183
+ ` [key: ${decode(config)(importer)(p.key)}]: ${decode(config)(
184
+ importer,
185
+ )(p.value)};`,
186
+ );
187
+ row.push("}");
188
+ brackets.push(row);
189
+ }
190
+ return top + brackets.map((row) => row.join("\n")).join(" & ");
191
+ };
192
+
193
+ const writeComment =
194
+ (atomics: MetadataAtomic[]) =>
195
+ (description: string | null, jsDocTags: IJsDocTagInfo[]): string[] => {
196
+ const lines: string[] = [];
197
+ if (description?.length)
198
+ lines.push(...description.split("\n").map((s) => `${s}`));
199
+
200
+ const filtered: IJsDocTagInfo[] =
201
+ !!atomics.length && !!jsDocTags?.length
202
+ ? jsDocTags.filter(
203
+ (tag) =>
204
+ !atomics.some((a) =>
205
+ a.tags.some((r) => r.some((t) => t.kind === tag.name)),
206
+ ),
207
+ )
208
+ : jsDocTags ?? [];
209
+
210
+ if (description?.length && filtered.length) lines.push("");
211
+ if (filtered.length)
212
+ lines.push(
213
+ ...filtered.map((t) =>
214
+ t.text?.length
215
+ ? `@${t.name} ${t.text.map((e) => e.text).join("")}`
216
+ : `@${t.name}`,
217
+ ),
218
+ );
219
+ if (lines.length === 0) return [];
220
+ return ["/**", ...lines.map((s) => ` * ${s}`), " */"];
221
+ };
222
+
223
+ export const decode =
224
+ (config: INestiaConfig) =>
225
+ (importer: ImportDictionary) =>
226
+ (meta: Metadata, parentEscaped: boolean = false): string => {
227
+ const union: string[] = [];
228
+
229
+ // COALESCES
230
+ if (meta.any) union.push("any");
231
+ if (meta.nullable) union.push("null");
232
+ if (meta.isRequired() === false) union.push("undefined");
233
+ if (parentEscaped === false && meta.escaped)
234
+ union.push(decodeEscaped(config)(importer)(meta.escaped));
235
+
236
+ // ATOMICS
237
+ for (const atomic of meta.atomics)
238
+ union.push(decodeAtomic(importer)(atomic));
239
+ for (const constant of meta.constants)
240
+ union.push(decodeConstant(constant));
241
+ for (const tpl of meta.templates)
242
+ union.push(decodeTemplate(config)(importer)(tpl));
243
+
244
+ // ARRAYS
245
+ for (const array of meta.arrays)
246
+ union.push(decodeArray(config)(importer)(array));
247
+ for (const tuple of meta.tuples)
248
+ union.push(decodeTuple(config)(importer)(tuple));
249
+
250
+ // OBJECTS
251
+ for (const obj of meta.objects)
252
+ union.push(decodeObject(config)(importer)(obj));
253
+ for (const alias of meta.aliases)
254
+ union.push(decodeAlias(config)(importer)(alias));
255
+
256
+ // NATIVES
257
+ for (const native of meta.natives) union.push(native);
258
+ for (const set of meta.sets)
259
+ union.push(`Set<${decode(config)(importer)(set)}>`);
260
+ for (const map of meta.maps)
261
+ union.push(
262
+ `Map<${decode(config)(importer)(map.key)}, ${decode(config)(importer)(
263
+ map.value,
264
+ )}>`,
265
+ );
266
+ return union.join(" | ");
267
+ };
268
+
269
+ const decodeEscaped =
270
+ (config: INestiaConfig) =>
271
+ (importer: ImportDictionary) =>
272
+ (escaped: MetadataEscaped) => {
273
+ if (
274
+ escaped.original.size() === 1 &&
275
+ escaped.original.natives.length === 1 &&
276
+ escaped.original.natives[0] === "Date"
277
+ )
278
+ return `(string & ${importer.external({
279
+ type: true,
280
+ library: `typia/lib/tags/Format`,
281
+ instance: "Format",
282
+ })}<"date-time">)`;
283
+ return `(${decode(config)(importer)(escaped.returns, true)})`;
284
+ };
285
+
286
+ const decodeTypeTag =
287
+ (importer: ImportDictionary) =>
288
+ (tag: IMetadataTypeTag): string => {
289
+ const front: string = tag.name.split("<")[0];
290
+ if (NATIVE_TYPE_TAGS.has(front)) {
291
+ importer.external({
292
+ type: true,
293
+ library: `typia/lib/tags/${front}`,
294
+ instance: front,
295
+ });
296
+ return tag.name;
297
+ }
298
+ importer.external({
299
+ type: true,
300
+ library: `typia/lib/tags/TagBase`,
301
+ instance: "TagBase",
302
+ });
303
+ return `TagBase<${JSON.stringify(tag)}>`;
304
+ };
305
+
306
+ const decodeTypeTagMatrix =
307
+ (importer: ImportDictionary) =>
308
+ (base: string, tags: IMetadataTypeTag[][]): string => {
309
+ if (tags.length === 0) return base;
310
+ else if (tags.length === 1)
311
+ return `(${base} & ${tags[0]
312
+ .map((t) => decodeTypeTag(importer)(t))
313
+ .join(" & ")})`;
314
+ return (
315
+ "(" +
316
+ [
317
+ base,
318
+ ...tags.map(
319
+ (row) =>
320
+ `(${row.map((t) => decodeTypeTag(importer)(t)).join(" & ")})`,
321
+ ),
322
+ ] +
323
+ ")"
324
+ );
325
+ };
326
+
327
+ const decodeAtomic =
328
+ (importer: ImportDictionary) =>
329
+ (atomic: MetadataAtomic): string =>
330
+ decodeTypeTagMatrix(importer)(atomic.type, atomic.tags);
331
+
332
+ const decodeTemplate =
333
+ (config: INestiaConfig) =>
334
+ (importer: ImportDictionary) =>
335
+ (template: Metadata[]): string =>
336
+ "`" +
337
+ template
338
+ .map((meta) =>
339
+ meta.size() === 1 &&
340
+ meta.isRequired() &&
341
+ meta.nullable === false &&
342
+ meta.constants.length === 1
343
+ ? String(meta.constants[0].values[0]).split("`").join("\\`")
344
+ : `\${${decode(config)(importer)(meta)}}`,
345
+ )
346
+ .join("") +
347
+ "`";
348
+
349
+ const decodeConstant = (constant: MetadataConstant): string => {
350
+ if (constant.values.length === 0) return JSON.stringify(constant.values[0]);
351
+ return `(${constant.values.map((val) => JSON.stringify(val)).join(" | ")})`;
352
+ };
353
+
354
+ const decodeArray =
355
+ (config: INestiaConfig) =>
356
+ (importer: ImportDictionary) =>
357
+ (array: MetadataArray): string =>
358
+ decodeTypeTagMatrix(importer)(
359
+ `Array<${decode(config)(importer)(array.type.value)}>`,
360
+ array.tags,
361
+ );
362
+
363
+ const decodeTuple =
364
+ (config: INestiaConfig) =>
365
+ (importer: ImportDictionary) =>
366
+ (tuple: MetadataTuple): string =>
367
+ "[" +
368
+ tuple.type.elements.map((e) =>
369
+ e.rest
370
+ ? `...${decode(config)(importer)(e.rest)}[]`
371
+ : decode(config)(importer)(e),
372
+ ) +
373
+ "]";
374
+
375
+ const decodeAlias =
376
+ (config: INestiaConfig) =>
377
+ (importer: ImportDictionary) =>
378
+ (alias: MetadataAlias) => {
379
+ importInternalFile(config)(importer)(alias.name);
380
+ return alias.name;
381
+ };
382
+
383
+ const decodeObject =
384
+ (config: INestiaConfig) =>
385
+ (importer: ImportDictionary) =>
386
+ (object: MetadataObject) => {
387
+ importInternalFile(config)(importer)(object.name);
388
+ return object.name;
389
+ };
390
+
391
+ const importInternalFile =
392
+ (config: INestiaConfig) =>
393
+ (importer: ImportDictionary) =>
394
+ (name: string) => {
395
+ const top = name.split(".")[0];
396
+ if (importer.file === `${config.output}/structures/${top}.ts`) return;
397
+ importer.internal({
398
+ type: true,
399
+ file: `${config.output}/structures/${name.split(".")[0]}`,
400
+ instance: top,
401
+ });
402
+ };
403
+ }
404
+
405
+ const NATIVE_TYPE_TAGS = new Set([
406
+ "ExclusiveMinimum",
407
+ "ExclusiveMaximum",
408
+ "Format",
409
+ "Maximum",
410
+ "MaxItems",
411
+ "MaxLength",
412
+ "Minimum",
413
+ "MinItems",
414
+ "MinLength",
415
+ "MultipleOf",
416
+ "Pattern",
417
+ "Type",
418
+ ]);
419
+
420
+ interface IModule {
421
+ name: string;
422
+ children: Map<string, IModule>;
423
+ programmer?: (importer: ImportDictionary) => string;
424
+ }