@rest-vir/run-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 (32) hide show
  1. package/LICENSE-CC0 +121 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +23 -0
  4. package/dist/handle-request/endpoint-handler.d.ts +76 -0
  5. package/dist/handle-request/endpoint-handler.js +29 -0
  6. package/dist/handle-request/handle-cors.d.ts +37 -0
  7. package/dist/handle-request/handle-cors.js +141 -0
  8. package/dist/handle-request/handle-endpoint.d.ts +13 -0
  9. package/dist/handle-request/handle-endpoint.js +62 -0
  10. package/dist/handle-request/handle-request-method.d.ts +26 -0
  11. package/dist/handle-request/handle-request-method.js +33 -0
  12. package/dist/handle-request/handle-route.d.ts +15 -0
  13. package/dist/handle-request/handle-route.js +65 -0
  14. package/dist/handle-request/handle-search-params.d.ts +33 -0
  15. package/dist/handle-request/handle-search-params.js +32 -0
  16. package/dist/handle-request/handle-web-socket.d.ts +15 -0
  17. package/dist/handle-request/handle-web-socket.js +65 -0
  18. package/dist/handle-request/pre-handler.d.ts +18 -0
  19. package/dist/handle-request/pre-handler.js +123 -0
  20. package/dist/index.d.ts +18 -0
  21. package/dist/index.js +17 -0
  22. package/dist/start-service/attach-service.d.ts +45 -0
  23. package/dist/start-service/attach-service.js +124 -0
  24. package/dist/start-service/start-service-options.d.ts +83 -0
  25. package/dist/start-service/start-service-options.js +73 -0
  26. package/dist/start-service/start-service.d.ts +63 -0
  27. package/dist/start-service/start-service.js +85 -0
  28. package/dist/util/debug.d.ts +11 -0
  29. package/dist/util/debug.js +16 -0
  30. package/dist/util/headers.d.ts +11 -0
  31. package/dist/util/headers.js +19 -0
  32. package/package.json +72 -0
@@ -0,0 +1,33 @@
1
+ import { type ErrorHttpStatusCategories, type HttpStatusByCategory, type SelectFrom } from '@augment-vir/common';
2
+ import type { BaseSearchParams } from '@rest-vir/define-service';
3
+ import { type ImplementedEndpoint, type ImplementedWebSocket, type ServerRequest } from '@rest-vir/implement-service';
4
+ /**
5
+ * Handles a request's search params and compares it against the route's required search params
6
+ * shape, if it has any.
7
+ *
8
+ * @category Internal
9
+ * @category Package : @rest-vir/run-service
10
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
11
+ */
12
+ export declare function handleSearchParams({ request, route, }: Readonly<{
13
+ request: Readonly<Pick<ServerRequest, 'originalUrl'>>;
14
+ route: Readonly<SelectFrom<ImplementedEndpoint | ImplementedWebSocket, {
15
+ searchParamsShape: true;
16
+ service: {
17
+ logger: true;
18
+ serviceName: true;
19
+ };
20
+ path: true;
21
+ isEndpoint: true;
22
+ isWebSocket: true;
23
+ }>>;
24
+ }>): {
25
+ body?: string;
26
+ /**
27
+ * If this is set, then the response is sent with this status code and the given body (if
28
+ * any).
29
+ */
30
+ statusCode: HttpStatusByCategory<ErrorHttpStatusCategories>;
31
+ } | {
32
+ data: BaseSearchParams;
33
+ };
@@ -0,0 +1,32 @@
1
+ import { HttpStatus, stringify, wrapInTry, } from '@augment-vir/common';
2
+ import { RestVirHandlerError, } from '@rest-vir/implement-service';
3
+ import { assertValidShape } from 'object-shape-tester';
4
+ import { parseUrl } from 'url-vir';
5
+ /**
6
+ * Handles a request's search params and compares it against the route's required search params
7
+ * shape, if it has any.
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 function handleSearchParams({ request, route, }) {
14
+ const searchParams = parseUrl(request.originalUrl).searchParams;
15
+ const shape = route.searchParamsShape;
16
+ const validationError = shape
17
+ ? wrapInTry(() => {
18
+ assertValidShape(searchParams, shape, { allowExtraKeys: true });
19
+ return undefined;
20
+ })
21
+ : undefined;
22
+ if (validationError) {
23
+ route.service.logger.error(new RestVirHandlerError(route, `Search params failed for ${stringify(searchParams)}: ${validationError.message}`));
24
+ return {
25
+ body: 'Invalid search params.',
26
+ statusCode: HttpStatus.BadRequest,
27
+ };
28
+ }
29
+ return {
30
+ data: searchParams,
31
+ };
32
+ }
@@ -0,0 +1,15 @@
1
+ import { ImplementedWebSocket, ServerRequest } from '@rest-vir/implement-service';
2
+ import { type WebSocket as WsWebSocket } from 'ws';
3
+ /**
4
+ * Handles a WebSocket request.
5
+ *
6
+ * @category Internal
7
+ * @category Package : @rest-vir/run-service
8
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
9
+ */
10
+ export declare function handleWebSocketRequest(this: void, { attachId, request, implementedWebSocket, webSocket: wsWebSocket, }: Readonly<{
11
+ request: ServerRequest;
12
+ attachId: string;
13
+ implementedWebSocket: Readonly<ImplementedWebSocket>;
14
+ webSocket: WsWebSocket;
15
+ }>): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { assert } from '@augment-vir/assert';
2
+ import { ensureErrorClass, extractErrorMessage, stringify } from '@augment-vir/common';
3
+ import { overwriteWebSocketMethods, parseJsonWithUndefined, WebSocketLocation, } from '@rest-vir/define-service';
4
+ import { RestVirHandlerError, } from '@rest-vir/implement-service';
5
+ import { assertValidShape } from 'object-shape-tester';
6
+ /**
7
+ * Handles a WebSocket request.
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 async function handleWebSocketRequest({ attachId, request, implementedWebSocket, webSocket: wsWebSocket, }) {
14
+ const restVirContext = request.restVirContext?.[attachId];
15
+ assert.isDefined(restVirContext, 'restVirContext is not defined');
16
+ const webSocket = overwriteWebSocketMethods(implementedWebSocket, wsWebSocket, WebSocketLocation.OnHost);
17
+ const webSocketCallbackParams = {
18
+ context: restVirContext.context,
19
+ headers: request.headers,
20
+ log: implementedWebSocket.service.logger,
21
+ request,
22
+ service: implementedWebSocket.service,
23
+ webSocketDefinition: implementedWebSocket,
24
+ webSocket,
25
+ protocols: restVirContext.protocols,
26
+ searchParams: restVirContext.searchParams,
27
+ };
28
+ if (implementedWebSocket.implementation.onClose) {
29
+ webSocket.on('close', async () => {
30
+ await implementedWebSocket.implementation.onClose?.(webSocketCallbackParams);
31
+ });
32
+ }
33
+ if (implementedWebSocket.implementation.onMessage) {
34
+ webSocket.on('message', async (rawMessage) => {
35
+ let message;
36
+ try {
37
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
38
+ const stringRawMessage = String(rawMessage);
39
+ message = parseJsonWithUndefined(stringRawMessage);
40
+ if (implementedWebSocket.messageFromClientShape) {
41
+ assertValidShape(message, implementedWebSocket.messageFromClientShape, { allowExtraKeys: true }, 'Invalid message send shape.');
42
+ }
43
+ else if (message) {
44
+ throw new Error(`Did not expect any data from the client but got ${stringify(message)}.`);
45
+ }
46
+ }
47
+ catch (error) {
48
+ const errorMessage = `Failed to receive WebSocket message '${String(rawMessage)}': ${extractErrorMessage(error)}`;
49
+ implementedWebSocket.service.logger.error(ensureErrorClass(error, RestVirHandlerError, implementedWebSocket, errorMessage));
50
+ }
51
+ try {
52
+ await implementedWebSocket.implementation.onMessage?.({
53
+ ...webSocketCallbackParams,
54
+ message,
55
+ });
56
+ }
57
+ catch (error) {
58
+ implementedWebSocket.service.logger.error(ensureErrorClass(error, RestVirHandlerError, implementedWebSocket, `Failed to handle WebSocket message '${String(rawMessage)}': ${extractErrorMessage(error)}`));
59
+ }
60
+ });
61
+ }
62
+ if (implementedWebSocket.implementation.onConnection) {
63
+ await implementedWebSocket.implementation.onConnection(webSocketCallbackParams);
64
+ }
65
+ }
@@ -0,0 +1,18 @@
1
+ import { type SelectFrom } from '@augment-vir/common';
2
+ import { GenericServiceImplementation, ServerRequest, ServerResponse } from '@rest-vir/implement-service';
3
+ /**
4
+ * Handles a request before it gets to the actual route handlers.
5
+ *
6
+ * @category Internal
7
+ * @category Package : @rest-vir/run-service
8
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
9
+ */
10
+ export declare function preHandler(request: ServerRequest, response: ServerResponse, service: Readonly<SelectFrom<GenericServiceImplementation, {
11
+ webSockets: true;
12
+ endpoints: true;
13
+ serviceName: true;
14
+ createContext: true;
15
+ serviceOrigin: true;
16
+ requiredClientOrigin: true;
17
+ logger: true;
18
+ }>>, attachId: string): Promise<void>;
@@ -0,0 +1,123 @@
1
+ import { assertWrap } from '@augment-vir/assert';
2
+ import { ensureErrorAndPrependMessage, extractErrorMessage, HttpStatus, stringify, wrapInTry, } from '@augment-vir/common';
3
+ import { restVirServiceNameHeader, } from '@rest-vir/define-service';
4
+ import { matchUrlToService } from '@rest-vir/define-service/src/service/match-url.js';
5
+ import { HttpMethod, RestVirHandlerError, } from '@rest-vir/implement-service';
6
+ import { assertValidShape, isValidShape } from 'object-shape-tester';
7
+ import { handleHandlerResult } from './endpoint-handler.js';
8
+ import { handleCors } from './handle-cors.js';
9
+ import { handleRequestMethod } from './handle-request-method.js';
10
+ import { handleSearchParams } from './handle-search-params.js';
11
+ /**
12
+ * Handles a request before it gets to the actual route handlers.
13
+ *
14
+ * @category Internal
15
+ * @category Package : @rest-vir/run-service
16
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
17
+ */
18
+ export async function preHandler(request, response, service, attachId) {
19
+ response.header(restVirServiceNameHeader, service.serviceName);
20
+ const pathMatch = matchUrlToService(service, request.originalUrl);
21
+ if (!pathMatch) {
22
+ /** Nothing to do. */
23
+ return;
24
+ }
25
+ const endpointDefinition = pathMatch.endpointPath
26
+ ? service.endpoints[pathMatch.endpointPath]
27
+ : undefined;
28
+ const webSocketDefinition = request.ws && pathMatch.webSocketPath
29
+ ? service.webSockets[pathMatch.webSocketPath]
30
+ : undefined;
31
+ const route = endpointDefinition || webSocketDefinition;
32
+ if (!route) {
33
+ return;
34
+ }
35
+ const protocols = webSocketDefinition
36
+ ? (request.headers['sec-websocket-protocol'] || '').split(', ')
37
+ : [];
38
+ const protocolShapeError = webSocketDefinition?.protocolsShape
39
+ ? wrapInTry(() => assertValidShape(protocols, webSocketDefinition.protocolsShape, {
40
+ allowExtraKeys: true,
41
+ }))
42
+ : undefined;
43
+ if (protocolShapeError) {
44
+ service.logger.error(new RestVirHandlerError(route, extractErrorMessage(ensureErrorAndPrependMessage(protocolShapeError, `WebSocket protocols rejected (${stringify(protocols)}):`))));
45
+ response.statusCode = HttpStatus.BadRequest;
46
+ response.send('Invalid protocols.');
47
+ return;
48
+ }
49
+ if (handleHandlerResult(await handleCors({
50
+ request,
51
+ route,
52
+ }), response).responseSent ||
53
+ handleHandlerResult(handleRequestMethod({
54
+ request,
55
+ route,
56
+ }), response).responseSent) {
57
+ return;
58
+ }
59
+ const requestData = wrapInTry(() => extractRequestData(request.body, route));
60
+ if (requestData instanceof Error) {
61
+ service.logger.error(new RestVirHandlerError(route, `Rejected request body from '${request.originalUrl}': ${stringify(requestData)}`));
62
+ response.statusCode = HttpStatus.BadRequest;
63
+ response.send('Invalid body.');
64
+ return;
65
+ }
66
+ const contextParams = {
67
+ method: assertWrap.isEnumValue(request.method.toUpperCase(), HttpMethod),
68
+ request,
69
+ requestData,
70
+ requestHeaders: request.headers,
71
+ response,
72
+ service,
73
+ endpointDefinition: endpointDefinition,
74
+ webSocketDefinition: webSocketDefinition,
75
+ };
76
+ const searchParams = handleSearchParams({ request, route });
77
+ if (!('data' in searchParams)) {
78
+ handleHandlerResult(searchParams, response);
79
+ return;
80
+ }
81
+ try {
82
+ const contextOutput = await service.createContext?.(contextParams);
83
+ if (contextOutput?.reject) {
84
+ service.logger.error(new RestVirHandlerError(route, `Context creation rejected: '${request.originalUrl}'`));
85
+ handleHandlerResult({
86
+ body: contextOutput.reject.responseErrorMessage,
87
+ statusCode: contextOutput.reject.statusCode,
88
+ headers: contextOutput.reject.headers,
89
+ }, response);
90
+ return;
91
+ }
92
+ if (!request.restVirContext) {
93
+ request.restVirContext = {};
94
+ }
95
+ request.restVirContext[attachId] = {
96
+ context: contextOutput?.context,
97
+ requestData,
98
+ protocols,
99
+ searchParams: searchParams.data,
100
+ };
101
+ }
102
+ catch (error) {
103
+ throw ensureErrorAndPrependMessage(error, 'Failed to generate request context.');
104
+ }
105
+ }
106
+ function extractRequestData(body, route) {
107
+ const dataShape = 'requestDataShape' in route ? route.requestDataShape : undefined;
108
+ if (!dataShape) {
109
+ if (body) {
110
+ throw new Error(`Did not expect any request data but received it.`);
111
+ }
112
+ else {
113
+ return undefined;
114
+ }
115
+ }
116
+ if (!isValidShape(body, dataShape, {
117
+ /** Allow extra keys for forwards / backwards compatibility. */
118
+ allowExtraKeys: true,
119
+ })) {
120
+ throw new Error('Invalid request data.');
121
+ }
122
+ return body;
123
+ }
@@ -0,0 +1,18 @@
1
+ export { HttpMethod, HttpStatus } from '@augment-vir/common';
2
+ export { type ServerRequest, type ServerResponse } from '@rest-vir/implement-service';
3
+ export * from './handle-request/endpoint-handler.js';
4
+ export * from './handle-request/handle-cors.js';
5
+ export * from './handle-request/handle-endpoint.js';
6
+ export * from './handle-request/handle-request-method.js';
7
+ export * from './handle-request/handle-route.js';
8
+ export * from './handle-request/handle-search-params.js';
9
+ export * from './handle-request/handle-web-socket.js';
10
+ export * from './handle-request/pre-handler.js';
11
+ export * from './start-service/attach-service.js';
12
+ export * from './start-service/start-service-options.js';
13
+ export * from './start-service/start-service.js';
14
+ export * from './test/test-endpoint.js';
15
+ export * from './test/test-service.js';
16
+ export * from './test/test-web-socket.js';
17
+ export * from './util/debug.js';
18
+ export * from './util/headers.js';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ export { HttpMethod, HttpStatus } from '@augment-vir/common';
2
+ export * from './handle-request/endpoint-handler.js';
3
+ export * from './handle-request/handle-cors.js';
4
+ export * from './handle-request/handle-endpoint.js';
5
+ export * from './handle-request/handle-request-method.js';
6
+ export * from './handle-request/handle-route.js';
7
+ export * from './handle-request/handle-search-params.js';
8
+ export * from './handle-request/handle-web-socket.js';
9
+ export * from './handle-request/pre-handler.js';
10
+ export * from './start-service/attach-service.js';
11
+ export * from './start-service/start-service-options.js';
12
+ export * from './start-service/start-service.js';
13
+ export * from './test/test-endpoint.js';
14
+ export * from './test/test-service.js';
15
+ export * from './test/test-web-socket.js';
16
+ export * from './util/debug.js';
17
+ export * from './util/headers.js';
@@ -0,0 +1,45 @@
1
+ import { SelectFrom } from '@augment-vir/common';
2
+ import type { BaseSearchParams } from '@rest-vir/define-service';
3
+ import { GenericServiceImplementation } from '@rest-vir/implement-service';
4
+ import { type FastifyInstance } from 'fastify';
5
+ import { HandleRouteOptions } from '../handle-request/endpoint-handler.js';
6
+ declare module 'fastify' {
7
+ interface FastifyRequest {
8
+ restVirContext: {
9
+ [AttachId in string]: {
10
+ context: unknown;
11
+ requestData: unknown;
12
+ protocols: string[];
13
+ searchParams: BaseSearchParams;
14
+ };
15
+ } | undefined;
16
+ }
17
+ }
18
+ /**
19
+ * Attach all handlers for a {@link ServiceImplementation} to any existing Fastify server.
20
+ *
21
+ * @category Run Service
22
+ * @category Package : @rest-vir/run-service
23
+ * @example
24
+ *
25
+ * ```ts
26
+ * import fastify from 'fastify';
27
+ *
28
+ * const server = fastify();
29
+ *
30
+ * attachService(service, myServiceImplementation);
31
+ *
32
+ * await server.listen({port: 3000});
33
+ * ```
34
+ *
35
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
36
+ */
37
+ export declare function attachService(server: Readonly<FastifyInstance>, service: Readonly<SelectFrom<GenericServiceImplementation, {
38
+ webSockets: true;
39
+ endpoints: true;
40
+ serviceName: true;
41
+ createContext: true;
42
+ serviceOrigin: true;
43
+ requiredClientOrigin: true;
44
+ logger: true;
45
+ }>>, options?: Readonly<HandleRouteOptions>): Promise<void>;
@@ -0,0 +1,124 @@
1
+ import { ensureError, extractErrorMessage, getEnumValues, getObjectTypedKeys, HttpMethod, HttpStatus, randomString, } from '@augment-vir/common';
2
+ import fastifyWs from '@fastify/websocket';
3
+ import { RestVirHandlerError, } from '@rest-vir/implement-service';
4
+ import { handleRoute } from '../handle-request/handle-route.js';
5
+ import { preHandler } from '../handle-request/pre-handler.js';
6
+ const endpointFastifyMethods = getEnumValues(HttpMethod).filter((value) => {
7
+ return (
8
+ /** Fastify doesn't support the CONNECT method. */
9
+ value !== HttpMethod.Connect &&
10
+ /** We use the GET method in a separate handler so it can handle WebSocket requests as well. */
11
+ value !== HttpMethod.Get);
12
+ });
13
+ /**
14
+ * Attach all handlers for a {@link ServiceImplementation} to any existing Fastify server.
15
+ *
16
+ * @category Run Service
17
+ * @category Package : @rest-vir/run-service
18
+ * @example
19
+ *
20
+ * ```ts
21
+ * import fastify from 'fastify';
22
+ *
23
+ * const server = fastify();
24
+ *
25
+ * attachService(service, myServiceImplementation);
26
+ *
27
+ * await server.listen({port: 3000});
28
+ * ```
29
+ *
30
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
31
+ */
32
+ export async function attachService(server, service, options = {}) {
33
+ try {
34
+ const attachId = randomString(32);
35
+ if (!server.hasRequestDecorator('restVirContext')) {
36
+ server.decorateRequest('restVirContext');
37
+ }
38
+ if (!server.hasRequestDecorator('ws')) {
39
+ await server.register(fastifyWs, {
40
+ /* node:coverage ignore next 14: edge case handling */
41
+ errorHandler(error, webSocket, request) {
42
+ service.logger.error(new RestVirHandlerError({
43
+ isEndpoint: false,
44
+ isWebSocket: true,
45
+ path: request.originalUrl,
46
+ service,
47
+ }, extractErrorMessage(error)));
48
+ webSocket.terminate();
49
+ },
50
+ });
51
+ }
52
+ server.addHook('preValidation', async (request, response) => {
53
+ try {
54
+ await preHandler(request, response, service, attachId);
55
+ }
56
+ catch (error) {
57
+ service.logger.error(ensureError(error));
58
+ if (options.throwErrorsForExternalHandling) {
59
+ throw error;
60
+ }
61
+ else if (!response.sent) {
62
+ response.statusCode = HttpStatus.InternalServerError;
63
+ response.send();
64
+ }
65
+ }
66
+ });
67
+ const allPaths = new Set([
68
+ ...getObjectTypedKeys(service.webSockets),
69
+ ...getObjectTypedKeys(service.endpoints),
70
+ ]);
71
+ allPaths.forEach((path) => {
72
+ const webSocketDefinition = service.webSockets[path];
73
+ const endpoint = service.endpoints[path];
74
+ if (endpoint && webSocketDefinition) {
75
+ server.route({
76
+ method: endpointFastifyMethods,
77
+ url: path,
78
+ async handler(request, response) {
79
+ await handleRoute(undefined, request, response, endpoint, attachId, options);
80
+ },
81
+ });
82
+ server.route({
83
+ method: HttpMethod.Get,
84
+ url: path,
85
+ async handler(request, response) {
86
+ await handleRoute(undefined, request, response, endpoint, attachId, options);
87
+ },
88
+ async wsHandler(webSocket, request) {
89
+ await handleRoute(webSocket, request, undefined, webSocketDefinition, attachId, options);
90
+ },
91
+ });
92
+ }
93
+ else if (endpoint) {
94
+ server.route({
95
+ method: [
96
+ ...endpointFastifyMethods,
97
+ HttpMethod.Get,
98
+ ],
99
+ url: path,
100
+ async handler(request, response) {
101
+ await handleRoute(undefined, request, response, endpoint, attachId, options);
102
+ },
103
+ });
104
+ }
105
+ else if (webSocketDefinition) {
106
+ server.route({
107
+ method: HttpMethod.Get,
108
+ url: path,
109
+ handler(request, response) {
110
+ response.status(HttpStatus.NotFound).send();
111
+ },
112
+ async wsHandler(webSocket, request) {
113
+ await handleRoute(webSocket, request, undefined, webSocketDefinition, attachId, options);
114
+ },
115
+ });
116
+ }
117
+ });
118
+ /* node:coverage ignore next 4: this is just here to cover edge cases. */
119
+ }
120
+ catch (error) {
121
+ service.logger.error(ensureError(error));
122
+ throw error;
123
+ }
124
+ }
@@ -0,0 +1,83 @@
1
+ import { type PartialWithUndefined } from '@augment-vir/common';
2
+ /**
3
+ * Shape definition for `startService` options.
4
+ *
5
+ * @category Internal
6
+ * @category Package : @rest-vir/run-service
7
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
8
+ */
9
+ export declare const startServiceOptionsShape: import("object-shape-tester").ShapeDefinition<{
10
+ /**
11
+ * Prevent automatically choosing an available port if the provided port is already in use. This
12
+ * will cause `startService` to simply crash if the given port is in use.
13
+ *
14
+ * @default false
15
+ */
16
+ lockPort: boolean;
17
+ /**
18
+ * The port that the service should listen to requests on. Note that if `lockPort` is not set,
19
+ * `startService` will try to find the first available port _starting_ with this given `port`
20
+ * property (so the actual server may be listening to a different port).
21
+ *
22
+ * If this property is set to `false`, no port will be listened to (so you can manually do that
23
+ * later if you wish).
24
+ *
25
+ * @default 3000
26
+ */
27
+ port: number;
28
+ /**
29
+ * The number of workers to split the server into (for parallel request handling).
30
+ *
31
+ * @default cpus().length - 1
32
+ */
33
+ workerCount: number;
34
+ /**
35
+ * The host name that the server should listen to. In most cases this doesn't need to be set.
36
+ *
37
+ * @default 'localhost'
38
+ */
39
+ host: string;
40
+ /**
41
+ * If set to `true`, a multi-threaded service (`workerCount` > 1) will not automatically respawn
42
+ * its workers. This has no effect on single-threaded services (`workerCount` == 1).
43
+ *
44
+ * @default false
45
+ */
46
+ preventWorkerRespawn: boolean;
47
+ /**
48
+ * Set this to true to enable temporary extra logging. This should only be used in dev as it
49
+ * will fill up production log files if you have a decent amount of traffic.
50
+ *
51
+ * This works by overriding the given service's logger to ensure that it logs everything.
52
+ *
53
+ * @default false
54
+ */
55
+ debug: boolean;
56
+ }, false>;
57
+ /**
58
+ * Full options type for `startService`.
59
+ *
60
+ * @category Internal
61
+ * @category Package : @rest-vir/run-service
62
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
63
+ */
64
+ export type StartServiceOptions = typeof startServiceOptionsShape.runtimeType;
65
+ /**
66
+ * User-provided options type for `startService`.
67
+ *
68
+ * @category Internal
69
+ * @category Package : @rest-vir/run-service
70
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
71
+ * @see {@link startServiceOptionsShape} for option explanations.
72
+ */
73
+ export type StartServiceUserOptions = PartialWithUndefined<StartServiceOptions>;
74
+ /**
75
+ * Combines user defined options with default options to create a full options type for
76
+ * `startService`.
77
+ *
78
+ * @category Internal
79
+ * @category Package : @rest-vir/run-service
80
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
81
+ * @see {@link startServiceOptionsShape} for option explanations.
82
+ */
83
+ export declare function finalizeOptions(userOptions: Readonly<StartServiceUserOptions>): StartServiceOptions;
@@ -0,0 +1,73 @@
1
+ import { mergeDefinedProperties } from '@augment-vir/common';
2
+ import { cpus } from 'node:os';
3
+ import { assertValidShape, defineShape } from 'object-shape-tester';
4
+ /**
5
+ * Shape definition for `startService` options.
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 const startServiceOptionsShape = defineShape({
12
+ /**
13
+ * Prevent automatically choosing an available port if the provided port is already in use. This
14
+ * will cause `startService` to simply crash if the given port is in use.
15
+ *
16
+ * @default false
17
+ */
18
+ lockPort: false,
19
+ /**
20
+ * The port that the service should listen to requests on. Note that if `lockPort` is not set,
21
+ * `startService` will try to find the first available port _starting_ with this given `port`
22
+ * property (so the actual server may be listening to a different port).
23
+ *
24
+ * If this property is set to `false`, no port will be listened to (so you can manually do that
25
+ * later if you wish).
26
+ *
27
+ * @default 3000
28
+ */
29
+ port: 3000,
30
+ /**
31
+ * The number of workers to split the server into (for parallel request handling).
32
+ *
33
+ * @default cpus().length - 1
34
+ */
35
+ workerCount: cpus().length - 1,
36
+ /**
37
+ * The host name that the server should listen to. In most cases this doesn't need to be set.
38
+ *
39
+ * @default 'localhost'
40
+ */
41
+ host: 'localhost',
42
+ /**
43
+ * If set to `true`, a multi-threaded service (`workerCount` > 1) will not automatically respawn
44
+ * its workers. This has no effect on single-threaded services (`workerCount` == 1).
45
+ *
46
+ * @default false
47
+ */
48
+ preventWorkerRespawn: false,
49
+ /**
50
+ * Set this to true to enable temporary extra logging. This should only be used in dev as it
51
+ * will fill up production log files if you have a decent amount of traffic.
52
+ *
53
+ * This works by overriding the given service's logger to ensure that it logs everything.
54
+ *
55
+ * @default false
56
+ */
57
+ debug: false,
58
+ });
59
+ /**
60
+ * Combines user defined options with default options to create a full options type for
61
+ * `startService`.
62
+ *
63
+ * @category Internal
64
+ * @category Package : @rest-vir/run-service
65
+ * @package [`@rest-vir/run-service`](https://www.npmjs.com/package/@rest-vir/run-service)
66
+ * @see {@link startServiceOptionsShape} for option explanations.
67
+ */
68
+ export function finalizeOptions(userOptions) {
69
+ const options = mergeDefinedProperties(startServiceOptionsShape.defaultValue, userOptions);
70
+ options.workerCount = Math.max(1, options.workerCount);
71
+ assertValidShape(options, startServiceOptionsShape);
72
+ return options;
73
+ }