@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.
Files changed (50) hide show
  1. package/LICENSE-CC0 +121 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +88 -0
  4. package/dist/augments/json.d.ts +8 -0
  5. package/dist/augments/json.js +13 -0
  6. package/dist/dev-port/find-dev-port.d.ts +132 -0
  7. package/dist/dev-port/find-dev-port.js +156 -0
  8. package/dist/endpoint/endpoint-path.d.ts +27 -0
  9. package/dist/endpoint/endpoint-path.js +14 -0
  10. package/dist/endpoint/endpoint.d.ts +198 -0
  11. package/dist/endpoint/endpoint.js +80 -0
  12. package/dist/frontend-connect/connect-web-socket.d.ts +95 -0
  13. package/dist/frontend-connect/connect-web-socket.js +64 -0
  14. package/dist/frontend-connect/fetch-endpoint.d.ts +199 -0
  15. package/dist/frontend-connect/fetch-endpoint.js +135 -0
  16. package/dist/frontend-connect/generate-api.d.ts +102 -0
  17. package/dist/frontend-connect/generate-api.js +83 -0
  18. package/dist/frontend-connect/mock-client-web-socket.d.ts +187 -0
  19. package/dist/frontend-connect/mock-client-web-socket.js +198 -0
  20. package/dist/frontend-connect/web-socket-protocol-parse.d.ts +10 -0
  21. package/dist/frontend-connect/web-socket-protocol-parse.js +111 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.js +23 -0
  24. package/dist/service/define-service.d.ts +19 -0
  25. package/dist/service/define-service.js +133 -0
  26. package/dist/service/match-url.d.ts +30 -0
  27. package/dist/service/match-url.js +31 -0
  28. package/dist/service/minimal-service.d.ts +44 -0
  29. package/dist/service/minimal-service.js +1 -0
  30. package/dist/service/service-definition.d.ts +80 -0
  31. package/dist/service/service-definition.error.d.ts +35 -0
  32. package/dist/service/service-definition.error.js +34 -0
  33. package/dist/service/service-definition.js +1 -0
  34. package/dist/util/mock-fetch.d.ts +107 -0
  35. package/dist/util/mock-fetch.js +199 -0
  36. package/dist/util/no-param.d.ts +16 -0
  37. package/dist/util/no-param.js +8 -0
  38. package/dist/util/origin.d.ts +43 -0
  39. package/dist/util/origin.js +19 -0
  40. package/dist/util/path-to-regexp.d.ts +54 -0
  41. package/dist/util/path-to-regexp.js +307 -0
  42. package/dist/util/search-params.d.ts +9 -0
  43. package/dist/util/search-params.js +1 -0
  44. package/dist/web-socket/common-web-socket.d.ts +103 -0
  45. package/dist/web-socket/common-web-socket.js +28 -0
  46. package/dist/web-socket/overwrite-web-socket-methods.d.ts +276 -0
  47. package/dist/web-socket/overwrite-web-socket-methods.js +210 -0
  48. package/dist/web-socket/web-socket-definition.d.ts +170 -0
  49. package/dist/web-socket/web-socket-definition.js +78 -0
  50. package/package.json +68 -0
@@ -0,0 +1,198 @@
1
+ import { getOrSet, } from '@augment-vir/common';
2
+ import { parseJsonWithUndefined } from '../augments/json.js';
3
+ import { CommonWebSocketState, } from '../web-socket/common-web-socket.js';
4
+ import { WebSocketLocation, } from '../web-socket/overwrite-web-socket-methods.js';
5
+ /**
6
+ * Use this to create a mock WebSocket constructor for unit testing Web Socket connection.
7
+ *
8
+ * This creates a {@link MockClientWebSocket} constructor for connections from the client side with
9
+ * types for the given {@link WebSocketDefinition} and utilizing the given
10
+ * {@link MockClientWebSocketOptions} instance. This can be passed to `connectWebSocket` as the
11
+ * `webSocketConstructor` to allow unit testing on a client without spinning up an entire host to
12
+ * serve the WebSocket connection.
13
+ *
14
+ * @category Testing : Client (Frontend)
15
+ * @category Package : @rest-vir/define-service
16
+ * @example
17
+ *
18
+ * ```ts
19
+ * import {
20
+ * connectWebSocket,
21
+ * createMockClientWebSocketConstructor,
22
+ * } from '@rest-vir/define-service';
23
+ *
24
+ * const webSocket = await connectWebSocket(myService.webSockets['/my-path'], {
25
+ * webSocketConstructor: createMockClientWebSocketConstructor(
26
+ * myService.webSockets['/my-path'],
27
+ * {preventImmediateOpen: true},
28
+ * ),
29
+ * });
30
+ *
31
+ * // mock server responses without an actual server
32
+ * webSocket.sendFromHost(myMessage);
33
+ * ```
34
+ *
35
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
36
+ */
37
+ export function createMockClientWebSocketConstructor(webSocketToConnect, options = {}) {
38
+ return class extends MockClientWebSocket {
39
+ constructor(...params) {
40
+ super(...params, options);
41
+ }
42
+ };
43
+ }
44
+ /**
45
+ * A mock WebSocket constructor for connections from the client side. This can be passed to
46
+ * `connectWebSocket` as the `webSocketConstructor` to allow unit testing on a client without
47
+ * spinning up an entire host to serve the WebSocket connection.
48
+ *
49
+ * @category Internal
50
+ * @category Package : @rest-vir/define-service
51
+ * @example
52
+ *
53
+ * ```ts
54
+ * import {connectWebSocket, MockClientWebSocket} from '@rest-vir/define-service';
55
+ *
56
+ * const webSocket = await connectWebSocket(myService.webSockets['/my-path'], {
57
+ * webSocketConstructor: MockClientWebSocket<(typeof myService.webSockets)['/my-path']>,
58
+ * });
59
+ *
60
+ * // mock server responses without an actual server
61
+ * webSocket.sendFromHost(myMessage);
62
+ * ```
63
+ *
64
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
65
+ */
66
+ export class MockClientWebSocket {
67
+ /**
68
+ * Mocked WebSocket event listeners. While this is public, you should attach a listener using
69
+ * {@link MockClientWebSocket.addEventListener} method and remove them with
70
+ * {@link MockClientWebSocket.removeEventListener}, as you would with a normal WebSocket
71
+ * instance.
72
+ */
73
+ listeners = {};
74
+ /**
75
+ * Implements and mocks the standard `WebSocket.readyState` property.
76
+ *
77
+ * @see https://developer.mozilla.org/docs/Web/API/WebSocket/readyState
78
+ */
79
+ readyState = CommonWebSocketState.Connecting;
80
+ /**
81
+ * This callback will be called whenever a message is sent to or from the WebSocket. This is set
82
+ * via the constructor option `sendCallback`.
83
+ *
84
+ * @example
85
+ *
86
+ * ```ts
87
+ * import {MockClientWebSocket, connectWebSocket} from '@rest-vir/define-service';
88
+ *
89
+ * const webSocket = await connectWebSocket(myService.webSockets['/my-socket'], {
90
+ * webSocketConstructor: createMockClientWebSocketConstructor(
91
+ * myService.webSockets['/my-socket'],
92
+ * {
93
+ * sendCallback: (message) => {
94
+ * // handle the sent message here
95
+ * console.log('handled message:', message);
96
+ * },
97
+ * },
98
+ * ),
99
+ * });
100
+ * ```
101
+ */
102
+ sendCallback;
103
+ constructor(...[, , , options = {},]) {
104
+ if (!options.preventImmediateOpen) {
105
+ this.open();
106
+ }
107
+ if (options.sendCallback) {
108
+ this.sendCallback = options.sendCallback;
109
+ }
110
+ }
111
+ /**
112
+ * Manually set the WebSocket connection as opened. This is only necessary to use if you've set
113
+ * the constructor options to
114
+ */
115
+ open() {
116
+ setTimeout(() => {
117
+ if (this.readyState === CommonWebSocketState.Connecting) {
118
+ this.readyState = CommonWebSocketState.Open;
119
+ this.dispatchEvent('open', {});
120
+ }
121
+ });
122
+ }
123
+ /** Manually close and cleanup the WebSocket. */
124
+ close() {
125
+ this.dispatchEvent('close', {
126
+ code: 0,
127
+ reason: 'manually closed',
128
+ wasClean: true,
129
+ });
130
+ this.listeners = {};
131
+ this.readyState = CommonWebSocketState.Closed;
132
+ }
133
+ /**
134
+ * Dispatch a WebSocket event. This is primarily used within the MockClientWebSocket class
135
+ * itself but may also be used externally to test various WebSocket scenarios.
136
+ *
137
+ * Note that dispatching `'open'` and `'close'` events will not actually close or open the
138
+ * WebSocket. Use the {@link MockClientWebSocket.open} and {@link MockClientWebSocket.close}
139
+ * methods instead (which appropriately dispatch their events).
140
+ */
141
+ dispatchEvent(eventName, event) {
142
+ this.listeners[eventName]?.forEach((listener) => listener({
143
+ ...event,
144
+ target: this,
145
+ type: eventName,
146
+ }));
147
+ }
148
+ /**
149
+ * Implements and mocks the standard `WebSocket.addEventListener` property but with message type
150
+ * safety.
151
+ *
152
+ * @see https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener
153
+ */
154
+ addEventListener(eventName, listener) {
155
+ getOrSet(this.listeners, eventName, () => new Set()).add(listener);
156
+ }
157
+ /**
158
+ * Implements and mocks the standard `WebSocket.removeEventListener` property but with message
159
+ * type safety.
160
+ *
161
+ * @see https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener
162
+ */
163
+ removeEventListener(eventName, listener) {
164
+ this.listeners[eventName]?.delete(listener);
165
+ }
166
+ /**
167
+ * Implements and mocks the standard `WebSocket.send` property but with message type safety.
168
+ *
169
+ * @see https://developer.mozilla.org/docs/Web/API/WebSocket/send
170
+ */
171
+ send(data) {
172
+ if (this.readyState !== CommonWebSocketState.Open) {
173
+ return;
174
+ }
175
+ void this.sendCallback?.({
176
+ messageData: parseJsonWithUndefined(data),
177
+ messageSource: WebSocketLocation.OnClient,
178
+ webSocket: this,
179
+ });
180
+ }
181
+ /**
182
+ * Sends a message as if it came from the WebSocket host. Use this to unit test client-side
183
+ * WebSockets without needing a running host.
184
+ */
185
+ sendFromHost(data) {
186
+ if (this.readyState !== CommonWebSocketState.Open) {
187
+ return;
188
+ }
189
+ this.dispatchEvent('message', {
190
+ data,
191
+ });
192
+ void this.sendCallback?.({
193
+ messageData: data,
194
+ messageSource: WebSocketLocation.OnHost,
195
+ webSocket: this,
196
+ });
197
+ }
198
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names. This is extracted
3
+ * from the [`ws`](https://www.npmjs.com/package/ws) package. This is used internally to verify if a
4
+ * set of protocols is valid.
5
+ *
6
+ * @category Internal
7
+ * @category Package : @rest-vir/define-service
8
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
9
+ */
10
+ export declare function parseSecWebSocketProtocolHeader(header: string): Set<string>;
@@ -0,0 +1,111 @@
1
+ /* eslint-disable unicorn/number-literal-case */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ // cspell:ignore Einar Stangvik Arnout Kazemier Pinca
4
+ /**
5
+ * This file's contents were extracted from the [`ws`](https://www.npmjs.com/package/ws) package's
6
+ * following files:
7
+ *
8
+ * - https://github.com/websockets/ws/blob/b9ca55b0aa8c72b39a778542bd0fa9b6c455d4c4/lib/subprotocol.js
9
+ * - https://github.com/websockets/ws/blob/b9ca55b0aa8c72b39a778542bd0fa9b6c455d4c4/lib/validation.js
10
+ *
11
+ * That package has the following license:
12
+ *
13
+ * Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
14
+ * Copyright (c) 2013 Arnout Kazemier and contributors
15
+ * Copyright (c) 2016 Luigi Pinca and contributors
16
+ *
17
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
18
+ * this software and associated documentation files (the "Software"), to deal in
19
+ * the Software without restriction, including without limitation the rights to
20
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
21
+ * the Software, and to permit persons to whom the Software is furnished to do so,
22
+ * subject to the following conditions:
23
+ *
24
+ * The above copyright notice and this permission notice shall be included in all
25
+ * copies or substantial portions of the Software.
26
+ *
27
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
29
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
30
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
31
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
32
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
+ */
34
+ //
35
+ // Allowed token characters:
36
+ //
37
+ // '!', '#', '$', '%', '&', ''', '*', '+', '-',
38
+ // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
39
+ //
40
+ // tokenChars[32] === 0 // ' '
41
+ // tokenChars[33] === 1 // '!'
42
+ // tokenChars[34] === 0 // '"'
43
+ // ...
44
+ //
45
+ // prettier-ignore
46
+ const tokenChars = [
47
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
48
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
49
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
50
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
51
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
52
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
53
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
54
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
55
+ ];
56
+ /**
57
+ * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names. This is extracted
58
+ * from the [`ws`](https://www.npmjs.com/package/ws) package. This is used internally to verify if a
59
+ * set of protocols is valid.
60
+ *
61
+ * @category Internal
62
+ * @category Package : @rest-vir/define-service
63
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
64
+ */
65
+ export function parseSecWebSocketProtocolHeader(header) {
66
+ const protocols = new Set();
67
+ let start = -1;
68
+ let end = -1;
69
+ let i = 0;
70
+ for (i; i < header.length; i++) {
71
+ const code = header.codePointAt(i);
72
+ if (end === -1 && tokenChars[code] === 1) {
73
+ if (start === -1) {
74
+ start = i;
75
+ }
76
+ }
77
+ else if (i !== 0 && (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */) {
78
+ /* node:coverage ignore next 3: idk how to trigger this */
79
+ if (end === -1 && start !== -1) {
80
+ end = i;
81
+ }
82
+ }
83
+ else if (code === 0x2c /* ',' */) {
84
+ if (start === -1) {
85
+ throw new SyntaxError(`Unexpected character at index ${i}`);
86
+ }
87
+ if (end === -1) {
88
+ end = i;
89
+ }
90
+ const protocol = header.slice(start, end);
91
+ if (protocols.has(protocol)) {
92
+ throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
93
+ }
94
+ protocols.add(protocol);
95
+ start = end = -1;
96
+ }
97
+ else {
98
+ throw new SyntaxError(`Unexpected character at index ${i}`);
99
+ }
100
+ }
101
+ /* node:coverage ignore next 3: idk how to trigger this */
102
+ if (start === -1 || end !== -1) {
103
+ throw new SyntaxError('Unexpected end of input');
104
+ }
105
+ const protocol = header.slice(start, i);
106
+ if (protocols.has(protocol)) {
107
+ throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
108
+ }
109
+ protocols.add(protocol);
110
+ return protocols;
111
+ }
@@ -0,0 +1,23 @@
1
+ export { HttpMethod, HttpStatus } from '@augment-vir/common';
2
+ export * from './augments/json.js';
3
+ export * from './dev-port/find-dev-port.js';
4
+ export * from './endpoint/endpoint-path.js';
5
+ export * from './endpoint/endpoint.js';
6
+ export * from './frontend-connect/connect-web-socket.js';
7
+ export * from './frontend-connect/fetch-endpoint.js';
8
+ export * from './frontend-connect/generate-api.js';
9
+ export * from './frontend-connect/mock-client-web-socket.js';
10
+ export * from './frontend-connect/web-socket-protocol-parse.js';
11
+ export * from './service/define-service.js';
12
+ export * from './service/match-url.js';
13
+ export * from './service/minimal-service.js';
14
+ export * from './service/service-definition.error.js';
15
+ export * from './service/service-definition.js';
16
+ export * from './util/mock-fetch.js';
17
+ export * from './util/no-param.js';
18
+ export * from './util/origin.js';
19
+ export * from './util/path-to-regexp.js';
20
+ export * from './util/search-params.js';
21
+ export * from './web-socket/common-web-socket.js';
22
+ export * from './web-socket/overwrite-web-socket-methods.js';
23
+ export * from './web-socket/web-socket-definition.js';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ export { HttpMethod, HttpStatus } from '@augment-vir/common';
2
+ export * from './augments/json.js';
3
+ export * from './dev-port/find-dev-port.js';
4
+ export * from './endpoint/endpoint-path.js';
5
+ export * from './endpoint/endpoint.js';
6
+ export * from './frontend-connect/connect-web-socket.js';
7
+ export * from './frontend-connect/fetch-endpoint.js';
8
+ export * from './frontend-connect/generate-api.js';
9
+ export * from './frontend-connect/mock-client-web-socket.js';
10
+ export * from './frontend-connect/web-socket-protocol-parse.js';
11
+ export * from './service/define-service.js';
12
+ export * from './service/match-url.js';
13
+ export * from './service/minimal-service.js';
14
+ export * from './service/service-definition.error.js';
15
+ export * from './service/service-definition.js';
16
+ export * from './util/mock-fetch.js';
17
+ export * from './util/no-param.js';
18
+ export * from './util/origin.js';
19
+ export * from './util/path-to-regexp.js';
20
+ export * from './util/search-params.js';
21
+ export * from './web-socket/common-web-socket.js';
22
+ export * from './web-socket/overwrite-web-socket-methods.js';
23
+ export * from './web-socket/web-socket-definition.js';
@@ -0,0 +1,19 @@
1
+ import type { BaseServiceEndpointsInit, BaseServiceWebSocketsInit, ServiceDefinition, ServiceInit } from './service-definition.js';
2
+ /**
3
+ * The main entry point to the whole `@rest-vir/define-service` package. This function accepts a
4
+ * {@link ServiceInit} object and returns a fully defined {@link ServiceDefinition}.
5
+ *
6
+ * @category Define Service
7
+ * @category Package : @rest-vir/define-service
8
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
9
+ */
10
+ export declare function defineService<const ServiceName extends string, EndpointsInit extends BaseServiceEndpointsInit, WebSocketsInit extends BaseServiceWebSocketsInit>(serviceInit: ServiceInit<ServiceName, EndpointsInit, WebSocketsInit>): ServiceDefinition<ServiceName, EndpointsInit, WebSocketsInit>;
11
+ /**
12
+ * Asserts that the given input is a valid {@link ServiceDefinition} instance.
13
+ *
14
+ * @category Internal
15
+ * @category Package : @rest-vir/define-service
16
+ * @throws {@link ServiceDefinitionError} : if there is an issue
17
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
18
+ */
19
+ export declare function assertValidServiceDefinition(serviceDefinition: ServiceDefinition): asserts serviceDefinition is ServiceDefinition;
@@ -0,0 +1,133 @@
1
+ import { assert, check } from '@augment-vir/assert';
2
+ import { getObjectTypedEntries, mapObjectValues, stringify } from '@augment-vir/common';
3
+ import { assertValidShape, defineShape } from 'object-shape-tester';
4
+ import { assertValidEndpoint, attachEndpointShapeTypeGetters, endpointInitShape, } from '../endpoint/endpoint.js';
5
+ import { assertValidWebSocketDefinition, attachWebSocketShapeTypeGetters, webSocketInitShape, } from '../web-socket/web-socket-definition.js';
6
+ import { ensureServiceDefinitionError } from './service-definition.error.js';
7
+ /**
8
+ * The main entry point to the whole `@rest-vir/define-service` package. This function accepts a
9
+ * {@link ServiceInit} object and returns a fully defined {@link ServiceDefinition}.
10
+ *
11
+ * @category Define Service
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 defineService(serviceInit) {
16
+ const serviceDefinition = finalizeServiceDefinition(serviceInit);
17
+ assertValidServiceDefinition(serviceDefinition);
18
+ return serviceDefinition;
19
+ }
20
+ function finalizeServiceDefinition(serviceInit) {
21
+ try {
22
+ const minimalService = {
23
+ serviceName: serviceInit.serviceName,
24
+ serviceOrigin: serviceInit.serviceOrigin,
25
+ requiredClientOrigin: serviceInit.requiredClientOrigin,
26
+ };
27
+ /**
28
+ * Make the types less strict because we don't care what they are inside of this function's
29
+ * implementation. Just the return type is what matters.
30
+ */
31
+ const genericEndpoints = (serviceInit.endpoints || {});
32
+ const endpoints = mapObjectValues(genericEndpoints, (endpointPath, endpointInit) => {
33
+ assertValidShape({ searchParamsShape: undefined, ...endpointInit }, endpointInitShape);
34
+ const endpoint = {
35
+ ...endpointInit,
36
+ requestDataShape: endpointInit.requestDataShape
37
+ ? defineShape(endpointInit.requestDataShape, true)
38
+ : undefined,
39
+ responseDataShape: endpointInit.responseDataShape
40
+ ? defineShape(endpointInit.responseDataShape, true)
41
+ : undefined,
42
+ path: endpointPath,
43
+ service: minimalService,
44
+ customProps: endpointInit.customProps,
45
+ isEndpoint: true,
46
+ isWebSocket: false,
47
+ searchParamsShape: (endpointInit.searchParamsShape
48
+ ? defineShape(endpointInit.searchParamsShape)
49
+ : undefined),
50
+ };
51
+ attachEndpointShapeTypeGetters(endpoint);
52
+ return endpoint;
53
+ });
54
+ const genericWebSockets = (serviceInit.webSockets || {});
55
+ const webSockets = mapObjectValues(genericWebSockets, (webSocketPath, webSocketInit) => {
56
+ assertValidShape({ protocolsShape: undefined, searchParamsShape: undefined, ...webSocketInit }, webSocketInitShape);
57
+ const webSocketDefinition = {
58
+ ...webSocketInit,
59
+ messageFromClientShape: webSocketInit.messageFromClientShape
60
+ ? defineShape(webSocketInit.messageFromClientShape)
61
+ : undefined,
62
+ messageFromHostShape: webSocketInit.messageFromHostShape
63
+ ? defineShape(webSocketInit.messageFromHostShape)
64
+ : undefined,
65
+ protocolsShape: (webSocketInit.protocolsShape
66
+ ? defineShape(webSocketInit.protocolsShape)
67
+ : undefined),
68
+ searchParamsShape: (webSocketInit.searchParamsShape
69
+ ? defineShape(webSocketInit.searchParamsShape)
70
+ : undefined),
71
+ service: minimalService,
72
+ path: webSocketPath,
73
+ customProps: webSocketInit.customProps,
74
+ isEndpoint: false,
75
+ isWebSocket: true,
76
+ };
77
+ attachWebSocketShapeTypeGetters(webSocketDefinition);
78
+ return webSocketDefinition;
79
+ });
80
+ const serviceDefinition = {
81
+ serviceName: serviceInit.serviceName,
82
+ serviceOrigin: serviceInit.serviceOrigin,
83
+ init: {
84
+ ...serviceInit,
85
+ webSockets: (serviceInit.webSockets || {}),
86
+ endpoints: (serviceInit.endpoints || {}),
87
+ },
88
+ webSockets: webSockets,
89
+ requiredClientOrigin: serviceInit.requiredClientOrigin,
90
+ /** As cast needed again to narrow the type (for the return value) after broadening it. */
91
+ endpoints: endpoints,
92
+ };
93
+ return serviceDefinition;
94
+ }
95
+ catch (error) {
96
+ throw ensureServiceDefinitionError(error, {
97
+ path: undefined,
98
+ serviceName: serviceInit.serviceName,
99
+ isEndpoint: undefined,
100
+ isWebSocket: undefined,
101
+ });
102
+ }
103
+ }
104
+ /**
105
+ * Asserts that the given input is a valid {@link ServiceDefinition} instance.
106
+ *
107
+ * @category Internal
108
+ * @category Package : @rest-vir/define-service
109
+ * @throws {@link ServiceDefinitionError} : if there is an issue
110
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
111
+ */
112
+ export function assertValidServiceDefinition(serviceDefinition) {
113
+ try {
114
+ if (!serviceDefinition.serviceName || !check.isString(serviceDefinition.serviceName)) {
115
+ throw new Error(`Invalid service name: '${stringify(serviceDefinition.serviceName)}'. Expected a non-empty string.`);
116
+ }
117
+ assert.isDefined(serviceDefinition.requiredClientOrigin);
118
+ getObjectTypedEntries(serviceDefinition.endpoints).forEach(([, endpoint,]) => {
119
+ assertValidEndpoint(endpoint);
120
+ });
121
+ getObjectTypedEntries(serviceDefinition.webSockets).forEach(([, webSocketDefinition,]) => {
122
+ assertValidWebSocketDefinition(webSocketDefinition);
123
+ });
124
+ }
125
+ catch (error) {
126
+ throw ensureServiceDefinitionError(error, {
127
+ path: undefined,
128
+ serviceName: serviceDefinition.serviceName,
129
+ isEndpoint: undefined,
130
+ isWebSocket: undefined,
131
+ });
132
+ }
133
+ }
@@ -0,0 +1,30 @@
1
+ import { type RequireAtLeastOne } from 'type-fest';
2
+ import { EndpointPathBase } from '../endpoint/endpoint-path.js';
3
+ /**
4
+ * Given a raw path or URL, finds an endpoint or WebSocket path that will match in the given
5
+ * service. If no match is found, this returns `undefined`.
6
+ *
7
+ * @category Internal
8
+ * @category Package : @rest-vir/implement-service
9
+ * @package [`@rest-vir/implement-service`](https://www.npmjs.com/package/@rest-vir/implement-service)
10
+ */
11
+ export declare function matchUrlToService<const Service extends Readonly<{
12
+ webSockets: Record<EndpointPathBase, any>;
13
+ endpoints: Record<EndpointPathBase, any>;
14
+ }>>(this: void, service: Service,
15
+ /** The URL or path to match against. */
16
+ url: string): MatchedServicePath<Service> | undefined;
17
+ /**
18
+ * Output for {@link matchUrlToService}.
19
+ *
20
+ * @category Internal
21
+ * @category Package : @rest-vir/implement-service
22
+ * @package [`@rest-vir/implement-service`](https://www.npmjs.com/package/@rest-vir/implement-service)
23
+ */
24
+ export type MatchedServicePath<Service extends Readonly<{
25
+ webSockets: Record<EndpointPathBase, any>;
26
+ endpoints: Record<EndpointPathBase, any>;
27
+ }>> = RequireAtLeastOne<{
28
+ webSocketPath: keyof Service['webSockets'];
29
+ endpointPath: keyof Service['endpoints'];
30
+ }>;
@@ -0,0 +1,31 @@
1
+ import { getObjectTypedKeys } from '@augment-vir/common';
2
+ import { parseUrl } from 'url-vir';
3
+ import { match } from '../util/path-to-regexp.js';
4
+ /**
5
+ * Given a raw path or URL, finds an endpoint or WebSocket path that will match in the given
6
+ * service. If no match is found, this returns `undefined`.
7
+ *
8
+ * @category Internal
9
+ * @category Package : @rest-vir/implement-service
10
+ * @package [`@rest-vir/implement-service`](https://www.npmjs.com/package/@rest-vir/implement-service)
11
+ */
12
+ export function matchUrlToService(service,
13
+ /** The URL or path to match against. */
14
+ url) {
15
+ const { pathname } = parseUrl(url);
16
+ const endpointPath = getObjectTypedKeys(service.endpoints).find((endpointPath) => {
17
+ return match(endpointPath)(pathname);
18
+ });
19
+ const webSocketPath = getObjectTypedKeys(service.webSockets).find((webSocketPath) => {
20
+ return match(webSocketPath)(pathname);
21
+ });
22
+ if (!endpointPath && !webSocketPath) {
23
+ return undefined;
24
+ }
25
+ else {
26
+ return {
27
+ ...(endpointPath ? { endpointPath } : {}),
28
+ ...(webSocketPath ? { webSocketPath: webSocketPath } : {}),
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,44 @@
1
+ import { type IsEqual } from 'type-fest';
2
+ import { type OriginRequirement } from '../util/origin.js';
3
+ /**
4
+ * This is a minimal service definition with only data to send and handle fetch requests to that
5
+ * service. Each endpoint definition gets a copy of this embedded into it so the endpoint alone can
6
+ * be used as an input to the endpoint fetch function.
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 MinimalService<ServiceName extends string = string> = {
13
+ serviceName: IsEqual<ServiceName, ''> extends true ? never : ServiceName;
14
+ /**
15
+ * The origin at which the service will be hosted. Fetch requests and WebSocket connections will
16
+ * be sent to this service will be sent to this origin.
17
+ *
18
+ * It is recommended to use a ternary to switch between dev and prod origins.
19
+ *
20
+ * @example
21
+ *
22
+ * ```ts
23
+ * import {defineService} from '@rest-vir/define-service';
24
+ *
25
+ * defineService({
26
+ * serviceOrigin: isDev ? 'http://localhost:3000' : 'https://example.com',
27
+ * });
28
+ * ```
29
+ *
30
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Location for help on which part of the URL is the origin (if necessary).
31
+ */
32
+ serviceOrigin: string;
33
+ /**
34
+ * The service's `origin` requirement for all endpoint requests and WebSocket connections. This
35
+ * is used for CORS handshakes.
36
+ *
37
+ * This can be a string, a RegExp, a function, or an array of any of those. (If this is an
38
+ * array, the first matching array element will be used.)
39
+ *
40
+ * Set this to `AnyOrigin` (imported from `'@rest-vir/define-service'`) to allow any origins.
41
+ * Make sure that you're okay with the security impact this may have on your users of doing so.
42
+ */
43
+ requiredClientOrigin: NonNullable<OriginRequirement>;
44
+ };
@@ -0,0 +1 @@
1
+ export {};