@buildvision/nest-sdk-generator 1.1.3

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 (69) hide show
  1. package/.prettierrc +6 -0
  2. package/LICENSE.md +9 -0
  3. package/README.md +353 -0
  4. package/build/analyzer/builtin.d.ts +2 -0
  5. package/build/analyzer/builtin.js +11 -0
  6. package/build/analyzer/classdeps.d.ts +6 -0
  7. package/build/analyzer/classdeps.js +27 -0
  8. package/build/analyzer/controller.d.ts +26 -0
  9. package/build/analyzer/controller.js +103 -0
  10. package/build/analyzer/controllers.d.ts +7 -0
  11. package/build/analyzer/controllers.js +79 -0
  12. package/build/analyzer/decorator.d.ts +10 -0
  13. package/build/analyzer/decorator.js +29 -0
  14. package/build/analyzer/extractor.d.ts +107 -0
  15. package/build/analyzer/extractor.js +349 -0
  16. package/build/analyzer/index.d.ts +16 -0
  17. package/build/analyzer/index.js +101 -0
  18. package/build/analyzer/methods.d.ts +42 -0
  19. package/build/analyzer/methods.js +115 -0
  20. package/build/analyzer/module.d.ts +11 -0
  21. package/build/analyzer/module.js +41 -0
  22. package/build/analyzer/params.d.ts +38 -0
  23. package/build/analyzer/params.js +163 -0
  24. package/build/analyzer/route.d.ts +53 -0
  25. package/build/analyzer/route.js +131 -0
  26. package/build/analyzer/typedeps.d.ts +49 -0
  27. package/build/analyzer/typedeps.js +86 -0
  28. package/build/bin.d.ts +2 -0
  29. package/build/bin.js +39 -0
  30. package/build/config.d.ts +44 -0
  31. package/build/config.js +31 -0
  32. package/build/generator/genmodules.d.ts +24 -0
  33. package/build/generator/genmodules.js +134 -0
  34. package/build/generator/gentypes.d.ts +10 -0
  35. package/build/generator/gentypes.js +62 -0
  36. package/build/generator/index.d.ts +6 -0
  37. package/build/generator/index.js +83 -0
  38. package/build/generator/prettier.d.ts +16 -0
  39. package/build/generator/prettier.js +43 -0
  40. package/build/generator/sdk-interface.d.ts +1 -0
  41. package/build/generator/sdk-interface.js +36 -0
  42. package/build/logging.d.ts +6 -0
  43. package/build/logging.js +37 -0
  44. package/build/tsconfig.tsbuildinfo +1 -0
  45. package/build/utils.d.ts +9 -0
  46. package/build/utils.js +56 -0
  47. package/package.json +34 -0
  48. package/src/analyzer/builtin.ts +9 -0
  49. package/src/analyzer/classdeps.ts +37 -0
  50. package/src/analyzer/controller.ts +135 -0
  51. package/src/analyzer/controllers.ts +117 -0
  52. package/src/analyzer/decorator.ts +29 -0
  53. package/src/analyzer/extractor.ts +491 -0
  54. package/src/analyzer/index.ts +136 -0
  55. package/src/analyzer/methods.ts +166 -0
  56. package/src/analyzer/module.ts +56 -0
  57. package/src/analyzer/params.ts +241 -0
  58. package/src/analyzer/route.ts +156 -0
  59. package/src/analyzer/typedeps.ts +130 -0
  60. package/src/bin.ts +33 -0
  61. package/src/config.ts +89 -0
  62. package/src/generator/genmodules.ts +163 -0
  63. package/src/generator/gentypes.ts +73 -0
  64. package/src/generator/index.ts +92 -0
  65. package/src/generator/prettier.ts +46 -0
  66. package/src/generator/sdk-interface.ts +32 -0
  67. package/src/logging.ts +35 -0
  68. package/src/utils.ts +61 -0
  69. package/tsconfig.json +26 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file Analyzer for the source API's controllers
3
+ */
4
+
5
+ import * as os from 'os'
6
+ import * as path from 'path'
7
+ import { Project } from 'ts-morph'
8
+ import { debug, format, panic, warn } from '../logging'
9
+ import { findFileAbove } from '../utils'
10
+ import { analyzeController, SdkController } from './controller'
11
+ import { getModuleName } from './module'
12
+
13
+ export type SdkModules = Map<string, Map<string, SdkController>>
14
+
15
+ export function analyzeControllers(controllers: string[], absoluteSrcPath: string, project: Project): SdkModules {
16
+ /** Hierarchised SDK informations */
17
+ const collected = new Map<string, Map<string, SdkController>>()
18
+
19
+ /**
20
+ * Modules cache: contains for a given directory the nearest module file's path and name
21
+ * This allows to avoid having to analyze the whole directory structure for each controller
22
+ */
23
+ const modulesCache = new Map<string, string>()
24
+
25
+ /**
26
+ * Path of the declared modules
27
+ * When a module is detected and put in the cache, its name is registered here along with its path
28
+ * This allows to ensure there is no name clash between two different modules
29
+ */
30
+ const declaredModulesPath = new Map<string, string>()
31
+
32
+ debug(`Analyzing {yellow} controllers...`, controllers.length)
33
+
34
+ controllers.forEach((relativeControllerPath, i) => {
35
+ const absoluteControllerPath = path.resolve(absoluteSrcPath, relativeControllerPath)
36
+
37
+ debug(
38
+ '\n{cyan} {yellow}: {magentaBright} {cyan}\n',
39
+ '===== Analyzing controller',
40
+ `${i + 1}/${controllers.length}`,
41
+ relativeControllerPath,
42
+ '====='
43
+ )
44
+
45
+ const basePath = path.dirname(absoluteControllerPath)
46
+
47
+ let moduleName = modulesCache.get(basePath)
48
+
49
+ // Check if the module's name is in cache
50
+ if (!moduleName) {
51
+ // Else, find the nearest module file
52
+ const absoluteModulePath = findFileAbove(/^.*\.module\.ts$/, path.resolve(absoluteSrcPath, basePath))
53
+
54
+ if (absoluteModulePath === null) {
55
+ panic('No module file was found for controller at path: {yellow}', absoluteControllerPath)
56
+ }
57
+
58
+ const relativeModulePath = path.relative(absoluteSrcPath, absoluteModulePath)
59
+
60
+ // Get the module's name
61
+ moduleName = getModuleName(project, relativeModulePath, absoluteSrcPath)
62
+
63
+ debug('Discovered module: {yellow}', moduleName)
64
+
65
+ // Ensure this module is unique
66
+ const cachedModulePath = declaredModulesPath.get(moduleName)
67
+
68
+ if (cachedModulePath) {
69
+ panic(
70
+ `Two modules were declared with the same name {yellow}:\n` + `- One in {yellow}\n` + `- One in {yellow}`,
71
+ moduleName,
72
+ cachedModulePath,
73
+ relativeModulePath
74
+ )
75
+ }
76
+
77
+ modulesCache.set(basePath, moduleName)
78
+ }
79
+
80
+ if (moduleName in {}) {
81
+ panic(`Detected module whose name {yellow} collides with a JavaScript's native object property`, moduleName)
82
+ }
83
+
84
+ let moduleSdkInfos = collected.get(moduleName)
85
+
86
+ if (!moduleSdkInfos) {
87
+ moduleSdkInfos = new Map()
88
+ collected.set(moduleName, moduleSdkInfos)
89
+ }
90
+
91
+ if (i === 0) {
92
+ if (process.platform === 'linux' && os.release().toLocaleLowerCase().includes('microsoft') && absoluteSrcPath.startsWith('/mnt/')) {
93
+ warn("NOTE: On WSL, the first type analysis on a project located in Windows's filesystem may take a long time to complete.")
94
+ warn('Please consider moving your project to WSL, or running this tool directly from Windows')
95
+ }
96
+ }
97
+
98
+ const metadata = analyzeController(project, relativeControllerPath, absoluteSrcPath)
99
+
100
+ if (metadata instanceof Error) {
101
+ throw new Error(format('Failed to analyze controller at path {magenta}:\n{}', relativeControllerPath, metadata.message))
102
+ }
103
+
104
+ if (metadata) {
105
+ if (metadata.registrationName in {}) {
106
+ panic(
107
+ `Detected controller whose registration name {yellow} collides with a JavaScript's native object property`,
108
+ metadata.registrationName
109
+ )
110
+ }
111
+
112
+ moduleSdkInfos.set(metadata.camelClassName, metadata)
113
+ }
114
+ })
115
+
116
+ return collected
117
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file Utilities for analyzing the source API's decorators
3
+ */
4
+
5
+ import { Decorator, Node } from 'ts-morph'
6
+ import { format } from '../logging'
7
+
8
+ /**
9
+ * Expect a decorator to have a single, string literal argument
10
+ * @param dec The decorator
11
+ * @returns Nothing if the decorator has no argument, a { lit: string } if the decorator has a string literal, an Error else
12
+ */
13
+ export function expectSingleStrLitDecorator(dec: Decorator): string | null | Error {
14
+ const args = dec.getArguments()
15
+
16
+ if (args.length > 1) {
17
+ return new Error(`Multiple (${args.length}) arguments were provided to the decorator`)
18
+ } else if (args.length === 0) {
19
+ return null
20
+ }
21
+
22
+ const [arg] = args
23
+
24
+ if (!Node.isStringLiteral(arg)) {
25
+ return new Error(format('The argument provided to the decorator is not a string literal:\n>>> {cyan}', arg.getText()))
26
+ }
27
+
28
+ return arg.getLiteralText()
29
+ }
@@ -0,0 +1,491 @@
1
+ /**
2
+ * @file Types extractor to use in the generated SDK
3
+ */
4
+
5
+ import * as path from 'path'
6
+ import { Node, Project, SourceFile } from 'ts-morph'
7
+ import { MagicType } from '../config'
8
+ import { debug, format, panic, unreachable, warn } from '../logging'
9
+ import { analyzeClassDeps } from './classdeps'
10
+ import { SdkModules } from './controllers'
11
+ import { getImportResolvedType, ResolvedTypeDeps, resolveTypeDependencies } from './typedeps'
12
+
13
+ /** Valid extensions for TypeScript module files */
14
+ export const MODULE_EXTENSIONS = ['.ts', '.d.ts', '.tsx', '.d.tsx', '.js', '.jsx']
15
+
16
+ /**
17
+ * Location of an imported type
18
+ */
19
+ export interface TypeLocation {
20
+ readonly typename: string
21
+ readonly relativePathNoExt: string
22
+ }
23
+
24
+ /**
25
+ * Location of an imported type after figuring out its extension
26
+ */
27
+ export interface TypeLocationWithExt extends TypeLocation {
28
+ readonly relativePath: string
29
+ }
30
+
31
+ /**
32
+ * Extracted imported type
33
+ */
34
+ export interface ExtractedType extends TypeLocationWithExt {
35
+ /** Type's declaration */
36
+ readonly content: string
37
+
38
+ /** Type parameters (e.g. <T>) */
39
+ readonly typeParams: string[]
40
+
41
+ /** Types this one depends on */
42
+ readonly dependencies: TypeLocationWithExt[]
43
+ }
44
+
45
+ // Maps files to records mapping themselves type names to their declaration code
46
+ export type TypesExtractorContent = Map<string, Map<string, ExtractedType>>
47
+
48
+ /**
49
+ * Types extractor
50
+ */
51
+ export class TypesExtractor {
52
+ constructor(
53
+ /** TS-Morph project */
54
+ public readonly project: Project,
55
+
56
+ /** Absolute source path */
57
+ public readonly absoluteSrcPath: string,
58
+
59
+ /** Magic types to replace non-portable types */
60
+ public readonly magicTypes: Array<MagicType>,
61
+
62
+ /** Extracted types */
63
+ public readonly extracted: TypesExtractorContent = new Map()
64
+ ) {}
65
+
66
+ /**
67
+ * Check if a type has already been extracted
68
+ */
69
+ hasExtractedType(loc: TypeLocationWithExt): boolean {
70
+ const files = this.extracted.get(loc.relativePath)
71
+ return files ? files.has(loc.typename) : false
72
+ }
73
+
74
+ /**
75
+ * Get a type that was previously extracted
76
+ */
77
+ getExtractedType(loc: TypeLocationWithExt): ExtractedType | null {
78
+ return this.extracted.get(loc.relativePath)?.get(loc.typename) ?? null
79
+ }
80
+
81
+ /**
82
+ * Find the extension of a TypeScript module
83
+ * @param loc
84
+ * @returns
85
+ */
86
+ guessExtractedTypeModuleFileExt(loc: TypeLocation): string | null {
87
+ for (const ext of MODULE_EXTENSIONS) {
88
+ if (this.extracted.has(loc.relativePathNoExt + ext)) {
89
+ return loc.relativePathNoExt + ext
90
+ }
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ /**
97
+ * Find if a type has previously been extracted, without providing its extension
98
+ * @param loc
99
+ * @returns
100
+ */
101
+ findExtractedTypeWithoutExt(loc: TypeLocation): ExtractedType | null {
102
+ for (const ext of MODULE_EXTENSIONS) {
103
+ const typ = this.extracted.get(loc.relativePathNoExt + ext)?.get(loc.typename)
104
+
105
+ if (typ) {
106
+ return typ
107
+ }
108
+ }
109
+
110
+ return null
111
+ }
112
+
113
+ /**
114
+ * Memorize an extracted type so it can be reused later on
115
+ * @param loc
116
+ * @param extracted
117
+ */
118
+ memorizeExtractedType(loc: TypeLocationWithExt, extracted: ExtractedType) {
119
+ let files = this.extracted.get(loc.relativePath)
120
+
121
+ if (!files) {
122
+ files = new Map()
123
+ this.extracted.set(loc.relativePath, files)
124
+ }
125
+
126
+ if (!files.has(loc.typename)) {
127
+ files.set(loc.typename, extracted)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Find the relative file location of a type
133
+ * @param loc
134
+ * @returns
135
+ */
136
+ findTypeRelativeFilePath(loc: TypeLocation): string | Error {
137
+ if (path.isAbsolute(loc.relativePathNoExt)) {
138
+ unreachable(
139
+ 'Internal error: got absolute file path in types extractor, when expecting a relative one (got {magentaBright})',
140
+ loc.relativePathNoExt
141
+ )
142
+ }
143
+
144
+ const absolutePathNoExt = path.resolve(this.absoluteSrcPath, loc.relativePathNoExt)
145
+
146
+ const cached = this.findExtractedTypeWithoutExt(loc)
147
+
148
+ if (cached) {
149
+ return cached.relativePath
150
+ }
151
+
152
+ let relativeFilePath: string | null = null
153
+
154
+ for (const ext of MODULE_EXTENSIONS) {
155
+ try {
156
+ this.project.getSourceFileOrThrow(absolutePathNoExt + ext)
157
+ relativeFilePath = loc.relativePathNoExt + ext
158
+ } catch (e) {
159
+ continue
160
+ }
161
+ }
162
+
163
+ return (
164
+ relativeFilePath ??
165
+ new Error(
166
+ format('File {magenta} was not found (was expected to contain dependency type {yellow})', loc.relativePathNoExt, loc.typename)
167
+ )
168
+ )
169
+ }
170
+
171
+ /**
172
+ * Extract a type
173
+ * @param loc
174
+ * @param typesPath
175
+ * @returns
176
+ */
177
+ extractType(loc: TypeLocation, typesPath: string[] = []): ExtractedType | Error {
178
+ if (path.isAbsolute(loc.relativePathNoExt)) {
179
+ unreachable(
180
+ 'Internal error: got absolute file path in types extractor, when expecting a relative one (got {magentaBright})',
181
+ loc.relativePathNoExt
182
+ )
183
+ }
184
+
185
+ // Get the absolute path of the type's parent file
186
+ // We don't know its extension yet as imported file names in import statements don't have an extension
187
+ const absolutePathNoExt = path.resolve(this.absoluteSrcPath, loc.relativePathNoExt)
188
+
189
+ // If the type is already in cache, return it directly
190
+ const cached = this.findExtractedTypeWithoutExt(loc)
191
+
192
+ if (cached) {
193
+ return cached
194
+ }
195
+
196
+ // Try to find the path with extension of the file and get it as a TS-Morph file
197
+ let fileAndPath: [SourceFile, string] | null = null
198
+
199
+ for (const ext of MODULE_EXTENSIONS) {
200
+ try {
201
+ fileAndPath = [this.project.getSourceFileOrThrow(absolutePathNoExt + ext), loc.relativePathNoExt + ext]
202
+ } catch (e) {
203
+ continue
204
+ }
205
+ }
206
+
207
+ if (!fileAndPath) {
208
+ return new Error(
209
+ format('File {magenta} was not found (was expected to contain dependency type {yellow})', loc.relativePathNoExt, loc.typename)
210
+ )
211
+ }
212
+
213
+ const [file, relativeFilePath] = fileAndPath
214
+
215
+ // Use magic types to replace non-portable types
216
+ for (const magicType of this.magicTypes) {
217
+ if (relativeFilePath.endsWith(`node_modules/${magicType.nodeModuleFilePath}`) && loc.typename === magicType.typeName) {
218
+ debug(
219
+ '-> '.repeat(typesPath.length + 1) +
220
+ 'Found magic type {yellow} from external module file {magentaBright}, using provided placeholder.',
221
+ loc.typename,
222
+ relativeFilePath
223
+ )
224
+
225
+ const extracted: ExtractedType = {
226
+ content: '/** @file Magic placeholder from configuration file */\n\n' + magicType.placeholderContent,
227
+ relativePath: relativeFilePath,
228
+ relativePathNoExt: loc.relativePathNoExt,
229
+ typename: loc.typename,
230
+ typeParams: [],
231
+ dependencies: [],
232
+ }
233
+
234
+ typesPath.pop()
235
+
236
+ let types = this.extracted.get(relativeFilePath)
237
+
238
+ if (!types) {
239
+ types = new Map()
240
+ this.extracted.set(relativeFilePath, types)
241
+ }
242
+
243
+ types.set(loc.typename, extracted)
244
+
245
+ return extracted
246
+ }
247
+ }
248
+
249
+ debug('-> '.repeat(typesPath.length + 1) + 'Extracting type {yellow} from file {magentaBright}...', loc.typename, relativeFilePath)
250
+
251
+ // Analyze the type's declaration
252
+ const decl = file.forEachChildAsArray().find((node) => {
253
+ return (
254
+ (Node.isEnumDeclaration(node) ||
255
+ Node.isTypeAliasDeclaration(node) ||
256
+ Node.isInterfaceDeclaration(node) ||
257
+ Node.isClassDeclaration(node) ||
258
+ Node.isFunctionDeclaration(node)) &&
259
+ node.getName() === loc.typename
260
+ )
261
+ })
262
+
263
+ if (!decl) {
264
+ return new Error(format(`Type {yellow} was not found in file {magenta}`, loc.typename, relativeFilePath))
265
+ }
266
+
267
+ // Handle a limitation of the tool: you can't import two types from two files at the same path with just two different extensions
268
+ // Example: importing a type named "User" from two files in the same directory called "user.entity.ts" and "user.entity.js"
269
+ const typ = this.findExtractedTypeWithoutExt(loc)
270
+
271
+ if (typ && typ.relativePath !== relativeFilePath) {
272
+ panic(
273
+ 'Found two conflicting files at same path but with different extensions:\n> {magentaBright}\n> {magentaBright}',
274
+ typ.relativePath,
275
+ relativeFilePath
276
+ )
277
+ }
278
+
279
+ /** Resolved type's dependencies */
280
+ let resolvedDeps: ResolvedTypeDeps[]
281
+
282
+ /** Type's parameters (e.g. <T>) */
283
+ let typeParams: string[]
284
+
285
+ /** Type declaration */
286
+ let extractedDecl = decl.getText()
287
+
288
+ // Handle enumerations
289
+ if (Node.isEnumDeclaration(decl)) {
290
+ resolvedDeps = []
291
+ typeParams = []
292
+ }
293
+
294
+ // Handle type aliases
295
+ else if (Node.isTypeAliasDeclaration(decl)) {
296
+ resolvedDeps = [resolveTypeDependencies(decl.getType(), relativeFilePath, this.absoluteSrcPath)]
297
+ typeParams = []
298
+ }
299
+
300
+ // Handle interfaces
301
+ else if (Node.isInterfaceDeclaration(decl)) {
302
+ resolvedDeps = analyzeClassDeps(decl, relativeFilePath, this.absoluteSrcPath)
303
+ typeParams = decl.getTypeParameters().map((tp) => tp.getText())
304
+ }
305
+
306
+ // Handle classes
307
+ // Methods are not handled because they shouldn't be used as DTOs and won't be decodable from JSON in all cases
308
+ // This part is tricky as we remake the class from scratch using the informations we have on it, given we have to get rid
309
+ // of methods as well as removing decorators
310
+ else if (Node.isClassDeclaration(decl)) {
311
+ const classHead = decl.getText().match(/\b(export[^{]+class[^{]+{)/)
312
+
313
+ if (!classHead) {
314
+ unreachable('Internal error: failed to match class head in declaration: {yellow}', decl.getText())
315
+ }
316
+
317
+ extractedDecl = classHead[1]
318
+
319
+ const index = decl.getType().getStringIndexType() ?? decl.getType().getNumberIndexType()
320
+
321
+ if (index) {
322
+ extractedDecl += '\npublic [input: string | number]: ' + getImportResolvedType(index)
323
+ }
324
+
325
+ // Export all members
326
+ for (const member of decl.getMembers()) {
327
+ if (!Node.isPropertyDeclaration(member)) {
328
+ warn('Found non-property member in class {cyan}: {magenta}', decl.getName() ?? '<anonymous>', member.getText())
329
+ continue
330
+ }
331
+
332
+ const memberType = member.getType()
333
+
334
+ extractedDecl += `\npublic ${member.getName()}${
335
+ member.getText().includes('?') ? '?' : member.getText().includes('!:') ? '!' : ''
336
+ }: ${getImportResolvedType(memberType)};`
337
+ }
338
+
339
+ extractedDecl += '\n}'
340
+
341
+ resolvedDeps = analyzeClassDeps(decl, relativeFilePath, this.absoluteSrcPath)
342
+ typeParams = decl.getTypeParameters().map((tp) => tp.getText())
343
+ }
344
+
345
+ // Handle functions
346
+ else if (Node.isFunctionDeclaration(decl)) {
347
+ resolvedDeps = []
348
+ typeParams = []
349
+ }
350
+
351
+ // Handle unknown types
352
+ else {
353
+ panic('Unknown node type when extracting types: ' + decl.getKindName())
354
+ }
355
+
356
+ /** Normalized dependencies */
357
+ const dependencies: TypeLocationWithExt[] = []
358
+
359
+ // Ensure we're not stuck in an infinite loop where we analyze a type A, then its dependency B, which itself depends on A, and so on
360
+ if (typesPath.includes(loc.typename)) {
361
+ unreachable(`Internal error: infinite loop detected in types extracted: {yellow}`, typesPath.join(' -> '))
362
+ }
363
+
364
+ typesPath.push(loc.typename)
365
+
366
+ // Analyze all dependencies
367
+ for (const dependencyLoc of locateTypesFile(resolvedDeps)) {
368
+ // If the "dependency" is one of the type's parameters (e.g. "T"), ignore this part
369
+ if (typeParams.includes(dependencyLoc.typename)) {
370
+ continue
371
+ }
372
+
373
+ // Find the dependency's relative path
374
+ let relativePath: string | Error
375
+
376
+ const cached = this.findExtractedTypeWithoutExt(dependencyLoc)
377
+
378
+ if (cached) {
379
+ relativePath = cached.relativePath
380
+ } else if (typesPath.includes(dependencyLoc.typename)) {
381
+ relativePath = this.findTypeRelativeFilePath(dependencyLoc)
382
+ } else {
383
+ const extracted = this.extractType(dependencyLoc, typesPath)
384
+ relativePath = extracted instanceof Error ? extracted : extracted.relativePath
385
+ }
386
+
387
+ if (relativePath instanceof Error) {
388
+ return new Error(
389
+ format(
390
+ '├─ Failed to extract type {yellow} due to an error in dependency type {yellow}\nfrom file {magenta} :\n{}',
391
+ loc.typename,
392
+ dependencyLoc.typename,
393
+ relativeFilePath,
394
+ relativePath.message.replace(/^/gm, ' ')
395
+ )
396
+ )
397
+ }
398
+
399
+ // Update dependencies
400
+ dependencies.push({ ...dependencyLoc, relativePath })
401
+ }
402
+
403
+ const extracted: ExtractedType = {
404
+ ...loc,
405
+ relativePath: relativeFilePath,
406
+ typeParams,
407
+ content: extractedDecl,
408
+ dependencies,
409
+ }
410
+
411
+ typesPath.pop()
412
+
413
+ let types = this.extracted.get(relativeFilePath)
414
+
415
+ if (!types) {
416
+ types = new Map()
417
+ this.extracted.set(relativeFilePath, types)
418
+ }
419
+
420
+ // Save the dependency
421
+ types.set(loc.typename, extracted)
422
+
423
+ return extracted
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Locate the files containing a list of a resolved types
429
+ * @param resolvedTypes
430
+ * @returns
431
+ */
432
+ export function locateTypesFile(resolvedTypes: Array<ResolvedTypeDeps>): TypeLocation[] {
433
+ const out = new Array<TypeLocation>()
434
+
435
+ for (const resolved of resolvedTypes) {
436
+ for (const [file, types] of resolved.dependencies) {
437
+ for (const typename of types) {
438
+ if (!out.find((loc) => loc.typename === typename && loc.relativePathNoExt === file)) {
439
+ out.push({ typename, relativePathNoExt: file })
440
+ }
441
+ }
442
+ }
443
+
444
+ for (const typename of resolved.localTypes) {
445
+ if (!out.find((loc) => loc.typename === typename && loc.relativePathNoExt === resolved.relativeFilePath)) {
446
+ out.push({ typename, relativePathNoExt: resolved.relativeFilePath })
447
+ }
448
+ }
449
+ }
450
+
451
+ return out
452
+ }
453
+
454
+ /**
455
+ * Flatten a tree of resolved type dependencies
456
+ * @param sdkModules
457
+ * @returns
458
+ */
459
+ export function flattenSdkResolvedTypes(sdkModules: SdkModules): ResolvedTypeDeps[] {
460
+ const flattened = new Array<ResolvedTypeDeps>()
461
+
462
+ for (const module of sdkModules.values()) {
463
+ for (const controller of module.values()) {
464
+ for (const method of controller.methods.values()) {
465
+ const { parameters: args, query, body } = method.params
466
+
467
+ flattened.push(method.returnType)
468
+
469
+ if (args) {
470
+ flattened.push(...args.values())
471
+ }
472
+
473
+ if (query) {
474
+ flattened.push(...query.values())
475
+ }
476
+
477
+ if (!body) {
478
+ continue
479
+ }
480
+
481
+ if (body.full) {
482
+ flattened.push(body.type)
483
+ } else {
484
+ flattened.push(...body.fields.values())
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ return flattened
491
+ }