@rest-vir/define-service 0.15.1 → 0.17.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.
@@ -4,7 +4,6 @@ import { type RequireAtLeastOne } from 'type-fest';
4
4
  import { type MinimalService } from '../service/minimal-service.js';
5
5
  import { type NoParam } from '../util/no-param.js';
6
6
  import { type OriginRequirement } from '../util/origin.js';
7
- import { type BaseSearchParams } from '../util/search-params.js';
8
7
  import { type EndpointPathBase } from './endpoint-path.js';
9
8
  /**
10
9
  * The type for setting up an individual endpoint, used in `defineService`.
@@ -122,7 +121,7 @@ export type WithFinalEndpointProps<Init, EndpointPath extends EndpointPathBase>
122
121
  RequestType: Init['requestDataShape'] extends NoParam ? any : undefined extends Init['requestDataShape'] ? undefined : ShapeToRuntimeType<ShapeDefinition<Init['requestDataShape'], true>, false, true>;
123
122
  ResponseType: Init['responseDataShape'] extends NoParam ? any : undefined extends Init['responseDataShape'] ? undefined : ShapeToRuntimeType<ShapeDefinition<Init['responseDataShape'], true>, false, true>;
124
123
  searchParamsShape: 'searchParamsShape' extends keyof Init ? ShapeDefinition<Init['searchParamsShape'], true> | undefined : undefined;
125
- SearchParamsType: 'searchParamsShape' extends keyof Init ? ShapeToRuntimeType<ShapeDefinition<Init['searchParamsShape'], true>, false, true> : BaseSearchParams;
124
+ SearchParamsType: 'searchParamsShape' extends keyof Init ? ShapeToRuntimeType<ShapeDefinition<Init['searchParamsShape'], true>, false, true> : undefined;
126
125
  customProps: 'customProps' extends keyof Init ? Init['customProps'] : undefined;
127
126
  }> : never) & {
128
127
  path: EndpointPath;
@@ -4,7 +4,7 @@ import { type PathParams } from '../endpoint/endpoint-path.js';
4
4
  import { type NoParam } from '../util/no-param.js';
5
5
  import { type CommonWebSocket } from '../web-socket/common-web-socket.js';
6
6
  import { type ClientWebSocket, type ConnectWebSocketListeners, type GenericConnectWebSocketParams } from '../web-socket/overwrite-web-socket-methods.js';
7
- import { type WebSocketDefinition } from '../web-socket/web-socket-definition.js';
7
+ import { type GenericWebSocketDefinition, type WebSocketDefinition } from '../web-socket/web-socket-definition.js';
8
8
  /**
9
9
  * Verifies that no WebSocket protocols in the given list are invalid. This doesn't have anything to
10
10
  * do with a `WebsocketDefinition` instance, it's just checking if the given protocols can even be
@@ -28,7 +28,8 @@ export declare function buildWebSocketUrl(webSocketDefinition: Readonly<SelectFr
28
28
  serviceName: true;
29
29
  serviceOrigin: true;
30
30
  };
31
- }>>, ...[{ pathParams },]: CollapsedConnectWebSocketParams): string;
31
+ searchParamsShape: true;
32
+ }>>, ...[{ pathParams, searchParams },]: CollapsedConnectWebSocketParams): string;
32
33
  /**
33
34
  * Params for {@link connectWebSocket}.
34
35
  *
@@ -64,7 +65,7 @@ export type ConnectWebSocketParams<WebSocketToConnect extends Readonly<SelectFro
64
65
  } : {
65
66
  protocols: WebSocketToConnect['ProtocolsType'];
66
67
  }) & (WebSocketToConnect['searchParamsShape'] extends undefined ? {
67
- searchParams?: string[];
68
+ searchParams?: never;
68
69
  } : {
69
70
  searchParams: WebSocketToConnect['SearchParamsType'];
70
71
  });
@@ -92,4 +93,4 @@ export type CollapsedConnectWebSocketParams<WebSocketToConnect extends Readonly<
92
93
  * @category Package : @rest-vir/define-service
93
94
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
94
95
  */
95
- export declare function connectWebSocket<const WebSocketToConnect extends Readonly<WebSocketDefinition> | NoParam, WebSocketClass extends CommonWebSocket>(webSocketDefinition: WebSocketToConnect extends WebSocketDefinition ? WebSocketToConnect : Readonly<WebSocketDefinition>, ...params: CollapsedConnectWebSocketParams<WebSocketToConnect, true, WebSocketClass>): Promise<ClientWebSocket<WebSocketToConnect, WebSocketClass>>;
96
+ export declare function connectWebSocket<const WebSocketToConnect extends Readonly<GenericWebSocketDefinition> | NoParam, WebSocketClass extends CommonWebSocket>(webSocketDefinition: WebSocketToConnect extends GenericWebSocketDefinition ? WebSocketToConnect : Readonly<WebSocketDefinition>, ...params: CollapsedConnectWebSocketParams<WebSocketToConnect, true, WebSocketClass>): Promise<ClientWebSocket<WebSocketToConnect, WebSocketClass>>;
@@ -31,7 +31,7 @@ export function assertValidWebSocketProtocols(protocols) {
31
31
  * @category Package : @rest-vir/define-service
32
32
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
33
33
  */
34
- export function buildWebSocketUrl(webSocketDefinition, ...[{ pathParams } = {},]) {
34
+ export function buildWebSocketUrl(webSocketDefinition, ...[{ pathParams, searchParams } = {},]) {
35
35
  const httpUrl = buildEndpointUrl({
36
36
  methods: {
37
37
  [HttpMethod.Get]: true,
@@ -40,7 +40,8 @@ export function buildWebSocketUrl(webSocketDefinition, ...[{ pathParams } = {},]
40
40
  requestDataShape: undefined,
41
41
  responseDataShape: undefined,
42
42
  service: webSocketDefinition.service,
43
- }, { pathParams });
43
+ searchParamsShape: webSocketDefinition.searchParamsShape,
44
+ }, { pathParams, searchParams });
44
45
  return buildUrl(httpUrl, {
45
46
  protocol: httpUrl.startsWith('https') ? 'wss' : 'ws',
46
47
  }).href;
@@ -3,6 +3,7 @@ import { type IsEqual, type IsNever } from 'type-fest';
3
3
  import { type PathParams } from '../endpoint/endpoint-path.js';
4
4
  import { type EndpointDefinition, type EndpointExecutorData, type GenericEndpointDefinition } from '../endpoint/endpoint.js';
5
5
  import { type NoParam } from '../util/no-param.js';
6
+ import { type BaseSearchParams } from '../util/search-params.js';
6
7
  /**
7
8
  * A general version of {@link FetchEndpointParams} to be used when accepting _any_ endpoint (like in
8
9
  * tests).
@@ -14,6 +15,7 @@ import { type NoParam } from '../util/no-param.js';
14
15
  export type GenericFetchEndpointParams = {
15
16
  pathParams?: Record<string, string> | undefined;
16
17
  requestData?: any;
18
+ searchParams?: BaseSearchParams | undefined;
17
19
  method?: HttpMethod | undefined;
18
20
  options?: Omit<RequestInit, 'body' | 'method'> | undefined;
19
21
  /**
@@ -50,6 +52,10 @@ export type FetchEndpointParams<EndpointToFetch extends SelectFrom<EndpointDefin
50
52
  } : {
51
53
  /** This endpoint has no path parameters to configure. */
52
54
  pathParams?: undefined;
55
+ }) & (EndpointToFetch['SearchParamsType'] extends undefined ? {
56
+ searchParams?: never;
57
+ } : {
58
+ searchParams: EndpointToFetch['SearchParamsType'];
53
59
  }) & (EndpointExecutorData<EndpointToFetch>['request'] extends undefined ? {
54
60
  /**
55
61
  * This endpoint does not accept any request data, so there is none to be
@@ -141,6 +147,7 @@ export declare function fetchEndpoint<const EndpointToFetch extends Readonly<Sel
141
147
  requestDataShape: true;
142
148
  path: true;
143
149
  responseDataShape: true;
150
+ searchParamsShape: true;
144
151
  methods: true;
145
152
  service: {
146
153
  serviceOrigin: true;
@@ -167,12 +174,13 @@ export declare function buildEndpointRequestInit<const EndpointToFetch extends R
167
174
  requestDataShape: true;
168
175
  path: true;
169
176
  responseDataShape: true;
177
+ searchParamsShape: true;
170
178
  methods: true;
171
179
  service: {
172
180
  serviceOrigin: true;
173
181
  serviceName: true;
174
182
  };
175
- }>, ...[{ method, options, pathParams, requestData },]: CollapsedFetchEndpointParams<EndpointToFetch, false>): {
183
+ }>, ...[{ method, options, pathParams, requestData, searchParams },]: CollapsedFetchEndpointParams<EndpointToFetch, false>): {
176
184
  url: string;
177
185
  requestInit: RequestInit;
178
186
  };
@@ -192,6 +200,7 @@ export declare function buildEndpointUrl<const EndpointToFetch extends Readonly<
192
200
  methods: true;
193
201
  requestDataShape: true;
194
202
  responseDataShape: true;
203
+ searchParamsShape: true;
195
204
  }>> | NoParam = NoParam>(endpoint: EndpointToFetch extends EndpointDefinition ? EndpointToFetch : SelectFrom<EndpointDefinition, {
196
205
  path: true;
197
206
  service: {
@@ -199,6 +208,7 @@ export declare function buildEndpointUrl<const EndpointToFetch extends Readonly<
199
208
  serviceName: true;
200
209
  };
201
210
  requestDataShape: true;
211
+ searchParamsShape: true;
202
212
  responseDataShape: true;
203
213
  methods: true;
204
- }>, { pathParams, }: Pick<EndpointToFetch extends NoParam ? Readonly<GenericFetchEndpointParams> : Readonly<FetchEndpointParams<Exclude<EndpointToFetch, NoParam>>>, 'pathParams'>): string;
214
+ }>, { pathParams, searchParams, }: Pick<EndpointToFetch extends NoParam ? Readonly<GenericFetchEndpointParams> : Readonly<FetchEndpointParams<Exclude<EndpointToFetch, NoParam>>>, 'pathParams' | 'searchParams'>): string;
@@ -93,7 +93,7 @@ export async function fetchEndpoint(endpoint, ...params) {
93
93
  * @category Package : @rest-vir/define-service
94
94
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
95
95
  */
96
- export function buildEndpointRequestInit(endpoint, ...[{ method, options = {}, pathParams, requestData } = {},]) {
96
+ export function buildEndpointRequestInit(endpoint, ...[{ method, options = {}, pathParams, requestData, searchParams } = {},]) {
97
97
  const headers = options.headers instanceof Headers
98
98
  ? Object.fromEntries(options.headers.entries())
99
99
  : check.isArray(options.headers)
@@ -112,7 +112,10 @@ export function buildEndpointRequestInit(endpoint, ...[{ method, options = {}, p
112
112
  headers['content-type'] = 'application/json';
113
113
  }
114
114
  }
115
- const url = buildEndpointUrl(endpoint, { pathParams });
115
+ const url = buildEndpointUrl(endpoint, {
116
+ pathParams,
117
+ searchParams,
118
+ });
116
119
  const requestInit = {
117
120
  ...options,
118
121
  headers,
@@ -139,9 +142,16 @@ export function buildEndpointRequestInit(endpoint, ...[{ method, options = {}, p
139
142
  * @category Package : @rest-vir/define-service
140
143
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
141
144
  */
142
- export function buildEndpointUrl(endpoint, { pathParams, }) {
145
+ export function buildEndpointUrl(endpoint, { pathParams, searchParams, }) {
143
146
  let pathParamsCount = 0;
147
+ if (endpoint.searchParamsShape) {
148
+ assertValidShape(searchParams, endpoint.searchParamsShape, {
149
+ /** Allow extra keys for forwards compatibility. */
150
+ allowExtraKeys: true,
151
+ }, `Invalid search params given to '${endpoint.path}' in service '${endpoint.service.serviceName}'`);
152
+ }
144
153
  const builtUrl = buildUrl(endpoint.service.serviceOrigin, {
154
+ search: searchParams,
145
155
  pathname: endpoint.path.replaceAll(/\/:([^/]+)/g, (wholeMatch, paramName) => {
146
156
  pathParamsCount++;
147
157
  if (pathParams && check.hasKey(pathParams, paramName) && pathParams[paramName]) {
@@ -156,7 +166,7 @@ export function buildEndpointUrl(endpoint, { pathParams, }) {
156
166
  }),
157
167
  }).href;
158
168
  if (!pathParamsCount && pathParams) {
159
- throw new Error(`Endpoint '${endpoint.path}' in service '${endpoint.service.serviceName}' does not allow any path params but some where set.`);
169
+ throw new Error(`'${endpoint.path}' in service '${endpoint.service.serviceName}' does not allow any path params but some where set.`);
160
170
  }
161
171
  return builtUrl;
162
172
  }
@@ -1,7 +1,7 @@
1
1
  import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
2
2
  import { CommonWebSocketState, type CommonWebSocket, type CommonWebSocketEventMap } from '../web-socket/common-web-socket.js';
3
3
  import { WebSocketLocation, type ClientWebSocket, type GenericConnectWebSocketParams } from '../web-socket/overwrite-web-socket-methods.js';
4
- import { type WebSocketDefinition } from '../web-socket/web-socket-definition.js';
4
+ import { type GenericWebSocketDefinition, type WebSocketDefinition } from '../web-socket/web-socket-definition.js';
5
5
  /**
6
6
  * Parameters for {@link MockClientWebSocketClientSendCallback}.
7
7
  *
@@ -102,7 +102,7 @@ export type MockClientWebSocketListeners = Partial<{
102
102
  *
103
103
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
104
104
  */
105
- export declare class MockClientWebSocket<const WebSocketToConnect extends WebSocketDefinition = any> implements CommonWebSocket {
105
+ export declare class MockClientWebSocket<const WebSocketToConnect extends GenericWebSocketDefinition = any> implements CommonWebSocket {
106
106
  /**
107
107
  * Mocked WebSocket event listeners. While this is public, you should attach a listener using
108
108
  * {@link MockClientWebSocket.addEventListener} method and remove them with
@@ -56,3 +56,27 @@ export declare const originRequirementShape: import("object-shape-tester").Shape
56
56
  }]>, import("object-shape-tester").ShapeClass<[RegExpConstructor]>, () => void, import("object-shape-tester").ShapeOr<[string, import("object-shape-tester").ShapeExact<readonly [{
57
57
  anyOrigin: boolean;
58
58
  }]>, import("object-shape-tester").ShapeClass<[RegExpConstructor]>, () => void]>[]]>, false>;
59
+ /**
60
+ * - `boolean`: the origin was explicitly checked and passed (`true`) or failed (`false`)
61
+ * - `undefined`: no origin checking occurred
62
+ * - `AnyOrigin`: requirements explicitly allow any origin.
63
+ */
64
+ export type OriginRequirementResult = boolean | undefined | AnyOrigin;
65
+ /**
66
+ * Checks the given origin against the given origin requirement and determine if the origin matches.
67
+ * See {@link OriginRequirementResult} for details on what each possible return value means.
68
+ *
69
+ * @category Internal
70
+ * @category Package : @rest-vir/define-service
71
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
72
+ */
73
+ export declare function checkOriginRequirement(origin: string | undefined, originRequirement: OriginRequirement): Promise<OriginRequirementResult>;
74
+ /**
75
+ * Narrower version of {@link checkOriginRequirement} that simply returns `true` if the origin
76
+ * matched or `false` otherwise.
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 declare function matchesOriginRequirement(origin: string | undefined, originRequirement: NonNullable<OriginRequirement>): Promise<boolean>;
@@ -31,3 +31,53 @@ export function isAnyOrigin(input) {
31
31
  export const originRequirementShape = defineShape(or(undefined, '', exact(AnyOrigin), classShape(RegExp), () => { }, [
32
32
  or('', exact(AnyOrigin), classShape(RegExp), () => { }),
33
33
  ]));
34
+ /**
35
+ * Checks the given origin against the given origin requirement and determine if the origin matches.
36
+ * See {@link OriginRequirementResult} for details on what each possible return value means.
37
+ *
38
+ * @category Internal
39
+ * @category Package : @rest-vir/define-service
40
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
41
+ */
42
+ export async function checkOriginRequirement(origin, originRequirement) {
43
+ if (isAnyOrigin(originRequirement)) {
44
+ /** Any origin has been explicitly allowed. */
45
+ return AnyOrigin;
46
+ }
47
+ else if (originRequirement === undefined) {
48
+ /** No checking occurred. */
49
+ return undefined;
50
+ }
51
+ else if (!origin) {
52
+ /** If there is an origin requirement but no origin then the origin automatically fails. */
53
+ return false;
54
+ }
55
+ else if (check.isString(originRequirement)) {
56
+ return origin === originRequirement;
57
+ }
58
+ else if (check.instanceOf(originRequirement, RegExp)) {
59
+ return !!originRequirement.exec(origin);
60
+ }
61
+ else if (check.isArray(originRequirement)) {
62
+ for (const requirement of originRequirement) {
63
+ if (await checkOriginRequirement(origin, requirement)) {
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ else {
70
+ return await originRequirement(origin);
71
+ }
72
+ }
73
+ /**
74
+ * Narrower version of {@link checkOriginRequirement} that simply returns `true` if the origin
75
+ * matched or `false` otherwise.
76
+ *
77
+ * @category Internal
78
+ * @category Package : @rest-vir/define-service
79
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
80
+ */
81
+ export async function matchesOriginRequirement(origin, originRequirement) {
82
+ return !!(await checkOriginRequirement(origin, originRequirement));
83
+ }
@@ -2,6 +2,7 @@ import { type MaybePromise, type Overwrite, type SelectFrom } from '@augment-vir
2
2
  import { type AnyDuration } from 'date-vir';
3
3
  import { type HasRequiredKeys } from 'type-fest';
4
4
  import { type NoParam } from '../util/no-param.js';
5
+ import { type BaseSearchParams } from '../util/search-params.js';
5
6
  import { type CommonWebSocket, type CommonWebSocketEventMap } from './common-web-socket.js';
6
7
  import { type WebSocketDefinition } from './web-socket-definition.js';
7
8
  /**
@@ -251,7 +252,7 @@ export type GenericConnectWebSocketParams<WebSocketClass extends CommonWebSocket
251
252
  * @default globalThis.WebSocket
252
253
  */
253
254
  webSocketConstructor?: (new (url: string, protocols: string[] | undefined, webSocketDefinition: WebSocketDefinition) => WebSocketClass) | undefined;
254
- searchParams?: unknown;
255
+ searchParams?: BaseSearchParams | undefined;
255
256
  };
256
257
  /**
257
258
  * Overwrites WebSocket methods with their rest-vir, type-safe replacements.
@@ -1,4 +1,4 @@
1
- import { waitUntil } from '@augment-vir/assert';
1
+ import { check, waitUntil } from '@augment-vir/assert';
2
2
  import { callAsynchronously, DeferredPromise, ensureErrorAndPrependMessage, getOrSet, stringify, wrapInTry, } from '@augment-vir/common';
3
3
  import { convertDuration } from 'date-vir';
4
4
  import { assertValidShape } from 'object-shape-tester';
@@ -138,6 +138,21 @@ export function overwriteWebSocketMethods(webSocketDefinition, rawWebSocket, web
138
138
  });
139
139
  return webSocket;
140
140
  }
141
+ function cleanUpWebSocketError(error) {
142
+ if (check.isObject(error)) {
143
+ delete error.webSocket;
144
+ if (check.hasKey(error, 'webSocketDefinition') &&
145
+ check.hasKey(error.webSocketDefinition, 'path')) {
146
+ error.path = error.webSocketDefinition.path;
147
+ }
148
+ delete error.webSocketDefinition;
149
+ return error;
150
+ /* node:coverage ignore next 3: edge case */
151
+ }
152
+ else {
153
+ return error;
154
+ }
155
+ }
141
156
  /**
142
157
  * Waits for a WebSocket to reach to the open state.
143
158
  *
@@ -149,7 +164,7 @@ export async function waitForOpenWebSocket(webSocket) {
149
164
  const webSocketOpenedPromise = new DeferredPromise();
150
165
  function errorListener(error) {
151
166
  if (!webSocketOpenedPromise.isSettled) {
152
- webSocketOpenedPromise.reject(ensureErrorAndPrependMessage(error, 'WebSocket connection failed.'));
167
+ webSocketOpenedPromise.reject(ensureErrorAndPrependMessage(cleanUpWebSocketError(error), 'WebSocket connection failed.'));
153
168
  }
154
169
  }
155
170
  webSocket.addEventListener('error', errorListener);
@@ -172,7 +187,7 @@ export async function waitForOpenWebSocket(webSocket) {
172
187
  }, undefined, 'WebSocket never opened')
173
188
  .catch((error) => {
174
189
  if (!webSocketOpenedPromise.isSettled) {
175
- webSocketOpenedPromise.reject(error);
190
+ webSocketOpenedPromise.reject(cleanUpWebSocketError(error));
176
191
  }
177
192
  });
178
193
  await webSocketOpenedPromise.promise.finally(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rest-vir/define-service",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "Define an connect to a declarative and type safe REST and WebSocket service.",
5
5
  "keywords": [
6
6
  "rest",