@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.
- package/package.json +13 -38
- package/src/rest/client/fetchRestApi.ts +47 -0
- package/src/rest/client/index.ts +1 -0
- package/src/rest/server/RestApi.ts +144 -0
- package/src/rest/server/buildRestApiRouteHandler.ts +26 -0
- package/{dist/rest/server/index.js → src/rest/server/index.ts} +0 -1
- package/src/rest/types/RestApiRequest.ts +14 -0
- package/src/rest/types/RestApiRequestHandler.ts +21 -0
- package/src/rest/types/RestApiRequestHandlerWithAuthentication.ts +12 -0
- package/src/rest/types/RestApiResponse.ts +5 -0
- package/src/rest/types/RestApiRoute.ts +34 -0
- package/src/rest/types/RestApiRouteHandler.ts +22 -0
- package/src/rest/types/RestApiRouteWithAuthentication.ts +12 -0
- package/src/rest/types/common/BearerToken.ts +1 -0
- package/src/rest/types/common/RestPathParams.ts +1 -0
- package/src/rest/types/common/RestQueryParams.ts +1 -0
- package/src/rest/types/common/RestRequestBody.ts +1 -0
- package/src/rest/types/common/RestRequestHeaders.ts +1 -0
- package/src/rest/types/common/RestRequestHeadersWithAuthorization.ts +11 -0
- package/src/rest/types/common/RestRequireAuthentication.ts +1 -0
- package/src/rest/types/common/RestResponseBody.ts +1 -0
- package/{dist/rest/types/common/index.js → src/rest/types/common/index.ts} +0 -1
- package/{dist/rest/types/index.js → src/rest/types/index.ts} +0 -1
- package/src/rest/types/internal/InternalRestApiRequest.ts +19 -0
- package/src/rest/types/internal/index.ts +2 -0
- package/src/types/OmitNever.ts +1 -0
- package/src/ws/client/index.ts +1 -0
- package/src/ws/client/useWsApi.ts +88 -0
- package/src/ws/server/WsApi.ts +75 -0
- package/src/ws/server/buildWsApiRouteHandler.ts +15 -0
- package/src/ws/server/closeConnection.ts +16 -0
- package/src/ws/server/getApiGatewayManagementApiClient.ts +11 -0
- package/src/ws/server/getConnection.ts +12 -0
- package/{dist/ws/server/index.js → src/ws/server/index.ts} +0 -1
- package/src/ws/server/sendResponse.ts +17 -0
- package/src/ws/server/trpc/TrpcSubscriptionRequest.ts +8 -0
- package/src/ws/server/trpc/TrpcSubscriptionResponse.ts +8 -0
- package/src/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.ts +105 -0
- package/src/ws/types/WsApiConnection.ts +4 -0
- package/src/ws/types/WsApiRequestHandler.ts +15 -0
- package/src/ws/types/WsApiRoute.ts +17 -0
- package/src/ws/types/WsApiRouteHandler.ts +14 -0
- package/src/ws/types/common/WsApiAction.ts +1 -0
- package/src/ws/types/common/WsApiRequest.ts +3 -0
- package/src/ws/types/common/WsApiResponse.ts +1 -0
- package/src/ws/types/common/index.ts +3 -0
- package/{dist/ws/types/index.js → src/ws/types/index.ts} +0 -1
- package/dist/rest/client/fetchRestApi.d.ts +0 -4
- package/dist/rest/client/fetchRestApi.d.ts.map +0 -1
- package/dist/rest/client/fetchRestApi.js +0 -19
- package/dist/rest/client/fetchRestApi.js.map +0 -1
- package/dist/rest/client/index.d.ts +0 -2
- package/dist/rest/client/index.d.ts.map +0 -1
- package/dist/rest/client/index.js +0 -2
- package/dist/rest/client/index.js.map +0 -1
- package/dist/rest/server/RestApi.d.ts +0 -13
- package/dist/rest/server/RestApi.d.ts.map +0 -1
- package/dist/rest/server/RestApi.js +0 -101
- package/dist/rest/server/RestApi.js.map +0 -1
- package/dist/rest/server/buildRestApiRouteHandler.d.ts +0 -3
- package/dist/rest/server/buildRestApiRouteHandler.d.ts.map +0 -1
- package/dist/rest/server/buildRestApiRouteHandler.js +0 -5
- package/dist/rest/server/buildRestApiRouteHandler.js.map +0 -1
- package/dist/rest/server/index.d.ts +0 -3
- package/dist/rest/server/index.d.ts.map +0 -1
- package/dist/rest/server/index.js.map +0 -1
- package/dist/rest/types/RestApiRequest.d.ts +0 -9
- package/dist/rest/types/RestApiRequest.d.ts.map +0 -1
- package/dist/rest/types/RestApiRequest.js +0 -2
- package/dist/rest/types/RestApiRequest.js.map +0 -1
- package/dist/rest/types/RestApiRequestHandler.d.ts +0 -5
- package/dist/rest/types/RestApiRequestHandler.d.ts.map +0 -1
- package/dist/rest/types/RestApiRequestHandler.js +0 -2
- package/dist/rest/types/RestApiRequestHandler.js.map +0 -1
- package/dist/rest/types/RestApiRequestHandlerWithAuthentication.d.ts +0 -4
- package/dist/rest/types/RestApiRequestHandlerWithAuthentication.d.ts.map +0 -1
- package/dist/rest/types/RestApiRequestHandlerWithAuthentication.js +0 -2
- package/dist/rest/types/RestApiRequestHandlerWithAuthentication.js.map +0 -1
- package/dist/rest/types/RestApiResponse.d.ts +0 -6
- package/dist/rest/types/RestApiResponse.d.ts.map +0 -1
- package/dist/rest/types/RestApiResponse.js +0 -2
- package/dist/rest/types/RestApiResponse.js.map +0 -1
- package/dist/rest/types/RestApiRoute.d.ts +0 -15
- package/dist/rest/types/RestApiRoute.d.ts.map +0 -1
- package/dist/rest/types/RestApiRoute.js +0 -2
- package/dist/rest/types/RestApiRoute.js.map +0 -1
- package/dist/rest/types/RestApiRouteHandler.d.ts +0 -8
- package/dist/rest/types/RestApiRouteHandler.d.ts.map +0 -1
- package/dist/rest/types/RestApiRouteHandler.js +0 -2
- package/dist/rest/types/RestApiRouteHandler.js.map +0 -1
- package/dist/rest/types/RestApiRouteWithAuthentication.d.ts +0 -4
- package/dist/rest/types/RestApiRouteWithAuthentication.d.ts.map +0 -1
- package/dist/rest/types/RestApiRouteWithAuthentication.js +0 -2
- package/dist/rest/types/RestApiRouteWithAuthentication.js.map +0 -1
- package/dist/rest/types/common/BearerToken.d.ts +0 -2
- package/dist/rest/types/common/BearerToken.d.ts.map +0 -1
- package/dist/rest/types/common/BearerToken.js +0 -2
- package/dist/rest/types/common/BearerToken.js.map +0 -1
- package/dist/rest/types/common/RestPathParams.d.ts +0 -2
- package/dist/rest/types/common/RestPathParams.d.ts.map +0 -1
- package/dist/rest/types/common/RestPathParams.js +0 -2
- package/dist/rest/types/common/RestPathParams.js.map +0 -1
- package/dist/rest/types/common/RestQueryParams.d.ts +0 -2
- package/dist/rest/types/common/RestQueryParams.d.ts.map +0 -1
- package/dist/rest/types/common/RestQueryParams.js +0 -2
- package/dist/rest/types/common/RestQueryParams.js.map +0 -1
- package/dist/rest/types/common/RestRequestBody.d.ts +0 -2
- package/dist/rest/types/common/RestRequestBody.d.ts.map +0 -1
- package/dist/rest/types/common/RestRequestBody.js +0 -2
- package/dist/rest/types/common/RestRequestBody.js.map +0 -1
- package/dist/rest/types/common/RestRequestHeaders.d.ts +0 -2
- package/dist/rest/types/common/RestRequestHeaders.d.ts.map +0 -1
- package/dist/rest/types/common/RestRequestHeaders.js +0 -2
- package/dist/rest/types/common/RestRequestHeaders.js.map +0 -1
- package/dist/rest/types/common/RestRequestHeadersWithAuthorization.d.ts +0 -8
- package/dist/rest/types/common/RestRequestHeadersWithAuthorization.d.ts.map +0 -1
- package/dist/rest/types/common/RestRequestHeadersWithAuthorization.js +0 -2
- package/dist/rest/types/common/RestRequestHeadersWithAuthorization.js.map +0 -1
- package/dist/rest/types/common/RestRequireAuthentication.d.ts +0 -2
- package/dist/rest/types/common/RestRequireAuthentication.d.ts.map +0 -1
- package/dist/rest/types/common/RestRequireAuthentication.js +0 -2
- package/dist/rest/types/common/RestRequireAuthentication.js.map +0 -1
- package/dist/rest/types/common/RestResponseBody.d.ts +0 -2
- package/dist/rest/types/common/RestResponseBody.d.ts.map +0 -1
- package/dist/rest/types/common/RestResponseBody.js +0 -2
- package/dist/rest/types/common/RestResponseBody.js.map +0 -1
- package/dist/rest/types/common/index.d.ts +0 -9
- package/dist/rest/types/common/index.d.ts.map +0 -1
- package/dist/rest/types/common/index.js.map +0 -1
- package/dist/rest/types/index.d.ts +0 -9
- package/dist/rest/types/index.d.ts.map +0 -1
- package/dist/rest/types/index.js.map +0 -1
- package/dist/rest/types/internal/InternalRestApiRequest.d.ts +0 -7
- package/dist/rest/types/internal/InternalRestApiRequest.d.ts.map +0 -1
- package/dist/rest/types/internal/InternalRestApiRequest.js +0 -2
- package/dist/rest/types/internal/InternalRestApiRequest.js.map +0 -1
- package/dist/rest/types/internal/index.d.ts +0 -3
- package/dist/rest/types/internal/index.d.ts.map +0 -1
- package/dist/rest/types/internal/index.js +0 -3
- package/dist/rest/types/internal/index.js.map +0 -1
- package/dist/types/OmitNever.d.ts +0 -4
- package/dist/types/OmitNever.d.ts.map +0 -1
- package/dist/types/OmitNever.js +0 -2
- package/dist/types/OmitNever.js.map +0 -1
- package/dist/ws/client/index.d.ts +0 -2
- package/dist/ws/client/index.d.ts.map +0 -1
- package/dist/ws/client/index.js +0 -2
- package/dist/ws/client/index.js.map +0 -1
- package/dist/ws/client/useWsApi.d.ts +0 -20
- package/dist/ws/client/useWsApi.d.ts.map +0 -1
- package/dist/ws/client/useWsApi.js +0 -59
- package/dist/ws/client/useWsApi.js.map +0 -1
- package/dist/ws/server/WsApi.d.ts +0 -8
- package/dist/ws/server/WsApi.d.ts.map +0 -1
- package/dist/ws/server/WsApi.js +0 -55
- package/dist/ws/server/WsApi.js.map +0 -1
- package/dist/ws/server/buildWsApiRouteHandler.d.ts +0 -3
- package/dist/ws/server/buildWsApiRouteHandler.d.ts.map +0 -1
- package/dist/ws/server/buildWsApiRouteHandler.js +0 -7
- package/dist/ws/server/buildWsApiRouteHandler.js.map +0 -1
- package/dist/ws/server/closeConnection.d.ts +0 -3
- package/dist/ws/server/closeConnection.d.ts.map +0 -1
- package/dist/ws/server/closeConnection.js +0 -13
- package/dist/ws/server/closeConnection.js.map +0 -1
- package/dist/ws/server/getApiGatewayManagementApiClient.d.ts +0 -3
- package/dist/ws/server/getApiGatewayManagementApiClient.d.ts.map +0 -1
- package/dist/ws/server/getApiGatewayManagementApiClient.js +0 -10
- package/dist/ws/server/getApiGatewayManagementApiClient.js.map +0 -1
- package/dist/ws/server/getConnection.d.ts +0 -4
- package/dist/ws/server/getConnection.d.ts.map +0 -1
- package/dist/ws/server/getConnection.js +0 -9
- package/dist/ws/server/getConnection.js.map +0 -1
- package/dist/ws/server/index.d.ts +0 -9
- package/dist/ws/server/index.d.ts.map +0 -1
- package/dist/ws/server/index.js.map +0 -1
- package/dist/ws/server/sendResponse.d.ts +0 -3
- package/dist/ws/server/sendResponse.d.ts.map +0 -1
- package/dist/ws/server/sendResponse.js +0 -9
- package/dist/ws/server/sendResponse.js.map +0 -1
- package/dist/ws/server/trpc/TrpcSubscriptionRequest.d.ts +0 -12
- package/dist/ws/server/trpc/TrpcSubscriptionRequest.d.ts.map +0 -1
- package/dist/ws/server/trpc/TrpcSubscriptionRequest.js +0 -2
- package/dist/ws/server/trpc/TrpcSubscriptionRequest.js.map +0 -1
- package/dist/ws/server/trpc/TrpcSubscriptionResponse.d.ts +0 -12
- package/dist/ws/server/trpc/TrpcSubscriptionResponse.d.ts.map +0 -1
- package/dist/ws/server/trpc/TrpcSubscriptionResponse.js +0 -2
- package/dist/ws/server/trpc/TrpcSubscriptionResponse.js.map +0 -1
- package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.d.ts +0 -7
- package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.d.ts.map +0 -1
- package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.js +0 -85
- package/dist/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.js.map +0 -1
- package/dist/ws/types/WsApiConnection.d.ts +0 -5
- package/dist/ws/types/WsApiConnection.d.ts.map +0 -1
- package/dist/ws/types/WsApiConnection.js +0 -2
- package/dist/ws/types/WsApiConnection.js.map +0 -1
- package/dist/ws/types/WsApiRequestHandler.d.ts +0 -11
- package/dist/ws/types/WsApiRequestHandler.d.ts.map +0 -1
- package/dist/ws/types/WsApiRequestHandler.js +0 -2
- package/dist/ws/types/WsApiRequestHandler.js.map +0 -1
- package/dist/ws/types/WsApiRoute.d.ts +0 -9
- package/dist/ws/types/WsApiRoute.d.ts.map +0 -1
- package/dist/ws/types/WsApiRoute.js +0 -2
- package/dist/ws/types/WsApiRoute.js.map +0 -1
- package/dist/ws/types/WsApiRouteHandler.d.ts +0 -8
- package/dist/ws/types/WsApiRouteHandler.d.ts.map +0 -1
- package/dist/ws/types/WsApiRouteHandler.js +0 -2
- package/dist/ws/types/WsApiRouteHandler.js.map +0 -1
- package/dist/ws/types/common/WsApiAction.d.ts +0 -2
- package/dist/ws/types/common/WsApiAction.d.ts.map +0 -1
- package/dist/ws/types/common/WsApiAction.js +0 -2
- package/dist/ws/types/common/WsApiAction.js.map +0 -1
- package/dist/ws/types/common/WsApiRequest.d.ts +0 -5
- package/dist/ws/types/common/WsApiRequest.d.ts.map +0 -1
- package/dist/ws/types/common/WsApiRequest.js +0 -2
- package/dist/ws/types/common/WsApiRequest.js.map +0 -1
- package/dist/ws/types/common/WsApiResponse.d.ts +0 -2
- package/dist/ws/types/common/WsApiResponse.d.ts.map +0 -1
- package/dist/ws/types/common/WsApiResponse.js +0 -2
- package/dist/ws/types/common/WsApiResponse.js.map +0 -1
- package/dist/ws/types/common/index.d.ts +0 -4
- package/dist/ws/types/common/index.d.ts.map +0 -1
- package/dist/ws/types/common/index.js +0 -4
- package/dist/ws/types/common/index.js.map +0 -1
- package/dist/ws/types/index.d.ts +0 -6
- package/dist/ws/types/index.d.ts.map +0 -1
- 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": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
5
6
|
"files": [
|
|
6
|
-
"
|
|
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
|
-
"./
|
|
14
|
-
"./rest/types": "./
|
|
15
|
-
"./rest/client": "./
|
|
16
|
-
"./rest/server": "./
|
|
17
|
-
"./ws/types": "./
|
|
18
|
-
"./ws/client": "./
|
|
19
|
-
"./ws/server": "./
|
|
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
|
-
"
|
|
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": "
|
|
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.
|
|
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
|
+
});
|
|
@@ -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,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
|
|
@@ -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 @@
|
|
|
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
|
+
};
|