@buildvision/nest-sdk-generator 2.0.0 → 2.2.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 (73) hide show
  1. package/.eslintrc.js +63 -0
  2. package/README.md +2 -2
  3. package/build/analyzer/classdeps.js +1 -1
  4. package/build/analyzer/controller.d.ts +4 -4
  5. package/build/analyzer/controller.js +3 -3
  6. package/build/analyzer/controllers.d.ts +1 -1
  7. package/build/analyzer/controllers.js +4 -4
  8. package/build/analyzer/decorator.d.ts +2 -2
  9. package/build/analyzer/decorator.js +19 -19
  10. package/build/analyzer/extractor.d.ts +4 -4
  11. package/build/analyzer/extractor.js +2 -2
  12. package/build/analyzer/index.d.ts +1 -1
  13. package/build/analyzer/index.js +2 -2
  14. package/build/analyzer/methods.d.ts +7 -7
  15. package/build/analyzer/methods.js +5 -6
  16. package/build/analyzer/module.js +2 -2
  17. package/build/analyzer/params.d.ts +12 -12
  18. package/build/analyzer/params.js +31 -15
  19. package/build/analyzer/route.d.ts +1 -1
  20. package/build/analyzer/route.js +6 -4
  21. package/build/analyzer/typedeps.d.ts +6 -6
  22. package/build/analyzer/typedeps.js +3 -3
  23. package/build/bin.js +9 -13
  24. package/build/config.d.ts +33 -19
  25. package/build/config.js +2 -2
  26. package/build/generator/genmodules.d.ts +29 -5
  27. package/build/generator/genmodules.js +58 -31
  28. package/build/generator/gentypes.js +1 -1
  29. package/build/generator/index.d.ts +1 -1
  30. package/build/generator/index.js +23 -17
  31. package/build/generator/prettier.d.ts +1 -1
  32. package/build/generator/prettier.js +2 -2
  33. package/build/generator/sdk-interface.d.ts +1 -1
  34. package/build/generator/sdk-interface.js +5 -5
  35. package/build/logging.js +1 -1
  36. package/build/rtk-query-generator/genmodules.d.ts +69 -0
  37. package/build/rtk-query-generator/genmodules.js +295 -0
  38. package/build/rtk-query-generator/gentypes.d.ts +10 -0
  39. package/build/rtk-query-generator/gentypes.js +62 -0
  40. package/build/rtk-query-generator/index.d.ts +6 -0
  41. package/build/rtk-query-generator/index.js +84 -0
  42. package/build/rtk-query-generator/prettier.d.ts +16 -0
  43. package/build/rtk-query-generator/prettier.js +43 -0
  44. package/build/rtk-query-generator/sdk-interface.d.ts +1 -0
  45. package/build/rtk-query-generator/sdk-interface.js +36 -0
  46. package/build/tsconfig.tsbuildinfo +1 -1
  47. package/build/utils.js +1 -1
  48. package/bun.lockb +0 -0
  49. package/package.json +15 -5
  50. package/src/analyzer/classdeps.ts +3 -0
  51. package/src/analyzer/controller.ts +12 -6
  52. package/src/analyzer/controllers.ts +7 -4
  53. package/src/analyzer/extractor.ts +9 -6
  54. package/src/analyzer/index.ts +5 -2
  55. package/src/analyzer/methods.ts +13 -12
  56. package/src/analyzer/module.ts +4 -10
  57. package/src/analyzer/params.ts +78 -64
  58. package/src/analyzer/route.ts +5 -3
  59. package/src/analyzer/typedeps.ts +13 -11
  60. package/src/bin.ts +11 -13
  61. package/src/config.ts +39 -24
  62. package/src/generator/genmodules.ts +90 -37
  63. package/src/generator/gentypes.ts +1 -0
  64. package/src/generator/index.ts +23 -19
  65. package/src/generator/prettier.ts +4 -2
  66. package/src/logging.ts +2 -1
  67. package/src/rtk-query-generator/genmodules.ts +407 -0
  68. package/src/rtk-query-generator/gentypes.ts +74 -0
  69. package/src/rtk-query-generator/index.ts +92 -0
  70. package/src/rtk-query-generator/prettier.ts +48 -0
  71. package/src/rtk-query-generator/sdk-interface.ts +32 -0
  72. package/src/utils.ts +2 -2
  73. package/tsconfig.tsbuildinfo +1 -1
@@ -3,37 +3,38 @@
3
3
  */
4
4
 
5
5
  import { ClassDeclaration, Decorator, MethodDeclaration, Node, ParameterDeclaration, Type } from 'ts-morph'
6
+
6
7
  import { debug, panic } from '../logging'
8
+
7
9
  import { SdkHttpMethod } from './methods'
8
- import { Route, paramsFromRoute } from './route'
10
+ import { paramsFromRoute, Route } from './route'
9
11
  import { ResolvedTypeDeps, resolveTypeDependencies } from './typedeps'
10
12
 
11
13
  /**
12
14
  * Type of a parameter that must be passed to an SDK method: Body, Query or Route Param
13
15
  * Supports accessing parameters individually by key or as a whole object
14
- *
16
+ *
15
17
  * EXAMPLE
16
- *
18
+ *
17
19
  * Controller Method:
18
- *
20
+ *
19
21
  * getStuff(@Query('email') email: string, @Query('companyId'): CompanyId)
20
- *
22
+ *
21
23
  * SDK Method Param:
22
24
  * { email: string, companyId: CompanyId }
23
- *
25
+ *
24
26
  * OR
25
- *
27
+ *
26
28
  * Controller Method:
27
29
  * type EmailAndCompanyId = { email: string, companyId: CompanyId }
28
30
  * getStuff(@Query(): EmailAndCompanyId)
29
- *
31
+ *
30
32
  * SDK Method Param:
31
33
  * EmailAndCompanyId
32
34
  */
33
35
 
34
-
35
- export type SdkMethodParam = SdkMethodParamMap | ResolvedTypeDeps | null;
36
- export type SdkMethodParamMap = { [key: string]: ResolvedTypeDeps };
36
+ export type SdkMethodParam = SdkMethodParamMap | ResolvedTypeDeps | null
37
+ export type SdkMethodParamMap = { [key: string]: ResolvedTypeDeps }
37
38
 
38
39
  export const isParamResolvedType = (param: SdkMethodParam): param is ResolvedTypeDeps => {
39
40
  if (param === null) {
@@ -56,36 +57,35 @@ export const getParamResolvedTypes = (param: SdkMethodParam): ResolvedTypeDeps[]
56
57
  * SDK interface for a controller's method's parameters
57
58
  */
58
59
  export interface SdkMethodParams {
59
- routeParams: SdkMethodParam,
60
- queryParams: SdkMethodParam,
61
- bodyParams: SdkMethodParam,
62
- context: MethodContext,
60
+ bodyParams: SdkMethodParam
61
+ context: MethodContext
62
+ queryParams: SdkMethodParam
63
+ routeParams: SdkMethodParam
63
64
  }
64
65
 
65
66
  export type MethodContext = {
66
- controllerClass: ClassDeclaration,
67
- method: MethodDeclaration,
68
- httpMethod: SdkHttpMethod,
69
- route: Route,
70
- filePath: string,
71
- absoluteSrcPath: string,
67
+ absoluteSrcPath: string
68
+ controllerClass: ClassDeclaration
69
+ filePath: string
70
+ httpMethod: SdkHttpMethod
71
+ method: MethodDeclaration
72
+ route: Route
72
73
  }
73
74
 
74
75
  export enum ArgDecorator {
76
+ Body = 'Body',
75
77
  Param = 'Param',
76
78
  Query = 'Query',
77
- Body = 'Body',
78
79
  }
79
80
 
80
81
  export type DecoratedArg = {
81
- arg: ParameterDeclaration,
82
- argParamKey: string | null,
83
- decorator: Decorator,
84
- decoratorType: ArgDecorator,
85
- context: MethodContext,
82
+ arg: ParameterDeclaration
83
+ argParamKey: string | null
84
+ context: MethodContext
85
+ decorator: Decorator
86
+ decoratorType: ArgDecorator
86
87
  }
87
88
 
88
-
89
89
  /**
90
90
  * Generate a SDK interface for a controller's method's parameters
91
91
  * @param httpMethod The method's HTTP method
@@ -95,11 +95,9 @@ export type DecoratedArg = {
95
95
  * @param absoluteSrcPath Absolute path to the source directory
96
96
  * @returns A SDK interface for the method's parameters
97
97
  */
98
- export function extractParams(
99
- context: MethodContext
100
- ): SdkMethodParams {
101
- const { route, filePath } = context
102
- const { Param, Query, Body } = extractDecoratedArgs(context)
98
+ export function extractParams(context: MethodContext): SdkMethodParams {
99
+ const { httpMethod, route } = context
100
+ const { Body, Param, Query } = extractDecoratedArgs(context)
103
101
  const resolvedParams: SdkMethodParams = {
104
102
  routeParams: mergeDecoratedArgs(Param),
105
103
  queryParams: mergeDecoratedArgs(Query),
@@ -107,37 +105,54 @@ export function extractParams(
107
105
  context,
108
106
  }
109
107
 
108
+ /**
109
+ * Validate Body Params are not used in GET requests
110
+ */
111
+ if (httpMethod === SdkHttpMethod.Get && resolvedParams.bodyParams) {
112
+ panicWithContext(`${httpMethod} request should not have @Body params, use @Query params instead`, context)
113
+ }
114
+
115
+ /**
116
+ * Validate Query Params are not used in non-GET requests
117
+ */
118
+ if (httpMethod !== SdkHttpMethod.Get && resolvedParams.queryParams) {
119
+ panicWithContext(`${httpMethod} request should not have @Query params, use @Body params instead`, context)
120
+ }
121
+
110
122
  /**
111
123
  * Validate Route Params exist in route URL
112
124
  */
113
125
  const { routeParams } = resolvedParams
114
126
  if (routeParams) {
115
- const allowedRouteParams = new Set(paramsFromRoute(route))
116
- const usedRouteParams = routeParams instanceof Type ?
117
- // If routeParams was resolved to a single type, assume there's only one decorated argument
118
- Param[0].arg.getType().getProperties().map((prop) => prop.getName()) :
119
- // If routeParams was resolved to a map, assume the keys are the route params
120
- Object.keys(routeParams)
121
-
127
+ const allowedRouteParams = new Set(paramsFromRoute(route))
128
+ const usedRouteParams =
129
+ routeParams instanceof Type
130
+ ? // If routeParams was resolved to a single type, assume there's only one decorated argument
131
+ Param[0].arg
132
+ .getType()
133
+ .getProperties()
134
+ .map((prop) => prop.getName())
135
+ : // If routeParams was resolved to a map, assume the keys are the route params
136
+ Object.keys(routeParams)
137
+
122
138
  for (const usedParam of usedRouteParams) {
123
139
  if (!allowedRouteParams.has(usedParam)) {
124
- panicWithContext(`Route param ${usedParam} does not appear in route URL`, context)
125
- }
140
+ panicWithContext(`Route param ${usedParam} does not appear in route URL`, context)
141
+ }
126
142
  }
127
143
  }
128
144
 
129
-
130
145
  return resolvedParams
131
146
  }
132
147
 
133
148
  export function extractDecoratedArgs(context: MethodContext): Record<ArgDecorator, DecoratedArg[]> {
134
- const decoratedArgs = {
135
- [ArgDecorator.Param]: [] as DecoratedArg[],
136
- [ArgDecorator.Query]: [] as DecoratedArg[],
137
- [ArgDecorator.Body]: [] as DecoratedArg[],
138
- }
149
+ const decoratedArgs = {
150
+ [ArgDecorator.Param]: [] as DecoratedArg[],
151
+ [ArgDecorator.Query]: [] as DecoratedArg[],
152
+ [ArgDecorator.Body]: [] as DecoratedArg[],
153
+ }
139
154
 
140
- const { method, filePath, controllerClass } = context
155
+ const { controllerClass, method } = context
141
156
  const args = method.getParameters()
142
157
 
143
158
  for (const arg of args) {
@@ -181,17 +196,19 @@ export function extractDecoratedArgs(context: MethodContext): Record<ArgDecorato
181
196
  return decoratedArgs
182
197
  }
183
198
 
184
- export function extractArgParamKey(context: MethodContext & {
185
- arg: ParameterDeclaration,
186
- decorator: Decorator,
187
- }): string | null {
188
- const { decorator, controllerClass, method, arg } = context
199
+ export function extractArgParamKey(
200
+ context: MethodContext & {
201
+ arg: ParameterDeclaration
202
+ decorator: Decorator
203
+ }
204
+ ): string | null {
205
+ const { arg, controllerClass, decorator, method } = context
189
206
  const decoratorArgs = decorator.getArguments()
190
207
 
191
208
  if (decoratorArgs.length > 1) {
192
209
  panic(
193
- `${controllerClass.getName()} ${method.getName()} ${decorator.getName()}() ${arg.getName()} argument decorator has multiple parameters`
194
- )
210
+ `${controllerClass.getName()} ${method.getName()} ${decorator.getName()}() ${arg.getName()} argument decorator has multiple parameters`
211
+ )
195
212
  } else if (decoratorArgs.length === 0) {
196
213
  return null
197
214
  }
@@ -204,10 +221,7 @@ export function extractArgParamKey(context: MethodContext & {
204
221
 
205
222
  const paramKey = decoratorArg.getLiteralText()
206
223
  if (paramKey in {}) {
207
- panicWithContext(
208
- `${decorator.getName()}('${paramKey}') param name collides with JavaScript native object property`,
209
- context
210
- )
224
+ panicWithContext(`${decorator.getName()}('${paramKey}') param name collides with JavaScript native object property`, context)
211
225
  }
212
226
 
213
227
  return paramKey
@@ -215,10 +229,10 @@ export function extractArgParamKey(context: MethodContext & {
215
229
 
216
230
  export function mergeDecoratedArgs(decoratedArgs: DecoratedArg[]): SdkMethodParam {
217
231
  let genericParam: SdkMethodParam | null = null
218
- let paramMap: SdkMethodParamMap = {}
232
+ const paramMap: SdkMethodParamMap = {}
219
233
 
220
234
  for (const decoratedArg of decoratedArgs) {
221
- const { argParamKey, decoratorType, arg, context } = decoratedArg
235
+ const { arg, argParamKey, context, decoratorType } = decoratedArg
222
236
  const resolvedType = resolveTypeDependencies(arg.getType(), context.filePath, context.absoluteSrcPath)
223
237
 
224
238
  if (argParamKey) {
@@ -227,7 +241,7 @@ export function mergeDecoratedArgs(decoratedArgs: DecoratedArg[]): SdkMethodPara
227
241
  }
228
242
 
229
243
  paramMap[argParamKey] = resolvedType
230
- } else {
244
+ } else {
231
245
  if (genericParam) {
232
246
  panicWithContext(`${decoratorType}() used twice in controller method`, decoratedArg.context)
233
247
  }
@@ -246,7 +260,7 @@ export function mergeDecoratedArgs(decoratedArgs: DecoratedArg[]): SdkMethodPara
246
260
  }
247
261
 
248
262
  const panicWithContext = (message: string, context: MethodContext) => {
249
- const { controllerClass, httpMethod, method, filePath } = context
263
+ const { controllerClass, filePath, httpMethod, method } = context
250
264
  const ctx = {
251
265
  controller: controllerClass.getName(),
252
266
  method: method.getName(),
@@ -51,6 +51,7 @@ export function analyzeUri(uriPath: string): Route | Error {
51
51
  if (part === '') continue
52
52
 
53
53
  // Ensure there is no generic character in the path as we don't support them
54
+ // eslint-disable-next-line no-useless-escape
54
55
  const genericErr = _findInvalid(part, /[\*\+\?]/)
55
56
 
56
57
  if (genericErr) {
@@ -91,7 +92,7 @@ export function analyzeUri(uriPath: string): Route | Error {
91
92
  * @param route
92
93
  */
93
94
  export function routePartIsParam(part: RoutePart): part is { readonly param: string } {
94
- return "param" in part
95
+ return 'param' in part
95
96
  }
96
97
 
97
98
  export function paramsFromRoute(route: Route): string[] {
@@ -119,11 +120,12 @@ export function debugUri(route: Route, color: (str: string) => string): string {
119
120
  * @param params
120
121
  */
121
122
  export function resolveRoute(route: Route, params: { [name: string]: string }): string | Error {
122
- let uri: string[] = []
123
+ const uri: string[] = []
123
124
 
124
125
  for (const part of route.parts) {
125
126
  if ('segment' in part) {
126
127
  uri.push(part.segment)
128
+ // eslint-disable-next-line no-prototype-builtins
127
129
  } else if (!params.hasOwnProperty(part.param)) {
128
130
  return new Error('Missing route parameter ' + part.param)
129
131
  } else {
@@ -140,7 +142,7 @@ export function resolveRoute(route: Route, params: { [name: string]: string }):
140
142
  * @param paramsProvider
141
143
  */
142
144
  export function resolveRouteWith(route: Route, paramsProvider: (param: string) => string | null): string | Error {
143
- let uri: string[] = []
145
+ const uri: string[] = []
144
146
 
145
147
  for (const part of route.parts) {
146
148
  if ('segment' in part) {
@@ -3,7 +3,9 @@
3
3
  */
4
4
 
5
5
  import * as path from 'path'
6
+
6
7
  import { ts, Type } from 'ts-morph'
8
+
7
9
  import { unreachable } from '../logging'
8
10
 
9
11
  /**
@@ -15,15 +17,6 @@ export const IMPORTED_TYPE_REGEX = /\bimport\(['"]([^'"]+)['"]\)\.([a-zA-Z0-9_]+
15
17
  * Type with resolved level 1 dependencies (does not look for dependencies' own dependencies!)
16
18
  */
17
19
  export interface ResolvedTypeDeps {
18
- /** The original raw type, with import("...") expressions */
19
- readonly rawType: string
20
-
21
- /** The resolved type, without imports */
22
- readonly resolvedType: string
23
-
24
- /** File from which the type originates */
25
- readonly relativeFilePath: string
26
-
27
20
  /**
28
21
  * The type dependencies used by the resolved type
29
22
  * A collection of scripts' relative paths mapped with the list of types imported from them
@@ -35,6 +28,15 @@ export interface ResolvedTypeDeps {
35
28
  * They may be either local types (declared in the same file than the one analyzed) or globally-defined types
36
29
  */
37
30
  readonly localTypes: string[]
31
+
32
+ /** The original raw type, with import("...") expressions */
33
+ readonly rawType: string
34
+
35
+ /** File from which the type originates */
36
+ readonly relativeFilePath: string
37
+
38
+ /** The resolved type, without imports */
39
+ readonly resolvedType: string
38
40
  }
39
41
 
40
42
  /**
@@ -55,8 +57,8 @@ export function resolveTypeDependencies(type: Type<ts.Type>, relativeFilePath: s
55
57
  )
56
58
  }
57
59
 
58
- let dependencies: ResolvedTypeDeps['dependencies'] = new Map()
59
- let localTypes: ResolvedTypeDeps['localTypes'] = []
60
+ const dependencies: ResolvedTypeDeps['dependencies'] = new Map()
61
+ const localTypes: ResolvedTypeDeps['localTypes'] = []
60
62
 
61
63
  /** Resolved type (without import statements) */
62
64
  const resolvedType: ResolvedTypeDeps['resolvedType'] = rawType.replace(IMPORTED_TYPE_REGEX, (_, matchedFilePath, type) => {
package/src/bin.ts CHANGED
@@ -1,30 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import * as path from 'path'
4
+
4
5
  import { analyzerCli } from './analyzer'
5
6
  import { config, configPath } from './config'
6
- import generatorCli from './generator'
7
+ import generateAPIClient from './generator'
7
8
  import { println } from './logging'
9
+ import generateRTKQueryEndpoints from './rtk-query-generator'
8
10
 
9
11
  async function main() {
10
12
  const started = Date.now()
11
13
 
12
14
  process.chdir(path.dirname(path.resolve(configPath)))
13
15
 
14
- switch (process.argv[3]) {
15
- case '--analyze':
16
- await analyzerCli(config)
17
- break
16
+ const sdkContent = await analyzerCli(config)
17
+
18
+ const outputType = process.argv[3]
18
19
 
19
- case '--generate':
20
- case undefined:
21
- const sdkContent = await analyzerCli(config)
22
- await generatorCli(config, sdkContent)
23
- break
20
+ if (outputType === '--generate') {
21
+ await generateAPIClient(config, sdkContent)
22
+ }
24
23
 
25
- default:
26
- console.error('ERROR: Unknown action provided (must be either "--analyze" or "--generate")')
27
- process.exit(1)
24
+ if (outputType === '--generate-rtk') {
25
+ await generateRTKQueryEndpoints(config, sdkContent)
28
26
  }
29
27
 
30
28
  println('{green}', '@ Done in ' + ((Date.now() - started) / 1000).toFixed(2) + 's')
package/src/config.ts CHANGED
@@ -1,29 +1,36 @@
1
- import chalk = require('chalk')
2
1
  import * as fs from 'fs'
3
2
  import * as path from 'path'
4
3
 
4
+ import chalk = require('chalk')
5
+
5
6
  /**
6
7
  * The configuration file's content
7
8
  * For details on what these options do, see the project's README
8
9
  */
9
10
  export interface Config {
10
- /** Enable verbose mode */
11
- readonly verbose?: boolean
12
-
13
- /** Disable colored output */
14
- readonly noColor?: boolean
15
-
16
11
  /** Path to the API's source directory */
17
12
  readonly apiInputPath: string
18
13
 
19
- /** Path to generate the SDK at */
20
- readonly sdkOutput: string
14
+ readonly controllerOutput?: {
15
+ endpointGroupName?: {
16
+ addSuffix?: string
17
+ removeSuffix?: string
18
+ }
19
+ exportName?: {
20
+ addSuffix?: string
21
+ removeSuffix?: string
22
+ }
23
+ fileName?: {
24
+ addSuffix?: string
25
+ removeSuffix?: string
26
+ }
27
+ }
21
28
 
22
- /** Path to the SDK interface file */
23
- readonly sdkInterfacePath: string
29
+ /** If the SDK interface file does not exist yet, create one automatically (enabled by default) */
30
+ readonly generateDefaultSdkInterface?: boolean
24
31
 
25
- /** List of magic types */
26
- readonly magicTypes?: MagicType[]
32
+ /** Write generation timestamp in each TypeScript file (enabled by default) */
33
+ readonly generateTimestamps?: boolean
27
34
 
28
35
  /** Show a JSON output */
29
36
  readonly jsonOutput?: string
@@ -31,23 +38,31 @@ export interface Config {
31
38
  /** Prettify the JSON output */
32
39
  readonly jsonPrettyOutput?: boolean
33
40
 
34
- /** Prettify the generated files (enabled by default) */
35
- readonly prettify?: boolean
41
+ /** List of magic types */
42
+ readonly magicTypes?: MagicType[]
43
+
44
+ /** Disable colored output */
45
+ readonly noColor?: boolean
46
+
47
+ /** If the output directory already exists, overwrite it (enabled by default) */
48
+ readonly overwriteOldOutputDir?: boolean
36
49
 
37
50
  /** Path to Prettier's configuration file */
38
51
  readonly prettierConfig?: string
39
52
 
40
- /** Path to custom tsconfig file */
41
- readonly tsconfigFile?: string
53
+ /** Prettify the generated files (enabled by default) */
54
+ readonly prettify?: boolean
42
55
 
43
- /** If the output directory already exists, overwrite it (enabled by default) */
44
- readonly overwriteOldOutputDir?: boolean
56
+ /** Path to the SDK interface file */
57
+ readonly sdkInterfacePath: string
45
58
 
46
- /** If the SDK interface file does not exist yet, create one automatically (enabled by default) */
47
- readonly generateDefaultSdkInterface?: boolean
59
+ /** Path to generate the SDK at */
60
+ readonly sdkOutputPath: string
61
+ /** Path to custom tsconfig file */
62
+ readonly tsconfigFile?: string
48
63
 
49
- /** Write generation timestamp in each TypeScript file (enabled by default) */
50
- readonly generateTimestamps?: boolean
64
+ /** Enable verbose mode */
65
+ readonly verbose?: boolean
51
66
  }
52
67
 
53
68
  /**
@@ -55,8 +70,8 @@ export interface Config {
55
70
  */
56
71
  export interface MagicType {
57
72
  readonly nodeModuleFilePath: string
58
- readonly typeName: string
59
73
  readonly placeholderContent: string
74
+ readonly typeName: string
60
75
  }
61
76
 
62
77
  /**