@rest-vir/run-service 0.19.0 → 0.19.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/dist/handle-request/endpoint-handler.d.ts +10 -1
- package/dist/handle-request/endpoint-handler.js +22 -1
- package/dist/handle-request/handle-endpoint.js +1 -0
- package/dist/handle-request/handle-route.d.ts +19 -6
- package/dist/handle-request/handle-route.js +25 -9
- package/dist/handle-request/handle-web-socket.js +1 -0
- package/dist/handle-request/pre-handler.d.ts +16 -10
- package/dist/handle-request/pre-handler.js +26 -22
- package/dist/handle-request/run-post-hook.d.ts +17 -0
- package/dist/handle-request/run-post-hook.js +59 -0
- package/dist/start-service/attach-service.d.ts +14 -6
- package/dist/start-service/attach-service.js +89 -85
- package/package.json +8 -8
|
@@ -65,6 +65,15 @@ export type EndpointHandlerParams = {
|
|
|
65
65
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
66
66
|
*/
|
|
67
67
|
export type EndpointHandler = (params: Readonly<EndpointHandlerParams>) => MaybePromise<HandledOutput>;
|
|
68
|
+
/**
|
|
69
|
+
* Handle the output of a handler without sending the response. Similar to
|
|
70
|
+
* {@link handleHandlerOutput} but this one does not send the response.
|
|
71
|
+
*
|
|
72
|
+
* @category Internal
|
|
73
|
+
* @category Package : @rest-vir/run-service
|
|
74
|
+
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
75
|
+
*/
|
|
76
|
+
export declare function handleHandlerOutputWithoutSending(result: Readonly<HandledOutput>, response: ServerResponse): undefined | HandledOutput;
|
|
68
77
|
/**
|
|
69
78
|
* Handle the output of a handler. Setting headers, sending the response, etc.
|
|
70
79
|
*
|
|
@@ -72,4 +81,4 @@ export type EndpointHandler = (params: Readonly<EndpointHandlerParams>) => Maybe
|
|
|
72
81
|
* @category Package : @rest-vir/run-service
|
|
73
82
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
74
83
|
*/
|
|
75
|
-
export declare function
|
|
84
|
+
export declare function handleHandlerOutput(result: Readonly<HandledOutput>, response: ServerResponse): undefined | FastifyReply;
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
/* node:coverage disable: this file is just types */
|
|
2
2
|
import { setResponseHeaders } from '../util/headers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Handle the output of a handler without sending the response. Similar to
|
|
5
|
+
* {@link handleHandlerOutput} but this one does not send the response.
|
|
6
|
+
*
|
|
7
|
+
* @category Internal
|
|
8
|
+
* @category Package : @rest-vir/run-service
|
|
9
|
+
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
10
|
+
*/
|
|
11
|
+
export function handleHandlerOutputWithoutSending(result, response) {
|
|
12
|
+
if (result?.headers) {
|
|
13
|
+
setResponseHeaders(response, result.headers);
|
|
14
|
+
}
|
|
15
|
+
if (result?.statusCode) {
|
|
16
|
+
response.statusCode = result.statusCode;
|
|
17
|
+
return {
|
|
18
|
+
body: result.body,
|
|
19
|
+
statusCode: result.statusCode,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
3
24
|
/**
|
|
4
25
|
* Handle the output of a handler. Setting headers, sending the response, etc.
|
|
5
26
|
*
|
|
@@ -7,7 +28,7 @@ import { setResponseHeaders } from '../util/headers.js';
|
|
|
7
28
|
* @category Package : @rest-vir/run-service
|
|
8
29
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
9
30
|
*/
|
|
10
|
-
export function
|
|
31
|
+
export function handleHandlerOutput(result, response) {
|
|
11
32
|
if (result?.headers) {
|
|
12
33
|
setResponseHeaders(response, result.headers);
|
|
13
34
|
}
|
|
@@ -11,6 +11,7 @@ import { assertValidShape } from 'object-shape-tester';
|
|
|
11
11
|
*/
|
|
12
12
|
export async function handleEndpointRequest({ endpoint, request, response, attachId, server, }) {
|
|
13
13
|
try {
|
|
14
|
+
// by this point in the request lifecycle, we know that these properties have been set.
|
|
14
15
|
const restVirContext = request.restVirContext?.[attachId];
|
|
15
16
|
assert.isDefined(restVirContext, 'restVirContext is not defined');
|
|
16
17
|
const context = restVirContext.context;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SelectFrom } from '@augment-vir/common';
|
|
2
|
+
import { type GenericServiceImplementation, type ImplementedEndpoint, type ImplementedWebSocket, type PostHook, type RunningServerInfo, type ServerRequest, type ServerResponse } from '@rest-vir/implement-service';
|
|
2
3
|
import { type WebSocket as WsWebSocket } from 'ws';
|
|
3
4
|
import { type HandleRouteOptions } from './endpoint-handler.js';
|
|
4
5
|
/**
|
|
@@ -8,8 +9,20 @@ import { type HandleRouteOptions } from './endpoint-handler.js';
|
|
|
8
9
|
* @category Package : @rest-vir/run-service
|
|
9
10
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
10
11
|
*/
|
|
11
|
-
export declare function handleRoute(
|
|
12
|
-
/** Endpoint requests won't have a `WebSocket`. */
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
export declare function handleRoute({ webSocket, request, response, route, attachId, server, options, postHook, service, }: {
|
|
13
|
+
/** Endpoint requests won't have a `WebSocket`. */ webSocket: WsWebSocket | undefined;
|
|
14
|
+
request: ServerRequest /** `WebSocket` requests won't have a response. */;
|
|
15
|
+
response: ServerResponse | undefined;
|
|
16
|
+
route: Readonly<ImplementedEndpoint | ImplementedWebSocket>;
|
|
17
|
+
attachId: string;
|
|
18
|
+
server: Readonly<RunningServerInfo>;
|
|
19
|
+
options: Readonly<Pick<HandleRouteOptions, 'throwErrorsForExternalHandling'>>;
|
|
20
|
+
postHook: PostHook | undefined;
|
|
21
|
+
service: Readonly<SelectFrom<GenericServiceImplementation, {
|
|
22
|
+
webSockets: true;
|
|
23
|
+
endpoints: true;
|
|
24
|
+
serviceName: true;
|
|
25
|
+
serviceOrigin: true;
|
|
26
|
+
requiredClientOrigin: true;
|
|
27
|
+
}>>;
|
|
28
|
+
}): Promise<void>;
|
|
@@ -2,9 +2,10 @@ import { assert, check } from '@augment-vir/assert';
|
|
|
2
2
|
import { ensureError, HttpStatus } from '@augment-vir/common';
|
|
3
3
|
import { RestVirHandlerError, } from '@rest-vir/implement-service';
|
|
4
4
|
import cluster from 'node:cluster';
|
|
5
|
-
import {
|
|
5
|
+
import { handleHandlerOutput, handleHandlerOutputWithoutSending, } from './endpoint-handler.js';
|
|
6
6
|
import { handleEndpointRequest } from './handle-endpoint.js';
|
|
7
7
|
import { handleWebSocketRequest } from './handle-web-socket.js';
|
|
8
|
+
import { runPostHook } from './run-post-hook.js';
|
|
8
9
|
/**
|
|
9
10
|
* Handles a WebSocket or Endpoint request.
|
|
10
11
|
*
|
|
@@ -12,11 +13,7 @@ import { handleWebSocketRequest } from './handle-web-socket.js';
|
|
|
12
13
|
* @category Package : @rest-vir/run-service
|
|
13
14
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
14
15
|
*/
|
|
15
|
-
export async function handleRoute(
|
|
16
|
-
/** Endpoint requests won't have a `WebSocket`. */
|
|
17
|
-
webSocket, request,
|
|
18
|
-
/** `WebSocket` requests won't have a response. */
|
|
19
|
-
response, route, attachId, server, options = {}) {
|
|
16
|
+
export async function handleRoute({ webSocket, request, response, route, attachId, server, options = {}, postHook, service, }) {
|
|
20
17
|
try {
|
|
21
18
|
const workerPid = cluster.isPrimary ? '' : process.pid;
|
|
22
19
|
const webSocketMarker = route.isWebSocket ? '(ws)' : '';
|
|
@@ -36,9 +33,28 @@ response, route, attachId, server, options = {}) {
|
|
|
36
33
|
attachId,
|
|
37
34
|
server,
|
|
38
35
|
});
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const endpointResult = handleHandlerOutputWithoutSending(result, response);
|
|
37
|
+
const postHookResult = (postHook &&
|
|
38
|
+
endpointResult?.statusCode &&
|
|
39
|
+
(await runPostHook({
|
|
40
|
+
attachId,
|
|
41
|
+
originalBody: endpointResult.body,
|
|
42
|
+
originalStatus: endpointResult.statusCode,
|
|
43
|
+
postHook,
|
|
44
|
+
request,
|
|
45
|
+
response,
|
|
46
|
+
server,
|
|
47
|
+
service,
|
|
48
|
+
}))) ||
|
|
49
|
+
endpointResult;
|
|
50
|
+
if (postHookResult) {
|
|
51
|
+
if (postHookResult.body == undefined) {
|
|
52
|
+
postHookResult.headers = {
|
|
53
|
+
...postHookResult.headers,
|
|
54
|
+
'content-type': undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return handleHandlerOutput(postHookResult, response);
|
|
42
58
|
}
|
|
43
59
|
}
|
|
44
60
|
else if (route.isWebSocket) {
|
|
@@ -11,6 +11,7 @@ import { assertValidShape } from 'object-shape-tester';
|
|
|
11
11
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
12
12
|
*/
|
|
13
13
|
export async function handleWebSocketRequest({ attachId, request, implementedWebSocket, webSocket: wsWebSocket, server, }) {
|
|
14
|
+
// by this point in the request lifecycle, we know that these properties have been set.
|
|
14
15
|
const restVirContext = request.restVirContext?.[attachId];
|
|
15
16
|
assert.isDefined(restVirContext, 'restVirContext is not defined');
|
|
16
17
|
const webSocket = overwriteWebSocketMethods(implementedWebSocket, wsWebSocket, WebSocketLocation.OnHost);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SelectFrom } from '@augment-vir/common';
|
|
2
2
|
import { type GenericServiceImplementation, type RunningServerInfo, type ServerRequest, type ServerResponse } from '@rest-vir/implement-service';
|
|
3
|
-
import { type
|
|
3
|
+
import { type HandledOutput } from './endpoint-handler.js';
|
|
4
4
|
/**
|
|
5
5
|
* Handles a request before it gets to the actual route handlers.
|
|
6
6
|
*
|
|
@@ -8,12 +8,18 @@ import { type FastifyReply } from 'fastify';
|
|
|
8
8
|
* @category Package : @rest-vir/run-service
|
|
9
9
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
10
10
|
*/
|
|
11
|
-
export declare function preHandler(request
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
export declare function preHandler({ request, response, service, server, attachId, }: {
|
|
12
|
+
request: ServerRequest;
|
|
13
|
+
response: ServerResponse;
|
|
14
|
+
service: Readonly<SelectFrom<GenericServiceImplementation, {
|
|
15
|
+
webSockets: true;
|
|
16
|
+
endpoints: true;
|
|
17
|
+
serviceName: true;
|
|
18
|
+
createContext: true;
|
|
19
|
+
serviceOrigin: true;
|
|
20
|
+
requiredClientOrigin: true;
|
|
21
|
+
logger: true;
|
|
22
|
+
}>>;
|
|
23
|
+
server: Readonly<RunningServerInfo>;
|
|
24
|
+
attachId: string;
|
|
25
|
+
}): Promise<Readonly<HandledOutput>>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { assertWrap } from '@augment-vir/assert';
|
|
2
|
-
import { ensureErrorAndPrependMessage, extractErrorMessage, HttpStatus, stringify, wrapInTry, } from '@augment-vir/common';
|
|
2
|
+
import { ensureErrorAndPrependMessage, extractErrorMessage, getOrSet, HttpStatus, stringify, wrapInTry, } from '@augment-vir/common';
|
|
3
3
|
import { isFormDataShape, matchUrlToService, restVirServiceNameHeader, } from '@rest-vir/define-service';
|
|
4
4
|
import { HttpMethod, RestVirHandlerError, } from '@rest-vir/implement-service';
|
|
5
5
|
import { assertValidShape, isValidShape } from 'object-shape-tester';
|
|
6
|
-
import {
|
|
6
|
+
import { handleHandlerOutputWithoutSending } from './endpoint-handler.js';
|
|
7
7
|
import { handleCors } from './handle-cors.js';
|
|
8
8
|
import { handleRequestMethod } from './handle-request-method.js';
|
|
9
9
|
import { handleSearchParams } from './handle-search-params.js';
|
|
@@ -14,7 +14,13 @@ import { handleSearchParams } from './handle-search-params.js';
|
|
|
14
14
|
* @category Package : @rest-vir/run-service
|
|
15
15
|
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
16
16
|
*/
|
|
17
|
-
export async function preHandler(request, response, service, server, attachId) {
|
|
17
|
+
export async function preHandler({ request, response, service, server, attachId, }) {
|
|
18
|
+
if (!request.restVirContext) {
|
|
19
|
+
request.restVirContext = {};
|
|
20
|
+
}
|
|
21
|
+
const attachedRestVirContext = getOrSet(request.restVirContext, attachId, () => {
|
|
22
|
+
return {};
|
|
23
|
+
});
|
|
18
24
|
response.header(restVirServiceNameHeader, service.serviceName);
|
|
19
25
|
const pathMatch = matchUrlToService(service, request.originalUrl);
|
|
20
26
|
if (!pathMatch) {
|
|
@@ -41,15 +47,17 @@ export async function preHandler(request, response, service, server, attachId) {
|
|
|
41
47
|
: undefined;
|
|
42
48
|
if (protocolShapeError) {
|
|
43
49
|
service.logger.error(new RestVirHandlerError(route, extractErrorMessage(ensureErrorAndPrependMessage(protocolShapeError, `WebSocket protocols rejected (${stringify(protocols)}):`))));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
return {
|
|
51
|
+
statusCode: HttpStatus.BadRequest,
|
|
52
|
+
body: 'Invalid protocol.',
|
|
53
|
+
};
|
|
47
54
|
}
|
|
48
|
-
|
|
55
|
+
attachedRestVirContext.protocols = protocols;
|
|
56
|
+
const subHandlerResponse = handleHandlerOutputWithoutSending(await handleCors({
|
|
49
57
|
request,
|
|
50
58
|
route,
|
|
51
59
|
}), response) ||
|
|
52
|
-
|
|
60
|
+
handleHandlerOutputWithoutSending(handleRequestMethod({
|
|
53
61
|
request,
|
|
54
62
|
route,
|
|
55
63
|
}), response);
|
|
@@ -59,15 +67,19 @@ export async function preHandler(request, response, service, server, attachId) {
|
|
|
59
67
|
const requestData = wrapInTry(() => extractRequestData(request.body, request.headers, route));
|
|
60
68
|
if (requestData instanceof Error) {
|
|
61
69
|
service.logger.error(new RestVirHandlerError(route, `Rejected request body from '${request.originalUrl}': ${stringify(requestData)}`));
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
return {
|
|
71
|
+
statusCode: HttpStatus.BadRequest,
|
|
72
|
+
body: 'Invalid body.',
|
|
73
|
+
};
|
|
65
74
|
}
|
|
75
|
+
attachedRestVirContext.requestData = requestData;
|
|
66
76
|
const searchParams = handleSearchParams({ request, route });
|
|
67
77
|
if (!('data' in searchParams)) {
|
|
68
|
-
return
|
|
78
|
+
return handleHandlerOutputWithoutSending(searchParams, response);
|
|
69
79
|
}
|
|
80
|
+
attachedRestVirContext.searchParams = searchParams.data;
|
|
70
81
|
const contextParams = {
|
|
82
|
+
pathParams: request.params,
|
|
71
83
|
method: assertWrap.isEnumValue(request.method.toUpperCase(), HttpMethod),
|
|
72
84
|
request,
|
|
73
85
|
requestData,
|
|
@@ -83,21 +95,13 @@ export async function preHandler(request, response, service, server, attachId) {
|
|
|
83
95
|
const contextOutput = await service.createContext?.(contextParams);
|
|
84
96
|
if (contextOutput?.reject) {
|
|
85
97
|
service.logger.error(new RestVirHandlerError(route, `Context creation rejected: '${request.originalUrl}'`));
|
|
86
|
-
return
|
|
98
|
+
return handleHandlerOutputWithoutSending({
|
|
87
99
|
body: contextOutput.reject.responseErrorMessage,
|
|
88
100
|
statusCode: contextOutput.reject.statusCode,
|
|
89
101
|
headers: contextOutput.reject.headers,
|
|
90
102
|
}, response);
|
|
91
103
|
}
|
|
92
|
-
|
|
93
|
-
request.restVirContext = {};
|
|
94
|
-
}
|
|
95
|
-
request.restVirContext[attachId] = {
|
|
96
|
-
context: contextOutput?.context,
|
|
97
|
-
requestData,
|
|
98
|
-
protocols,
|
|
99
|
-
searchParams: searchParams.data,
|
|
100
|
-
};
|
|
104
|
+
attachedRestVirContext.context = contextOutput?.context;
|
|
101
105
|
return undefined;
|
|
102
106
|
}
|
|
103
107
|
catch (error) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type HttpStatus, type SelectFrom } from '@augment-vir/common';
|
|
2
|
+
import { type GenericServiceImplementation, type PostHook, type RunningServerInfo } from '@rest-vir/implement-service';
|
|
3
|
+
import { type EndpointHandlerParams, type HandledOutput } from './endpoint-handler.js';
|
|
4
|
+
export declare function runPostHook(this: void, { request, response, attachId, server, postHook, service, originalBody, originalStatus, }: Readonly<Omit<EndpointHandlerParams, 'route'> & {
|
|
5
|
+
attachId: string;
|
|
6
|
+
server: Readonly<RunningServerInfo>;
|
|
7
|
+
postHook: PostHook;
|
|
8
|
+
originalBody: unknown;
|
|
9
|
+
originalStatus: HttpStatus;
|
|
10
|
+
service: Readonly<SelectFrom<GenericServiceImplementation, {
|
|
11
|
+
webSockets: true;
|
|
12
|
+
endpoints: true;
|
|
13
|
+
serviceName: true;
|
|
14
|
+
serviceOrigin: true;
|
|
15
|
+
requiredClientOrigin: true;
|
|
16
|
+
}>>;
|
|
17
|
+
}>): Promise<HandledOutput>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { assert, assertWrap } from '@augment-vir/assert';
|
|
2
|
+
import { matchUrlToService } from '@rest-vir/define-service';
|
|
3
|
+
import { HttpMethod, } from '@rest-vir/implement-service';
|
|
4
|
+
export async function runPostHook({ request, response, attachId, server, postHook, service, originalBody, originalStatus, }) {
|
|
5
|
+
const restVirContext = request.restVirContext?.[attachId];
|
|
6
|
+
assert.isDefined(restVirContext, 'restVirContext is not defined');
|
|
7
|
+
const context = restVirContext.context;
|
|
8
|
+
const requestData = restVirContext.requestData;
|
|
9
|
+
const searchParams = restVirContext.searchParams;
|
|
10
|
+
const pathMatch = matchUrlToService(service, request.originalUrl);
|
|
11
|
+
/* node:coverage ignore next 10 */
|
|
12
|
+
if (!pathMatch) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const endpointDefinition = pathMatch.endpointPath
|
|
16
|
+
? service.endpoints[pathMatch.endpointPath]
|
|
17
|
+
: undefined;
|
|
18
|
+
const webSocketDefinition = request.ws && pathMatch.webSocketPath
|
|
19
|
+
? service.webSockets[pathMatch.webSocketPath]
|
|
20
|
+
: undefined;
|
|
21
|
+
const postHookParams = {
|
|
22
|
+
pathParams: request.params,
|
|
23
|
+
method: assertWrap.isEnumValue(request.method.toUpperCase(), HttpMethod),
|
|
24
|
+
request,
|
|
25
|
+
requestData,
|
|
26
|
+
requestHeaders: request.headers,
|
|
27
|
+
response,
|
|
28
|
+
service,
|
|
29
|
+
endpointDefinition,
|
|
30
|
+
webSocketDefinition,
|
|
31
|
+
context,
|
|
32
|
+
searchParams,
|
|
33
|
+
server,
|
|
34
|
+
originalResponseData: originalBody,
|
|
35
|
+
originalStatus,
|
|
36
|
+
};
|
|
37
|
+
const result = await postHook(postHookParams);
|
|
38
|
+
if (result) {
|
|
39
|
+
return {
|
|
40
|
+
body: 'responseErrorMessage' in result
|
|
41
|
+
? result.responseErrorMessage
|
|
42
|
+
: 'responseData' in result
|
|
43
|
+
? result.responseData
|
|
44
|
+
: originalBody,
|
|
45
|
+
statusCode: 'statusCode' in result ? result.statusCode : originalStatus,
|
|
46
|
+
headers: {
|
|
47
|
+
...result.headers,
|
|
48
|
+
...(result.dataType
|
|
49
|
+
? {
|
|
50
|
+
'content-type': result.dataType,
|
|
51
|
+
}
|
|
52
|
+
: {}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -3,15 +3,23 @@ import { type BaseSearchParams, type MinimalService } from '@rest-vir/define-ser
|
|
|
3
3
|
import { type GenericServiceImplementation, type RunningServerInfo } from '@rest-vir/implement-service';
|
|
4
4
|
import { type FastifyInstance } from 'fastify';
|
|
5
5
|
import { type HandleRouteOptions } from '../handle-request/endpoint-handler.js';
|
|
6
|
+
/**
|
|
7
|
+
* Context attached to each fastify request object.
|
|
8
|
+
*
|
|
9
|
+
* @category Internal
|
|
10
|
+
* @category Package : @rest-vir/run-service
|
|
11
|
+
* @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
|
|
12
|
+
*/
|
|
13
|
+
export type RestVirRequestContext = {
|
|
14
|
+
context: unknown;
|
|
15
|
+
requestData: unknown;
|
|
16
|
+
protocols: string[];
|
|
17
|
+
searchParams: BaseSearchParams;
|
|
18
|
+
};
|
|
6
19
|
declare module 'fastify' {
|
|
7
20
|
interface FastifyRequest {
|
|
8
21
|
restVirContext: {
|
|
9
|
-
[AttachId in string]:
|
|
10
|
-
context: unknown;
|
|
11
|
-
requestData: unknown;
|
|
12
|
-
protocols: string[];
|
|
13
|
-
searchParams: BaseSearchParams;
|
|
14
|
-
};
|
|
22
|
+
[AttachId in string]: Partial<RestVirRequestContext>;
|
|
15
23
|
} | undefined;
|
|
16
24
|
}
|
|
17
25
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ensureError, extractErrorMessage, getEnumValues, getObjectTypedKeys, HttpMethod, HttpStatus,
|
|
1
|
+
import { assert, check } from '@augment-vir/assert';
|
|
2
|
+
import { ensureError, extractErrorMessage, getEnumValues, getObjectTypedKeys, HttpMethod, HttpStatus, randomString, } from '@augment-vir/common';
|
|
3
3
|
import fastifyWs from '@fastify/websocket';
|
|
4
|
-
import { matchUrlToService, } from '@rest-vir/define-service';
|
|
5
4
|
import { RestVirHandlerError, } from '@rest-vir/implement-service';
|
|
6
5
|
import { buildUrl, parseUrl } from 'url-vir';
|
|
6
|
+
import { handleHandlerOutput } from '../handle-request/endpoint-handler.js';
|
|
7
7
|
import { handleRoute } from '../handle-request/handle-route.js';
|
|
8
8
|
import { preHandler } from '../handle-request/pre-handler.js';
|
|
9
|
-
import {
|
|
9
|
+
import { runPostHook } from '../handle-request/run-post-hook.js';
|
|
10
10
|
const endpointFastifyMethods = getEnumValues(HttpMethod).filter((value) => {
|
|
11
11
|
return (
|
|
12
12
|
/** Fastify doesn't support the CONNECT method. */
|
|
@@ -53,94 +53,48 @@ export async function attachService(server, service, options = {}) {
|
|
|
53
53
|
},
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
const postHook = service.postHook;
|
|
56
57
|
server.addHook('preValidation', async (request, response) => {
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
+
const preHandlerResult = await preHandler({
|
|
60
|
+
request,
|
|
61
|
+
response,
|
|
62
|
+
service,
|
|
63
|
+
server: extractRunningServerInfo(service, server),
|
|
64
|
+
attachId,
|
|
65
|
+
});
|
|
66
|
+
if (preHandlerResult?.statusCode && postHook) {
|
|
67
|
+
const postHookResult = await runPostHook({
|
|
68
|
+
attachId,
|
|
69
|
+
originalBody: preHandlerResult.body,
|
|
70
|
+
originalStatus: preHandlerResult.statusCode,
|
|
71
|
+
postHook,
|
|
72
|
+
request,
|
|
73
|
+
response,
|
|
74
|
+
server: extractRunningServerInfo(service, server),
|
|
75
|
+
service,
|
|
76
|
+
});
|
|
77
|
+
if (postHookResult) {
|
|
78
|
+
return handleHandlerOutput(postHookResult, response);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return handleHandlerOutput(preHandlerResult, response);
|
|
59
82
|
}
|
|
60
83
|
catch (error) {
|
|
61
84
|
service.logger.error(ensureError(error));
|
|
62
85
|
if (options.throwErrorsForExternalHandling) {
|
|
63
86
|
throw error;
|
|
87
|
+
/* node:coverage ignore next 5 */
|
|
88
|
+
}
|
|
89
|
+
else if (response.sent) {
|
|
90
|
+
assert.never("Error encountered but response was already sent so there's nothing we can do about it.");
|
|
64
91
|
}
|
|
65
|
-
else
|
|
92
|
+
else {
|
|
66
93
|
response.statusCode = HttpStatus.InternalServerError;
|
|
67
|
-
response.send();
|
|
94
|
+
return response.send();
|
|
68
95
|
}
|
|
69
96
|
}
|
|
70
97
|
});
|
|
71
|
-
const postHook = service.postHook;
|
|
72
|
-
if (postHook) {
|
|
73
|
-
server.addHook('onSend', async (request, response, body) => {
|
|
74
|
-
/* node:coverage ignore next 4 */
|
|
75
|
-
const restVirContext = request.restVirContext?.[attachId];
|
|
76
|
-
if (!restVirContext) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
const context = restVirContext.context;
|
|
80
|
-
const requestData = restVirContext.requestData;
|
|
81
|
-
const searchParams = restVirContext.searchParams;
|
|
82
|
-
const pathMatch = matchUrlToService(service, request.originalUrl);
|
|
83
|
-
/* node:coverage ignore next 10 */
|
|
84
|
-
if (!pathMatch) {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
const endpointDefinition = pathMatch.endpointPath
|
|
88
|
-
? service.endpoints[pathMatch.endpointPath]
|
|
89
|
-
: undefined;
|
|
90
|
-
const webSocketDefinition = request.ws && pathMatch.webSocketPath
|
|
91
|
-
? service.webSockets[pathMatch.webSocketPath]
|
|
92
|
-
: undefined;
|
|
93
|
-
const postHookParams = {
|
|
94
|
-
context,
|
|
95
|
-
method: assertWrap.isEnumValue(request.method.toUpperCase(), HttpMethod),
|
|
96
|
-
request,
|
|
97
|
-
requestData,
|
|
98
|
-
requestHeaders: request.headers,
|
|
99
|
-
response,
|
|
100
|
-
service,
|
|
101
|
-
endpointDefinition: endpointDefinition,
|
|
102
|
-
webSocketDefinition: webSocketDefinition,
|
|
103
|
-
server: extractRunningServerInfo(service, server),
|
|
104
|
-
searchParams,
|
|
105
|
-
originalResponseData: body,
|
|
106
|
-
originalStatus: response.statusCode,
|
|
107
|
-
};
|
|
108
|
-
const result = await postHook(postHookParams);
|
|
109
|
-
if (result) {
|
|
110
|
-
if (result.headers) {
|
|
111
|
-
setResponseHeaders(response, result.headers);
|
|
112
|
-
}
|
|
113
|
-
if (result.dataType) {
|
|
114
|
-
setResponseHeaders(response, {
|
|
115
|
-
'content-type': result.dataType,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
if (result.statusCode) {
|
|
119
|
-
response.status(result.statusCode);
|
|
120
|
-
}
|
|
121
|
-
if (isErrorHttpStatus(result.statusCode ?? response.statusCode) &&
|
|
122
|
-
'responseErrorMessage' in result) {
|
|
123
|
-
if (result.responseErrorMessage == undefined) {
|
|
124
|
-
/** Clear the body. */
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
return result.responseErrorMessage;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else if ('responseData' in result) {
|
|
132
|
-
if (result.responseData == undefined) {
|
|
133
|
-
/** Clear the body. */
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
return result.responseData;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return undefined;
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
98
|
const allPaths = new Set([
|
|
145
99
|
...getObjectTypedKeys(service.webSockets),
|
|
146
100
|
...getObjectTypedKeys(service.endpoints),
|
|
@@ -153,17 +107,47 @@ export async function attachService(server, service, options = {}) {
|
|
|
153
107
|
method: endpointFastifyMethods,
|
|
154
108
|
url: path,
|
|
155
109
|
handler(request, response) {
|
|
156
|
-
return handleRoute(
|
|
110
|
+
return handleRoute({
|
|
111
|
+
webSocket: undefined,
|
|
112
|
+
request,
|
|
113
|
+
response,
|
|
114
|
+
route: endpoint,
|
|
115
|
+
attachId,
|
|
116
|
+
server: extractRunningServerInfo(service, server),
|
|
117
|
+
options,
|
|
118
|
+
postHook,
|
|
119
|
+
service,
|
|
120
|
+
});
|
|
157
121
|
},
|
|
158
122
|
});
|
|
159
123
|
server.route({
|
|
160
124
|
method: HttpMethod.Get,
|
|
161
125
|
url: path,
|
|
162
126
|
handler(request, response) {
|
|
163
|
-
return handleRoute(
|
|
127
|
+
return handleRoute({
|
|
128
|
+
webSocket: undefined,
|
|
129
|
+
request,
|
|
130
|
+
response,
|
|
131
|
+
route: endpoint,
|
|
132
|
+
attachId,
|
|
133
|
+
server: extractRunningServerInfo(service, server),
|
|
134
|
+
options,
|
|
135
|
+
postHook,
|
|
136
|
+
service,
|
|
137
|
+
});
|
|
164
138
|
},
|
|
165
139
|
wsHandler(webSocket, request) {
|
|
166
|
-
return handleRoute(
|
|
140
|
+
return handleRoute({
|
|
141
|
+
webSocket,
|
|
142
|
+
request,
|
|
143
|
+
response: undefined,
|
|
144
|
+
route: webSocketDefinition,
|
|
145
|
+
attachId,
|
|
146
|
+
server: extractRunningServerInfo(service, server),
|
|
147
|
+
options,
|
|
148
|
+
postHook,
|
|
149
|
+
service,
|
|
150
|
+
});
|
|
167
151
|
},
|
|
168
152
|
});
|
|
169
153
|
}
|
|
@@ -175,7 +159,17 @@ export async function attachService(server, service, options = {}) {
|
|
|
175
159
|
],
|
|
176
160
|
url: path,
|
|
177
161
|
handler(request, response) {
|
|
178
|
-
return handleRoute(
|
|
162
|
+
return handleRoute({
|
|
163
|
+
webSocket: undefined,
|
|
164
|
+
request,
|
|
165
|
+
response,
|
|
166
|
+
route: endpoint,
|
|
167
|
+
attachId,
|
|
168
|
+
server: extractRunningServerInfo(service, server),
|
|
169
|
+
options,
|
|
170
|
+
postHook,
|
|
171
|
+
service,
|
|
172
|
+
});
|
|
179
173
|
},
|
|
180
174
|
});
|
|
181
175
|
}
|
|
@@ -187,7 +181,17 @@ export async function attachService(server, service, options = {}) {
|
|
|
187
181
|
return response.status(HttpStatus.NotFound).send();
|
|
188
182
|
},
|
|
189
183
|
wsHandler(webSocket, request) {
|
|
190
|
-
return handleRoute(
|
|
184
|
+
return handleRoute({
|
|
185
|
+
webSocket,
|
|
186
|
+
request,
|
|
187
|
+
response: undefined,
|
|
188
|
+
route: webSocketDefinition,
|
|
189
|
+
attachId,
|
|
190
|
+
server: extractRunningServerInfo(service, server),
|
|
191
|
+
options,
|
|
192
|
+
postHook,
|
|
193
|
+
service,
|
|
194
|
+
});
|
|
191
195
|
},
|
|
192
196
|
});
|
|
193
197
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rest-vir/run-service",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
4
4
|
"description": "Run a service defined by @rest-vir/define-service and implemented by @rest-vir/implement-service.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rest",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"test:update": "npm test update"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@augment-vir/assert": "^31.
|
|
43
|
-
"@augment-vir/common": "^31.
|
|
44
|
-
"@augment-vir/node": "^31.
|
|
42
|
+
"@augment-vir/assert": "^31.20.0",
|
|
43
|
+
"@augment-vir/common": "^31.20.0",
|
|
44
|
+
"@augment-vir/node": "^31.20.0",
|
|
45
45
|
"@fastify/websocket": "^11.0.2",
|
|
46
|
-
"@rest-vir/define-service": "^0.19.
|
|
47
|
-
"@rest-vir/implement-service": "^0.19.
|
|
46
|
+
"@rest-vir/define-service": "^0.19.2",
|
|
47
|
+
"@rest-vir/implement-service": "^0.19.2",
|
|
48
48
|
"cluster-vir": "^0.1.0",
|
|
49
49
|
"date-vir": "^7.3.1",
|
|
50
50
|
"fastify": "^5.3.3",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"url-vir": "^2.1.3"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@augment-vir/test": "^31.
|
|
57
|
+
"@augment-vir/test": "^31.20.0",
|
|
58
58
|
"@fastify/multipart": "^9.0.3",
|
|
59
59
|
"@types/connect": "^3.4.38",
|
|
60
|
-
"@types/node": "^22.15.
|
|
60
|
+
"@types/node": "^22.15.21",
|
|
61
61
|
"@types/ws": "^8.18.1",
|
|
62
62
|
"c8": "^10.1.3",
|
|
63
63
|
"istanbul-smart-text-reporter": "^1.1.5",
|