@mstuercke/api-utils 7.0.2 → 7.0.3

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 (41) hide show
  1. package/package.json +7 -12
  2. package/src/__mocks__/ignoreConsoleErrors.ts +0 -2
  3. package/src/ws/server/WsApi.ts +3 -3
  4. package/src/ws/server/buildWsApiRouteHandler.ts +1 -1
  5. package/src/ws/server/closeConnection.ts +2 -2
  6. package/src/ws/server/getConnection.ts +1 -1
  7. package/src/ws/server/index.ts +8 -8
  8. package/src/ws/server/sendResponse.ts +2 -2
  9. package/src/ws/server/trpc/awsLambdaTrpcSubscriptionRequestHandler.ts +5 -5
  10. package/src/ws/types/WsApiRequestHandler.ts +3 -3
  11. package/src/ws/types/WsApiRoute.ts +1 -1
  12. package/src/ws/types/WsApiRouteHandler.ts +3 -3
  13. package/src/ws/types/common/WsApiRequest.ts +1 -1
  14. package/src/ws/types/common/index.ts +3 -3
  15. package/src/ws/types/index.ts +5 -5
  16. package/src/rest/client/fetchRestApi.ts +0 -49
  17. package/src/rest/client/index.ts +0 -1
  18. package/src/rest/server/RestApi.ts +0 -153
  19. package/src/rest/server/buildRestApiRouteHandler.ts +0 -33
  20. package/src/rest/server/index.ts +0 -2
  21. package/src/rest/types/RestApiRequest.ts +0 -14
  22. package/src/rest/types/RestApiRequestHandler.ts +0 -21
  23. package/src/rest/types/RestApiRequestHandlerWithAuthentication.ts +0 -10
  24. package/src/rest/types/RestApiResponse.ts +0 -5
  25. package/src/rest/types/RestApiRoute.ts +0 -32
  26. package/src/rest/types/RestApiRouteHandler.ts +0 -29
  27. package/src/rest/types/RestApiRouteWithAuthentication.ts +0 -10
  28. package/src/rest/types/common/BearerToken.ts +0 -1
  29. package/src/rest/types/common/RestPathParams.ts +0 -1
  30. package/src/rest/types/common/RestQueryParams.ts +0 -1
  31. package/src/rest/types/common/RestRequestBody.ts +0 -1
  32. package/src/rest/types/common/RestRequestHeaders.ts +0 -1
  33. package/src/rest/types/common/RestRequestHeadersWithAuthorization.ts +0 -13
  34. package/src/rest/types/common/RestRequireAuthentication.ts +0 -1
  35. package/src/rest/types/common/RestResponseBody.ts +0 -1
  36. package/src/rest/types/common/index.ts +0 -8
  37. package/src/rest/types/index.ts +0 -8
  38. package/src/rest/types/internal/InternalRestApiRequest.ts +0 -21
  39. package/src/rest/types/internal/index.ts +0 -2
  40. package/src/ws/client/index.ts +0 -1
  41. package/src/ws/client/useWsApi.ts +0 -99
package/package.json CHANGED
@@ -1,19 +1,14 @@
1
1
  {
2
2
  "name": "@mstuercke/api-utils",
3
- "version": "7.0.2",
3
+ "version": "7.0.3",
4
4
  "type": "module",
5
- "main": "src/index.ts",
6
5
  "files": [
7
6
  "src/**/!(*.test|*.fixture).ts",
8
7
  "README.md"
9
8
  ],
10
9
  "exports": {
11
10
  "./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
11
  "./ws/types": "./src/ws/types/index.ts",
16
- "./ws/client": "./src/ws/client/index.ts",
17
12
  "./ws/server": "./src/ws/server/index.ts"
18
13
  },
19
14
  "typesVersions": {
@@ -39,26 +34,26 @@
39
34
  }
40
35
  },
41
36
  "scripts": {
42
- "test:unit": "bun test",
37
+ "test:unit": "vitest run --coverage",
43
38
  "typeCheck": "tsc",
44
39
  "bump:major": "npm version major --git-tag-version false",
45
40
  "bump:minor": "npm version minor --git-tag-version false",
46
41
  "bump:patch": "npm version patch --git-tag-version false",
47
- "release": "bun run scripts/release.ts"
42
+ "release": "dotenv -- node --no-warnings --trace-uncaught --loader ts-node/esm scripts/release.ts"
48
43
  },
49
44
  "dependencies": {
50
45
  "path-to-regexp": "^8.0.0"
51
46
  },
52
47
  "peerDependencies": {
53
48
  "@aws-sdk/client-apigatewaymanagementapi": "^3.438.0",
54
- "@trpc/server": "^11.0.0-rc.748",
49
+ "@trpc/server": "^11.0.0-rc.795",
55
50
  "@types/aws-lambda": "8.10.147"
56
51
  },
57
52
  "devDependencies": {
58
- "@mstuercke/bun-utils": "7.0.6",
59
- "@trpc/server": "^11.0.0-rc.748",
53
+ "@aws-sdk/client-apigatewaymanagementapi": "^3.438.0",
54
+ "@mstuercke/node-utils": "0.0.1",
55
+ "@trpc/server": "^11.0.0-rc.795",
60
56
  "@types/aws-lambda": "8.10.147",
61
- "@types/bun": "1.2.2",
62
57
  "@types/path-to-regexp": "1.7.0",
63
58
  "@types/react": "19.0.8",
64
59
  "aws-sdk-client-mock": "4.1.0",
@@ -1,5 +1,3 @@
1
- import {afterEach, beforeEach} from 'bun:test'
2
-
3
1
  export const ignoreConsoleErrors = (...messagesToIgnore: string[]) => {
4
2
  let consoleError_original: typeof console.error
5
3
  beforeEach(() => {
@@ -6,9 +6,9 @@ import type {
6
6
  WsApiRequestHandler,
7
7
  WsApiResponse,
8
8
  WsApiRouteHandler,
9
- } from '../types'
10
- import {closeConnection} from './closeConnection'
11
- import {sendResponse} from './sendResponse'
9
+ } from '../types/index.ts'
10
+ import {closeConnection} from './closeConnection.ts'
11
+ import {sendResponse} from './sendResponse.ts'
12
12
 
13
13
  export class WsApi {
14
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5,7 +5,7 @@ import type {
5
5
  WsApiResponse,
6
6
  WsApiRoute,
7
7
  WsApiRouteHandler,
8
- } from '../types'
8
+ } from '../types/index.ts'
9
9
 
10
10
  export const buildWsApiRouteHandler = <
11
11
  Action extends WsApiAction,
@@ -1,6 +1,6 @@
1
1
  import {DeleteConnectionCommand} from '@aws-sdk/client-apigatewaymanagementapi'
2
- import type {WsApiConnection} from '../types'
3
- import {getApiGatewayManagementApiClient} from './getApiGatewayManagementApiClient'
2
+ import type {WsApiConnection} from '../types/index.ts'
3
+ import {getApiGatewayManagementApiClient} from './getApiGatewayManagementApiClient.ts'
4
4
 
5
5
  export const closeConnection = async ({endpoint, connectionId}: WsApiConnection) => {
6
6
  try {
@@ -1,5 +1,5 @@
1
1
  import type {APIGatewayProxyEvent} from 'aws-lambda'
2
- import type {WsApiConnection} from '../types'
2
+ import type {WsApiConnection} from '../types/index.ts'
3
3
 
4
4
  export const getConnection = (event: APIGatewayProxyEvent): WsApiConnection => {
5
5
  if (!event.requestContext.connectionId) throw 'no connectionId'
@@ -1,8 +1,8 @@
1
- export * from './trpc/TrpcSubscriptionRequest'
2
- export * from './trpc/TrpcSubscriptionResponse'
3
- export * from './trpc/awsLambdaTrpcSubscriptionRequestHandler'
4
- export * from './buildWsApiRouteHandler'
5
- export * from './closeConnection'
6
- export * from './getConnection'
7
- export * from './sendResponse'
8
- export * from './WsApi'
1
+ export * from './trpc/TrpcSubscriptionRequest.ts'
2
+ export * from './trpc/TrpcSubscriptionResponse.ts'
3
+ export * from './trpc/awsLambdaTrpcSubscriptionRequestHandler.ts'
4
+ export * from './buildWsApiRouteHandler.ts'
5
+ export * from './closeConnection.ts'
6
+ export * from './getConnection.ts'
7
+ export * from './sendResponse.ts'
8
+ export * from './WsApi.ts'
@@ -1,6 +1,6 @@
1
1
  import {PostToConnectionCommand} from '@aws-sdk/client-apigatewaymanagementapi'
2
- import type {WsApiConnection, WsApiResponse} from '../types'
3
- import {getApiGatewayManagementApiClient} from './getApiGatewayManagementApiClient'
2
+ import type {WsApiConnection, WsApiResponse} from '../types/index.ts'
3
+ import {getApiGatewayManagementApiClient} from './getApiGatewayManagementApiClient.ts'
4
4
 
5
5
  export const sendResponse = async <Response extends WsApiResponse>(
6
6
  {endpoint, connectionId}: WsApiConnection,
@@ -1,10 +1,10 @@
1
1
  import {GetConnectionCommand, GoneException, PostToConnectionCommand} from '@aws-sdk/client-apigatewaymanagementapi'
2
2
  import {type AnyTRPCRouter, callTRPCProcedure, type inferRouterContext} from '@trpc/server'
3
3
  import type {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from 'aws-lambda'
4
- import {getApiGatewayManagementApiClient} from '../getApiGatewayManagementApiClient'
5
- import {getConnection} from '../getConnection'
6
- import type {TrpcSubscriptionRequest} from './TrpcSubscriptionRequest'
7
- import type {TrpcSubscriptionResponse} from './TrpcSubscriptionResponse'
4
+ import {getApiGatewayManagementApiClient} from '../getApiGatewayManagementApiClient.ts'
5
+ import {getConnection} from '../getConnection.ts'
6
+ import type {TrpcSubscriptionRequest} from './TrpcSubscriptionRequest.ts'
7
+ import type {TrpcSubscriptionResponse} from './TrpcSubscriptionResponse.ts'
8
8
 
9
9
  export const awsLambdaTrpcSubscriptionRequestHandler = <Router extends AnyTRPCRouter>(opts: {
10
10
  router: Router
@@ -57,7 +57,7 @@ export const awsLambdaTrpcSubscriptionRequestHandler = <Router extends AnyTRPCRo
57
57
  })
58
58
 
59
59
  const procedure: AsyncGenerator = await callTRPCProcedure({
60
- _def: router._def,
60
+ router,
61
61
  path: request.params.path,
62
62
  type: 'subscription',
63
63
  ctx: (await createContext?.(event)) || {},
@@ -1,6 +1,6 @@
1
- import type {OmitNever} from '../../types/OmitNever'
2
- import type {WsApiConnection} from './WsApiConnection'
3
- import type {WsApiAction, WsApiRequest, WsApiResponse} from './common'
1
+ import type {OmitNever} from '../../types/OmitNever.ts'
2
+ import type {WsApiConnection} from './WsApiConnection.ts'
3
+ import type {WsApiAction, WsApiRequest, WsApiResponse} from './common/index.ts'
4
4
 
5
5
  export type WsApiRequestHandler<
6
6
  Action extends WsApiAction,
@@ -1,4 +1,4 @@
1
- import type {WsApiAction, WsApiRequest, WsApiResponse} from './common'
1
+ import type {WsApiAction, WsApiRequest, WsApiResponse} from './common/index.ts'
2
2
 
3
3
  export type WsApiRoute<
4
4
  Action extends WsApiAction,
@@ -1,6 +1,6 @@
1
- import type {WsApiRequestHandler} from './WsApiRequestHandler'
2
- import type {WsApiRoute} from './WsApiRoute'
3
- import type {WsApiAction, WsApiRequest, WsApiResponse} from './common'
1
+ import type {WsApiRequestHandler} from './WsApiRequestHandler.ts'
2
+ import type {WsApiRoute} from './WsApiRoute.ts'
3
+ import type {WsApiAction, WsApiRequest, WsApiResponse} from './common/index.ts'
4
4
 
5
5
  export type WsApiRouteHandler<
6
6
  Action extends WsApiAction,
@@ -1,3 +1,3 @@
1
- import type {WsApiAction} from './WsApiAction'
1
+ import type {WsApiAction} from './WsApiAction.ts'
2
2
 
3
3
  export type WsApiRequest<Action extends WsApiAction> = {action: Action}
@@ -1,3 +1,3 @@
1
- export * from './WsApiRequest'
2
- export * from './WsApiResponse'
3
- export * from './WsApiAction'
1
+ export * from './WsApiRequest.ts'
2
+ export * from './WsApiResponse.ts'
3
+ export * from './WsApiAction.ts'
@@ -1,5 +1,5 @@
1
- export * from './common'
2
- export * from './WsApiConnection'
3
- export * from './WsApiRequestHandler'
4
- export * from './WsApiRoute'
5
- export * from './WsApiRouteHandler'
1
+ export * from './common/index.ts'
2
+ export * from './WsApiConnection.ts'
3
+ export * from './WsApiRequestHandler.ts'
4
+ export * from './WsApiRoute.ts'
5
+ export * from './WsApiRouteHandler.ts'
@@ -1,49 +0,0 @@
1
- import type {
2
- RestApiRequest,
3
- RestApiRoute,
4
- RestPathParams,
5
- RestQueryParams,
6
- RestRequestBody,
7
- RestRequestHeaders,
8
- RestRequestHeadersWithAuthorization,
9
- RestRequireAuthentication,
10
- RestResponseBody,
11
- } from '../types'
12
- import type {OmitNever} from '../types/internal'
13
-
14
- export async function fetchRestApi<
15
- PathParams extends RestPathParams,
16
- QueryParams extends RestQueryParams,
17
- RequestHeaders extends RestRequestHeaders,
18
- RequestBody extends RestRequestBody,
19
- ResponseBody extends RestResponseBody,
20
- RequireAuthentication extends RestRequireAuthentication,
21
- >(
22
- restApiUrl: string,
23
- route: RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>,
24
- request: OmitNever<
25
- RestApiRequest<
26
- PathParams,
27
- QueryParams,
28
- RestRequestHeadersWithAuthorization<RequestHeaders, RequireAuthentication>,
29
- RequestBody
30
- >
31
- >,
32
- ): Promise<ResponseBody> {
33
- const pathParams = 'pathParams' in request ? (request.pathParams as PathParams) : (undefined as never)
34
- const queryParams = 'queryParams' in request ? (request.queryParams as QueryParams) : (undefined as never)
35
- const headers = 'headers' in request ? (request.headers as RequestHeaders) : (undefined as never)
36
- const body = 'body' in request ? (request.body as RequestBody) : (undefined as never)
37
-
38
- const url = `${restApiUrl}${route.buildUrl(pathParams, queryParams)}`
39
- const response = await fetch(url, {
40
- method: route.method,
41
- headers: headers,
42
- body: body ? JSON.stringify(body) : null,
43
- })
44
-
45
- if (!response.ok && response.status !== 404) throw `Request ${route.method} ${url} failed`
46
-
47
- const text = (await response.text()).trim()
48
- return response.ok && response.status !== 204 ? (text ? JSON.parse(text) : null) : null
49
- }
@@ -1 +0,0 @@
1
- export * from './fetchRestApi'
@@ -1,153 +0,0 @@
1
- import type {APIGatewayProxyEvent, APIGatewayProxyResult} from 'aws-lambda'
2
- import {URL} from 'node:url'
3
- import {match} from 'path-to-regexp'
4
- import type {
5
- RestApiRouteHandler,
6
- RestPathParams,
7
- RestQueryParams,
8
- RestRequestBody,
9
- RestRequestHeaders,
10
- RestRequireAuthentication,
11
- RestResponseBody,
12
- } from '../types'
13
-
14
- const DEFAULT_RESPONSE_HEADERS = {
15
- 'Content-Type': 'application/json; charset=utf-8',
16
- 'Access-Control-Allow-Origin': '*',
17
- 'Access-Control-Allow-Methods': '*',
18
- 'Access-Control-Allow-Headers': '*',
19
- } as const
20
-
21
- export type AnyRestApiRouteHandler = RestApiRouteHandler<
22
- RestPathParams,
23
- RestQueryParams,
24
- RestRequestHeaders,
25
- RestRequestBody,
26
- RestResponseBody,
27
- RestRequireAuthentication
28
- >
29
-
30
- type MatchingRouteHandler = {
31
- routeHandler: AnyRestApiRouteHandler
32
- pathParams: RestPathParams
33
- }
34
-
35
- export class RestApi {
36
- private routeHandlers: AnyRestApiRouteHandler[] = []
37
-
38
- addRouteHandler<
39
- PathParams extends RestPathParams,
40
- QueryParams extends RestQueryParams,
41
- RequestHeaders extends RestRequestHeaders,
42
- RequestBody extends RestRequestBody,
43
- ResponseBody extends RestResponseBody,
44
- RequireAuthentication extends RestRequireAuthentication,
45
- >(
46
- routeHandler: RestApiRouteHandler<
47
- PathParams,
48
- QueryParams,
49
- RequestHeaders,
50
- RequestBody,
51
- ResponseBody,
52
- RequireAuthentication
53
- >,
54
- ) {
55
- this.routeHandlers = [...this.routeHandlers, routeHandler as unknown as AnyRestApiRouteHandler]
56
- return this
57
- }
58
-
59
- async handleRequest(
60
- proxyEvent: APIGatewayProxyEvent,
61
- onError?: (error: unknown) => Promise<void>,
62
- ): Promise<APIGatewayProxyResult> {
63
- try {
64
- const url = new URL(`${proxyEvent.headers?.['origin'] || 'https://no-origin'}${proxyEvent.path}`)
65
- const routeHandler = this.getMatchingRouteHandler(url, proxyEvent.httpMethod)
66
- if (!routeHandler && proxyEvent.httpMethod === 'OPTIONS') {
67
- const allowedHttpMethods = this.getMatchingRouteHandlers(url).map(({routeHandler}) => routeHandler.route.method)
68
- return {
69
- statusCode: 200,
70
- body: '',
71
- headers: {
72
- ...DEFAULT_RESPONSE_HEADERS,
73
- 'Access-Control-Allow-Methods': allowedHttpMethods.join(','),
74
- },
75
- }
76
- }
77
-
78
- if (!routeHandler)
79
- return {
80
- statusCode: 405,
81
- headers: DEFAULT_RESPONSE_HEADERS,
82
- body: `Method ${proxyEvent.httpMethod} not allowed on ${proxyEvent.path}`,
83
- }
84
-
85
- const {
86
- routeHandler: {requestHandler, route},
87
- pathParams,
88
- } = routeHandler
89
- const userId = proxyEvent.requestContext.authorizer?.principalId
90
- if (route.requireAuthentication && !userId) return {statusCode: 401, body: ''}
91
-
92
- const responseBody = await requestHandler({
93
- pathParams,
94
- queryParams: this.extractQueryParams(proxyEvent),
95
- headers: proxyEvent.headers as Record<string, string>,
96
- body: proxyEvent.body && JSON.parse(proxyEvent.body),
97
- userId,
98
- })
99
- return {
100
- statusCode: responseBody.status,
101
- body: responseBody.body ? JSON.stringify(responseBody.body) : '',
102
- headers: {
103
- ...DEFAULT_RESPONSE_HEADERS,
104
- ...(responseBody.headers || {}),
105
- },
106
- }
107
- } catch (error) {
108
- if (onError) await onError(error)
109
-
110
- console.error(error)
111
- return {
112
- statusCode: 500,
113
- headers: DEFAULT_RESPONSE_HEADERS,
114
- body: JSON.stringify({error}),
115
- }
116
- }
117
- }
118
-
119
- private getMatchingRouteHandlers(url: URL): MatchingRouteHandler[] {
120
- return this.routeHandlers.reduce((matchingRouteHandlers, routeHandler) => {
121
- const pathParams = this.extractPathParams(routeHandler, url)
122
- const matchingRouteHandler: MatchingRouteHandler = {
123
- routeHandler,
124
- pathParams,
125
- }
126
- return pathParams ? [...matchingRouteHandlers, matchingRouteHandler] : matchingRouteHandlers
127
- }, [] as MatchingRouteHandler[])
128
- }
129
-
130
- private getMatchingRouteHandler(url: URL, httpMethod: string): MatchingRouteHandler | undefined {
131
- return this.getMatchingRouteHandlers(url).find(({routeHandler}) => routeHandler.route.method === httpMethod)
132
- }
133
-
134
- private extractPathParams(routeHandler: AnyRestApiRouteHandler, url: URL): object | never {
135
- const matcher = match(routeHandler.route.path, {decode: decodeURIComponent})
136
- return (matcher(url.pathname) || null)?.params as object | never
137
- }
138
-
139
- private extractQueryParams(proxyEvent: APIGatewayProxyEvent): object {
140
- const keys = new Set([
141
- ...Object.keys(proxyEvent.queryStringParameters || {}),
142
- ...Object.keys(proxyEvent.multiValueQueryStringParameters || {}),
143
- ])
144
-
145
- const queryParams: {[key: string]: string | string[] | undefined} = {}
146
- for (const key of keys.values()) {
147
- const singleValue = proxyEvent.queryStringParameters?.[key]
148
- const multiValues = proxyEvent.multiValueQueryStringParameters?.[key] || []
149
- queryParams[key] = multiValues.length > 1 ? multiValues : singleValue
150
- }
151
- return queryParams
152
- }
153
- }
@@ -1,33 +0,0 @@
1
- import type {
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<
23
- PathParams,
24
- QueryParams,
25
- RequestHeaders,
26
- RequestBody,
27
- ResponseBody,
28
- RequireAuthentication
29
- >,
30
- ): RestApiRouteHandler<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication> => ({
31
- route,
32
- requestHandler,
33
- })
@@ -1,2 +0,0 @@
1
- export * from './buildRestApiRouteHandler'
2
- export * from './RestApi'
@@ -1,14 +0,0 @@
1
- import type {RestPathParams, RestQueryParams, RestRequestBody, RestRequestHeaders} from './common'
2
- import type {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,
9
- > = OmitNever<{
10
- pathParams: PathParams
11
- queryParams: QueryParams
12
- headers: RequestHeaders
13
- body: RequestBody
14
- }>
@@ -1,21 +0,0 @@
1
- import type {RestApiResponse} from './RestApiResponse'
2
- import type {
3
- RestPathParams,
4
- RestQueryParams,
5
- RestRequestBody,
6
- RestRequestHeaders,
7
- RestRequireAuthentication,
8
- RestResponseBody,
9
- } from './common'
10
- import type {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
- > = (
20
- request: InternalRestApiRequest<PathParams, QueryParams, RequestHeaders, RequestBody, RequireAuthentication>,
21
- ) => Promise<RestApiResponse<ResponseBody>>
@@ -1,10 +0,0 @@
1
- import type {RestApiRequestHandler} from './RestApiRequestHandler'
2
- import type {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>
@@ -1,5 +0,0 @@
1
- export interface RestApiResponse<ResponseBody> {
2
- status: number
3
- headers?: Record<string, string>
4
- body?: ResponseBody
5
- }
@@ -1,32 +0,0 @@
1
- import type {
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
- }
@@ -1,29 +0,0 @@
1
- import type {RestApiRequestHandler} from './RestApiRequestHandler'
2
- import type {RestApiRoute} from './RestApiRoute'
3
- import type {
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
- > {
20
- route: RestApiRoute<PathParams, QueryParams, RequestHeaders, RequestBody, ResponseBody, RequireAuthentication>
21
- requestHandler: RestApiRequestHandler<
22
- PathParams,
23
- QueryParams,
24
- RequestHeaders,
25
- RequestBody,
26
- ResponseBody,
27
- RequireAuthentication
28
- >
29
- }
@@ -1,10 +0,0 @@
1
- import type {RestApiRoute} from './RestApiRoute'
2
- import type {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>
@@ -1 +0,0 @@
1
- export type BearerToken = `Bearer ${string}`
@@ -1 +0,0 @@
1
- export type RestPathParams = object | never
@@ -1 +0,0 @@
1
- export type RestQueryParams = object | never
@@ -1 +0,0 @@
1
- export type RestRequestBody = object | never
@@ -1 +0,0 @@
1
- export type RestRequestHeaders = Record<string, string> | never
@@ -1,13 +0,0 @@
1
- import type {BearerToken} from './BearerToken'
2
- import type {RestRequestHeaders} from './RestRequestHeaders'
3
- import type {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]
11
- ? AuthorizationHeader
12
- : RequestHeaders & AuthorizationHeader
13
- : RequestHeaders
@@ -1 +0,0 @@
1
- export type RestRequireAuthentication = boolean
@@ -1 +0,0 @@
1
- export type RestResponseBody = object | object[] | null | never
@@ -1,8 +0,0 @@
1
- export * from './BearerToken'
2
- export * from './RestRequestHeadersWithAuthorization'
3
- export * from './RestPathParams'
4
- export * from './RestQueryParams'
5
- export * from './RestRequestBody'
6
- export * from './RestRequestHeaders'
7
- export * from './RestRequireAuthentication'
8
- export * from './RestResponseBody'
@@ -1,8 +0,0 @@
1
- export * from './common'
2
- export * from './RestApiRequest'
3
- export * from './RestApiRequestHandler'
4
- export * from './RestApiRequestHandlerWithAuthentication'
5
- export * from './RestApiResponse'
6
- export * from './RestApiRoute'
7
- export * from './RestApiRouteHandler'
8
- export * from './RestApiRouteWithAuthentication'
@@ -1,21 +0,0 @@
1
- import type {OmitNever} from '../../../types/OmitNever'
2
- import type {RestApiRequest} from '../RestApiRequest'
3
- import type {
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,
17
- > = OmitNever<
18
- RestApiRequest<PathParams, QueryParams, RequestHeaders, RequestBody> & {
19
- userId: RequireAuthentication extends true ? string : never
20
- }
21
- >
@@ -1,2 +0,0 @@
1
- export * from './InternalRestApiRequest'
2
- export * from '../../../types/OmitNever'
@@ -1 +0,0 @@
1
- export * from './useWsApi'
@@ -1,99 +0,0 @@
1
- import {useCallback, useMemo, useRef, useState} from 'react'
2
- import type {WsApiAction, WsApiRequest, WsApiResponse, WsApiRoute} from '../types'
3
-
4
- type WsApiConnection<Action extends WsApiAction, Request extends WsApiRequest<Action>> = {
5
- disconnect: () => void
6
- sendRequest: (request: Request) => void
7
- }
8
-
9
- export type WsApiOptions<
10
- Action extends WsApiAction,
11
- Request extends WsApiRequest<Action>,
12
- Response extends WsApiResponse,
13
- > = {
14
- onConnected?: (connection: WsApiConnection<Action, Request>) => void
15
- onResponse?: (response: Response, connection: WsApiConnection<Action, Request>) => void
16
- onError?: (error: unknown, connection: WsApiConnection<Action, Request>) => void
17
- onDisconnected?: () => void
18
- }
19
- export const useWsApi = <
20
- Action extends WsApiAction,
21
- Request extends WsApiRequest<Action>,
22
- Response extends WsApiResponse,
23
- >(
24
- wsApiUrl: string,
25
- route: WsApiRoute<Action, Request, Response> | undefined,
26
- ) => {
27
- const [error, setError] = useState<boolean>(false)
28
- const [connected, setConnected] = useState<boolean>(false)
29
- const webSocket = useRef<WebSocket>(undefined)
30
-
31
- const sendRequest = useCallback(
32
- (request: Omit<Request, 'action'>) => {
33
- if (!route) throw 'Not route'
34
- if (!webSocket.current) throw 'Not connected'
35
-
36
- webSocket.current.send(
37
- JSON.stringify({
38
- ...request,
39
- action: route.action,
40
- }),
41
- )
42
- },
43
- [route],
44
- )
45
- const disconnect = useCallback(() => webSocket.current?.close(), [])
46
- const connection: WsApiConnection<Action, Request> = useMemo(() => {
47
- return {
48
- disconnect,
49
- sendRequest,
50
- }
51
- }, [disconnect, sendRequest])
52
-
53
- const connect = useCallback(
54
- async (options: WsApiOptions<Action, Request, Response>) => {
55
- return new Promise<void>((resolve) => {
56
- if (
57
- webSocket.current &&
58
- (webSocket.current.readyState === WebSocket.CONNECTING || webSocket.current.readyState === WebSocket.OPEN)
59
- )
60
- throw 'Already connected'
61
-
62
- webSocket.current = new WebSocket(wsApiUrl)
63
-
64
- webSocket.current.onopen = () => {
65
- setConnected(true)
66
- setError(false)
67
- options?.onConnected?.(connection)
68
- }
69
-
70
- webSocket.current.onmessage = (ev) => {
71
- if (!ev.data || ev.data === '{}') return
72
- setError(false)
73
- options?.onResponse?.(JSON.parse(ev.data as string), connection)
74
- }
75
-
76
- webSocket.current.onerror = (error) => {
77
- setError(false)
78
- options?.onError?.(error, connection)
79
- }
80
-
81
- webSocket.current.onclose = () => {
82
- setConnected(false)
83
- options?.onDisconnected?.()
84
- webSocket.current = undefined
85
- resolve()
86
- }
87
- })
88
- },
89
- [wsApiUrl, connection],
90
- )
91
-
92
- return {
93
- connect,
94
- connected,
95
- connection,
96
- error,
97
- disconnect,
98
- }
99
- }