@mstuercke/api-utils 4.3.1 → 5.0.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 (226) hide show
  1. package/package.json +13 -38
  2. package/src/rest/client/fetchRestApi.ts +47 -0
  3. package/src/rest/client/index.ts +1 -0
  4. package/src/rest/server/RestApi.ts +144 -0
  5. package/src/rest/server/buildRestApiRouteHandler.ts +26 -0
  6. package/{dist/rest/server/index.js → src/rest/server/index.ts} +0 -1
  7. package/src/rest/types/RestApiRequest.ts +14 -0
  8. package/src/rest/types/RestApiRequestHandler.ts +21 -0
  9. package/src/rest/types/RestApiRequestHandlerWithAuthentication.ts +12 -0
  10. package/src/rest/types/RestApiResponse.ts +5 -0
  11. package/src/rest/types/RestApiRoute.ts +34 -0
  12. package/src/rest/types/RestApiRouteHandler.ts +22 -0
  13. package/src/rest/types/RestApiRouteWithAuthentication.ts +12 -0
  14. package/src/rest/types/common/BearerToken.ts +1 -0
  15. package/src/rest/types/common/RestPathParams.ts +1 -0
  16. package/src/rest/types/common/RestQueryParams.ts +1 -0
  17. package/src/rest/types/common/RestRequestBody.ts +1 -0
  18. package/src/rest/types/common/RestRequestHeaders.ts +1 -0
  19. package/src/rest/types/common/RestRequestHeadersWithAuthorization.ts +11 -0
  20. package/src/rest/types/common/RestRequireAuthentication.ts +1 -0
  21. package/src/rest/types/common/RestResponseBody.ts +1 -0
  22. package/{dist/rest/types/common/index.js → src/rest/types/common/index.ts} +0 -1
  23. package/{dist/rest/types/index.js → src/rest/types/index.ts} +0 -1
  24. package/src/rest/types/internal/InternalRestApiRequest.ts +19 -0
  25. package/src/rest/types/internal/index.ts +2 -0
  26. package/src/types/OmitNever.ts +1 -0
  27. package/src/ws/client/index.ts +1 -0
  28. package/src/ws/client/useWsApi.ts +88 -0
  29. package/src/ws/server/WsApi.ts +75 -0
  30. package/src/ws/server/buildWsApiRouteHandler.ts +15 -0
  31. package/src/ws/server/closeConnection.ts +16 -0
  32. package/src/ws/server/getApiGatewayManagementApiClient.ts +11 -0
  33. package/src/ws/server/getConnection.ts +12 -0
  34. package/{dist/ws/server/index.js → src/ws/server/index.ts} +0 -1
  35. package/src/ws/server/sendResponse.ts +17 -0
  36. package/src/ws/server/trpc/TrpcSubscriptionRequest.ts +8 -0
  37. package/src/ws/server/trpc/TrpcSubscriptionResponse.ts +8 -0
  38. package/src/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.ts +105 -0
  39. package/src/ws/types/WsApiConnection.ts +4 -0
  40. package/src/ws/types/WsApiRequestHandler.ts +15 -0
  41. package/src/ws/types/WsApiRoute.ts +17 -0
  42. package/src/ws/types/WsApiRouteHandler.ts +14 -0
  43. package/src/ws/types/common/WsApiAction.ts +1 -0
  44. package/src/ws/types/common/WsApiRequest.ts +3 -0
  45. package/src/ws/types/common/WsApiResponse.ts +1 -0
  46. package/src/ws/types/common/index.ts +3 -0
  47. package/{dist/ws/types/index.js → src/ws/types/index.ts} +0 -1
  48. package/dist/rest/client/fetchRestApi.d.ts +0 -4
  49. package/dist/rest/client/fetchRestApi.d.ts.map +0 -1
  50. package/dist/rest/client/fetchRestApi.js +0 -19
  51. package/dist/rest/client/fetchRestApi.js.map +0 -1
  52. package/dist/rest/client/index.d.ts +0 -2
  53. package/dist/rest/client/index.d.ts.map +0 -1
  54. package/dist/rest/client/index.js +0 -2
  55. package/dist/rest/client/index.js.map +0 -1
  56. package/dist/rest/server/RestApi.d.ts +0 -13
  57. package/dist/rest/server/RestApi.d.ts.map +0 -1
  58. package/dist/rest/server/RestApi.js +0 -101
  59. package/dist/rest/server/RestApi.js.map +0 -1
  60. package/dist/rest/server/buildRestApiRouteHandler.d.ts +0 -3
  61. package/dist/rest/server/buildRestApiRouteHandler.d.ts.map +0 -1
  62. package/dist/rest/server/buildRestApiRouteHandler.js +0 -5
  63. package/dist/rest/server/buildRestApiRouteHandler.js.map +0 -1
  64. package/dist/rest/server/index.d.ts +0 -3
  65. package/dist/rest/server/index.d.ts.map +0 -1
  66. package/dist/rest/server/index.js.map +0 -1
  67. package/dist/rest/types/RestApiRequest.d.ts +0 -9
  68. package/dist/rest/types/RestApiRequest.d.ts.map +0 -1
  69. package/dist/rest/types/RestApiRequest.js +0 -2
  70. package/dist/rest/types/RestApiRequest.js.map +0 -1
  71. package/dist/rest/types/RestApiRequestHandler.d.ts +0 -5
  72. package/dist/rest/types/RestApiRequestHandler.d.ts.map +0 -1
  73. package/dist/rest/types/RestApiRequestHandler.js +0 -2
  74. package/dist/rest/types/RestApiRequestHandler.js.map +0 -1
  75. package/dist/rest/types/RestApiRequestHandlerWithAuthentication.d.ts +0 -4
  76. package/dist/rest/types/RestApiRequestHandlerWithAuthentication.d.ts.map +0 -1
  77. package/dist/rest/types/RestApiRequestHandlerWithAuthentication.js +0 -2
  78. package/dist/rest/types/RestApiRequestHandlerWithAuthentication.js.map +0 -1
  79. package/dist/rest/types/RestApiResponse.d.ts +0 -6
  80. package/dist/rest/types/RestApiResponse.d.ts.map +0 -1
  81. package/dist/rest/types/RestApiResponse.js +0 -2
  82. package/dist/rest/types/RestApiResponse.js.map +0 -1
  83. package/dist/rest/types/RestApiRoute.d.ts +0 -15
  84. package/dist/rest/types/RestApiRoute.d.ts.map +0 -1
  85. package/dist/rest/types/RestApiRoute.js +0 -2
  86. package/dist/rest/types/RestApiRoute.js.map +0 -1
  87. package/dist/rest/types/RestApiRouteHandler.d.ts +0 -8
  88. package/dist/rest/types/RestApiRouteHandler.d.ts.map +0 -1
  89. package/dist/rest/types/RestApiRouteHandler.js +0 -2
  90. package/dist/rest/types/RestApiRouteHandler.js.map +0 -1
  91. package/dist/rest/types/RestApiRouteWithAuthentication.d.ts +0 -4
  92. package/dist/rest/types/RestApiRouteWithAuthentication.d.ts.map +0 -1
  93. package/dist/rest/types/RestApiRouteWithAuthentication.js +0 -2
  94. package/dist/rest/types/RestApiRouteWithAuthentication.js.map +0 -1
  95. package/dist/rest/types/common/BearerToken.d.ts +0 -2
  96. package/dist/rest/types/common/BearerToken.d.ts.map +0 -1
  97. package/dist/rest/types/common/BearerToken.js +0 -2
  98. package/dist/rest/types/common/BearerToken.js.map +0 -1
  99. package/dist/rest/types/common/RestPathParams.d.ts +0 -2
  100. package/dist/rest/types/common/RestPathParams.d.ts.map +0 -1
  101. package/dist/rest/types/common/RestPathParams.js +0 -2
  102. package/dist/rest/types/common/RestPathParams.js.map +0 -1
  103. package/dist/rest/types/common/RestQueryParams.d.ts +0 -2
  104. package/dist/rest/types/common/RestQueryParams.d.ts.map +0 -1
  105. package/dist/rest/types/common/RestQueryParams.js +0 -2
  106. package/dist/rest/types/common/RestQueryParams.js.map +0 -1
  107. package/dist/rest/types/common/RestRequestBody.d.ts +0 -2
  108. package/dist/rest/types/common/RestRequestBody.d.ts.map +0 -1
  109. package/dist/rest/types/common/RestRequestBody.js +0 -2
  110. package/dist/rest/types/common/RestRequestBody.js.map +0 -1
  111. package/dist/rest/types/common/RestRequestHeaders.d.ts +0 -2
  112. package/dist/rest/types/common/RestRequestHeaders.d.ts.map +0 -1
  113. package/dist/rest/types/common/RestRequestHeaders.js +0 -2
  114. package/dist/rest/types/common/RestRequestHeaders.js.map +0 -1
  115. package/dist/rest/types/common/RestRequestHeadersWithAuthorization.d.ts +0 -8
  116. package/dist/rest/types/common/RestRequestHeadersWithAuthorization.d.ts.map +0 -1
  117. package/dist/rest/types/common/RestRequestHeadersWithAuthorization.js +0 -2
  118. package/dist/rest/types/common/RestRequestHeadersWithAuthorization.js.map +0 -1
  119. package/dist/rest/types/common/RestRequireAuthentication.d.ts +0 -2
  120. package/dist/rest/types/common/RestRequireAuthentication.d.ts.map +0 -1
  121. package/dist/rest/types/common/RestRequireAuthentication.js +0 -2
  122. package/dist/rest/types/common/RestRequireAuthentication.js.map +0 -1
  123. package/dist/rest/types/common/RestResponseBody.d.ts +0 -2
  124. package/dist/rest/types/common/RestResponseBody.d.ts.map +0 -1
  125. package/dist/rest/types/common/RestResponseBody.js +0 -2
  126. package/dist/rest/types/common/RestResponseBody.js.map +0 -1
  127. package/dist/rest/types/common/index.d.ts +0 -9
  128. package/dist/rest/types/common/index.d.ts.map +0 -1
  129. package/dist/rest/types/common/index.js.map +0 -1
  130. package/dist/rest/types/index.d.ts +0 -9
  131. package/dist/rest/types/index.d.ts.map +0 -1
  132. package/dist/rest/types/index.js.map +0 -1
  133. package/dist/rest/types/internal/InternalRestApiRequest.d.ts +0 -7
  134. package/dist/rest/types/internal/InternalRestApiRequest.d.ts.map +0 -1
  135. package/dist/rest/types/internal/InternalRestApiRequest.js +0 -2
  136. package/dist/rest/types/internal/InternalRestApiRequest.js.map +0 -1
  137. package/dist/rest/types/internal/index.d.ts +0 -3
  138. package/dist/rest/types/internal/index.d.ts.map +0 -1
  139. package/dist/rest/types/internal/index.js +0 -3
  140. package/dist/rest/types/internal/index.js.map +0 -1
  141. package/dist/types/OmitNever.d.ts +0 -4
  142. package/dist/types/OmitNever.d.ts.map +0 -1
  143. package/dist/types/OmitNever.js +0 -2
  144. package/dist/types/OmitNever.js.map +0 -1
  145. package/dist/ws/client/index.d.ts +0 -2
  146. package/dist/ws/client/index.d.ts.map +0 -1
  147. package/dist/ws/client/index.js +0 -2
  148. package/dist/ws/client/index.js.map +0 -1
  149. package/dist/ws/client/useWsApi.d.ts +0 -20
  150. package/dist/ws/client/useWsApi.d.ts.map +0 -1
  151. package/dist/ws/client/useWsApi.js +0 -59
  152. package/dist/ws/client/useWsApi.js.map +0 -1
  153. package/dist/ws/server/WsApi.d.ts +0 -8
  154. package/dist/ws/server/WsApi.d.ts.map +0 -1
  155. package/dist/ws/server/WsApi.js +0 -55
  156. package/dist/ws/server/WsApi.js.map +0 -1
  157. package/dist/ws/server/buildWsApiRouteHandler.d.ts +0 -3
  158. package/dist/ws/server/buildWsApiRouteHandler.d.ts.map +0 -1
  159. package/dist/ws/server/buildWsApiRouteHandler.js +0 -7
  160. package/dist/ws/server/buildWsApiRouteHandler.js.map +0 -1
  161. package/dist/ws/server/closeConnection.d.ts +0 -3
  162. package/dist/ws/server/closeConnection.d.ts.map +0 -1
  163. package/dist/ws/server/closeConnection.js +0 -13
  164. package/dist/ws/server/closeConnection.js.map +0 -1
  165. package/dist/ws/server/getApiGatewayManagementApiClient.d.ts +0 -3
  166. package/dist/ws/server/getApiGatewayManagementApiClient.d.ts.map +0 -1
  167. package/dist/ws/server/getApiGatewayManagementApiClient.js +0 -10
  168. package/dist/ws/server/getApiGatewayManagementApiClient.js.map +0 -1
  169. package/dist/ws/server/getConnection.d.ts +0 -4
  170. package/dist/ws/server/getConnection.d.ts.map +0 -1
  171. package/dist/ws/server/getConnection.js +0 -9
  172. package/dist/ws/server/getConnection.js.map +0 -1
  173. package/dist/ws/server/index.d.ts +0 -9
  174. package/dist/ws/server/index.d.ts.map +0 -1
  175. package/dist/ws/server/index.js.map +0 -1
  176. package/dist/ws/server/sendResponse.d.ts +0 -3
  177. package/dist/ws/server/sendResponse.d.ts.map +0 -1
  178. package/dist/ws/server/sendResponse.js +0 -9
  179. package/dist/ws/server/sendResponse.js.map +0 -1
  180. package/dist/ws/server/trpc/TrpcSubscriptionRequest.d.ts +0 -12
  181. package/dist/ws/server/trpc/TrpcSubscriptionRequest.d.ts.map +0 -1
  182. package/dist/ws/server/trpc/TrpcSubscriptionRequest.js +0 -2
  183. package/dist/ws/server/trpc/TrpcSubscriptionRequest.js.map +0 -1
  184. package/dist/ws/server/trpc/TrpcSubscriptionResponse.d.ts +0 -12
  185. package/dist/ws/server/trpc/TrpcSubscriptionResponse.d.ts.map +0 -1
  186. package/dist/ws/server/trpc/TrpcSubscriptionResponse.js +0 -2
  187. package/dist/ws/server/trpc/TrpcSubscriptionResponse.js.map +0 -1
  188. package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.d.ts +0 -7
  189. package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.d.ts.map +0 -1
  190. package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.js +0 -85
  191. package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.js.map +0 -1
  192. package/dist/ws/types/WsApiConnection.d.ts +0 -5
  193. package/dist/ws/types/WsApiConnection.d.ts.map +0 -1
  194. package/dist/ws/types/WsApiConnection.js +0 -2
  195. package/dist/ws/types/WsApiConnection.js.map +0 -1
  196. package/dist/ws/types/WsApiRequestHandler.d.ts +0 -11
  197. package/dist/ws/types/WsApiRequestHandler.d.ts.map +0 -1
  198. package/dist/ws/types/WsApiRequestHandler.js +0 -2
  199. package/dist/ws/types/WsApiRequestHandler.js.map +0 -1
  200. package/dist/ws/types/WsApiRoute.d.ts +0 -9
  201. package/dist/ws/types/WsApiRoute.d.ts.map +0 -1
  202. package/dist/ws/types/WsApiRoute.js +0 -2
  203. package/dist/ws/types/WsApiRoute.js.map +0 -1
  204. package/dist/ws/types/WsApiRouteHandler.d.ts +0 -8
  205. package/dist/ws/types/WsApiRouteHandler.d.ts.map +0 -1
  206. package/dist/ws/types/WsApiRouteHandler.js +0 -2
  207. package/dist/ws/types/WsApiRouteHandler.js.map +0 -1
  208. package/dist/ws/types/common/WsApiAction.d.ts +0 -2
  209. package/dist/ws/types/common/WsApiAction.d.ts.map +0 -1
  210. package/dist/ws/types/common/WsApiAction.js +0 -2
  211. package/dist/ws/types/common/WsApiAction.js.map +0 -1
  212. package/dist/ws/types/common/WsApiRequest.d.ts +0 -5
  213. package/dist/ws/types/common/WsApiRequest.d.ts.map +0 -1
  214. package/dist/ws/types/common/WsApiRequest.js +0 -2
  215. package/dist/ws/types/common/WsApiRequest.js.map +0 -1
  216. package/dist/ws/types/common/WsApiResponse.d.ts +0 -2
  217. package/dist/ws/types/common/WsApiResponse.d.ts.map +0 -1
  218. package/dist/ws/types/common/WsApiResponse.js +0 -2
  219. package/dist/ws/types/common/WsApiResponse.js.map +0 -1
  220. package/dist/ws/types/common/index.d.ts +0 -4
  221. package/dist/ws/types/common/index.d.ts.map +0 -1
  222. package/dist/ws/types/common/index.js +0 -4
  223. package/dist/ws/types/common/index.js.map +0 -1
  224. package/dist/ws/types/index.d.ts +0 -6
  225. package/dist/ws/types/index.d.ts.map +0 -1
  226. package/dist/ws/types/index.js.map +0 -1
package/package.json CHANGED
@@ -1,51 +1,26 @@
1
1
  {
2
2
  "name": "@mstuercke/api-utils",
3
- "version": "4.3.1",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
+ "main": "src/index.ts",
5
6
  "files": [
6
- "dist/**/*.js",
7
- "dist/**/*.js.map",
8
- "dist/**/*.d.ts",
9
- "dist/**/*.d.ts.map",
7
+ "src/**/!(*.test|*.fixture).ts",
10
8
  "README.md"
11
9
  ],
12
10
  "exports": {
13
- "./dist": null,
14
- "./rest/types": "./dist/rest/types/index.js",
15
- "./rest/client": "./dist/rest/client/index.js",
16
- "./rest/server": "./dist/rest/server/index.js",
17
- "./ws/types": "./dist/ws/types/index.js",
18
- "./ws/client": "./dist/ws/client/index.js",
19
- "./ws/server": "./dist/ws/server/index.js"
20
- },
21
- "typesVersions": {
22
- "*": {
23
- "rest/types": [
24
- "dist/rest/types"
25
- ],
26
- "rest/client": [
27
- "dist/rest/client"
28
- ],
29
- "rest/server": [
30
- "dist/rest/server"
31
- ],
32
- "ws/types": [
33
- "dist/ws/types"
34
- ],
35
- "ws/client": [
36
- "dist/ws/client"
37
- ],
38
- "ws/server": [
39
- "dist/ws/server"
40
- ]
41
- }
11
+ "./src": null,
12
+ "./rest/types": "./src/rest/types/index.ts",
13
+ "./rest/client": "./src/rest/client/index.ts",
14
+ "./rest/server": "./src/rest/server/index.ts",
15
+ "./ws/types": "./src/ws/types/index.ts",
16
+ "./ws/client": "./src/ws/client/index.ts",
17
+ "./ws/server": "./src/ws/server/index.ts"
42
18
  },
43
19
  "scripts": {
44
20
  "lint": "eslint ./src --max-warnings 0",
45
21
  "lint:fix": "eslint ./src --fix",
46
22
  "test:unit": "bun test",
47
- "clean": "rm -rf dist",
48
- "build": "bun scripts/build.ts",
23
+ "compile": "tsc",
49
24
  "bump:major": "npm version major --git-tag-version false",
50
25
  "bump:minor": "npm version minor --git-tag-version false",
51
26
  "bump:patch": "npm version patch --git-tag-version false",
@@ -60,10 +35,10 @@
60
35
  "@types/aws-lambda": "^8.10.133"
61
36
  },
62
37
  "devDependencies": {
63
- "@mstuercke/bun-utils": "5.0.4",
38
+ "@mstuercke/bun-utils": "6.0.0",
64
39
  "@trpc/server": "next",
65
40
  "@types/aws-lambda": "^8.10.133",
66
- "@types/bun": "^1.0.5",
41
+ "@types/bun": "^1.0.11",
67
42
  "@types/path-to-regexp": "^1.7.0",
68
43
  "@types/react": "^18.2.33",
69
44
  "aws-sdk-client-mock": "^4.0.0",
@@ -0,0 +1,47 @@
1
+ import {
2
+ RestApiRequest,
3
+ RestApiRoute,
4
+ RestPathParams,
5
+ RestQueryParams,
6
+ RestRequestBody,
7
+ RestRequestHeaders,
8
+ RestRequestHeadersWithAuthorization,
9
+ RestRequireAuthentication,
10
+ RestResponseBody,
11
+ } from '../types';
12
+ import {OmitNever} from '../types/internal';
13
+
14
+
15
+ export async function fetchRestApi<
16
+ PathParams extends RestPathParams,
17
+ QueryParams extends RestQueryParams,
18
+ RequestHeaders extends RestRequestHeaders,
19
+ RequestBody extends RestRequestBody,
20
+ ResponseBody extends RestResponseBody,
21
+ RequireAuthentication extends RestRequireAuthentication>(
22
+ restApiUrl: string,
23
+ route: RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
24
+ request: OmitNever<RestApiRequest<PathParams, QueryParams, RestRequestHeadersWithAuthorization<RequestHeaders, RequireAuthentication>, RequestBody>>,
25
+ ): Promise<ResponseBody> {
26
+ const pathParams = 'pathParams' in request ? request.pathParams as PathParams : undefined as never;
27
+ const queryParams = 'queryParams' in request ? request.queryParams as QueryParams : undefined as never;
28
+ const headers = 'headers' in request ? request.headers as RequestHeaders : undefined as never;
29
+ const body = 'body' in request ? request.body as RequestBody : undefined as never;
30
+
31
+ const url = `${restApiUrl}${route.buildUrl(pathParams, queryParams)}`;
32
+ const response = await fetch(
33
+ url,
34
+ {
35
+ method: route.method,
36
+ headers: headers,
37
+ body: body ? JSON.stringify(body) : null,
38
+ });
39
+
40
+ if (!response.ok && response.status !== 404)
41
+ throw `Request ${route.method} ${url} failed`;
42
+
43
+ const text = (await response.text()).trim();
44
+ return response.ok && response.status !== 204
45
+ ? text ? JSON.parse(text) : null
46
+ : null;
47
+ }
@@ -0,0 +1 @@
1
+ export * from './fetchRestApi'
@@ -0,0 +1,144 @@
1
+ import {APIGatewayProxyEvent, APIGatewayProxyResult} from 'aws-lambda';
2
+ import {URL} from 'node:url';
3
+ import {match} from 'path-to-regexp';
4
+ import {
5
+ RestApiRouteHandler,
6
+ RestPathParams,
7
+ RestQueryParams,
8
+ RestRequestBody,
9
+ RestRequestHeaders,
10
+ RestRequireAuthentication,
11
+ RestResponseBody,
12
+ } from '../types';
13
+
14
+
15
+ const DEFAULT_RESPONSE_HEADERS = {
16
+ 'Content-Type': 'application/json; charset=utf-8',
17
+ 'Access-Control-Allow-Origin': '*',
18
+ 'Access-Control-Allow-Methods': '*',
19
+ 'Access-Control-Allow-Headers': '*',
20
+ } as const;
21
+
22
+ export type AnyRestApiRouteHandler = RestApiRouteHandler<
23
+ RestPathParams,
24
+ RestQueryParams,
25
+ RestRequestHeaders,
26
+ RestRequestBody,
27
+ RestResponseBody,
28
+ RestRequireAuthentication
29
+ >
30
+
31
+ type MatchingRouteHandler = {
32
+ routeHandler: AnyRestApiRouteHandler,
33
+ pathParams: RestPathParams,
34
+ }
35
+
36
+ export class RestApi {
37
+ private routeHandlers: AnyRestApiRouteHandler[] = [];
38
+
39
+ addRouteHandler<
40
+ PathParams extends RestPathParams,
41
+ QueryParams extends RestQueryParams,
42
+ RequestHeaders extends RestRequestHeaders,
43
+ RequestBody extends RestRequestBody,
44
+ ResponseBody extends RestResponseBody,
45
+ RequireAuthentication extends RestRequireAuthentication>(
46
+ routeHandler: RestApiRouteHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>) {
47
+ this.routeHandlers = [...this.routeHandlers, routeHandler as unknown as AnyRestApiRouteHandler];
48
+ return this;
49
+ }
50
+
51
+ async handleRequest(proxyEvent: APIGatewayProxyEvent, onError?: (error: unknown) => Promise<void>): Promise<APIGatewayProxyResult> {
52
+ try {
53
+ const url = new URL(`${proxyEvent.headers?.['origin'] || 'https://no-origin'}${proxyEvent.path}`);
54
+ const routeHandler = this.getMatchingRouteHandler(url, proxyEvent.httpMethod);
55
+ if (!routeHandler && proxyEvent.httpMethod === 'OPTIONS') {
56
+ const allowedHttpMethods = this.getMatchingRouteHandlers(url).map(({routeHandler}) => routeHandler.route.method);
57
+ return {
58
+ statusCode: 200,
59
+ body: '',
60
+ headers: {
61
+ ...DEFAULT_RESPONSE_HEADERS,
62
+ 'Access-Control-Allow-Methods': allowedHttpMethods.join(','),
63
+ },
64
+ };
65
+ }
66
+
67
+ if (!routeHandler)
68
+ return {
69
+ statusCode: 405,
70
+ headers: DEFAULT_RESPONSE_HEADERS,
71
+ body: `Method ${proxyEvent.httpMethod} not allowed on ${proxyEvent.path}`,
72
+ };
73
+
74
+ const {routeHandler: {requestHandler, route}, pathParams} = routeHandler;
75
+ const userId = proxyEvent.requestContext.authorizer?.principalId;
76
+ if (route.requireAuthentication && !userId)
77
+ return {statusCode: 401, body: ''};
78
+
79
+ const responseBody = await requestHandler({
80
+ pathParams,
81
+ queryParams: this.extractQueryParams(proxyEvent),
82
+ headers: proxyEvent.headers as Record<string, string>,
83
+ body: proxyEvent.body && JSON.parse(proxyEvent.body),
84
+ userId,
85
+ });
86
+ return {
87
+ statusCode: responseBody.status,
88
+ body: responseBody.body ? JSON.stringify(responseBody.body) : '',
89
+ headers: {
90
+ ...DEFAULT_RESPONSE_HEADERS,
91
+ ...(responseBody.headers || {}),
92
+ },
93
+ };
94
+ } catch (error) {
95
+ if (onError)
96
+ await onError(error);
97
+
98
+ console.error(error);
99
+ return {
100
+ statusCode: 500,
101
+ headers: DEFAULT_RESPONSE_HEADERS,
102
+ body: JSON.stringify({error}),
103
+ };
104
+ }
105
+ }
106
+
107
+ private getMatchingRouteHandlers(url: URL): MatchingRouteHandler[] {
108
+ return this.routeHandlers.reduce((matchingRouteHandlers, routeHandler) => {
109
+ const pathParams = this.extractPathParams(routeHandler, url);
110
+ const matchingRouteHandler: MatchingRouteHandler = {
111
+ routeHandler,
112
+ pathParams,
113
+ };
114
+ return pathParams
115
+ ? [...matchingRouteHandlers, matchingRouteHandler]
116
+ : matchingRouteHandlers;
117
+ }, [] as MatchingRouteHandler[]);
118
+ }
119
+
120
+ private getMatchingRouteHandler(url: URL, httpMethod: string): MatchingRouteHandler | undefined {
121
+ return this.getMatchingRouteHandlers(url)
122
+ .find(({routeHandler}) => routeHandler.route.method === httpMethod);
123
+ }
124
+
125
+ private extractPathParams(routeHandler: AnyRestApiRouteHandler, url: URL): object | never {
126
+ const matcher = match(routeHandler.route.path, {decode: decodeURIComponent});
127
+ return (matcher(url.pathname) || null)?.params as object | never;
128
+ }
129
+
130
+ private extractQueryParams(proxyEvent: APIGatewayProxyEvent): object {
131
+ const keys = new Set([
132
+ ...Object.keys(proxyEvent.queryStringParameters || {}),
133
+ ...Object.keys(proxyEvent.multiValueQueryStringParameters || {}),
134
+ ]);
135
+
136
+ const queryParams: { [key: string]: string | string[] | undefined } = {};
137
+ for (const key of keys.values()) {
138
+ const singleValue = proxyEvent.queryStringParameters?.[key];
139
+ const multiValues = proxyEvent.multiValueQueryStringParameters?.[key] || [];
140
+ queryParams[key] = multiValues.length > 1 ? multiValues : singleValue;
141
+ }
142
+ return queryParams;
143
+ }
144
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ RestApiRequestHandler,
3
+ RestApiRoute,
4
+ RestApiRouteHandler,
5
+ RestPathParams,
6
+ RestQueryParams,
7
+ RestRequestBody,
8
+ RestRequestHeaders,
9
+ RestRequireAuthentication,
10
+ RestResponseBody,
11
+ } from '../types';
12
+
13
+ export const buildRestApiRouteHandler = <
14
+ PathParams extends RestPathParams = never,
15
+ QueryParams extends RestQueryParams = never,
16
+ RequestHeaders extends RestRequestHeaders = never,
17
+ RequestBody extends RestRequestBody = never,
18
+ ResponseBody extends RestResponseBody = never,
19
+ RequireAuthentication extends RestRequireAuthentication = false
20
+ >(
21
+ route: RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
22
+ requestHandler: RestApiRequestHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
23
+ ): RestApiRouteHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication> => ({
24
+ route,
25
+ requestHandler,
26
+ });
@@ -1,3 +1,2 @@
1
1
  export * from './buildRestApiRouteHandler';
2
2
  export * from './RestApi';
3
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ import {RestPathParams, RestQueryParams, RestRequestBody, RestRequestHeaders} from './common';
2
+ import {OmitNever} from './internal';
3
+
4
+ export type RestApiRequest<
5
+ PathParams extends RestPathParams = never,
6
+ QueryParams extends RestQueryParams = never,
7
+ RequestHeaders extends RestRequestHeaders = never,
8
+ RequestBody extends RestRequestBody = never> = OmitNever<{
9
+ pathParams: PathParams,
10
+ queryParams: QueryParams,
11
+ headers: RequestHeaders,
12
+ body: RequestBody
13
+ }>
14
+
@@ -0,0 +1,21 @@
1
+ import {RestApiResponse} from './RestApiResponse';
2
+ import {
3
+ RestPathParams,
4
+ RestQueryParams,
5
+ RestRequestBody,
6
+ RestRequestHeaders,
7
+ RestRequireAuthentication,
8
+ RestResponseBody,
9
+ } from './common';
10
+ import {InternalRestApiRequest} from './internal';
11
+
12
+ export type RestApiRequestHandler<
13
+ PathParams extends RestPathParams = never,
14
+ QueryParams extends RestQueryParams = never,
15
+ RequestHeaders extends RestRequestHeaders = never,
16
+ RequestBody extends RestRequestBody = never,
17
+ ResponseBody extends RestResponseBody = never,
18
+ RequireAuthentication extends RestRequireAuthentication = false
19
+ > = (request: InternalRestApiRequest<PathParams, QueryParams, RequestHeaders, RequestBody, RequireAuthentication>) => Promise<RestApiResponse<ResponseBody>>;
20
+
21
+
@@ -0,0 +1,12 @@
1
+ import {RestApiRequestHandler} from './RestApiRequestHandler';
2
+ import {RestPathParams, RestQueryParams, RestRequestBody, RestRequestHeaders, RestResponseBody} from './common';
3
+
4
+ export type RestApiRequestHandlerWithAuthentication<
5
+ PathParams extends RestPathParams = never,
6
+ QueryParams extends RestQueryParams = never,
7
+ RequestHeaders extends RestRequestHeaders = never,
8
+ RequestBody extends RestRequestBody = never,
9
+ ResponseBody extends RestResponseBody = never,
10
+ > = RestApiRequestHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, true>
11
+
12
+
@@ -0,0 +1,5 @@
1
+ export interface RestApiResponse<ResponseBody> {
2
+ status: number;
3
+ headers?: Record<string, string>;
4
+ body?: ResponseBody;
5
+ }
@@ -0,0 +1,34 @@
1
+ import {
2
+ RestPathParams,
3
+ RestQueryParams,
4
+ RestRequestBody,
5
+ RestRequestHeaders,
6
+ RestRequireAuthentication,
7
+ RestResponseBody,
8
+ } from './common';
9
+
10
+ export type RestHttpMethod = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT' | 'OPTIONS'
11
+ export type RestRoutePath = `/${string}`
12
+
13
+ export type RestApiRoute<
14
+ PathParams extends RestPathParams = never,
15
+ QueryParams extends RestQueryParams = never,
16
+ RequestHeaders extends RestRequestHeaders = never,
17
+ RequestBody extends RestRequestBody = never,
18
+ ResponseBody extends RestResponseBody = never,
19
+ RequireAuthentication extends RestRequireAuthentication = false
20
+ > = {
21
+ method: RestHttpMethod;
22
+ path: RestRoutePath;
23
+ buildUrl: (pathParams: PathParams, queryParams: QueryParams) => RestRoutePath;
24
+ requireAuthentication: RequireAuthentication;
25
+
26
+ // this is a workaround to force optional generics to be typesafe
27
+ __types?: {
28
+ RequestHeaders: RequestHeaders
29
+ RequestBody: RequestBody
30
+ ResponseBody: ResponseBody
31
+ };
32
+ }
33
+
34
+
@@ -0,0 +1,22 @@
1
+ import {RestApiRequestHandler} from './RestApiRequestHandler';
2
+ import {RestApiRoute} from './RestApiRoute';
3
+ import {
4
+ RestPathParams,
5
+ RestQueryParams,
6
+ RestRequestBody,
7
+ RestRequestHeaders,
8
+ RestRequireAuthentication,
9
+ RestResponseBody,
10
+ } from './common';
11
+
12
+ export interface RestApiRouteHandler<
13
+ PathParams extends RestPathParams = never,
14
+ QueryParams extends RestQueryParams = never,
15
+ RequestHeaders extends RestRequestHeaders = never,
16
+ RequestBody extends RestRequestBody = never,
17
+ ResponseBody extends RestResponseBody = never,
18
+ RequireAuthentication extends RestRequireAuthentication = false> {
19
+ route: RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
20
+ requestHandler: RestApiRequestHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
21
+ }
22
+
@@ -0,0 +1,12 @@
1
+ import {RestApiRoute} from './RestApiRoute';
2
+ import {RestPathParams, RestQueryParams, RestRequestBody, RestRequestHeaders, RestResponseBody} from './common';
3
+
4
+ export type RestApiRouteWithAuthentication<
5
+ PathParams extends RestPathParams = never,
6
+ QueryParams extends RestQueryParams = never,
7
+ RequestHeaders extends RestRequestHeaders = never,
8
+ RequestBody extends RestRequestBody = never,
9
+ ResponseBody extends RestResponseBody = never,
10
+ > = RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, true>
11
+
12
+
@@ -0,0 +1 @@
1
+ export type BearerToken = `Bearer ${string}`
@@ -0,0 +1 @@
1
+ export type RestPathParams = object | never
@@ -0,0 +1 @@
1
+ export type RestQueryParams = object | never
@@ -0,0 +1 @@
1
+ export type RestRequestBody = object | never
@@ -0,0 +1 @@
1
+ export type RestRequestHeaders = Record<string, string> | never
@@ -0,0 +1,11 @@
1
+ import {BearerToken} from "./BearerToken";
2
+ import {RestRequestHeaders} from './RestRequestHeaders';
3
+ import {RestRequireAuthentication} from './RestRequireAuthentication';
4
+
5
+ export type AuthorizationHeader = { Authorization: BearerToken }
6
+ export type RestRequestHeadersWithAuthorization<
7
+ RequestHeaders extends RestRequestHeaders,
8
+ RequireAuthentication extends RestRequireAuthentication
9
+ > = RequireAuthentication extends true
10
+ ? [RequestHeaders] extends [never] ? AuthorizationHeader : RequestHeaders & AuthorizationHeader
11
+ : RequestHeaders
@@ -0,0 +1 @@
1
+ export type RestRequireAuthentication = boolean
@@ -0,0 +1 @@
1
+ export type RestResponseBody = object | object[] | null | never
@@ -6,4 +6,3 @@ export * from './RestRequestBody';
6
6
  export * from './RestRequestHeaders';
7
7
  export * from './RestRequireAuthentication';
8
8
  export * from './RestResponseBody';
9
- //# sourceMappingURL=index.js.map
@@ -6,4 +6,3 @@ export * from './RestApiResponse';
6
6
  export * from './RestApiRoute';
7
7
  export * from './RestApiRouteHandler';
8
8
  export * from './RestApiRouteWithAuthentication';
9
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import {OmitNever} from '../../../types/OmitNever';
2
+ import {RestApiRequest} from '../RestApiRequest';
3
+ import {
4
+ RestPathParams,
5
+ RestQueryParams,
6
+ RestRequestBody,
7
+ RestRequestHeaders,
8
+ RestRequireAuthentication,
9
+ } from '../common';
10
+
11
+ export type InternalRestApiRequest<
12
+ PathParams extends RestPathParams = never,
13
+ QueryParams extends RestQueryParams = never,
14
+ RequestHeaders extends RestRequestHeaders = never,
15
+ RequestBody extends RestRequestBody = never,
16
+ RequireAuthentication extends RestRequireAuthentication = false> = OmitNever<
17
+ RestApiRequest<PathParams, QueryParams, RequestHeaders, RequestBody>
18
+ & { userId: RequireAuthentication extends true ? string : never }>
19
+
@@ -0,0 +1,2 @@
1
+ export * from './InternalRestApiRequest'
2
+ export * from '../../../types/OmitNever'
@@ -0,0 +1 @@
1
+ export type OmitNever<T> = { [P in keyof T as T[P] extends never ? never : P]: T[P] };
@@ -0,0 +1 @@
1
+ export * from './useWsApi'
@@ -0,0 +1,88 @@
1
+ import {useCallback, useMemo, useRef, useState} from 'react';
2
+ import {WsApiAction, WsApiRequest, WsApiResponse, WsApiRoute} from '../types';
3
+
4
+ type WsApiConnection<
5
+ Action extends WsApiAction,
6
+ Request extends WsApiRequest<Action>,
7
+ > = {
8
+ disconnect: () => void
9
+ sendRequest: (request: Request) => void
10
+ }
11
+
12
+ export type WsApiOptions<
13
+ Action extends WsApiAction,
14
+ Request extends WsApiRequest<Action>,
15
+ Response extends WsApiResponse> = {
16
+ onConnected?: (connection: WsApiConnection<Action, Request>) => void
17
+ onResponse?: (response: Response, connection: WsApiConnection<Action, Request>) => void
18
+ onError?: (error: unknown, connection: WsApiConnection<Action, Request>) => void
19
+ onDisconnected?: () => void
20
+ }
21
+ export const useWsApi = <
22
+ Action extends WsApiAction,
23
+ Request extends WsApiRequest<Action>,
24
+ Response extends WsApiResponse
25
+ >(wsApiUrl: string, route: WsApiRoute<Action, Request, Response> | undefined) => {
26
+ const [error, setError] = useState<boolean>(false);
27
+ const [connected, setConnected] = useState<boolean>(false);
28
+ const webSocket = useRef<WebSocket>();
29
+
30
+ const sendRequest = useCallback((request: Omit<Request, 'action'>) => {
31
+ if (!route) throw 'Not route';
32
+ if (!webSocket.current) throw 'Not connected';
33
+
34
+ webSocket.current.send(JSON.stringify({
35
+ ...request,
36
+ action: route.action,
37
+ }));
38
+ }, [route]);
39
+ const disconnect = useCallback(() => webSocket.current?.close(), [webSocket]);
40
+ const connection: WsApiConnection<Action, Request> = useMemo(() => {
41
+ return {
42
+ disconnect,
43
+ sendRequest,
44
+ };
45
+ }, [disconnect, sendRequest]);
46
+
47
+ const connect = useCallback(async (options: WsApiOptions<Action, Request, Response>) => {
48
+ return new Promise<void>((resolve) => {
49
+ if (webSocket.current && (webSocket.current.readyState === WebSocket.CONNECTING || webSocket.current.readyState === WebSocket.OPEN))
50
+ throw 'Already connected';
51
+
52
+ webSocket.current = new WebSocket(wsApiUrl);
53
+
54
+ webSocket.current.onopen = () => {
55
+ setConnected(true);
56
+ setError(false);
57
+ options?.onConnected?.(connection);
58
+ };
59
+
60
+ webSocket.current.onmessage = (ev) => {
61
+ if (!ev.data || ev.data === '{}')
62
+ return;
63
+ setError(false);
64
+ options?.onResponse?.(JSON.parse(ev.data as string), connection);
65
+ };
66
+
67
+ webSocket.current.onerror = (error) => {
68
+ setError(false);
69
+ options?.onError?.(error, connection);
70
+ };
71
+
72
+ webSocket.current.onclose = () => {
73
+ setConnected(false);
74
+ options?.onDisconnected?.();
75
+ webSocket.current = undefined;
76
+ resolve();
77
+ };
78
+ });
79
+ }, [wsApiUrl, connection]);
80
+
81
+ return {
82
+ connect,
83
+ connected,
84
+ connection,
85
+ error,
86
+ disconnect,
87
+ };
88
+ };