@rest-vir/define-service 0.0.2
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/LICENSE-CC0 +121 -0
- package/LICENSE-MIT +21 -0
- package/README.md +88 -0
- package/dist/augments/json.d.ts +8 -0
- package/dist/augments/json.js +13 -0
- package/dist/dev-port/find-dev-port.d.ts +132 -0
- package/dist/dev-port/find-dev-port.js +156 -0
- package/dist/endpoint/endpoint-path.d.ts +27 -0
- package/dist/endpoint/endpoint-path.js +14 -0
- package/dist/endpoint/endpoint.d.ts +198 -0
- package/dist/endpoint/endpoint.js +80 -0
- package/dist/frontend-connect/connect-web-socket.d.ts +95 -0
- package/dist/frontend-connect/connect-web-socket.js +64 -0
- package/dist/frontend-connect/fetch-endpoint.d.ts +199 -0
- package/dist/frontend-connect/fetch-endpoint.js +135 -0
- package/dist/frontend-connect/generate-api.d.ts +102 -0
- package/dist/frontend-connect/generate-api.js +83 -0
- package/dist/frontend-connect/mock-client-web-socket.d.ts +187 -0
- package/dist/frontend-connect/mock-client-web-socket.js +198 -0
- package/dist/frontend-connect/web-socket-protocol-parse.d.ts +10 -0
- package/dist/frontend-connect/web-socket-protocol-parse.js +111 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/service/define-service.d.ts +19 -0
- package/dist/service/define-service.js +133 -0
- package/dist/service/match-url.d.ts +30 -0
- package/dist/service/match-url.js +31 -0
- package/dist/service/minimal-service.d.ts +44 -0
- package/dist/service/minimal-service.js +1 -0
- package/dist/service/service-definition.d.ts +80 -0
- package/dist/service/service-definition.error.d.ts +35 -0
- package/dist/service/service-definition.error.js +34 -0
- package/dist/service/service-definition.js +1 -0
- package/dist/util/mock-fetch.d.ts +107 -0
- package/dist/util/mock-fetch.js +199 -0
- package/dist/util/no-param.d.ts +16 -0
- package/dist/util/no-param.js +8 -0
- package/dist/util/origin.d.ts +43 -0
- package/dist/util/origin.js +19 -0
- package/dist/util/path-to-regexp.d.ts +54 -0
- package/dist/util/path-to-regexp.js +307 -0
- package/dist/util/search-params.d.ts +9 -0
- package/dist/util/search-params.js +1 -0
- package/dist/web-socket/common-web-socket.d.ts +103 -0
- package/dist/web-socket/common-web-socket.js +28 -0
- package/dist/web-socket/overwrite-web-socket-methods.d.ts +276 -0
- package/dist/web-socket/overwrite-web-socket-methods.js +210 -0
- package/dist/web-socket/web-socket-definition.d.ts +170 -0
- package/dist/web-socket/web-socket-definition.js +78 -0
- package/package.json +68 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { addPrefix, filterMap, getObjectTypedEntries, HttpMethod, } from '@augment-vir/common';
|
|
3
|
+
import { assertValidShape } from 'object-shape-tester';
|
|
4
|
+
import { buildUrl } from 'url-vir';
|
|
5
|
+
function defaultFetch(...[url, requestInit,]) {
|
|
6
|
+
return fetch(url, requestInit);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Extracts an array of all allowed methods for the given endpoint definition.
|
|
10
|
+
*
|
|
11
|
+
* @category Internal
|
|
12
|
+
* @category Package : @rest-vir/define-service
|
|
13
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
14
|
+
*/
|
|
15
|
+
export function getAllowedEndpointMethods(endpoint) {
|
|
16
|
+
return filterMap(getObjectTypedEntries(endpoint.methods), ([methodName,]) => methodName, (methodName, [, allowed,]) => allowed);
|
|
17
|
+
}
|
|
18
|
+
function filterToValidMethod(endpoint, chosenMethod) {
|
|
19
|
+
if (chosenMethod && (chosenMethod === HttpMethod.Options || endpoint.methods[chosenMethod])) {
|
|
20
|
+
return chosenMethod;
|
|
21
|
+
}
|
|
22
|
+
else if (chosenMethod) {
|
|
23
|
+
throw new Error(`Given HTTP method '${chosenMethod}' is not allowed for endpoint '${endpoint.path}' in service '${endpoint.service.serviceName}'`);
|
|
24
|
+
}
|
|
25
|
+
const allowedMethods = getAllowedEndpointMethods(endpoint);
|
|
26
|
+
if (check.isLengthExactly(allowedMethods, 1)) {
|
|
27
|
+
return allowedMethods[0];
|
|
28
|
+
}
|
|
29
|
+
else if (allowedMethods.length) {
|
|
30
|
+
throw new Error(`Endpoint '${endpoint.path}' in service '${endpoint.service.serviceName}' allows multiple HTTP methods, one must be chosen.`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new Error(`Endpoint '${endpoint.path}' in service '${endpoint.service.serviceName}' has no allowed HTTP methods. Requests cannot be sent.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Send a request to an endpoint definition with type safe parameters.
|
|
38
|
+
*
|
|
39
|
+
* This can safely be used in frontend _or_ backend code.
|
|
40
|
+
*
|
|
41
|
+
* @category Client (Frontend) Connection
|
|
42
|
+
* @category Package : @rest-vir/define-service
|
|
43
|
+
* @example
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* import {fetchEndpoint} from '@rest-vir/define-service';
|
|
47
|
+
*
|
|
48
|
+
* const {data, response} = await fetchEndpoint(myService.endpoints['/my-endpoint']);
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
52
|
+
*/
|
|
53
|
+
export async function fetchEndpoint(endpoint, ...params) {
|
|
54
|
+
const { requestData, fetch } = params[0] || {};
|
|
55
|
+
if (requestData) {
|
|
56
|
+
if (endpoint.requestDataShape) {
|
|
57
|
+
assertValidShape(requestData, endpoint.requestDataShape, { allowExtraKeys: true });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
throw new Error(`Request data was given but endpoint '${endpoint.path}' is not expecting any request data.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { requestInit, url } = buildEndpointRequestInit(endpoint, ...params);
|
|
64
|
+
/* node:coverage ignore next: all tests mock fetch so we're never going to have a fallback here. */
|
|
65
|
+
const response = await (fetch || defaultFetch)(url, requestInit, endpoint);
|
|
66
|
+
const responseData = endpoint.responseDataShape ? await response.json() : undefined;
|
|
67
|
+
if (endpoint.responseDataShape) {
|
|
68
|
+
assertValidShape(responseData, endpoint.responseDataShape, { allowExtraKeys: true });
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
data: responseData,
|
|
72
|
+
response,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build request init and URL for fetching an endpoint. Used in {@link fetchEndpoint}.
|
|
77
|
+
*
|
|
78
|
+
* @category Internal
|
|
79
|
+
* @category Package : @rest-vir/define-service
|
|
80
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
81
|
+
*/
|
|
82
|
+
export function buildEndpointRequestInit(endpoint, ...[{ method, options = {}, pathParams, requestData } = {},]) {
|
|
83
|
+
const headers = options.headers instanceof Headers
|
|
84
|
+
? Object.fromEntries(options.headers.entries())
|
|
85
|
+
: check.isArray(options.headers)
|
|
86
|
+
? Object.fromEntries(options.headers)
|
|
87
|
+
: options.headers || {};
|
|
88
|
+
const hasContentTypeHeader = Object.keys(headers).some((headerKey) => headerKey.toLowerCase() === 'content-type');
|
|
89
|
+
if (!hasContentTypeHeader && check.isObject(requestData)) {
|
|
90
|
+
headers['content-type'] = 'application/json';
|
|
91
|
+
}
|
|
92
|
+
const url = buildEndpointUrl(endpoint, { pathParams });
|
|
93
|
+
const requestInit = {
|
|
94
|
+
...options,
|
|
95
|
+
headers,
|
|
96
|
+
method: filterToValidMethod(endpoint, method),
|
|
97
|
+
...(requestData
|
|
98
|
+
? {
|
|
99
|
+
body: JSON.stringify(requestData),
|
|
100
|
+
}
|
|
101
|
+
: {}),
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
url,
|
|
105
|
+
requestInit,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates and finalizes a URL for sending fetches to the given endpoint.
|
|
110
|
+
*
|
|
111
|
+
* @category Internal
|
|
112
|
+
* @category Package : @rest-vir/define-service
|
|
113
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
114
|
+
*/
|
|
115
|
+
export function buildEndpointUrl(endpoint, { pathParams, }) {
|
|
116
|
+
let pathParamsCount = 0;
|
|
117
|
+
const builtUrl = buildUrl(endpoint.service.serviceOrigin, {
|
|
118
|
+
pathname: endpoint.path.replaceAll(/\/:([^/]+)/g, (wholeMatch, paramName) => {
|
|
119
|
+
pathParamsCount++;
|
|
120
|
+
if (pathParams && check.hasKey(pathParams, paramName) && pathParams[paramName]) {
|
|
121
|
+
return addPrefix({
|
|
122
|
+
value: pathParams[paramName],
|
|
123
|
+
prefix: '/',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
throw new Error(`Missing value for path param '${paramName}'.`);
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
}).href;
|
|
131
|
+
if (!pathParamsCount && pathParams) {
|
|
132
|
+
throw new Error(`Endpoint '${endpoint.path}' in service '${endpoint.service.serviceName}' does not allow any path params but some where set.`);
|
|
133
|
+
}
|
|
134
|
+
return builtUrl;
|
|
135
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { MaybePromise, Overwrite, PartialWithUndefined, type Values } from '@augment-vir/common';
|
|
2
|
+
import type { GenericEndpointDefinition } from '../endpoint/endpoint.js';
|
|
3
|
+
import type { ServiceDefinition } from '../service/service-definition.js';
|
|
4
|
+
import type { CommonWebSocket } from '../web-socket/common-web-socket.js';
|
|
5
|
+
import { ClientWebSocket, GenericConnectWebSocketParams } from '../web-socket/overwrite-web-socket-methods.js';
|
|
6
|
+
import type { WebSocketDefinition } from '../web-socket/web-socket-definition.js';
|
|
7
|
+
import { CollapsedConnectWebSocketParams } from './connect-web-socket.js';
|
|
8
|
+
import { CollapsedFetchEndpointParams, FetchEndpointOutput } from './fetch-endpoint.js';
|
|
9
|
+
/**
|
|
10
|
+
* An API generated by {@link generateApi}. This can be easily mocked by wrapping this API in
|
|
11
|
+
* {@link makeMockApi}.
|
|
12
|
+
*
|
|
13
|
+
* @category Internal
|
|
14
|
+
* @category Package : @rest-vir/define-service
|
|
15
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
16
|
+
*/
|
|
17
|
+
export type RestVirApi<SpecificService extends ServiceDefinition> = {
|
|
18
|
+
endpoints: {
|
|
19
|
+
[EndpointPath in keyof SpecificService['endpoints']]: SpecificService['endpoints'][EndpointPath] extends GenericEndpointDefinition ? SpecificService['endpoints'][EndpointPath] & {
|
|
20
|
+
/** Send a fetch request to this endpoint. */
|
|
21
|
+
fetch(...params: CollapsedFetchEndpointParams<SpecificService['endpoints'][EndpointPath]>): Promise<FetchEndpointOutput<SpecificService['endpoints'][EndpointPath]>>;
|
|
22
|
+
} : never;
|
|
23
|
+
};
|
|
24
|
+
webSockets: {
|
|
25
|
+
[WebSocketPath in keyof SpecificService['webSockets']]: SpecificService['webSockets'][WebSocketPath] extends WebSocketDefinition ? SpecificService['webSockets'][WebSocketPath] & {
|
|
26
|
+
/** Connect to this WebSocket. */
|
|
27
|
+
connect(...params: CollapsedConnectWebSocketParams<SpecificService['webSockets'][WebSocketPath], false, globalThis.WebSocket>): Promise<ClientWebSocket<SpecificService['webSockets'][WebSocketPath], globalThis.WebSocket>>;
|
|
28
|
+
} : never;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Creates an API from the given service definition, with automatically generated endpoint fetch and
|
|
33
|
+
* WebSocket connect methods.
|
|
34
|
+
*
|
|
35
|
+
* Use this to ship auto-generated API objects to internal or external teams so they have type safe
|
|
36
|
+
* access to your REST and WebSocket server.
|
|
37
|
+
*
|
|
38
|
+
* The generated API can easily mocked by wrapping it in {@link makeMockApi}.
|
|
39
|
+
*
|
|
40
|
+
* @category Create API
|
|
41
|
+
* @category Package : @rest-vir/define-service
|
|
42
|
+
* @example
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* import {generateApi} from '@rest-vir/define-service';
|
|
46
|
+
*
|
|
47
|
+
* export const myApi = generateApi(myServiceDefinition);
|
|
48
|
+
*
|
|
49
|
+
* myApi.endpoints['/my-endpoint'].fetch();
|
|
50
|
+
* myApi.webSockets['/my-web-socket'].connect();
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
54
|
+
*/
|
|
55
|
+
export declare function generateApi<const SpecificService extends ServiceDefinition>(service: SpecificService): RestVirApi<SpecificService>;
|
|
56
|
+
/**
|
|
57
|
+
* Mock inputs for {@link makeMockApi}.
|
|
58
|
+
*
|
|
59
|
+
* @category Internal
|
|
60
|
+
* @category Package : @rest-vir/define-service
|
|
61
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
62
|
+
*/
|
|
63
|
+
export type RestVirApiMocks<Api extends RestVirApi<any>, WebSocketClass extends CommonWebSocket> = PartialWithUndefined<{
|
|
64
|
+
/**
|
|
65
|
+
* A custom fetch mock that will override the default, if provided. This can safely be omitted
|
|
66
|
+
* to use the default JavaScript built-in global `fetch` function.
|
|
67
|
+
*
|
|
68
|
+
* See {@link createMockEndpointFetch} or {@link createMockEndpointResponse} for assistance
|
|
69
|
+
* mocking this.
|
|
70
|
+
*
|
|
71
|
+
* @default globalThis.fetch
|
|
72
|
+
*/
|
|
73
|
+
fetch?: (url: string, requestInit: RequestInit, endpoint?: Values<Api['endpoints']> | undefined) => MaybePromise<Response>;
|
|
74
|
+
webSocketConstructor?: GenericConnectWebSocketParams<WebSocketClass>['webSocketConstructor'];
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Overwrites types for an existing {@link RestVirApi} instance with mocks. This is created by
|
|
78
|
+
* {@link makeMockApi}.
|
|
79
|
+
*
|
|
80
|
+
* @category Internal
|
|
81
|
+
* @category Package : @rest-vir/define-service
|
|
82
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
83
|
+
*/
|
|
84
|
+
export type MockExistingApi<Api extends RestVirApi<any>, WebSocketClass extends CommonWebSocket> = Overwrite<Api, {
|
|
85
|
+
webSockets: {
|
|
86
|
+
[WebSocketPath in keyof Api['webSockets']]: Api['webSockets'][WebSocketPath] extends WebSocketDefinition ? Overwrite<Api['webSockets'][WebSocketPath], {
|
|
87
|
+
/** Connect to this WebSocket. */
|
|
88
|
+
connect(...params: CollapsedConnectWebSocketParams<Api['webSockets'][WebSocketPath], false, WebSocketClass>): Promise<ClientWebSocket<Api['webSockets'][WebSocketPath], WebSocketClass>>;
|
|
89
|
+
}> : never;
|
|
90
|
+
};
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Overwrites a generated API's fetch and WebSocket connect methods with a mocked fetch function and
|
|
94
|
+
* a mocked WebSocket constructor so you can have full control over them for unit-testing purposes.
|
|
95
|
+
*
|
|
96
|
+
* With this, you can unit test your clients without spinning up a mock API server.
|
|
97
|
+
*
|
|
98
|
+
* @category Testing : Client (Frontend)
|
|
99
|
+
* @category Package : @rest-vir/define-service
|
|
100
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
101
|
+
*/
|
|
102
|
+
export declare function makeMockApi<const Api extends RestVirApi<any>, const WebSocketClass extends CommonWebSocket>(api: Readonly<Api>, mocks: RestVirApiMocks<Api, WebSocketClass>): MockExistingApi<Api, WebSocketClass>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mapObjectValues, } from '@augment-vir/common';
|
|
2
|
+
import { connectWebSocket } from './connect-web-socket.js';
|
|
3
|
+
import { fetchEndpoint, } from './fetch-endpoint.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates an API from the given service definition, with automatically generated endpoint fetch and
|
|
6
|
+
* WebSocket connect methods.
|
|
7
|
+
*
|
|
8
|
+
* Use this to ship auto-generated API objects to internal or external teams so they have type safe
|
|
9
|
+
* access to your REST and WebSocket server.
|
|
10
|
+
*
|
|
11
|
+
* The generated API can easily mocked by wrapping it in {@link makeMockApi}.
|
|
12
|
+
*
|
|
13
|
+
* @category Create API
|
|
14
|
+
* @category Package : @rest-vir/define-service
|
|
15
|
+
* @example
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import {generateApi} from '@rest-vir/define-service';
|
|
19
|
+
*
|
|
20
|
+
* export const myApi = generateApi(myServiceDefinition);
|
|
21
|
+
*
|
|
22
|
+
* myApi.endpoints['/my-endpoint'].fetch();
|
|
23
|
+
* myApi.webSockets['/my-web-socket'].connect();
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
27
|
+
*/
|
|
28
|
+
export function generateApi(service) {
|
|
29
|
+
return {
|
|
30
|
+
endpoints: mapObjectValues(service.endpoints, (endpointPath, endpointDefinition) => {
|
|
31
|
+
return {
|
|
32
|
+
...endpointDefinition,
|
|
33
|
+
fetch: (...params) => {
|
|
34
|
+
return fetchEndpoint(endpointDefinition, ...params);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}),
|
|
38
|
+
webSockets: mapObjectValues(service.webSockets, (webSocketPath, webSocketDefinition) => {
|
|
39
|
+
return {
|
|
40
|
+
...webSocketDefinition,
|
|
41
|
+
connect: (...params) => {
|
|
42
|
+
return connectWebSocket(webSocketDefinition, ...params);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Overwrites a generated API's fetch and WebSocket connect methods with a mocked fetch function and
|
|
50
|
+
* a mocked WebSocket constructor so you can have full control over them for unit-testing purposes.
|
|
51
|
+
*
|
|
52
|
+
* With this, you can unit test your clients without spinning up a mock API server.
|
|
53
|
+
*
|
|
54
|
+
* @category Testing : Client (Frontend)
|
|
55
|
+
* @category Package : @rest-vir/define-service
|
|
56
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
57
|
+
*/
|
|
58
|
+
export function makeMockApi(api, mocks) {
|
|
59
|
+
return {
|
|
60
|
+
endpoints: mapObjectValues(api.endpoints, (endpointPath, endpointDefinition) => {
|
|
61
|
+
return {
|
|
62
|
+
...endpointDefinition,
|
|
63
|
+
fetch: (...params) => {
|
|
64
|
+
return fetchEndpoint(endpointDefinition, {
|
|
65
|
+
fetch: mocks.fetch,
|
|
66
|
+
...params[0],
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}),
|
|
71
|
+
webSockets: mapObjectValues(api.webSockets, (webSocketPath, webSocketDefinition) => {
|
|
72
|
+
return {
|
|
73
|
+
...webSocketDefinition,
|
|
74
|
+
connect: (...params) => {
|
|
75
|
+
return connectWebSocket(webSocketDefinition, {
|
|
76
|
+
webSocketConstructor: mocks.webSocketConstructor,
|
|
77
|
+
...params[0],
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { CommonWebSocket, CommonWebSocketEventMap, CommonWebSocketState } from '../web-socket/common-web-socket.js';
|
|
3
|
+
import { GenericConnectWebSocketParams, WebSocketLocation, type ClientWebSocket } from '../web-socket/overwrite-web-socket-methods.js';
|
|
4
|
+
import { WebSocketDefinition } from '../web-socket/web-socket-definition.js';
|
|
5
|
+
/**
|
|
6
|
+
* Parameters for {@link MockClientWebSocketClientSendCallback}.
|
|
7
|
+
*
|
|
8
|
+
* @category Internal
|
|
9
|
+
* @category Package : @rest-vir/define-service
|
|
10
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
11
|
+
*/
|
|
12
|
+
export type MockClientWebSocketClientSendCallbackParams<WebSocketToConnect extends WebSocketDefinition> = {
|
|
13
|
+
webSocket: ClientWebSocket<WebSocketToConnect, MockClientWebSocket<WebSocketToConnect>>;
|
|
14
|
+
messageData: WebSocketToConnect['MessageFromClientType'];
|
|
15
|
+
messageSource: WebSocketLocation;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Type for {@link MockClientWebSocket.sendCallback}.
|
|
19
|
+
*
|
|
20
|
+
* @category Internal
|
|
21
|
+
* @category Package : @rest-vir/define-service
|
|
22
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
23
|
+
*/
|
|
24
|
+
export type MockClientWebSocketClientSendCallback<WebSocketToConnect extends WebSocketDefinition> = (params: MockClientWebSocketClientSendCallbackParams<WebSocketToConnect>) => MaybePromise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Options for {@link MockClientWebSocket} and {@link createMockClientWebSocketConstructor}.
|
|
27
|
+
*
|
|
28
|
+
* @category Internal
|
|
29
|
+
* @category Package : @rest-vir/define-service
|
|
30
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
31
|
+
*/
|
|
32
|
+
export type MockClientWebSocketOptions<WebSocketToConnect extends WebSocketDefinition> = PartialWithUndefined<{
|
|
33
|
+
/**
|
|
34
|
+
* Set this to `true` if you want to prevent this mock WebSocket from immediately opening
|
|
35
|
+
* itself. You can then use `.open()` at any time to manually open it.
|
|
36
|
+
*/
|
|
37
|
+
preventImmediateOpen: boolean;
|
|
38
|
+
sendCallback: MockClientWebSocketClientSendCallback<WebSocketToConnect>;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Use this to create a mock WebSocket constructor for unit testing Web Socket connection.
|
|
42
|
+
*
|
|
43
|
+
* This creates a {@link MockClientWebSocket} constructor for connections from the client side with
|
|
44
|
+
* types for the given {@link WebSocketDefinition} and utilizing the given
|
|
45
|
+
* {@link MockClientWebSocketOptions} instance. This can be passed to `connectWebSocket` as the
|
|
46
|
+
* `webSocketConstructor` to allow unit testing on a client without spinning up an entire host to
|
|
47
|
+
* serve the WebSocket connection.
|
|
48
|
+
*
|
|
49
|
+
* @category Testing : Client (Frontend)
|
|
50
|
+
* @category Package : @rest-vir/define-service
|
|
51
|
+
* @example
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* import {
|
|
55
|
+
* connectWebSocket,
|
|
56
|
+
* createMockClientWebSocketConstructor,
|
|
57
|
+
* } from '@rest-vir/define-service';
|
|
58
|
+
*
|
|
59
|
+
* const webSocket = await connectWebSocket(myService.webSockets['/my-path'], {
|
|
60
|
+
* webSocketConstructor: createMockClientWebSocketConstructor(
|
|
61
|
+
* myService.webSockets['/my-path'],
|
|
62
|
+
* {preventImmediateOpen: true},
|
|
63
|
+
* ),
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // mock server responses without an actual server
|
|
67
|
+
* webSocket.sendFromHost(myMessage);
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
71
|
+
*/
|
|
72
|
+
export declare function createMockClientWebSocketConstructor<const WebSocketToConnect extends WebSocketDefinition>(webSocketToConnect: Readonly<WebSocketToConnect>, options?: Readonly<MockClientWebSocketOptions<WebSocketToConnect>>): typeof MockClientWebSocket<WebSocketToConnect>;
|
|
73
|
+
/**
|
|
74
|
+
* Type for {@link MockClientWebSocket.listeners}.
|
|
75
|
+
*
|
|
76
|
+
* @category Internal
|
|
77
|
+
* @category Package : @rest-vir/define-service
|
|
78
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
79
|
+
*/
|
|
80
|
+
export type MockClientWebSocketListeners = Partial<{
|
|
81
|
+
[EventName in keyof CommonWebSocketEventMap]: Set<(event: CommonWebSocketEventMap[EventName]) => void>;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* A mock WebSocket constructor for connections from the client side. This can be passed to
|
|
85
|
+
* `connectWebSocket` as the `webSocketConstructor` to allow unit testing on a client without
|
|
86
|
+
* spinning up an entire host to serve the WebSocket connection.
|
|
87
|
+
*
|
|
88
|
+
* @category Internal
|
|
89
|
+
* @category Package : @rest-vir/define-service
|
|
90
|
+
* @example
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* import {connectWebSocket, MockClientWebSocket} from '@rest-vir/define-service';
|
|
94
|
+
*
|
|
95
|
+
* const webSocket = await connectWebSocket(myService.webSockets['/my-path'], {
|
|
96
|
+
* webSocketConstructor: MockClientWebSocket<(typeof myService.webSockets)['/my-path']>,
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* // mock server responses without an actual server
|
|
100
|
+
* webSocket.sendFromHost(myMessage);
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
|
|
104
|
+
*/
|
|
105
|
+
export declare class MockClientWebSocket<const WebSocketToConnect extends WebSocketDefinition = any> implements CommonWebSocket {
|
|
106
|
+
/**
|
|
107
|
+
* Mocked WebSocket event listeners. While this is public, you should attach a listener using
|
|
108
|
+
* {@link MockClientWebSocket.addEventListener} method and remove them with
|
|
109
|
+
* {@link MockClientWebSocket.removeEventListener}, as you would with a normal WebSocket
|
|
110
|
+
* instance.
|
|
111
|
+
*/
|
|
112
|
+
listeners: MockClientWebSocketListeners;
|
|
113
|
+
/**
|
|
114
|
+
* Implements and mocks the standard `WebSocket.readyState` property.
|
|
115
|
+
*
|
|
116
|
+
* @see https://developer.mozilla.org/docs/Web/API/WebSocket/readyState
|
|
117
|
+
*/
|
|
118
|
+
readyState: CommonWebSocketState;
|
|
119
|
+
/**
|
|
120
|
+
* This callback will be called whenever a message is sent to or from the WebSocket. This is set
|
|
121
|
+
* via the constructor option `sendCallback`.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* import {MockClientWebSocket, connectWebSocket} from '@rest-vir/define-service';
|
|
127
|
+
*
|
|
128
|
+
* const webSocket = await connectWebSocket(myService.webSockets['/my-socket'], {
|
|
129
|
+
* webSocketConstructor: createMockClientWebSocketConstructor(
|
|
130
|
+
* myService.webSockets['/my-socket'],
|
|
131
|
+
* {
|
|
132
|
+
* sendCallback: (message) => {
|
|
133
|
+
* // handle the sent message here
|
|
134
|
+
* console.log('handled message:', message);
|
|
135
|
+
* },
|
|
136
|
+
* },
|
|
137
|
+
* ),
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
sendCallback: MockClientWebSocketClientSendCallback<WebSocketToConnect> | undefined;
|
|
142
|
+
constructor(...[, , , options,]: [
|
|
143
|
+
...ConstructorParameters<NonNullable<GenericConnectWebSocketParams<any>['webSocketConstructor']>>,
|
|
144
|
+
options?: Readonly<MockClientWebSocketOptions<WebSocketToConnect>>
|
|
145
|
+
]);
|
|
146
|
+
/**
|
|
147
|
+
* Manually set the WebSocket connection as opened. This is only necessary to use if you've set
|
|
148
|
+
* the constructor options to
|
|
149
|
+
*/
|
|
150
|
+
open(): void;
|
|
151
|
+
/** Manually close and cleanup the WebSocket. */
|
|
152
|
+
close(): void;
|
|
153
|
+
/**
|
|
154
|
+
* Dispatch a WebSocket event. This is primarily used within the MockClientWebSocket class
|
|
155
|
+
* itself but may also be used externally to test various WebSocket scenarios.
|
|
156
|
+
*
|
|
157
|
+
* Note that dispatching `'open'` and `'close'` events will not actually close or open the
|
|
158
|
+
* WebSocket. Use the {@link MockClientWebSocket.open} and {@link MockClientWebSocket.close}
|
|
159
|
+
* methods instead (which appropriately dispatch their events).
|
|
160
|
+
*/
|
|
161
|
+
dispatchEvent<const EventName extends keyof CommonWebSocketEventMap>(eventName: EventName, event: Omit<CommonWebSocketEventMap[EventName], 'type' | 'target'>): void;
|
|
162
|
+
/**
|
|
163
|
+
* Implements and mocks the standard `WebSocket.addEventListener` property but with message type
|
|
164
|
+
* safety.
|
|
165
|
+
*
|
|
166
|
+
* @see https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener
|
|
167
|
+
*/
|
|
168
|
+
addEventListener<const EventName extends keyof CommonWebSocketEventMap>(eventName: EventName, listener: (event: CommonWebSocketEventMap[EventName]) => void): void;
|
|
169
|
+
/**
|
|
170
|
+
* Implements and mocks the standard `WebSocket.removeEventListener` property but with message
|
|
171
|
+
* type safety.
|
|
172
|
+
*
|
|
173
|
+
* @see https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener
|
|
174
|
+
*/
|
|
175
|
+
removeEventListener<const EventName extends keyof CommonWebSocketEventMap>(eventName: EventName, listener: (event: CommonWebSocketEventMap[EventName]) => void): void;
|
|
176
|
+
/**
|
|
177
|
+
* Implements and mocks the standard `WebSocket.send` property but with message type safety.
|
|
178
|
+
*
|
|
179
|
+
* @see https://developer.mozilla.org/docs/Web/API/WebSocket/send
|
|
180
|
+
*/
|
|
181
|
+
send(data: any): void;
|
|
182
|
+
/**
|
|
183
|
+
* Sends a message as if it came from the WebSocket host. Use this to unit test client-side
|
|
184
|
+
* WebSockets without needing a running host.
|
|
185
|
+
*/
|
|
186
|
+
sendFromHost(data: WebSocketToConnect['MessageFromHostType']): void;
|
|
187
|
+
}
|