@rest-vir/define-service 1.4.0 → 1.5.1

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.
@@ -7,7 +7,10 @@ import { wrapInTry } from '@augment-vir/common';
7
7
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
8
8
  */
9
9
  export function parseJsonWithUndefined(data) {
10
+ if (!data || data === 'undefined') {
11
+ return undefined;
12
+ }
10
13
  return wrapInTry(() => JSON.parse(data), {
11
- fallbackValue: data === 'undefined' ? undefined : data,
14
+ fallbackValue: data,
12
15
  });
13
16
  }
@@ -97,7 +97,9 @@ export async function findDevServicePort(service, options = {}) {
97
97
  * @throws Error if the max scan distance has been reached without finding a valid port.
98
98
  * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
99
99
  */
100
- export async function findLivePort(originWithStartingPort, pathToCheck, { fetch = globalThis.fetch, maxScanDistance = 100, isValidResponse, timeout = { seconds: 10 }, } = {}) {
100
+ export async function findLivePort(originWithStartingPort, pathToCheck, { fetch = globalThis.fetch, maxScanDistance = 100, isValidResponse, timeout = {
101
+ seconds: 10,
102
+ }, } = {}) {
101
103
  const { port: originalPort } = parseUrl(originWithStartingPort);
102
104
  if (!originalPort) {
103
105
  return undefined;
@@ -106,7 +108,9 @@ export async function findLivePort(originWithStartingPort, pathToCheck, { fetch
106
108
  assert.isNumber(startingPort, `Given origin doesn't have a valid port.`);
107
109
  let findDistance = 0;
108
110
  let foundValidPort = false;
109
- const timeoutMs = convertDuration(timeout, { milliseconds: true }).milliseconds;
111
+ const timeoutMs = convertDuration(timeout, {
112
+ milliseconds: true,
113
+ }).milliseconds;
110
114
  const startTime = Date.now();
111
115
  while (!foundValidPort) {
112
116
  const port = findDistance + startingPort;
@@ -104,7 +104,7 @@ export declare const endpointInitShape: Shape<{
104
104
  }> | import("@sinclair/typebox").TUnsafe<RegExp> | import("@sinclair/typebox").TString | import("@sinclair/typebox").TUndefined | import("@sinclair/typebox").TUnsafe<() => void> | import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TUnsafe<{
105
105
  anyOrigin: boolean;
106
106
  }> | import("@sinclair/typebox").TUnsafe<RegExp> | import("@sinclair/typebox").TString | import("@sinclair/typebox").TUnsafe<() => void>)[]>>)[]>>>;
107
- methods: import("@sinclair/typebox").TUnsafe<Partial<Record<HttpMethod, boolean>>>;
107
+ methods: Shape<import("@sinclair/typebox").TUnsafe<Partial<Record<HttpMethod, boolean>>>>;
108
108
  bypassResponseValidation: Shape<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TBoolean | import("@sinclair/typebox").TNull | import("@sinclair/typebox").TUndefined)[]>>>;
109
109
  customProps: Shape<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TUndefined | import("@sinclair/typebox").TUnsafe<Record<never, unknown>>)[]>>>;
110
110
  }>;
@@ -41,7 +41,10 @@ export function buildWebSocketUrl(webSocketDefinition, ...[{ pathParams, searchP
41
41
  responseDataShape: undefined,
42
42
  service: webSocketDefinition.service,
43
43
  searchParamsShape: webSocketDefinition.searchParamsShape,
44
- }, { pathParams, searchParams });
44
+ }, {
45
+ pathParams,
46
+ searchParams,
47
+ });
45
48
  return buildUrl(httpUrl, {
46
49
  protocol: httpUrl.startsWith('https') ? 'wss' : 'ws',
47
50
  }).href;
@@ -95,7 +95,10 @@ export type FetchEndpointOutput<EndpointToFetch extends Readonly<SelectFrom<Endp
95
95
  response: Readonly<Response>;
96
96
  }> | Readonly<{
97
97
  ok: false;
98
- data: string | undefined;
98
+ data: EndpointToFetch extends SelectFrom<EndpointDefinition, {
99
+ requestDataShape: true;
100
+ responseDataShape: true;
101
+ }> ? EndpointExecutorData<EndpointToFetch>['response'] | string | undefined : any;
99
102
  response: Readonly<Response>;
100
103
  }>;
101
104
  /**
@@ -1,6 +1,6 @@
1
1
  import { check } from '@augment-vir/assert';
2
2
  import { addPrefix, filterMap, getObjectTypedEntries, HttpMethod, mapObject, } from '@augment-vir/common';
3
- import { assertValidShape } from 'object-shape-tester';
3
+ import { assertValidShape, checkWrapValidShape } from 'object-shape-tester';
4
4
  import { buildUrl } from 'url-vir';
5
5
  import { parseJsonWithUndefined } from '../augments/json.js';
6
6
  function defaultFetch(...[url, requestInit,]) {
@@ -55,7 +55,9 @@ export async function fetchEndpoint(endpoint, ...params) {
55
55
  const { requestData, fetch, bypassResponseValidation } = params[0] || {};
56
56
  if (requestData) {
57
57
  if (endpoint.requestDataShape) {
58
- assertValidShape(requestData, endpoint.requestDataShape, { allowExtraKeys: true });
58
+ assertValidShape(requestData, endpoint.requestDataShape, {
59
+ allowExtraKeys: true,
60
+ });
59
61
  }
60
62
  else {
61
63
  throw new Error(`Request data was given but endpoint '${endpoint.path}' is not expecting any request data.`);
@@ -64,12 +66,15 @@ export async function fetchEndpoint(endpoint, ...params) {
64
66
  const { requestInit, url } = buildEndpointRequestInit(endpoint, ...params);
65
67
  /* node:coverage ignore next: all tests mock fetch so we're never going to have a fallback here. */
66
68
  const response = await (fetch || defaultFetch)(url, requestInit, endpoint);
69
+ const responseText = await response.clone().text();
70
+ const responseData = endpoint.responseDataShape
71
+ ? parseJsonWithUndefined(responseText)
72
+ : undefined;
67
73
  if (response.ok) {
68
- const responseData = endpoint.responseDataShape
69
- ? parseJsonWithUndefined(await response.text())
70
- : undefined;
71
74
  if (endpoint.responseDataShape && !bypassResponseValidation) {
72
- assertValidShape(responseData, endpoint.responseDataShape, { allowExtraKeys: true });
75
+ assertValidShape(responseData, endpoint.responseDataShape, {
76
+ allowExtraKeys: true,
77
+ });
73
78
  }
74
79
  return {
75
80
  ok: true,
@@ -78,10 +83,16 @@ export async function fetchEndpoint(endpoint, ...params) {
78
83
  };
79
84
  }
80
85
  else {
86
+ const validResponseData = endpoint.responseDataShape
87
+ ? bypassResponseValidation
88
+ ? responseData
89
+ : checkWrapValidShape(responseData, endpoint.responseDataShape, {
90
+ allowExtraKeys: true,
91
+ })
92
+ : undefined;
81
93
  return {
82
94
  ok: false,
83
- /** This will be an error message. */
84
- data: (await response.text()) || undefined,
95
+ data: validResponseData || responseText || undefined,
85
96
  response,
86
97
  };
87
98
  }
@@ -172,7 +183,10 @@ export function buildEndpointUrl(endpoint, { pathParams, searchParams, wildcard,
172
183
  throw new Error(`Missing value for path param '${paramName}'.`);
173
184
  }
174
185
  })
175
- .replace(/\/\*$/, addPrefix({ value: wildcard || '', prefix: '/' }));
186
+ .replace(/\/\*$/, addPrefix({
187
+ value: wildcard || '',
188
+ prefix: '/',
189
+ }));
176
190
  const builtUrl = buildUrl(endpoint.service.serviceOrigin, {
177
191
  search: searchParams,
178
192
  pathname,
@@ -30,7 +30,10 @@ function finalizeServiceDefinition(serviceInit) {
30
30
  */
31
31
  const genericEndpoints = (serviceInit.endpoints || {});
32
32
  const endpoints = mapObjectValues(genericEndpoints, (endpointPath, endpointInit) => {
33
- assertValidShape({ searchParamsShape: undefined, ...endpointInit }, endpointInitShape);
33
+ assertValidShape({
34
+ searchParamsShape: undefined,
35
+ ...endpointInit,
36
+ }, endpointInitShape);
34
37
  const endpoint = {
35
38
  ...endpointInit,
36
39
  requestDataShape: endpointInit.requestDataShape == undefined
@@ -53,7 +56,11 @@ function finalizeServiceDefinition(serviceInit) {
53
56
  });
54
57
  const genericWebSockets = (serviceInit.webSockets || {});
55
58
  const webSockets = mapObjectValues(genericWebSockets, (webSocketPath, webSocketInit) => {
56
- assertValidShape({ protocolsShape: undefined, searchParamsShape: undefined, ...webSocketInit }, webSocketInitShape);
59
+ assertValidShape({
60
+ protocolsShape: undefined,
61
+ searchParamsShape: undefined,
62
+ ...webSocketInit,
63
+ }, webSocketInitShape);
57
64
  const webSocketDefinition = {
58
65
  ...webSocketInit,
59
66
  messageFromClientShape: webSocketInit.messageFromClientShape
@@ -24,8 +24,16 @@ url) {
24
24
  }
25
25
  else {
26
26
  return {
27
- ...(endpointPath ? { endpointPath } : {}),
28
- ...(webSocketPath ? { webSocketPath } : {}),
27
+ ...(endpointPath
28
+ ? {
29
+ endpointPath,
30
+ }
31
+ : {}),
32
+ ...(webSocketPath
33
+ ? {
34
+ webSocketPath,
35
+ }
36
+ : {}),
29
37
  };
30
38
  }
31
39
  }
@@ -90,23 +90,47 @@ function* lexer(str) {
90
90
  const value = chars[i];
91
91
  const type = SIMPLE_TOKENS[value];
92
92
  if (type) {
93
- yield { type, index: i++, value };
93
+ yield {
94
+ type,
95
+ index: i++,
96
+ value,
97
+ };
94
98
  }
95
99
  else if (value === '\\') {
96
- yield { type: 'ESCAPED', index: i++, value: chars[i++] };
100
+ yield {
101
+ type: 'ESCAPED',
102
+ index: i++,
103
+ value: chars[i++],
104
+ };
97
105
  }
98
106
  else if (value === ':') {
99
107
  const value = name();
100
- yield { type: 'PARAM', index: i, value };
108
+ yield {
109
+ type: 'PARAM',
110
+ index: i,
111
+ value,
112
+ };
101
113
  }
102
114
  else if (value === '*') {
103
- yield { type: 'WILDCARD', index: i++, value: '*' };
115
+ yield {
116
+ type: 'WILDCARD',
117
+ index: i++,
118
+ value: '*',
119
+ };
104
120
  }
105
121
  else {
106
- yield { type: 'CHAR', index: i, value: chars[i++] };
122
+ yield {
123
+ type: 'CHAR',
124
+ index: i,
125
+ value: chars[i++],
126
+ };
107
127
  }
108
128
  }
109
- return { type: 'END', index: i, value: '' };
129
+ return {
130
+ type: 'END',
131
+ index: i,
132
+ value: '',
133
+ };
110
134
  }
111
135
  class Iter {
112
136
  tokens;
@@ -162,7 +186,10 @@ function parse(stringToParse) {
162
186
  while (true) {
163
187
  const path = iterator.text();
164
188
  if (path) {
165
- tokens.push({ type: 'text', value: path });
189
+ tokens.push({
190
+ type: 'text',
191
+ value: path,
192
+ });
166
193
  }
167
194
  const param = iterator.tryConsume('PARAM');
168
195
  if (param) {
@@ -227,7 +254,10 @@ export function match(path) {
227
254
  const decoder = decoders[i - 1];
228
255
  params[key.name] = decoder(m[i]);
229
256
  }
230
- return { path, params };
257
+ return {
258
+ path,
259
+ params,
260
+ };
231
261
  };
232
262
  }
233
263
  function pathToRegexp(path) {
@@ -241,7 +271,10 @@ function pathToRegexp(path) {
241
271
  pattern += `(?:${escape(DEFAULT_DELIMITER)}$)?`;
242
272
  pattern += '$';
243
273
  const regexp = new RegExp(pattern, flags);
244
- return { regexp, keys };
274
+ return {
275
+ regexp,
276
+ keys,
277
+ };
245
278
  }
246
279
  /** Path or array of paths to normalize. */
247
280
  function* flat(path) {
@@ -300,7 +333,7 @@ function negate(delimiter, backtrack) {
300
333
  }
301
334
  return `(?:(?!${escape(delimiter)})[^${escape(backtrack)}])`;
302
335
  }
303
- if (delimiter.length < 2) {
336
+ else if (delimiter.length < 2) {
304
337
  return `(?:(?!${escape(backtrack)})[^${escape(delimiter)}])`;
305
338
  }
306
339
  return String.raw `(?:(?!${escape(backtrack)}|${escape(delimiter)})[\s\S])`;
@@ -103,7 +103,9 @@ export function overwriteWebSocketMethods(webSocketDefinition, rawWebSocket, web
103
103
  originalRemoveEventListener.call(webSocket, eventName, existing);
104
104
  }
105
105
  },
106
- async sendAndWaitForReply({ message, timeout = { seconds: 10 }, replyCheck, } = {}) {
106
+ async sendAndWaitForReply({ message, timeout = {
107
+ seconds: 10,
108
+ }, replyCheck, } = {}) {
107
109
  const deferredReply = new DeferredPromise();
108
110
  async function listener({ message, }) {
109
111
  if (!deferredReply.isSettled) {
@@ -119,7 +121,9 @@ export function overwriteWebSocketMethods(webSocketDefinition, rawWebSocket, web
119
121
  if (!deferredReply.isSettled) {
120
122
  deferredReply.reject('Timeout: got no reply from the host.');
121
123
  }
122
- }, convertDuration(timeout, { milliseconds: true }).milliseconds);
124
+ }, convertDuration(timeout, {
125
+ milliseconds: true,
126
+ }).milliseconds);
123
127
  webSocket.addEventListener('message', listener);
124
128
  webSocket.send(message);
125
129
  try {
@@ -174,7 +178,7 @@ export async function waitForOpenWebSocket(webSocket) {
174
178
  if (webSocketOpenedPromise.isSettled) {
175
179
  return true;
176
180
  }
177
- if (webSocket.readyState === CommonWebSocketState.Closed) {
181
+ else if (webSocket.readyState === CommonWebSocketState.Closed) {
178
182
  webSocketOpenedPromise.reject('WebSocket closed while waiting for it to open.');
179
183
  return true;
180
184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rest-vir/define-service",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Define an connect to a declarative and type safe REST and WebSocket service.",
5
5
  "keywords": [
6
6
  "rest",
@@ -40,24 +40,24 @@
40
40
  "test:update": "npm test update"
41
41
  },
42
42
  "dependencies": {
43
- "@augment-vir/assert": "^31.55.0",
44
- "@augment-vir/common": "^31.55.0",
45
- "date-vir": "^8.1.0",
46
- "type-fest": "^5.3.1",
43
+ "@augment-vir/assert": "^31.68.1",
44
+ "@augment-vir/common": "^31.68.1",
45
+ "date-vir": "^8.2.1",
46
+ "type-fest": "^5.4.4",
47
47
  "url-vir": "^2.1.7"
48
48
  },
49
49
  "devDependencies": {
50
- "@augment-vir/test": "^31.55.0",
51
- "@web/dev-server-esbuild": "^1.0.4",
50
+ "@augment-vir/test": "^31.68.1",
51
+ "@web/dev-server-esbuild": "^1.0.5",
52
52
  "@web/test-runner": "^0.20.2",
53
53
  "@web/test-runner-commands": "^0.9.0",
54
54
  "@web/test-runner-playwright": "^0.11.1",
55
55
  "@web/test-runner-visual-regression": "^0.10.0",
56
56
  "istanbul-smart-text-reporter": "^1.1.5",
57
57
  "markdown-code-example-inserter": "^3.0.3",
58
- "object-shape-tester": "^6.10.1",
59
- "typedoc": "^0.28.15",
60
- "ws": "^8.18.3"
58
+ "object-shape-tester": "^6.11.0",
59
+ "typedoc": "^0.28.17",
60
+ "ws": "^8.19.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "object-shape-tester": ">=5"