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
package/.eslintrc.js ADDED
@@ -0,0 +1,26 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ parserOptions: {
5
+ project: 'tsconfig.json',
6
+ tsconfigRootDir: __dirname,
7
+ sourceType: 'module',
8
+ },
9
+ plugins: ['simple-import-sort', 'unused-imports', '@typescript-eslint/eslint-plugin'],
10
+ extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended'],
11
+ root: true,
12
+ env: {
13
+ node: true,
14
+ jest: true,
15
+ },
16
+ ignorePatterns: ['**/dist/**', '*.md'],
17
+ rules: {
18
+ '@typescript-eslint/interface-name-prefix': 'off',
19
+ '@typescript-eslint/explicit-function-return-type': 'off',
20
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
21
+ '@typescript-eslint/no-explicit-any': 'off',
22
+ '@typescript-eslint/no-namespace': 'off',
23
+ 'simple-import-sort/imports': 'error',
24
+ 'simple-import-sort/exports': 'error',
25
+ },
26
+ }
package/.prettierrc.js ADDED
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ semi: false,
3
+ singleQuote: true,
4
+ printWidth: 110,
5
+ useTabs: true,
6
+ }
7
+
package/README.md ADDED
@@ -0,0 +1,383 @@
1
+ # Moonflower
2
+
3
+ > This section is work-in-progress.
4
+
5
+ Moonflower is a TypeScript-first [Koa Router](https://www.npmjs.com/package/koa-router) extension that allows for automatic [OpenAPI](https://www.openapis.org/what-is-openapi) spec generation directly from backend code, without any extra work. Combined with an ORM like [Prisma.js](https://www.npmjs.com/package/prisma) and an API client generator on frontend, it allows for creation of end-to-end type safe REST API.
6
+
7
+ Moonflower provides type safe 'hooks' that handle runtime validation and return clean types with minimal boilerplate.
8
+
9
+ ## Feature Overview
10
+
11
+ - React hooks inspired Node.js backend REST API
12
+ - Fully type safe definitions for path, query and header params, request body and response
13
+ - Minimal boilerplate code
14
+ - Out-of-the-box OpenAPI 3.1.0 spec generation
15
+
16
+ ## Usage example
17
+
18
+ Every variable and parameter in this example is fully typed.
19
+
20
+ ```ts
21
+ const router = new Router()
22
+
23
+ router.post('/auth', (ctx) => {
24
+ useApiEndpoint({
25
+ name: 'createAccount',
26
+ summary: 'Registration endpoint',
27
+ description: 'Creates a new user account with provided credentials',
28
+ })
29
+
30
+ const body = useRequestBody(ctx, {
31
+ email: EmailValidator,
32
+ username: NonEmptyStringValidator,
33
+ password: NonEmptyStringValidator,
34
+ })
35
+
36
+ const user = UserService.register(body.email, body.username, body.password)
37
+ const token = TokenService.generateJwtToken(user)
38
+
39
+ return {
40
+ accessToken: token,
41
+ }
42
+ })
43
+ ```
44
+
45
+ Outputs the following spec (parts omitted for brevity):
46
+
47
+ ```json
48
+ "/auth": {
49
+ "post": {
50
+ "operationId": "createAccount",
51
+ "summary": "Registration endpoint",
52
+ "description": "Creates a new user account with provided credentials",
53
+ "parameters": [],
54
+ "requestBody": {
55
+ "content": {
56
+ "application/json": {
57
+ "schema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "email": {
61
+ "type": "string"
62
+ },
63
+ "username": {
64
+ "type": "string"
65
+ },
66
+ "password": {
67
+ "type": "string"
68
+ }
69
+ },
70
+ "required": [
71
+ "email",
72
+ "username",
73
+ "password"
74
+ ]
75
+ }
76
+ },
77
+ }
78
+ },
79
+ "responses": {
80
+ "200": {
81
+ "content": {
82
+ "application/json": {
83
+ "schema": {
84
+ "oneOf": [{
85
+ "type": "object",
86
+ "properties": {
87
+ "accessToken": {
88
+ "type": "string"
89
+ }
90
+ },
91
+ "required": [
92
+ "accessToken"
93
+ ]
94
+ }]
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Getting Started
105
+
106
+ > This package requires a TypeScript based project. OpenAPI spec is created through parsing the TypeScript AST and can't be done in plain JS projects.
107
+
108
+ Install dependencies:
109
+
110
+ ```bash
111
+ yarn add moonflower koa @koa/router koa-bodyparser
112
+ ```
113
+
114
+ Create a Koa instance with required middleware
115
+
116
+ ```ts
117
+ import Koa from 'koa'
118
+ import bodyParser from 'koa-bodyparser'
119
+ import { HttpErrorHandler, initOpenApiEngine, Router } from 'moonflower'
120
+
121
+ const app = new Koa()
122
+ const myRouter = new Router()
123
+
124
+ myRouter.get('/api/hello', (ctx) => {
125
+ /* Unlike in Koa, all endpoints must do a plain return.
126
+ * However, Koa context is still available for edge cases.
127
+ */
128
+ return {
129
+ greeting: 'hello world',
130
+ }
131
+ })
132
+
133
+ app
134
+ // Returns standard HTTP errors for common requests
135
+ .use(HttpErrorHandler)
136
+ .use(
137
+ // Required for requests with body
138
+ bodyParser({
139
+ enableTypes: ['text', 'json', 'form'],
140
+ })
141
+ )
142
+ // Register the router in Koa
143
+ .use(myRouter.routes())
144
+ .use(myRouter.allowedMethods())
145
+ .use(
146
+ // Enables collection of OpenAPI spec from code
147
+ initOpenApiEngine({
148
+ tsconfigPath: './tsconfig.json',
149
+ })
150
+ )
151
+ ```
152
+
153
+ ### Planned features
154
+
155
+ - Support for list query params
156
+ - Support for thrown errors in OpenApi engine
157
+ - Support for binary data responses
158
+ - Support for multipart form data
159
+
160
+ ### Known issues
161
+
162
+ - Exported models are referenced by name only, leading to potential name collisions
163
+
164
+ # Hooks
165
+ > This section is work-in-progress.
166
+
167
+ ```ts
168
+ // Dangling hooks
169
+ useApiHeader({...})
170
+
171
+ // Endpoint hooks
172
+ const params = usePathParams(ctx, {...})
173
+ const query = useQueryParams(ctx, {...})
174
+ const body = useRequestBody(ctx, {...})
175
+ const rawBody = useRequestRawBody(ctx, {...})
176
+ ```
177
+
178
+ # Validators
179
+
180
+ Validators are run for every parameter received from the client. Failed validation or an error thrown during validation will return 400 Bad Request to the user.
181
+
182
+ **Example:**
183
+
184
+ ```ts
185
+ const query = useQueryParams(ctx, {
186
+ name: RequiredParam(StringValidator),
187
+ fooBar: OptionalParam<{ foo: string; bar: string }>({
188
+ parse: (v) => JSON.parse(v),
189
+ validate: (v) => !!v.foo && !!v.bar
190
+ }),
191
+ })
192
+
193
+ query.name // type is 'string'
194
+ query.fooBar // type is '{ foo: string; bar: string } | undefined'
195
+ ```
196
+
197
+ ## Built-in validators
198
+
199
+ The most common validators are available out-of-the-box.
200
+
201
+ ```ts
202
+ const query = useQueryParams(ctx, {
203
+ myNumber: NumberValidator,
204
+ myString: StringValidator,
205
+ myBoolean: BooleanValidator,
206
+ })
207
+
208
+ query.myNumber // type is 'number'
209
+ query.myString // type is 'string'
210
+ query.myBoolean // type is 'boolean'
211
+ ```
212
+
213
+ ## Required and optional parameters
214
+ ```ts
215
+ const query = useQueryParams(ctx, {
216
+ predefinedBool: BooleanValidator,
217
+ optionalBool: OptionalParam(BooleanValidator),
218
+ customBool: RequiredParam({
219
+ parse: (v) => v === '1',
220
+ }),
221
+ customOptionalBool: OptionalParam({
222
+ parse: (v) => v === '1',
223
+ }),
224
+ })
225
+
226
+ query.predefinedBool // type is 'boolean'
227
+ query.optionalBool // type is 'boolean | undefined'
228
+ query.customBool // type is 'boolean'
229
+ query.customOptionalBool // type is 'boolean | undefined'
230
+ ```
231
+
232
+ ## Custom validators
233
+
234
+ Custom validators are simple objects that can be defined either inline, or elsewhere for reusability.
235
+
236
+ ### Inline
237
+
238
+ ```ts
239
+ const query = useQueryParams(ctx, {
240
+ numberInRange: RequiredParam({
241
+ parse: (v) => Number(v),
242
+ validate: (v) => !isNaN(v) && v >= 0 && v <= 100,
243
+ }),
244
+ optionalBoolean: OptionalParam({
245
+ parse: (v) => v === '1',
246
+ }),
247
+ })
248
+
249
+ query.numberInRange // type is 'number'
250
+ query.optionalBoolean // type is 'boolean | undefined'
251
+ ```
252
+
253
+ ### Using Zod
254
+
255
+ Zod schemas are compatible with validators, allowing you to directly use them to define runtime validation.
256
+
257
+ ```ts
258
+ const body = useRequestBody(ctx, {
259
+ email: RequiredParam(z.string().email()),
260
+ password: RequiredParam(z.string()),
261
+ })
262
+
263
+ const { email, password } = body
264
+ ```
265
+
266
+ ## Anatomy of a validator
267
+
268
+ A validator contains a number of functions that are useful to check and transform incoming data.
269
+
270
+ ### Parse
271
+
272
+ > `parse: (v: string) => T extends any`
273
+
274
+ The only required function of a validator. It takes the raw input param and transforms it into correct data type. The return type of `parse` will match the one specified in the `RequiredParam` or `OptionalParam` generics, or will be used to infer the type.
275
+
276
+ Make sure that it returns a correctly typed object.
277
+
278
+ ### Validate
279
+
280
+ > `validate: (v: T) => boolean`
281
+
282
+ This function is called on incoming data after it is parsed.
283
+
284
+ Returning `false` or any falsy value will cause the validation to fail, and `400 Bad Request` to be sent back to the client.
285
+
286
+ ### Prevalidate
287
+
288
+ > `prevalidate: (v: string) => boolean`
289
+
290
+ This function is called on incoming data before it is parsed. Useful in cases where rehydration function is slow (i.e. includes a DB read), and some premature validation is desired. In most cases, however, `validate` is preferred.
291
+
292
+ The behaviour is identical to `validate`, aside from the call order.
293
+
294
+ ### Type inference
295
+
296
+ In many cases, type of the parameter can be inferred from the return value of `parse` function. For more complex objects, it is possible to specify the type with `as ...` clause:
297
+
298
+
299
+ ```ts
300
+ useQueryParams(ctx, {
301
+ fooBar: RequiredParam({
302
+ prevalidate: (v) => v.length > 5,
303
+ parse: (v) => JSON.parse(v) as { foo: string; bar: string },
304
+ validate: (v) => !!v.foo && !!v.bar
305
+ }),
306
+ })
307
+ ```
308
+
309
+ Alternatively, the expected type can be mentioned in `RequiredParam`, `OptionalParam` or `PathParam` generics:
310
+
311
+ ```ts
312
+ useQueryParams(ctx, {
313
+ fooBar: RequiredParam<{ foo: string; bar: string }>({
314
+ prevalidate: (v) => v.length > 5,
315
+ parse: (v) => JSON.parse(v),
316
+ validate: (v) => !!v.foo && !!v.bar
317
+ }),
318
+ })
319
+ ```
320
+
321
+ ### Avoid
322
+
323
+ While the following is valid code, the type of the parameter can't be inferred as TS will not parse this as Validator type. The type of `validate` will be `(v: any) => boolean`, which is unsafe.
324
+
325
+ ```ts
326
+ useQueryParams(ctx, {
327
+ myParam: {
328
+ parse: (v) => Number(v),
329
+ validate: (v) => v > 0,
330
+ optional: false,
331
+ },
332
+ })
333
+ ```
334
+
335
+ # Path params
336
+
337
+ Path params have extra binding to the endpoint path. Only the properties mentioned in the path can be used.
338
+
339
+ ```ts
340
+ router.get('/user/:userId', (ctx) => {
341
+ const params = usePathParams(ctx, {
342
+ userId: StringValidator, // valid
343
+ username: StringValidator, // 'username' is not a path param
344
+ })
345
+
346
+ params.userId // type is 'string'
347
+ }
348
+ ```
349
+
350
+ ## Optional path params
351
+
352
+ Following standard Koa way of defining an optional param, a param marked by a question mark is considered optional.
353
+
354
+ ```ts
355
+ router.get('/user/:userId?', (ctx) => {
356
+ const params = usePathParams(ctx, {
357
+ userId: StringValidator,
358
+ })
359
+
360
+ params.userId // type is 'string | undefined'
361
+ }
362
+ ```
363
+
364
+ ## Custom path parameters
365
+
366
+ As parameter optionaliy is defined in a path, `RequiredParam` and `OptionalParam` will be ignored. To reduce confusion, `PathParam` is available.
367
+
368
+ ```ts
369
+ router.get('/user/:numberId', (ctx) => {
370
+ usePathParams(ctx, {
371
+ numberId: PathParam({
372
+ parse: (v) => Number(v),
373
+ validate: (v) => !isNaN(v) && v >= 0 && v <= 100,
374
+ })
375
+ })
376
+ }
377
+ ```
378
+
379
+ ## Escape hatch
380
+
381
+ All Koa and Koa Router APIs are still available in case some functionality is unavailable through Moonflower. Endpoints provide a `ctx` prop, and the router expose `koaRouter` which is raw underlying router implementation.
382
+
383
+ However, avoiding Moonflower's API will degrade the quality of the generated spec.
package/cli/cli.ts ADDED
@@ -0,0 +1,59 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import yargs, { ArgumentsCamelCase } from 'yargs'
4
+ import { hideBin } from 'yargs/helpers'
5
+
6
+ import { prepareOpenApiSpec } from '../src/openapi/analyzerModule/analyzerModule'
7
+ import { generateOpenApiSpec } from '../src/openapi/generatorModule'
8
+ import { OpenApiManager } from '../src/openapi/manager/OpenApiManager'
9
+ import { printAnalysisStats } from './prettyprint'
10
+
11
+ const originalConsole = console.info
12
+ console.info = (message, ...args) => {
13
+ originalConsole(`${message}`, ...args)
14
+ }
15
+
16
+ yargs(hideBin(process.argv))
17
+ .showHelpOnFail(true)
18
+ .command({
19
+ command: 'openapi <targetPath>',
20
+ describe: 'Generates the current openapi spec into a specified file path',
21
+ builder: {
22
+ targetPath: {
23
+ describe: 'Target path',
24
+ demandOption: true,
25
+ type: 'string',
26
+ coerce: (f) => path.resolve(f),
27
+ },
28
+
29
+ tsConfigPath: {
30
+ describe: 'tsconfig',
31
+ type: 'string',
32
+ coerce: (f) => path.resolve(f),
33
+ },
34
+ },
35
+
36
+ handler(argv: ArgumentsCamelCase<{ targetPath: string; tsConfigPath?: string }>) {
37
+ if (fs.existsSync(argv.targetPath)) {
38
+ console.error(`[Error] File already exists at ${argv.targetPath}`)
39
+ return
40
+ }
41
+
42
+ if (argv.tsConfigPath && !fs.existsSync(argv.tsConfigPath)) {
43
+ console.error(`[Error] Unable to find a tsconfig file at ${argv.tsConfigPath}`)
44
+ return
45
+ }
46
+
47
+ prepareOpenApiSpec({
48
+ tsconfigPath: argv.tsConfigPath ?? 'tsconfig.json',
49
+ })
50
+
51
+ const manager = OpenApiManager.getInstance()
52
+ printAnalysisStats(manager.getStats())
53
+
54
+ const spec = generateOpenApiSpec(manager)
55
+ fs.writeFileSync(argv.targetPath, JSON.stringify(spec))
56
+ },
57
+ })
58
+ .demandCommand()
59
+ .parse()
package/cli/entry.cjs ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/cli/cli.js')
@@ -0,0 +1,16 @@
1
+ import { ApiAnalysisStats } from '../src/openapi/manager/OpenApiManager'
2
+
3
+ export const printAnalysisStats = (stats: ApiAnalysisStats) => {
4
+ stats.explicitRouterFiles.forEach((file) => printRouterFile(file))
5
+ stats.discoveredRouterFiles.forEach((file) => printRouterFile(file))
6
+ }
7
+
8
+ export const printRouterFile = (file: ApiAnalysisStats['discoveredRouterFiles'][number]) => {
9
+ console.info(`${file.path}`)
10
+ file.routers.forEach((r) => {
11
+ console.info(`└ ${r.name}`)
12
+ r.endpoints.forEach((e) => {
13
+ console.info(` └ ${e}`)
14
+ })
15
+ })
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../cli/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const fs = __importStar(require("fs"));
30
+ const path = __importStar(require("path"));
31
+ const yargs_1 = __importDefault(require("yargs"));
32
+ const helpers_1 = require("yargs/helpers");
33
+ const analyzerModule_1 = require("../src/openapi/analyzerModule/analyzerModule");
34
+ const generatorModule_1 = require("../src/openapi/generatorModule");
35
+ const OpenApiManager_1 = require("../src/openapi/manager/OpenApiManager");
36
+ const prettyprint_1 = require("./prettyprint");
37
+ const originalConsole = console.info;
38
+ console.info = (message, ...args) => {
39
+ originalConsole(`${message}`, ...args);
40
+ };
41
+ (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
42
+ .showHelpOnFail(true)
43
+ .command({
44
+ command: 'openapi <targetPath>',
45
+ describe: 'Generates the current openapi spec into a specified file path',
46
+ builder: {
47
+ targetPath: {
48
+ describe: 'Target path',
49
+ demandOption: true,
50
+ type: 'string',
51
+ coerce: (f) => path.resolve(f),
52
+ },
53
+ tsConfigPath: {
54
+ describe: 'tsconfig',
55
+ type: 'string',
56
+ coerce: (f) => path.resolve(f),
57
+ },
58
+ },
59
+ handler(argv) {
60
+ var _a;
61
+ if (fs.existsSync(argv.targetPath)) {
62
+ console.error(`[Error] File already exists at ${argv.targetPath}`);
63
+ return;
64
+ }
65
+ if (argv.tsConfigPath && !fs.existsSync(argv.tsConfigPath)) {
66
+ console.error(`[Error] Unable to find a tsconfig file at ${argv.tsConfigPath}`);
67
+ return;
68
+ }
69
+ (0, analyzerModule_1.prepareOpenApiSpec)({
70
+ tsconfigPath: (_a = argv.tsConfigPath) !== null && _a !== void 0 ? _a : 'tsconfig.json',
71
+ });
72
+ const manager = OpenApiManager_1.OpenApiManager.getInstance();
73
+ (0, prettyprint_1.printAnalysisStats)(manager.getStats());
74
+ const spec = (0, generatorModule_1.generateOpenApiSpec)(manager);
75
+ fs.writeFileSync(argv.targetPath, JSON.stringify(spec));
76
+ },
77
+ })
78
+ .demandCommand()
79
+ .parse();
@@ -0,0 +1,4 @@
1
+ import { ApiAnalysisStats } from '../src/openapi/manager/OpenApiManager';
2
+ export declare const printAnalysisStats: (stats: ApiAnalysisStats) => void;
3
+ export declare const printRouterFile: (file: ApiAnalysisStats['discoveredRouterFiles'][number]) => void;
4
+ //# sourceMappingURL=prettyprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prettyprint.d.ts","sourceRoot":"","sources":["../../cli/prettyprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AAExE,eAAO,MAAM,kBAAkB,UAAW,gBAAgB,SAGzD,CAAA;AAED,eAAO,MAAM,eAAe,SAAU,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAAC,SAQtF,CAAA"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printRouterFile = exports.printAnalysisStats = void 0;
4
+ const printAnalysisStats = (stats) => {
5
+ stats.explicitRouterFiles.forEach((file) => (0, exports.printRouterFile)(file));
6
+ stats.discoveredRouterFiles.forEach((file) => (0, exports.printRouterFile)(file));
7
+ };
8
+ exports.printAnalysisStats = printAnalysisStats;
9
+ const printRouterFile = (file) => {
10
+ console.info(`${file.path}`);
11
+ file.routers.forEach((r) => {
12
+ console.info(`└ ${r.name}`);
13
+ r.endpoints.forEach((e) => {
14
+ console.info(` └ ${e}`);
15
+ });
16
+ });
17
+ };
18
+ exports.printRouterFile = printRouterFile;
@@ -0,0 +1,13 @@
1
+ import { StatusCodes } from 'http-status-codes';
2
+ export interface HttpError {
3
+ status: StatusCodes;
4
+ reason: string;
5
+ message: string;
6
+ }
7
+ export declare class BaseHttpError extends Error implements HttpError {
8
+ status: StatusCodes;
9
+ message: string;
10
+ reason: string;
11
+ constructor(status: StatusCodes, message: string);
12
+ }
13
+ //# sourceMappingURL=BaseHttpError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseHttpError.d.ts","sourceRoot":"","sources":["../../../src/errors/BaseHttpError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEhE,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,aAAc,SAAQ,KAAM,YAAW,SAAS;IAGzC,MAAM,EAAE,WAAW;IAAS,OAAO,EAAE,MAAM;IAFvD,MAAM,EAAE,MAAM,CAAA;gBAEF,MAAM,EAAE,WAAW,EAAS,OAAO,EAAE,MAAM;CAI9D"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseHttpError = void 0;
4
+ const http_status_codes_1 = require("http-status-codes");
5
+ class BaseHttpError extends Error {
6
+ constructor(status, message) {
7
+ super(message);
8
+ this.status = status;
9
+ this.message = message;
10
+ this.reason = (0, http_status_codes_1.getReasonPhrase)(status);
11
+ }
12
+ }
13
+ exports.BaseHttpError = BaseHttpError;
@@ -0,0 +1,3 @@
1
+ import Koa from 'koa';
2
+ export declare const HttpErrorHandler: (ctx: Koa.ParameterizedContext, next: Koa.Next) => Promise<void>;
3
+ //# sourceMappingURL=HttpErrorHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpErrorHandler.d.ts","sourceRoot":"","sources":["../../../src/errors/HttpErrorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAA;AAIrB,eAAO,MAAM,gBAAgB,QAAe,IAAI,oBAAoB,QAAQ,IAAI,IAAI,kBAenF,CAAA"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpErrorHandler = void 0;
4
+ const BaseHttpError_1 = require("./BaseHttpError");
5
+ const HttpErrorHandler = async (ctx, next) => {
6
+ try {
7
+ await next();
8
+ }
9
+ catch (err) {
10
+ if (err instanceof BaseHttpError_1.BaseHttpError) {
11
+ ctx.status = err.status;
12
+ ctx.body = {
13
+ status: err.status,
14
+ reason: err.reason,
15
+ message: err.message,
16
+ };
17
+ }
18
+ else {
19
+ throw err;
20
+ }
21
+ }
22
+ };
23
+ exports.HttpErrorHandler = HttpErrorHandler;