moonflower 0.9.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 (268) hide show
  1. package/.eslintrc.js +26 -0
  2. package/.prettierrc.js +7 -0
  3. package/README.md +383 -0
  4. package/cli/cli.ts +59 -0
  5. package/cli/entry.cjs +3 -0
  6. package/cli/prettyprint.ts +16 -0
  7. package/dist/cli/cli.d.ts +2 -0
  8. package/dist/cli/cli.d.ts.map +1 -0
  9. package/dist/cli/cli.js +79 -0
  10. package/dist/cli/prettyprint.d.ts +4 -0
  11. package/dist/cli/prettyprint.d.ts.map +1 -0
  12. package/dist/cli/prettyprint.js +18 -0
  13. package/dist/src/errors/BaseHttpError.d.ts +13 -0
  14. package/dist/src/errors/BaseHttpError.d.ts.map +1 -0
  15. package/dist/src/errors/BaseHttpError.js +13 -0
  16. package/dist/src/errors/HttpErrorHandler.d.ts +3 -0
  17. package/dist/src/errors/HttpErrorHandler.d.ts.map +1 -0
  18. package/dist/src/errors/HttpErrorHandler.js +23 -0
  19. package/dist/src/errors/UserFacingErrors.d.ts +11 -0
  20. package/dist/src/errors/UserFacingErrors.d.ts.map +1 -0
  21. package/dist/src/errors/UserFacingErrors.js +23 -0
  22. package/dist/src/hooks/authentication/useAuth.d.ts +3 -0
  23. package/dist/src/hooks/authentication/useAuth.d.ts.map +1 -0
  24. package/dist/src/hooks/authentication/useAuth.js +7 -0
  25. package/dist/src/hooks/authentication/useOptionalAuth.d.ts +3 -0
  26. package/dist/src/hooks/authentication/useOptionalAuth.d.ts.map +1 -0
  27. package/dist/src/hooks/authentication/useOptionalAuth.js +16 -0
  28. package/dist/src/hooks/useApiEndpoint.d.ts +8 -0
  29. package/dist/src/hooks/useApiEndpoint.d.ts.map +1 -0
  30. package/dist/src/hooks/useApiEndpoint.js +7 -0
  31. package/dist/src/hooks/useApiHeader/index.d.ts +2 -0
  32. package/dist/src/hooks/useApiHeader/index.d.ts.map +1 -0
  33. package/dist/src/hooks/useApiHeader/index.js +17 -0
  34. package/dist/src/hooks/useApiHeader/useApiHeader.d.ts +3 -0
  35. package/dist/src/hooks/useApiHeader/useApiHeader.d.ts.map +1 -0
  36. package/dist/src/hooks/useApiHeader/useApiHeader.js +6 -0
  37. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.d.ts +2 -0
  38. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.d.ts.map +1 -0
  39. package/dist/src/hooks/useApiHeader/useApiHeader.spec.data.js +22 -0
  40. package/dist/src/hooks/useCookieParams.d.ts +9 -0
  41. package/dist/src/hooks/useCookieParams.d.ts.map +1 -0
  42. package/dist/src/hooks/useCookieParams.js +50 -0
  43. package/dist/src/hooks/useExposeApiModel/index.d.ts +2 -0
  44. package/dist/src/hooks/useExposeApiModel/index.d.ts.map +1 -0
  45. package/dist/src/hooks/useExposeApiModel/index.js +17 -0
  46. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.d.ts +3 -0
  47. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.d.ts.map +1 -0
  48. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.js +9 -0
  49. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.d.ts +2 -0
  50. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.d.ts.map +1 -0
  51. package/dist/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.js +16 -0
  52. package/dist/src/hooks/useHeaderParams.d.ts +12 -0
  53. package/dist/src/hooks/useHeaderParams.d.ts.map +1 -0
  54. package/dist/src/hooks/useHeaderParams.js +52 -0
  55. package/dist/src/hooks/usePathParams.d.ts +22 -0
  56. package/dist/src/hooks/usePathParams.d.ts.map +1 -0
  57. package/dist/src/hooks/usePathParams.js +46 -0
  58. package/dist/src/hooks/useQueryParams.d.ts +9 -0
  59. package/dist/src/hooks/useQueryParams.d.ts.map +1 -0
  60. package/dist/src/hooks/useQueryParams.js +50 -0
  61. package/dist/src/hooks/useRequestBody.d.ts +9 -0
  62. package/dist/src/hooks/useRequestBody.d.ts.map +1 -0
  63. package/dist/src/hooks/useRequestBody.js +59 -0
  64. package/dist/src/hooks/useRequestRawBody.d.ts +7 -0
  65. package/dist/src/hooks/useRequestRawBody.d.ts.map +1 -0
  66. package/dist/src/hooks/useRequestRawBody.js +34 -0
  67. package/dist/src/index.d.ts +18 -0
  68. package/dist/src/index.d.ts.map +1 -0
  69. package/dist/src/index.js +33 -0
  70. package/dist/src/openapi/analyzerModule/analyzerModule.d.ts +18 -0
  71. package/dist/src/openapi/analyzerModule/analyzerModule.d.ts.map +1 -0
  72. package/dist/src/openapi/analyzerModule/analyzerModule.js +192 -0
  73. package/dist/src/openapi/analyzerModule/nodeParsers.d.ts +19 -0
  74. package/dist/src/openapi/analyzerModule/nodeParsers.d.ts.map +1 -0
  75. package/dist/src/openapi/analyzerModule/nodeParsers.js +521 -0
  76. package/dist/src/openapi/analyzerModule/parseEndpoint.d.ts +4 -0
  77. package/dist/src/openapi/analyzerModule/parseEndpoint.d.ts.map +1 -0
  78. package/dist/src/openapi/analyzerModule/parseEndpoint.js +246 -0
  79. package/dist/src/openapi/analyzerModule/parseExposedModels.d.ts +5 -0
  80. package/dist/src/openapi/analyzerModule/parseExposedModels.d.ts.map +1 -0
  81. package/dist/src/openapi/analyzerModule/parseExposedModels.js +32 -0
  82. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.d.ts +2 -0
  83. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.d.ts.map +1 -0
  84. package/dist/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.js +400 -0
  85. package/dist/src/openapi/analyzerModule/types.d.ts +53 -0
  86. package/dist/src/openapi/analyzerModule/types.d.ts.map +1 -0
  87. package/dist/src/openapi/analyzerModule/types.js +2 -0
  88. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.d.ts +8 -0
  89. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.d.ts.map +1 -0
  90. package/dist/src/openapi/discoveryModule/discoverImports/discoverImports.js +33 -0
  91. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.d.ts +4 -0
  92. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.d.ts.map +1 -0
  93. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.js +8 -0
  94. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.d.ts +4 -0
  95. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.d.ts.map +1 -0
  96. package/dist/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.js +8 -0
  97. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.d.ts +17 -0
  98. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.d.ts.map +1 -0
  99. package/dist/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.js +80 -0
  100. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.d.ts +6 -0
  101. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.d.ts.map +1 -0
  102. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.js +31 -0
  103. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.d.ts +5 -0
  104. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.d.ts.map +1 -0
  105. package/dist/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.js +38 -0
  106. package/dist/src/openapi/discoveryModule/index.d.ts +3 -0
  107. package/dist/src/openapi/discoveryModule/index.d.ts.map +1 -0
  108. package/dist/src/openapi/discoveryModule/index.js +18 -0
  109. package/dist/src/openapi/generatorModule/generateComponentSchemas.d.ts +4 -0
  110. package/dist/src/openapi/generatorModule/generateComponentSchemas.d.ts.map +1 -0
  111. package/dist/src/openapi/generatorModule/generateComponentSchemas.js +12 -0
  112. package/dist/src/openapi/generatorModule/generatePaths.d.ts +10 -0
  113. package/dist/src/openapi/generatorModule/generatePaths.d.ts.map +1 -0
  114. package/dist/src/openapi/generatorModule/generatePaths.js +116 -0
  115. package/dist/src/openapi/generatorModule/generatorModule.d.ts +16 -0
  116. package/dist/src/openapi/generatorModule/generatorModule.d.ts.map +1 -0
  117. package/dist/src/openapi/generatorModule/generatorModule.js +18 -0
  118. package/dist/src/openapi/generatorModule/getSchema.d.ts +36 -0
  119. package/dist/src/openapi/generatorModule/getSchema.d.ts.map +1 -0
  120. package/dist/src/openapi/generatorModule/getSchema.js +133 -0
  121. package/dist/src/openapi/generatorModule/index.d.ts +5 -0
  122. package/dist/src/openapi/generatorModule/index.d.ts.map +1 -0
  123. package/dist/src/openapi/generatorModule/index.js +20 -0
  124. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.d.ts +515 -0
  125. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.d.ts.map +1 -0
  126. package/dist/src/openapi/generatorModule/test/openApiGenerator.spec.data.js +1119 -0
  127. package/dist/src/openapi/initOpenApiEngine.d.ts +4 -0
  128. package/dist/src/openapi/initOpenApiEngine.d.ts.map +1 -0
  129. package/dist/src/openapi/initOpenApiEngine.js +14 -0
  130. package/dist/src/openapi/manager/OpenApiManager.d.ts +67 -0
  131. package/dist/src/openapi/manager/OpenApiManager.d.ts.map +1 -0
  132. package/dist/src/openapi/manager/OpenApiManager.js +86 -0
  133. package/dist/src/openapi/router/OpenApiRouter.d.ts +4 -0
  134. package/dist/src/openapi/router/OpenApiRouter.d.ts.map +1 -0
  135. package/dist/src/openapi/router/OpenApiRouter.js +11 -0
  136. package/dist/src/openapi/types.d.ts +81 -0
  137. package/dist/src/openapi/types.d.ts.map +1 -0
  138. package/dist/src/openapi/types.js +2 -0
  139. package/dist/src/router/Router.d.ts +23 -0
  140. package/dist/src/router/Router.d.ts.map +1 -0
  141. package/dist/src/router/Router.js +81 -0
  142. package/dist/src/router/responseValueToJson.d.ts +2 -0
  143. package/dist/src/router/responseValueToJson.d.ts.map +1 -0
  144. package/dist/src/router/responseValueToJson.js +10 -0
  145. package/dist/src/setupTests.d.ts +1 -0
  146. package/dist/src/setupTests.d.ts.map +1 -0
  147. package/dist/src/setupTests.js +3 -0
  148. package/dist/src/test/TestAppRouter.d.ts +8 -0
  149. package/dist/src/test/TestAppRouter.d.ts.map +1 -0
  150. package/dist/src/test/TestAppRouter.js +58 -0
  151. package/dist/src/test/app.d.ts +3 -0
  152. package/dist/src/test/app.d.ts.map +1 -0
  153. package/dist/src/test/app.js +41 -0
  154. package/dist/src/utils/TypeUtils.d.ts +22 -0
  155. package/dist/src/utils/TypeUtils.d.ts.map +1 -0
  156. package/dist/src/utils/TypeUtils.js +2 -0
  157. package/dist/src/utils/fromZodSchema.d.ts +2 -0
  158. package/dist/src/utils/fromZodSchema.d.ts.map +1 -0
  159. package/dist/src/utils/fromZodSchema.js +6 -0
  160. package/dist/src/utils/loadTestData.d.ts +2 -0
  161. package/dist/src/utils/loadTestData.d.ts.map +1 -0
  162. package/dist/src/utils/loadTestData.js +39 -0
  163. package/dist/src/utils/mockContext.d.ts +20 -0
  164. package/dist/src/utils/mockContext.d.ts.map +1 -0
  165. package/dist/src/utils/mockContext.js +85 -0
  166. package/dist/src/utils/nameOf.d.ts +5 -0
  167. package/dist/src/utils/nameOf.d.ts.map +1 -0
  168. package/dist/src/utils/nameOf.js +7 -0
  169. package/dist/src/utils/object.d.ts +7 -0
  170. package/dist/src/utils/object.d.ts.map +1 -0
  171. package/dist/src/utils/object.js +21 -0
  172. package/dist/src/utils/printers.d.ts +6 -0
  173. package/dist/src/utils/printers.d.ts.map +1 -0
  174. package/dist/src/utils/printers.js +76 -0
  175. package/dist/src/utils/validationMessages.d.ts +18 -0
  176. package/dist/src/utils/validationMessages.d.ts.map +1 -0
  177. package/dist/src/utils/validationMessages.js +43 -0
  178. package/dist/src/validators/BuiltInValidators.d.ts +61 -0
  179. package/dist/src/validators/BuiltInValidators.d.ts.map +1 -0
  180. package/dist/src/validators/BuiltInValidators.js +66 -0
  181. package/dist/src/validators/InternalParamWrappers.d.ts +5 -0
  182. package/dist/src/validators/InternalParamWrappers.d.ts.map +1 -0
  183. package/dist/src/validators/InternalParamWrappers.js +5 -0
  184. package/dist/src/validators/ParamWrappers.d.ts +11 -0
  185. package/dist/src/validators/ParamWrappers.d.ts.map +1 -0
  186. package/dist/src/validators/ParamWrappers.js +9 -0
  187. package/dist/src/validators/types.d.ts +18 -0
  188. package/dist/src/validators/types.d.ts.map +1 -0
  189. package/dist/src/validators/types.js +2 -0
  190. package/dist/tsconfig.build.tsbuildinfo +1 -0
  191. package/package.json +59 -0
  192. package/src/errors/BaseHttpError.ts +16 -0
  193. package/src/errors/HttpErrorHandler.ts +20 -0
  194. package/src/errors/UserFacingErrors.ts +39 -0
  195. package/src/hooks/authentication/useAuth.ts +8 -0
  196. package/src/hooks/authentication/useOptionalAuth.ts +17 -0
  197. package/src/hooks/useApiEndpoint.spec.ts +11 -0
  198. package/src/hooks/useApiEndpoint.ts +10 -0
  199. package/src/hooks/useApiHeader/index.ts +1 -0
  200. package/src/hooks/useApiHeader/useApiHeader.spec.data.ts +22 -0
  201. package/src/hooks/useApiHeader/useApiHeader.spec.ts +34 -0
  202. package/src/hooks/useApiHeader/useApiHeader.ts +6 -0
  203. package/src/hooks/useCookieParams.spec.ts +174 -0
  204. package/src/hooks/useCookieParams.ts +73 -0
  205. package/src/hooks/useExposeApiModel/index.ts +1 -0
  206. package/src/hooks/useExposeApiModel/useExposeApiModel.spec.data.ts +48 -0
  207. package/src/hooks/useExposeApiModel/useExposeApiModel.spec.ts +388 -0
  208. package/src/hooks/useExposeApiModel/useExposeApiModel.ts +9 -0
  209. package/src/hooks/useHeaderParams.spec.ts +186 -0
  210. package/src/hooks/useHeaderParams.ts +83 -0
  211. package/src/hooks/usePathParams.spec.ts +161 -0
  212. package/src/hooks/usePathParams.ts +89 -0
  213. package/src/hooks/useQueryParams.spec.ts +224 -0
  214. package/src/hooks/useQueryParams.ts +73 -0
  215. package/src/hooks/useRequestBody.spec.ts +215 -0
  216. package/src/hooks/useRequestBody.ts +94 -0
  217. package/src/hooks/useRequestRawBody.spec.ts +154 -0
  218. package/src/hooks/useRequestRawBody.ts +56 -0
  219. package/src/index.ts +17 -0
  220. package/src/openapi/analyzerModule/analyzerModule.ts +228 -0
  221. package/src/openapi/analyzerModule/nodeParsers.ts +648 -0
  222. package/src/openapi/analyzerModule/parseEndpoint.ts +305 -0
  223. package/src/openapi/analyzerModule/parseExposedModels.ts +34 -0
  224. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.data.ts +521 -0
  225. package/src/openapi/analyzerModule/test/openApiAnalyzer.spec.ts +1043 -0
  226. package/src/openapi/analyzerModule/types.ts +72 -0
  227. package/src/openapi/discoveryModule/discoverImports/discoverImports.ts +43 -0
  228. package/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterA.spec.data.ts +7 -0
  229. package/src/openapi/discoveryModule/discoverRouterFiles/data/testRouterB.spec.data.ts +7 -0
  230. package/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.spec.ts +36 -0
  231. package/src/openapi/discoveryModule/discoverRouterFiles/discoverRouterFiles.ts +80 -0
  232. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.data.ts +42 -0
  233. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.spec.ts +18 -0
  234. package/src/openapi/discoveryModule/discoverRouters/discoverRouters.ts +39 -0
  235. package/src/openapi/discoveryModule/index.ts +2 -0
  236. package/src/openapi/generatorModule/generateComponentSchemas.ts +12 -0
  237. package/src/openapi/generatorModule/generatePaths.ts +138 -0
  238. package/src/openapi/generatorModule/generatorModule.ts +17 -0
  239. package/src/openapi/generatorModule/getSchema.ts +169 -0
  240. package/src/openapi/generatorModule/index.ts +4 -0
  241. package/src/openapi/generatorModule/test/openApiGenerator.spec.data.ts +1119 -0
  242. package/src/openapi/generatorModule/test/openApiGenerator.spec.ts +783 -0
  243. package/src/openapi/initOpenApiEngine.ts +20 -0
  244. package/src/openapi/manager/OpenApiManager.ts +153 -0
  245. package/src/openapi/router/OpenApiRouter.ts +11 -0
  246. package/src/openapi/types.ts +86 -0
  247. package/src/router/Router.ts +123 -0
  248. package/src/router/responseValueToJson.ts +6 -0
  249. package/src/setupTests.ts +3 -0
  250. package/src/test/TestAppRouter.ts +76 -0
  251. package/src/test/app.spec.ts +130 -0
  252. package/src/test/app.ts +43 -0
  253. package/src/utils/TypeUtils.ts +51 -0
  254. package/src/utils/loadTestData.ts +15 -0
  255. package/src/utils/mockContext.ts +86 -0
  256. package/src/utils/nameOf.ts +7 -0
  257. package/src/utils/object.spec.ts +27 -0
  258. package/src/utils/object.ts +17 -0
  259. package/src/utils/printers.spec.ts +103 -0
  260. package/src/utils/printers.ts +49 -0
  261. package/src/utils/validationMessages.ts +65 -0
  262. package/src/validators/BuiltInValidators.ts +64 -0
  263. package/src/validators/InternalParamWrappers.ts +14 -0
  264. package/src/validators/ParamWrappers.ts +22 -0
  265. package/src/validators/types.ts +35 -0
  266. package/tsconfig.build.json +15 -0
  267. package/tsconfig.json +29 -0
  268. package/vite.config.ts +16 -0
@@ -0,0 +1,20 @@
1
+ import Koa from 'koa'
2
+
3
+ import { prepareOpenApiSpec } from './analyzerModule/analyzerModule'
4
+ import { OpenApiRouter } from './router/OpenApiRouter'
5
+
6
+ /**
7
+ * Middleware to initialize the openApi engine.
8
+ * Can be at any position in the middleware execution order.
9
+ * All files with routers or exposed models must be included in `props.sourceFilePaths`.
10
+ * @param props Paths to files to analyze, relative to project root.
11
+ */
12
+ export const initOpenApiEngine = (props: Parameters<typeof prepareOpenApiSpec>[0]) => {
13
+ prepareOpenApiSpec(props)
14
+
15
+ const builtInRoutes = OpenApiRouter.routes()
16
+ const builtInAllowedMethods = OpenApiRouter.allowedMethods()
17
+ return (ctx: Koa.ParameterizedContext<any, any>, next: Koa.Next) => {
18
+ return builtInRoutes(ctx, () => builtInAllowedMethods(ctx, next))
19
+ }
20
+ }
@@ -0,0 +1,153 @@
1
+ import { Router } from '../../router/Router'
2
+ import { EndpointData, ExposedModelData } from '../types'
3
+
4
+ type UrlType = `${'http' | 'https'}://${string}.${string}`
5
+
6
+ export type ApiDocsHeader = {
7
+ title: string
8
+ version: string
9
+ description?: string
10
+ termsOfService?: UrlType
11
+ contact?: {
12
+ name?: string
13
+ url?: UrlType
14
+ email?: string
15
+ }
16
+ license?: {
17
+ name?: string
18
+ url?: UrlType
19
+ }
20
+ }
21
+
22
+ export type ApiDocsPreferences = {
23
+ allowOptionalPathParams: boolean
24
+ }
25
+
26
+ export type ApiAnalysisStats = {
27
+ explicitRouterFiles: {
28
+ path: string
29
+ routers: {
30
+ name: string
31
+ endpoints: string[]
32
+ }[]
33
+ }[]
34
+ discoveredRouterFiles: {
35
+ path: string
36
+ routers: {
37
+ name: string
38
+ endpoints: string[]
39
+ }[]
40
+ }[]
41
+ }
42
+
43
+ export class OpenApiManager {
44
+ private static instance: OpenApiManager | null = null
45
+
46
+ private isInitialized = false
47
+ private registeredRouters: Router[] = []
48
+
49
+ constructor(
50
+ private apiDocsHeader: ApiDocsHeader,
51
+ private exposedModels: ExposedModelData[],
52
+ private endpoints: EndpointData[],
53
+ private preferences: ApiDocsPreferences,
54
+ private stats: ApiAnalysisStats
55
+ ) {}
56
+
57
+ public isReady(): boolean {
58
+ return this.isInitialized
59
+ }
60
+
61
+ public hasExposedModel(name: string) {
62
+ return this.exposedModels.some((model) => model.name === name)
63
+ }
64
+
65
+ public getExposedModels() {
66
+ return this.exposedModels
67
+ }
68
+
69
+ public setExposedModels(models: ExposedModelData[]) {
70
+ this.exposedModels = models
71
+ return this
72
+ }
73
+
74
+ public setEndpoints(endpoints: EndpointData[]) {
75
+ this.endpoints = endpoints
76
+ return this
77
+ }
78
+
79
+ public markAsReady() {
80
+ this.isInitialized = true
81
+ return this
82
+ }
83
+
84
+ public getHeader(): ApiDocsHeader {
85
+ return this.apiDocsHeader
86
+ }
87
+
88
+ public setHeader(docs: ApiDocsHeader) {
89
+ this.apiDocsHeader = docs
90
+ return this
91
+ }
92
+
93
+ public getEndpoints() {
94
+ return this.endpoints
95
+ }
96
+
97
+ public getPreferences() {
98
+ return this.preferences
99
+ }
100
+
101
+ public setPreferences(preferences: ApiDocsPreferences) {
102
+ this.preferences = {
103
+ ...preferences,
104
+ }
105
+ return this
106
+ }
107
+
108
+ public getStats() {
109
+ return this.stats
110
+ }
111
+
112
+ public setStats(stats: ApiAnalysisStats) {
113
+ this.stats = {
114
+ ...stats,
115
+ }
116
+ return this
117
+ }
118
+
119
+ public getRouters(): readonly Router[] {
120
+ return this.registeredRouters
121
+ }
122
+
123
+ public registerRouters(routers: Router<any, any>[]) {
124
+ routers.forEach((r) => this.registeredRouters.push(r))
125
+ return this
126
+ }
127
+
128
+ public reset() {
129
+ this.exposedModels = []
130
+ this.endpoints = []
131
+ }
132
+
133
+ public static getInstance() {
134
+ if (!OpenApiManager.instance) {
135
+ OpenApiManager.instance = new OpenApiManager(
136
+ {
137
+ title: 'Default title',
138
+ version: '1.0.0',
139
+ },
140
+ [],
141
+ [],
142
+ {
143
+ allowOptionalPathParams: false,
144
+ },
145
+ {
146
+ discoveredRouterFiles: [],
147
+ explicitRouterFiles: [],
148
+ }
149
+ )
150
+ }
151
+ return OpenApiManager.instance
152
+ }
153
+ }
@@ -0,0 +1,11 @@
1
+ import { Router } from '../../router/Router'
2
+ import { generateOpenApiSpec } from '../generatorModule/generatorModule'
3
+ import { OpenApiManager } from '../manager/OpenApiManager'
4
+
5
+ const router = new Router({ skipOpenApiAnalysis: true })
6
+
7
+ router.get('/api-json', () => {
8
+ return generateOpenApiSpec(OpenApiManager.getInstance())
9
+ })
10
+
11
+ export const OpenApiRouter = router
@@ -0,0 +1,86 @@
1
+ import { ShapeOfType } from './analyzerModule/types'
2
+ import { SchemaType } from './generatorModule/getSchema'
3
+
4
+ type PathParam = {
5
+ name: string
6
+ in: 'query' | 'path' | 'header'
7
+ description: string
8
+ required: true | false | undefined
9
+ }
10
+
11
+ export type PathDefinition = {
12
+ summary?: string
13
+ description: string
14
+ operationId?: string
15
+ parameters: PathParam[]
16
+ requestBody: any
17
+ tags?: string[]
18
+ responses: Record<
19
+ string,
20
+ {
21
+ description: string
22
+ content?: {
23
+ 'application/json': {
24
+ schema: {
25
+ oneOf: SchemaType[]
26
+ }
27
+ }
28
+ }
29
+ }
30
+ >
31
+ }
32
+
33
+ export type ExposedModelData = {
34
+ name: string
35
+ shape: string | ShapeOfType[]
36
+ }
37
+
38
+ export type EndpointData = {
39
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
40
+ path: string
41
+ sourceFilePath: string
42
+ name?: string
43
+ summary?: string
44
+ description?: string
45
+ tags?: string[]
46
+ requestPathParams: {
47
+ identifier: string
48
+ signature: string | ShapeOfType[]
49
+ optional: boolean
50
+ description?: string
51
+ errorMessage?: string
52
+ }[]
53
+ requestQuery: {
54
+ identifier: string
55
+ signature: string | ShapeOfType[]
56
+ optional: boolean
57
+ description?: string
58
+ errorMessage?: string
59
+ }[]
60
+ requestHeaders: {
61
+ identifier: string
62
+ signature: string | ShapeOfType[]
63
+ optional: boolean
64
+ description?: string
65
+ errorMessage?: string
66
+ }[]
67
+ rawBody?: {
68
+ signature: string | ShapeOfType[]
69
+ optional: boolean
70
+ description?: string
71
+ errorMessage?: string
72
+ }
73
+ objectBody: {
74
+ identifier: string
75
+ signature: string | ShapeOfType[]
76
+ optional: boolean
77
+ description?: string
78
+ errorMessage?: string
79
+ }[]
80
+ responses: {
81
+ status: number
82
+ signature: string | ShapeOfType[]
83
+ description?: string
84
+ errorMessage?: string
85
+ }[]
86
+ }
@@ -0,0 +1,123 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import KoaRouter from '@koa/router'
3
+ import Koa from 'koa'
4
+
5
+ import { OpenApiManager } from '../openapi/manager/OpenApiManager'
6
+ import { ExtractedRequestParams } from '../utils/TypeUtils'
7
+ import { responseValueToJson } from './responseValueToJson'
8
+
9
+ type Props = {
10
+ skipOpenApiAnalysis: boolean
11
+ }
12
+
13
+ export class Router<StateT = Koa.DefaultState, ContextT = Koa.DefaultContext> {
14
+ public koaRouter: KoaRouter = new KoaRouter()
15
+
16
+ public constructor(props: Props = { skipOpenApiAnalysis: false }) {
17
+ if (!props.skipOpenApiAnalysis) {
18
+ const openApiManager = OpenApiManager.getInstance()
19
+ openApiManager.registerRouters([this])
20
+ }
21
+ }
22
+
23
+ public use(...middleware: Array<KoaRouter.Middleware<StateT, ContextT>>) {
24
+ // @ts-ignore
25
+ this.koaRouter.use(...middleware)
26
+ return this
27
+ }
28
+
29
+ public with<ResponseTypeT extends Record<string, any>>(
30
+ middleware: (ctx: Koa.ParameterizedContext<ContextT>) => ResponseTypeT
31
+ ) {
32
+ type AugmentedData = ResponseTypeT extends Promise<any> ? Awaited<ResponseTypeT> : ResponseTypeT
33
+ this.koaRouter.use(async (ctx, next) => {
34
+ // @ts-ignore
35
+ const userData = await Promise.resolve(middleware(ctx))
36
+ Object.keys(userData).forEach((key) => {
37
+ ctx[key] = userData[key]
38
+ })
39
+ await next()
40
+ })
41
+ return this as Router<StateT, ContextT & AugmentedData>
42
+ }
43
+
44
+ public get<P extends string>(
45
+ path: P,
46
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
47
+ ) {
48
+ this.koaRouter.get(path, async (ctx) => {
49
+ // @ts-ignore
50
+ const responseValue = await callback(ctx, undefined)
51
+ ctx.body = responseValueToJson(responseValue)
52
+ })
53
+ return this
54
+ }
55
+
56
+ public post<P extends string>(
57
+ path: P,
58
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
59
+ ) {
60
+ this.koaRouter.post(path, async (ctx) => {
61
+ // @ts-ignore
62
+ const responseValue = await callback(ctx, undefined)
63
+ ctx.body = responseValueToJson(responseValue)
64
+ })
65
+ return this
66
+ }
67
+
68
+ public put<P extends string>(
69
+ path: P,
70
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
71
+ ) {
72
+ this.koaRouter.put(path, async (ctx) => {
73
+ // @ts-ignore
74
+ const responseValue = await callback(ctx, undefined)
75
+ ctx.body = responseValueToJson(responseValue)
76
+ })
77
+ return this
78
+ }
79
+
80
+ public delete<P extends string>(
81
+ path: P,
82
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
83
+ ) {
84
+ this.koaRouter.delete(path, async (ctx) => {
85
+ // @ts-ignore
86
+ const responseValue = await callback(ctx, undefined)
87
+ ctx.body = responseValueToJson(responseValue)
88
+ })
89
+ return this
90
+ }
91
+
92
+ public del<P extends string>(
93
+ path: P,
94
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
95
+ ) {
96
+ this.koaRouter.del(path, async (ctx) => {
97
+ // @ts-ignore
98
+ const responseValue = await callback(ctx, undefined)
99
+ ctx.body = responseValueToJson(responseValue)
100
+ })
101
+ return this
102
+ }
103
+
104
+ public patch<P extends string>(
105
+ path: P,
106
+ callback: KoaRouter.Middleware<StateT, ContextT & ExtractedRequestParams<P>>
107
+ ) {
108
+ this.koaRouter.patch(path, async (ctx) => {
109
+ // @ts-ignore
110
+ const responseValue = await callback(ctx, undefined)
111
+ ctx.body = responseValueToJson(responseValue)
112
+ })
113
+ return this
114
+ }
115
+
116
+ public routes() {
117
+ return this.koaRouter.routes()
118
+ }
119
+
120
+ public allowedMethods() {
121
+ return this.koaRouter.allowedMethods()
122
+ }
123
+ }
@@ -0,0 +1,6 @@
1
+ export const responseValueToJson = (value: any) => {
2
+ if (typeof value === 'string') {
3
+ return value
4
+ }
5
+ return JSON.stringify(value, (_, value) => (typeof value === 'bigint' ? value.toString() : value))
6
+ }
@@ -0,0 +1,3 @@
1
+ console.info = () => {
2
+ /* Empty */
3
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ BadRequestError,
3
+ EmailValidator,
4
+ NonEmptyStringValidator,
5
+ UnauthorizedError,
6
+ useQueryParams,
7
+ } from '..'
8
+ import { Router as RenamedRouter } from '../router/Router'
9
+
10
+ const myRouter = new RenamedRouter()
11
+ .use((_, next) => next())
12
+ .with(() => {
13
+ const user = { id: '123' }
14
+ return {
15
+ user,
16
+ }
17
+ })
18
+
19
+ myRouter.get('/test/hello', () => {
20
+ return {
21
+ greeting: 'hello world',
22
+ }
23
+ })
24
+
25
+ myRouter.get('/test/query', (ctx) => {
26
+ const { email, string } = useQueryParams(ctx, {
27
+ email: EmailValidator,
28
+ string: NonEmptyStringValidator,
29
+ })
30
+ return {
31
+ email,
32
+ string,
33
+ }
34
+ })
35
+
36
+ myRouter.post('/test/post', () => {
37
+ return 'post response'
38
+ })
39
+
40
+ myRouter.del('/test/del', () => {
41
+ // Empty
42
+ })
43
+
44
+ myRouter.delete('/test/delete', () => {
45
+ // Empty
46
+ })
47
+
48
+ myRouter.patch('/test/patch', () => {
49
+ return 'patch response'
50
+ })
51
+
52
+ myRouter.get('/test/error/generic', () => {
53
+ throw new Error('Generic error')
54
+ })
55
+
56
+ myRouter.get('/test/error/unauthorized', () => {
57
+ throw new UnauthorizedError('Test error')
58
+ })
59
+
60
+ myRouter.get('/test/error/badrequest', () => {
61
+ throw new BadRequestError('Test error')
62
+ })
63
+
64
+ myRouter.get('/test/get/bigint', () => {
65
+ return {
66
+ foo: BigInt(100),
67
+ }
68
+ })
69
+
70
+ myRouter.get('/test/get/middleware-data', (ctx) => {
71
+ return {
72
+ user: ctx.user,
73
+ }
74
+ })
75
+
76
+ export const TestAppRouter = myRouter
@@ -0,0 +1,130 @@
1
+ import request from 'supertest'
2
+ import { vi } from 'vitest'
3
+
4
+ import { generateOpenApiSpec } from '../openapi/generatorModule/generatorModule'
5
+ import { app } from './app'
6
+
7
+ describe('TestAppRouter', () => {
8
+ it('handles get request correctly', async () => {
9
+ const response = await request(app.callback()).get('/test/hello')
10
+ expect(response.status).toBe(200)
11
+ expect(response.text).toBe(JSON.stringify({ greeting: 'hello world' }))
12
+ })
13
+
14
+ it('handles query params', async () => {
15
+ const response = await request(app.callback()).get('/test/query?email=test@test.com&string=someval')
16
+ expect(response.status).toBe(200)
17
+ expect(response.text).toBe(JSON.stringify({ email: 'test@test.com', string: 'someval' }))
18
+ })
19
+
20
+ it('handles incorrect method', async () => {
21
+ const response = await request(app.callback()).post('/test/hello')
22
+ expect(response.status).toBe(405)
23
+ expect(response.text).toBe('Method Not Allowed')
24
+ })
25
+
26
+ it('handles post request correctly', async () => {
27
+ const response = await request(app.callback()).post('/test/post')
28
+ expect(response.status).toBe(200)
29
+ expect(response.text).toBe('post response')
30
+ })
31
+
32
+ it('handles del request correctly', async () => {
33
+ const response = await request(app.callback()).del('/test/del')
34
+ expect(response.status).toBe(204)
35
+ expect(response.text).toBe('')
36
+ })
37
+
38
+ it('handles delete request correctly', async () => {
39
+ const response = await request(app.callback()).delete('/test/delete')
40
+ expect(response.status).toBe(204)
41
+ expect(response.text).toBe('')
42
+ })
43
+
44
+ it('handles patch request correctly', async () => {
45
+ const response = await request(app.callback()).patch('/test/patch')
46
+ expect(response.status).toBe(200)
47
+ expect(response.text).toBe('patch response')
48
+ })
49
+
50
+ it('rethrows a generic error', async () => {
51
+ const consoleError = console.error
52
+ console.error = vi.fn()
53
+ const response = await request(app.callback()).get('/test/error/generic')
54
+ expect(response.status).toBe(500)
55
+ expect(response.text).toBe('Internal Server Error')
56
+ console.error = consoleError
57
+ })
58
+
59
+ it('handles an unauthorized error correctly', async () => {
60
+ const response = await request(app.callback()).get('/test/error/unauthorized')
61
+ expect(response.status).toBe(401)
62
+ expect(response.text).toBe(JSON.stringify({ status: 401, reason: 'Unauthorized', message: 'Test error' }))
63
+ })
64
+
65
+ it('handles a bad request error correctly', async () => {
66
+ const response = await request(app.callback()).get('/test/error/badrequest')
67
+ expect(response.status).toBe(400)
68
+ expect(response.text).toBe(JSON.stringify({ status: 400, reason: 'Bad Request', message: 'Test error' }))
69
+ })
70
+
71
+ it('handles bigint return value correctly', async () => {
72
+ const response = await request(app.callback()).get('/test/get/bigint')
73
+ expect(response.status).toBe(200)
74
+ expect(response.text).toBe(JSON.stringify({ foo: '100' }))
75
+ })
76
+
77
+ it('includes middleware data from context', async () => {
78
+ const response = await request(app.callback()).get('/test/get/middleware-data')
79
+ expect(response.status).toBe(200)
80
+ expect(response.text).toBe(JSON.stringify({ user: { id: '123' } }))
81
+ })
82
+ })
83
+
84
+ describe('OpenApiRouter', () => {
85
+ it('sends the openapi spec', async () => {
86
+ const response = await request(app.callback()).get('/api-json')
87
+ expect(response.status).toBe(200)
88
+ const responseJson = JSON.parse(response.text) as ReturnType<typeof generateOpenApiSpec>
89
+ expect(responseJson.openapi).toBe('3.1.0')
90
+ expect(responseJson.info).toEqual({
91
+ title: 'Test title',
92
+ version: '1.0.0',
93
+ description: 'Test description',
94
+ termsOfService: 'http://example.com',
95
+ contact: {
96
+ name: 'QA Engineer',
97
+ url: 'http://best-qa.com',
98
+ email: 'admin@best-qa.com',
99
+ },
100
+ license: {
101
+ name: 'MIT',
102
+ url: 'http://best-qa.com/license',
103
+ },
104
+ })
105
+ expect(responseJson.paths['/test/hello']).toEqual({
106
+ get: {
107
+ description: '',
108
+ parameters: [],
109
+ responses: {
110
+ '200': {
111
+ description: '',
112
+ content: {
113
+ 'application/json': {
114
+ schema: {
115
+ oneOf: [
116
+ {
117
+ type: 'object',
118
+ properties: { greeting: { type: 'string' } },
119
+ required: ['greeting'],
120
+ },
121
+ ],
122
+ },
123
+ },
124
+ },
125
+ },
126
+ },
127
+ },
128
+ })
129
+ })
130
+ })
@@ -0,0 +1,43 @@
1
+ import Koa from 'koa'
2
+ import bodyParser from 'koa-bodyparser'
3
+ import { resolve } from 'path'
4
+
5
+ import { HttpErrorHandler, initOpenApiEngine, useApiHeader as useRenamedApiHeader } from '..'
6
+ import { TestAppRouter } from './TestAppRouter'
7
+
8
+ export const app = new Koa()
9
+
10
+ useRenamedApiHeader({
11
+ title: 'Test title',
12
+ version: '1.0.0',
13
+ description: 'Test description',
14
+ termsOfService: 'http://example.com',
15
+ contact: {
16
+ name: 'QA Engineer',
17
+ url: 'http://best-qa.com',
18
+ email: 'admin@best-qa.com',
19
+ },
20
+ license: {
21
+ name: 'MIT',
22
+ url: 'http://best-qa.com/license',
23
+ },
24
+ })
25
+
26
+ app
27
+ .use(HttpErrorHandler)
28
+ .use(
29
+ bodyParser({
30
+ enableTypes: ['text', 'json', 'form'],
31
+ })
32
+ )
33
+ .use(TestAppRouter.routes())
34
+ .use(TestAppRouter.allowedMethods())
35
+ .use(
36
+ initOpenApiEngine({
37
+ tsconfigPath: './tsconfig.json',
38
+ sourceFileDiscovery: {
39
+ rootPath: resolve(__dirname, '.'),
40
+ },
41
+ sourceFilePaths: ['./src/test/TestAppRouter.ts'],
42
+ })
43
+ )
@@ -0,0 +1,51 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ export type RemoveFirstFromTuple<T extends any[]> = T['length'] extends 0
3
+ ? undefined
4
+ : ((...b: T) => void) extends (a: infer Q, ...b: infer I) => void
5
+ ? I
6
+ : []
7
+
8
+ export type SplitStringBy<S extends string, D extends string> = string extends S
9
+ ? string[]
10
+ : S extends ''
11
+ ? []
12
+ : S extends `${infer T}${D}${infer U}`
13
+ ? [T, ...SplitStringBy<U, D>]
14
+ : [S]
15
+
16
+ type PickParams<S extends string[], P extends string> = S['length'] extends 0
17
+ ? []
18
+ : S[0] extends `${P}${string}`
19
+ ? // @ts-ignore
20
+ [S[0], ...PickParams<RemoveFirstFromTuple<S>, P>]
21
+ : // @ts-ignore
22
+ PickParams<RemoveFirstFromTuple<S>, P>
23
+
24
+ export type Substring<S extends string[]> = S['length'] extends 0
25
+ ? []
26
+ : // @ts-ignore
27
+ [SplitStringBy<S[0], ':'>[1], ...Substring<RemoveFirstFromTuple<S>>]
28
+
29
+ export type ExtractedRequestParams<S extends string> = {
30
+ parsedPathParams: PickParams<SplitStringBy<S, '/'>, ':'>
31
+ }
32
+
33
+ export type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
34
+ ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
35
+ : S extends `${infer P1}-${infer P2}${infer P3}`
36
+ ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
37
+ : S
38
+
39
+ export type KeysToCamelCase<T> = {
40
+ [K in keyof T as CamelCase<string & K>]: T[K] extends Record<any, any> ? KeysToCamelCase<T[K]> : T[K]
41
+ }
42
+
43
+ type RemoveLeadingColon<S extends string> = S['length'] extends 0 ? never : SplitStringBy<S, ':'>[1]
44
+ type RemoveTrailingQuestion<S extends string> = S['length'] extends 0 ? never : SplitStringBy<S, '?'>[0]
45
+ export type CleanUpPathParam<S> = S extends string
46
+ ? //@ts-ignore
47
+ RemoveLeadingColon<RemoveTrailingQuestion<S>> extends string
48
+ ? //@ts-ignore
49
+ RemoveLeadingColon<RemoveTrailingQuestion<S>>
50
+ : ''
51
+ : never