@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.
- package/dist/augments/json.js +4 -1
- package/dist/dev-port/find-dev-port.js +6 -2
- package/dist/endpoint/endpoint.d.ts +1 -1
- package/dist/frontend-connect/connect-web-socket.js +4 -1
- package/dist/frontend-connect/fetch-endpoint.d.ts +4 -1
- package/dist/frontend-connect/fetch-endpoint.js +23 -9
- package/dist/service/define-service.js +9 -2
- package/dist/service/match-url.js +10 -2
- package/dist/util/path-to-regexp.js +43 -10
- package/dist/web-socket/overwrite-web-socket-methods.js +7 -3
- package/package.json +10 -10
package/dist/augments/json.js
CHANGED
|
@@ -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
|
|
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 = {
|
|
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, {
|
|
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
|
-
}, {
|
|
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:
|
|
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, {
|
|
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, {
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
28
|
-
|
|
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 {
|
|
93
|
+
yield {
|
|
94
|
+
type,
|
|
95
|
+
index: i++,
|
|
96
|
+
value,
|
|
97
|
+
};
|
|
94
98
|
}
|
|
95
99
|
else if (value === '\\') {
|
|
96
|
-
yield {
|
|
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 {
|
|
108
|
+
yield {
|
|
109
|
+
type: 'PARAM',
|
|
110
|
+
index: i,
|
|
111
|
+
value,
|
|
112
|
+
};
|
|
101
113
|
}
|
|
102
114
|
else if (value === '*') {
|
|
103
|
-
yield {
|
|
115
|
+
yield {
|
|
116
|
+
type: 'WILDCARD',
|
|
117
|
+
index: i++,
|
|
118
|
+
value: '*',
|
|
119
|
+
};
|
|
104
120
|
}
|
|
105
121
|
else {
|
|
106
|
-
yield {
|
|
122
|
+
yield {
|
|
123
|
+
type: 'CHAR',
|
|
124
|
+
index: i,
|
|
125
|
+
value: chars[i++],
|
|
126
|
+
};
|
|
107
127
|
}
|
|
108
128
|
}
|
|
109
|
-
return {
|
|
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({
|
|
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 {
|
|
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 {
|
|
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 = {
|
|
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, {
|
|
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.
|
|
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.
|
|
44
|
-
"@augment-vir/common": "^31.
|
|
45
|
-
"date-vir": "^8.1
|
|
46
|
-
"type-fest": "^5.
|
|
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.
|
|
51
|
-
"@web/dev-server-esbuild": "^1.0.
|
|
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.
|
|
59
|
-
"typedoc": "^0.28.
|
|
60
|
-
"ws": "^8.
|
|
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"
|