@lakutata/entrypoint 3.0.0-beta.1 → 3.0.0-beta.3
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/README.md +3 -3
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +7 -1
- package/dist/cjs/adapter/express/Responders.d.ts +21 -3
- package/dist/cjs/adapter/express/Responders.js +16 -7
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +7 -1
- package/dist/cjs/adapter/fastify/Responders.d.ts +21 -3
- package/dist/cjs/adapter/fastify/Responders.js +15 -6
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +7 -1
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +19 -3
- package/dist/cjs/lib/Connections.d.ts +10 -0
- package/dist/cjs/lib/Connections.js +19 -0
- package/dist/cjs/lib/Envelope.d.ts +13 -0
- package/dist/cjs/lib/Envelope.js +32 -0
- package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +7 -1
- package/dist/esm/adapter/express/Responders.js +16 -7
- package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +7 -1
- package/dist/esm/adapter/fastify/Responders.js +15 -6
- package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +7 -1
- package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +19 -3
- package/dist/esm/lib/Connections.js +16 -0
- package/dist/esm/lib/Envelope.js +28 -0
- package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +2 -1
- package/dist/types/adapter/express/Responders.d.ts +21 -3
- package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/types/adapter/fastify/Responders.d.ts +21 -3
- package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +2 -1
- package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +2 -1
- package/dist/types/lib/Connections.d.ts +10 -0
- package/dist/types/lib/Envelope.d.ts +13 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ import {buildEntrypoints, Controller, type ActionInvocation, type Next} from 'la
|
|
|
111
111
|
import {GET, UseMiddlewares} from 'lakutata/decorator/ctrl'
|
|
112
112
|
|
|
113
113
|
//Runs around the action: before its input validation and its access control
|
|
114
|
-
async function
|
|
114
|
+
async function timing(invocation: ActionInvocation, next: Next): Promise<unknown> {
|
|
115
115
|
const started: number = Date.now()
|
|
116
116
|
try {
|
|
117
117
|
return await next()
|
|
@@ -120,7 +120,7 @@ async function Timing(invocation: ActionInvocation, next: Next): Promise<unknown
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
@UseMiddlewares(
|
|
123
|
+
@UseMiddlewares(timing)
|
|
124
124
|
class HealthController extends Controller {
|
|
125
125
|
@GET('/health')
|
|
126
126
|
public async health(): Promise<string> {
|
|
@@ -132,7 +132,7 @@ Application.run(() => ({
|
|
|
132
132
|
id: 'health.app',
|
|
133
133
|
name: 'Health',
|
|
134
134
|
components: {
|
|
135
|
-
entrypoint: buildEntrypoints({controllers: [HealthController], middlewares: [
|
|
135
|
+
entrypoint: buildEntrypoints({controllers: [HealthController], middlewares: [timing]})
|
|
136
136
|
}
|
|
137
137
|
}))
|
|
138
138
|
```
|
|
@@ -123,7 +123,8 @@ export type ExpressHTTPEntrypointOptions = {
|
|
|
123
123
|
*
|
|
124
124
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
125
125
|
* entrypoint stops, the server stops accepting connections, the event streams end, and the requests in progress are
|
|
126
|
-
* given up to one second after they settle to send their responses before the remaining connections are closed
|
|
126
|
+
* given up to one second after they settle to send their responses before the remaining connections are closed — the
|
|
127
|
+
* connections taken over by an upgrade (the WebSockets of the application) too, which would hold the stop forever.
|
|
127
128
|
* @param options The adapter options; by default it listens on port 80 of all the interfaces with a new express
|
|
128
129
|
* application.
|
|
129
130
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
@@ -11,6 +11,7 @@ const HTTPContext_js_1 = require("../../context/HTTPContext.js");
|
|
|
11
11
|
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
12
12
|
const Result_js_1 = require("../../lib/Result.js");
|
|
13
13
|
const RouteNotFoundException_js_1 = require("../../exceptions/RouteNotFoundException.js");
|
|
14
|
+
const Connections_js_1 = require("../../lib/Connections.js");
|
|
14
15
|
const Responders_js_1 = require("./Responders.js");
|
|
15
16
|
/**
|
|
16
17
|
* The first segments of the routes
|
|
@@ -56,7 +57,8 @@ function eventFrame(data, event, id) {
|
|
|
56
57
|
*
|
|
57
58
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
58
59
|
* entrypoint stops, the server stops accepting connections, the event streams end, and the requests in progress are
|
|
59
|
-
* given up to one second after they settle to send their responses before the remaining connections are closed
|
|
60
|
+
* given up to one second after they settle to send their responses before the remaining connections are closed — the
|
|
61
|
+
* connections taken over by an upgrade (the WebSockets of the application) too, which would hold the stop forever.
|
|
60
62
|
* @param options The adapter options; by default it listens on port 80 of all the interfaces with a new express
|
|
61
63
|
* application.
|
|
62
64
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
@@ -107,6 +109,7 @@ function expressHTTPEntrypoint(options = {}) {
|
|
|
107
109
|
const input = options.input ?? ((request) => ({ ...request.query, ...(request.body && typeof request.body === 'object' ? request.body : {}) }));
|
|
108
110
|
const streams = new Set();
|
|
109
111
|
let server;
|
|
112
|
+
let destroyConnections;
|
|
110
113
|
let closed;
|
|
111
114
|
let stopping = false;
|
|
112
115
|
//The responses of the actions not sent yet
|
|
@@ -245,6 +248,7 @@ function expressHTTPEntrypoint(options = {}) {
|
|
|
245
248
|
start: async (runtime) => {
|
|
246
249
|
const app = options.app ?? (0, express_1.default)();
|
|
247
250
|
server = (0, node_http_1.createServer)(options.server ?? {}, app);
|
|
251
|
+
destroyConnections = (0, Connections_js_1.trackConnections)(server);
|
|
248
252
|
const routes = new Map();
|
|
249
253
|
for (const action of runtime.actions)
|
|
250
254
|
routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(action.address.method));
|
|
@@ -294,6 +298,8 @@ function expressHTTPEntrypoint(options = {}) {
|
|
|
294
298
|
})]);
|
|
295
299
|
clearTimeout(timer);
|
|
296
300
|
server?.closeAllConnections();
|
|
301
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
302
|
+
destroyConnections?.();
|
|
297
303
|
await closed;
|
|
298
304
|
await options.stop?.();
|
|
299
305
|
}
|
|
@@ -49,12 +49,23 @@ export type JSONEnvelopeOptions = {
|
|
|
49
49
|
* @default `errorStatus`
|
|
50
50
|
*/
|
|
51
51
|
actionErrorStatus?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The extra fields of the envelope, such as the `timestamp`, `nonce` or `requestId` of the envelopes of the
|
|
54
|
+
* applications of lakutata 2.x: called for each response (a result or an error), its fields added after `errno`,
|
|
55
|
+
* `errMsg` and `data`, which they never replace. When it throws, a result is answered as an error, and an error
|
|
56
|
+
* without the extra fields.
|
|
57
|
+
* @param request The express request being answered.
|
|
58
|
+
* @returns The fields to add to the envelope.
|
|
59
|
+
* @default no extra field
|
|
60
|
+
*/
|
|
61
|
+
extra?: (request: Request) => Record<string, unknown>;
|
|
52
62
|
};
|
|
53
63
|
/**
|
|
54
64
|
* Create a responder for {@link expressHTTPEntrypoint} that wraps every response in the envelope
|
|
55
65
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
56
66
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
57
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
67
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
68
|
+
* fields to both.
|
|
58
69
|
* @param options The envelope options.
|
|
59
70
|
* @returns The responder, to give to the `respond` option of `expressHTTPEntrypoint`.
|
|
60
71
|
* @example
|
|
@@ -79,8 +90,15 @@ export type JSONEnvelopeOptions = {
|
|
|
79
90
|
* entrypoint: buildEntrypoints({
|
|
80
91
|
* controllers: [PingController],
|
|
81
92
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
82
|
-
* //found and the errors of the express middlewares with their status (404, 401…)
|
|
83
|
-
*
|
|
93
|
+
* //found and the errors of the express middlewares with their status (404, 401…); every envelope with
|
|
94
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
95
|
+
* http: expressHTTPEntrypoint({
|
|
96
|
+
* port: 8080,
|
|
97
|
+
* respond: jsonEnvelope({
|
|
98
|
+
* actionErrorStatus: 200,
|
|
99
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
100
|
+
* })
|
|
101
|
+
* })
|
|
84
102
|
* })
|
|
85
103
|
* }
|
|
86
104
|
* })
|
|
@@ -5,6 +5,7 @@ exports.jsonEnvelope = jsonEnvelope;
|
|
|
5
5
|
const HTTPErrors_js_1 = require("../../lib/HTTPErrors.js");
|
|
6
6
|
Object.defineProperty(exports, "describeError", { enumerable: true, get: function () { return HTTPErrors_js_1.describeError; } });
|
|
7
7
|
Object.defineProperty(exports, "statusOfError", { enumerable: true, get: function () { return HTTPErrors_js_1.statusOfError; } });
|
|
8
|
+
const Envelope_js_1 = require("../../lib/Envelope.js");
|
|
8
9
|
/**
|
|
9
10
|
* The default responder of {@link expressHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
10
11
|
* status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
|
|
@@ -26,7 +27,8 @@ exports.JSONResponder = {
|
|
|
26
27
|
* Create a responder for {@link expressHTTPEntrypoint} that wraps every response in the envelope
|
|
27
28
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
28
29
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
29
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
30
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
31
|
+
* fields to both.
|
|
30
32
|
* @param options The envelope options.
|
|
31
33
|
* @returns The responder, to give to the `respond` option of `expressHTTPEntrypoint`.
|
|
32
34
|
* @example
|
|
@@ -51,8 +53,15 @@ exports.JSONResponder = {
|
|
|
51
53
|
* entrypoint: buildEntrypoints({
|
|
52
54
|
* controllers: [PingController],
|
|
53
55
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
54
|
-
* //found and the errors of the express middlewares with their status (404, 401…)
|
|
55
|
-
*
|
|
56
|
+
* //found and the errors of the express middlewares with their status (404, 401…); every envelope with
|
|
57
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
58
|
+
* http: expressHTTPEntrypoint({
|
|
59
|
+
* port: 8080,
|
|
60
|
+
* respond: jsonEnvelope({
|
|
61
|
+
* actionErrorStatus: 200,
|
|
62
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
63
|
+
* })
|
|
64
|
+
* })
|
|
56
65
|
* })
|
|
57
66
|
* }
|
|
58
67
|
* })
|
|
@@ -60,12 +69,12 @@ exports.JSONResponder = {
|
|
|
60
69
|
*/
|
|
61
70
|
function jsonEnvelope(options = {}) {
|
|
62
71
|
return {
|
|
63
|
-
success: (response, data) => {
|
|
64
|
-
response.json({ errno: null, errMsg: null, data: data === undefined ? null : data });
|
|
72
|
+
success: (response, data, request) => {
|
|
73
|
+
response.json((0, Envelope_js_1.envelopeOf)({ errno: null, errMsg: null, data: data === undefined ? null : data }, options.extra?.(request)));
|
|
65
74
|
},
|
|
66
|
-
error: (response, error,
|
|
75
|
+
error: (response, error, request, origin) => {
|
|
67
76
|
const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
|
|
68
|
-
response.status(status ?? (0, HTTPErrors_js_1.statusOfError)(error)).json({ ...(0, HTTPErrors_js_1.describeError)(error), data: null });
|
|
77
|
+
response.status(status ?? (0, HTTPErrors_js_1.statusOfError)(error)).json((0, Envelope_js_1.envelopeOf)({ ...(0, HTTPErrors_js_1.describeError)(error), data: null }, (0, Envelope_js_1.errorExtraOf)(options.extra, request)));
|
|
69
78
|
}
|
|
70
79
|
};
|
|
71
80
|
}
|
|
@@ -109,7 +109,8 @@ export type FastifyHTTPEntrypointOptions = {
|
|
|
109
109
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
110
110
|
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
111
111
|
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
112
|
-
* remaining connections are closed
|
|
112
|
+
* remaining connections are closed — the connections taken over by an upgrade (the WebSockets of the application) too,
|
|
113
|
+
* which would hold the stop forever.
|
|
113
114
|
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
114
115
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
115
116
|
* @example
|
|
@@ -11,6 +11,7 @@ const HTTPContext_js_1 = require("../../context/HTTPContext.js");
|
|
|
11
11
|
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
12
12
|
const Result_js_1 = require("../../lib/Result.js");
|
|
13
13
|
const RouteNotFoundException_js_1 = require("../../exceptions/RouteNotFoundException.js");
|
|
14
|
+
const Connections_js_1 = require("../../lib/Connections.js");
|
|
14
15
|
const Responders_js_1 = require("./Responders.js");
|
|
15
16
|
/**
|
|
16
17
|
* The content types of the common file extensions
|
|
@@ -85,7 +86,8 @@ function isFastifyInstance(value) {
|
|
|
85
86
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
86
87
|
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
87
88
|
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
88
|
-
* remaining connections are closed
|
|
89
|
+
* remaining connections are closed — the connections taken over by an upgrade (the WebSockets of the application) too,
|
|
90
|
+
* which would hold the stop forever.
|
|
89
91
|
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
90
92
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
91
93
|
* @example
|
|
@@ -137,6 +139,7 @@ function fastifyHTTPEntrypoint(options = {}) {
|
|
|
137
139
|
}));
|
|
138
140
|
const streams = new Set();
|
|
139
141
|
let app;
|
|
142
|
+
let destroyConnections;
|
|
140
143
|
let closed;
|
|
141
144
|
let stopping = false;
|
|
142
145
|
//The responses not sent yet
|
|
@@ -266,6 +269,7 @@ function fastifyHTTPEntrypoint(options = {}) {
|
|
|
266
269
|
start: async (runtime) => {
|
|
267
270
|
const fastify = isFastifyInstance(options.fastify) ? options.fastify : (0, fastify_1.default)(options.fastify ?? {});
|
|
268
271
|
app = fastify;
|
|
272
|
+
destroyConnections = (0, Connections_js_1.trackConnections)(fastify.server);
|
|
269
273
|
const routes = new Map();
|
|
270
274
|
for (const action of runtime.actions)
|
|
271
275
|
routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(String(action.address.method).toUpperCase()));
|
|
@@ -332,6 +336,8 @@ function fastifyHTTPEntrypoint(options = {}) {
|
|
|
332
336
|
})]);
|
|
333
337
|
clearTimeout(timer);
|
|
334
338
|
app?.server.closeAllConnections();
|
|
339
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
340
|
+
destroyConnections?.();
|
|
335
341
|
await closed;
|
|
336
342
|
await options.stop?.();
|
|
337
343
|
}
|
|
@@ -48,12 +48,23 @@ export type FastifyJSONEnvelopeOptions = {
|
|
|
48
48
|
* @default `errorStatus`
|
|
49
49
|
*/
|
|
50
50
|
actionErrorStatus?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The extra fields of the envelope, such as the `timestamp`, `nonce` or `requestId` of the envelopes of the
|
|
53
|
+
* applications of lakutata 2.x: called for each response (a result or an error), its fields added after `errno`,
|
|
54
|
+
* `errMsg` and `data`, which they never replace. When it throws, a result is answered as an error, and an error
|
|
55
|
+
* without the extra fields.
|
|
56
|
+
* @param request The fastify request being answered.
|
|
57
|
+
* @returns The fields to add to the envelope.
|
|
58
|
+
* @default no extra field
|
|
59
|
+
*/
|
|
60
|
+
extra?: (request: FastifyRequest) => Record<string, unknown>;
|
|
51
61
|
};
|
|
52
62
|
/**
|
|
53
63
|
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
54
64
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
55
65
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
56
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
66
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
67
|
+
* fields to both.
|
|
57
68
|
* @param options The envelope options.
|
|
58
69
|
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
59
70
|
* @example
|
|
@@ -78,8 +89,15 @@ export type FastifyJSONEnvelopeOptions = {
|
|
|
78
89
|
* entrypoint: buildEntrypoints({
|
|
79
90
|
* controllers: [PingController],
|
|
80
91
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
81
|
-
* //found and the errors of the hooks with their status (404, 401…)
|
|
82
|
-
*
|
|
92
|
+
* //found and the errors of the hooks with their status (404, 401…); every envelope with
|
|
93
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
94
|
+
* http: fastifyHTTPEntrypoint({
|
|
95
|
+
* port: 8080,
|
|
96
|
+
* respond: fastifyJSONEnvelope({
|
|
97
|
+
* actionErrorStatus: 200,
|
|
98
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
99
|
+
* })
|
|
100
|
+
* })
|
|
83
101
|
* })
|
|
84
102
|
* }
|
|
85
103
|
* })
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FastifyJSONResponder = void 0;
|
|
4
4
|
exports.fastifyJSONEnvelope = fastifyJSONEnvelope;
|
|
5
5
|
const HTTPErrors_js_1 = require("../../lib/HTTPErrors.js");
|
|
6
|
+
const Envelope_js_1 = require("../../lib/Envelope.js");
|
|
6
7
|
/**
|
|
7
8
|
* Send JSON
|
|
8
9
|
* @param reply
|
|
@@ -33,7 +34,8 @@ exports.FastifyJSONResponder = {
|
|
|
33
34
|
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
34
35
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
35
36
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
36
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
37
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
38
|
+
* fields to both.
|
|
37
39
|
* @param options The envelope options.
|
|
38
40
|
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
39
41
|
* @example
|
|
@@ -58,8 +60,15 @@ exports.FastifyJSONResponder = {
|
|
|
58
60
|
* entrypoint: buildEntrypoints({
|
|
59
61
|
* controllers: [PingController],
|
|
60
62
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
61
|
-
* //found and the errors of the hooks with their status (404, 401…)
|
|
62
|
-
*
|
|
63
|
+
* //found and the errors of the hooks with their status (404, 401…); every envelope with
|
|
64
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
65
|
+
* http: fastifyHTTPEntrypoint({
|
|
66
|
+
* port: 8080,
|
|
67
|
+
* respond: fastifyJSONEnvelope({
|
|
68
|
+
* actionErrorStatus: 200,
|
|
69
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
70
|
+
* })
|
|
71
|
+
* })
|
|
63
72
|
* })
|
|
64
73
|
* }
|
|
65
74
|
* })
|
|
@@ -67,11 +76,11 @@ exports.FastifyJSONResponder = {
|
|
|
67
76
|
*/
|
|
68
77
|
function fastifyJSONEnvelope(options = {}) {
|
|
69
78
|
return {
|
|
70
|
-
success: (reply, data) => sendJSON(reply, { errno: null, errMsg: null, data: data === undefined ? null : data }),
|
|
71
|
-
error: (reply, error,
|
|
79
|
+
success: (reply, data, request) => sendJSON(reply, (0, Envelope_js_1.envelopeOf)({ errno: null, errMsg: null, data: data === undefined ? null : data }, options.extra?.(request))),
|
|
80
|
+
error: (reply, error, request, origin) => {
|
|
72
81
|
const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
|
|
73
82
|
reply.code(status ?? (0, HTTPErrors_js_1.statusOfError)(error));
|
|
74
|
-
sendJSON(reply, { ...(0, HTTPErrors_js_1.describeError)(error), data: null });
|
|
83
|
+
sendJSON(reply, (0, Envelope_js_1.envelopeOf)({ ...(0, HTTPErrors_js_1.describeError)(error), data: null }, (0, Envelope_js_1.errorExtraOf)(options.extra, request)));
|
|
75
84
|
}
|
|
76
85
|
};
|
|
77
86
|
}
|
|
@@ -77,7 +77,8 @@ export declare function httpRequestOfContext(context: ServiceContext): IncomingM
|
|
|
77
77
|
*
|
|
78
78
|
* When a client disconnects before its response, the action's abort signal fires. When the entrypoint stops, the new
|
|
79
79
|
* requests are answered with an `EntrypointStoppedException` error; once the requests in progress end, the event
|
|
80
|
-
* streams are closed and the server shuts down
|
|
80
|
+
* streams are closed and the server shuts down, closing its remaining connections (the ones taken over by an upgrade of
|
|
81
|
+
* the application too).
|
|
81
82
|
* @param options The adapter options; by default it listens on port 3000 of all the interfaces, on the path `/`.
|
|
82
83
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
83
84
|
* @example
|
|
@@ -8,6 +8,7 @@ const ServiceContext_js_1 = require("../../context/ServiceContext.js");
|
|
|
8
8
|
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
9
9
|
const ServiceResponseCodec_js_1 = require("../../lib/ServiceResponseCodec.js");
|
|
10
10
|
const EntrypointStoppedException_js_1 = require("../../exceptions/EntrypointStoppedException.js");
|
|
11
|
+
const Connections_js_1 = require("../../lib/Connections.js");
|
|
11
12
|
/**
|
|
12
13
|
* Get the HTTP request carrying a service action's request, when {@link httpServiceEntrypoint} serves it (to read its
|
|
13
14
|
* headers, its remote address…).
|
|
@@ -92,7 +93,8 @@ async function readBody(request, limit) {
|
|
|
92
93
|
*
|
|
93
94
|
* When a client disconnects before its response, the action's abort signal fires. When the entrypoint stops, the new
|
|
94
95
|
* requests are answered with an `EntrypointStoppedException` error; once the requests in progress end, the event
|
|
95
|
-
* streams are closed and the server shuts down
|
|
96
|
+
* streams are closed and the server shuts down, closing its remaining connections (the ones taken over by an upgrade of
|
|
97
|
+
* the application too).
|
|
96
98
|
* @param options The adapter options; by default it listens on port 3000 of all the interfaces, on the path `/`.
|
|
97
99
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
98
100
|
* @example
|
|
@@ -135,6 +137,7 @@ function httpServiceEntrypoint(options = {}) {
|
|
|
135
137
|
const eventsPath = `${base}/events`;
|
|
136
138
|
const limit = options.limit ?? 10 * 1024 * 1024;
|
|
137
139
|
let server;
|
|
140
|
+
let destroyConnections;
|
|
138
141
|
let stopping = false;
|
|
139
142
|
let keepAlive;
|
|
140
143
|
//The event streams of each client (a client may listen several times)
|
|
@@ -222,6 +225,7 @@ function httpServiceEntrypoint(options = {}) {
|
|
|
222
225
|
//The event streams kept open by the proxies
|
|
223
226
|
keepAlive = setInterval(() => clients.forEach((streams) => streams.forEach((stream) => stream.write(': keep-alive\n\n'))), 15000);
|
|
224
227
|
keepAlive.unref();
|
|
228
|
+
destroyConnections = (0, Connections_js_1.trackConnections)(server);
|
|
225
229
|
await new Promise((resolve, reject) => {
|
|
226
230
|
server.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
227
231
|
server.off('error', reject);
|
|
@@ -242,6 +246,8 @@ function httpServiceEntrypoint(options = {}) {
|
|
|
242
246
|
return;
|
|
243
247
|
const closing = new Promise((resolve) => void server.close(() => resolve()));
|
|
244
248
|
server.closeAllConnections();
|
|
249
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
250
|
+
destroyConnections?.();
|
|
245
251
|
await closing;
|
|
246
252
|
},
|
|
247
253
|
notify: (clientId, event, args) => {
|
|
@@ -103,7 +103,8 @@ export type JSONRPCServiceEntrypointOptions = JSONRPCMappingOptions & {
|
|
|
103
103
|
*
|
|
104
104
|
* When an HTTP client disconnects before its response, the action's abort signal fires. When the entrypoint stops,
|
|
105
105
|
* stdio stops reading, the new requests are answered with `STOPPING`, and once the requests in progress are answered
|
|
106
|
-
* the WebSocket connections are closed (code 1001) and the
|
|
106
|
+
* the WebSocket connections are closed (code 1001; the ones not answering within a second are terminated) and the
|
|
107
|
+
* server shuts down, closing its remaining connections (the ones taken over by an upgrade of the application too).
|
|
107
108
|
* @param options The adapter options; by default it serves HTTP on port 3000 of all the interfaces, on the path `/`.
|
|
108
109
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
109
110
|
* @example
|
|
@@ -39,6 +39,7 @@ const node_http_1 = require("node:http");
|
|
|
39
39
|
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
40
40
|
const JSONRPCProtocol_js_1 = require("./JSONRPCProtocol.js");
|
|
41
41
|
const ApplicationOf_js_1 = require("../../lib/ApplicationOf.js");
|
|
42
|
+
const Connections_js_1 = require("../../lib/Connections.js");
|
|
42
43
|
/**
|
|
43
44
|
* The headers of the responses of HTTP: the tools of the browsers may call the adapter
|
|
44
45
|
*/
|
|
@@ -173,7 +174,8 @@ function readMessages(input, framing, limit, receive, ended, oversized) {
|
|
|
173
174
|
*
|
|
174
175
|
* When an HTTP client disconnects before its response, the action's abort signal fires. When the entrypoint stops,
|
|
175
176
|
* stdio stops reading, the new requests are answered with `STOPPING`, and once the requests in progress are answered
|
|
176
|
-
* the WebSocket connections are closed (code 1001) and the
|
|
177
|
+
* the WebSocket connections are closed (code 1001; the ones not answering within a second are terminated) and the
|
|
178
|
+
* server shuts down, closing its remaining connections (the ones taken over by an upgrade of the application too).
|
|
177
179
|
* @param options The adapter options; by default it serves HTTP on port 3000 of all the interfaces, on the path `/`.
|
|
178
180
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
179
181
|
* @example
|
|
@@ -228,6 +230,7 @@ function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
228
230
|
const path = (options.path ?? '/').replace(/(.)\/+$/, '$1');
|
|
229
231
|
let stopping = false;
|
|
230
232
|
let server;
|
|
233
|
+
let destroyConnections;
|
|
231
234
|
let webSocketServer;
|
|
232
235
|
let stopReading;
|
|
233
236
|
let heartbeat;
|
|
@@ -263,6 +266,7 @@ function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
263
266
|
return handled;
|
|
264
267
|
};
|
|
265
268
|
const listen = async (target) => {
|
|
269
|
+
destroyConnections = (0, Connections_js_1.trackConnections)(target);
|
|
266
270
|
await new Promise((resolve, reject) => {
|
|
267
271
|
target.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
268
272
|
target.off('error', reject);
|
|
@@ -375,13 +379,25 @@ function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
375
379
|
await Promise.allSettled([...inProgress]);
|
|
376
380
|
clients.clear();
|
|
377
381
|
if (webSocketServer) {
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
//The WebSockets are given a second to answer their close, then the ones not answering are terminated (the
|
|
383
|
+
//close of the server waits for them all)
|
|
384
|
+
const sockets = webSocketServer.clients;
|
|
385
|
+
const closedSockets = new Promise((resolve) => webSocketServer.close(() => resolve()));
|
|
386
|
+
sockets.forEach((socket) => socket.close(1001, 'The service stops'));
|
|
387
|
+
let timer;
|
|
388
|
+
await Promise.race([closedSockets, new Promise((resolve) => {
|
|
389
|
+
timer = setTimeout(resolve, 1000);
|
|
390
|
+
})]);
|
|
391
|
+
clearTimeout(timer);
|
|
392
|
+
sockets.forEach((socket) => socket.terminate());
|
|
393
|
+
await closedSockets;
|
|
380
394
|
}
|
|
381
395
|
if (!server)
|
|
382
396
|
return;
|
|
383
397
|
const closing = new Promise((resolve) => void server.close(() => resolve()));
|
|
384
398
|
server.closeAllConnections();
|
|
399
|
+
//The WebSockets not answering their close, and the connections taken over by an upgrade of the application, hold the close of the server
|
|
400
|
+
destroyConnections?.();
|
|
385
401
|
await closing;
|
|
386
402
|
},
|
|
387
403
|
notify: (clientId, event, args) => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Track the connections of a server, to destroy the ones its `closeAllConnections()` leaves open: the connections taken
|
|
4
|
+
* over by an upgrade (a WebSocket: socket.io, ws, the hot reload of a development server) or by a CONNECT request,
|
|
5
|
+
* which hold the close of the server forever. Unlike a listener of 'upgrade', it does not change how the server answers
|
|
6
|
+
* the requests (without one, an upgrade request is answered as any request).
|
|
7
|
+
* @param server The server whose connections are tracked, before it listens.
|
|
8
|
+
* @returns A function destroying the connections still open.
|
|
9
|
+
*/
|
|
10
|
+
export declare function trackConnections(server: Server): () => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackConnections = trackConnections;
|
|
4
|
+
/**
|
|
5
|
+
* Track the connections of a server, to destroy the ones its `closeAllConnections()` leaves open: the connections taken
|
|
6
|
+
* over by an upgrade (a WebSocket: socket.io, ws, the hot reload of a development server) or by a CONNECT request,
|
|
7
|
+
* which hold the close of the server forever. Unlike a listener of 'upgrade', it does not change how the server answers
|
|
8
|
+
* the requests (without one, an upgrade request is answered as any request).
|
|
9
|
+
* @param server The server whose connections are tracked, before it listens.
|
|
10
|
+
* @returns A function destroying the connections still open.
|
|
11
|
+
*/
|
|
12
|
+
function trackConnections(server) {
|
|
13
|
+
const sockets = new Set();
|
|
14
|
+
server.on('connection', (socket) => {
|
|
15
|
+
sockets.add(socket);
|
|
16
|
+
socket.once('close', () => sockets.delete(socket));
|
|
17
|
+
});
|
|
18
|
+
return () => sockets.forEach((socket) => void socket.destroy());
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The envelope of a response: its fields, then the extra fields not named as one of them (an extra field never
|
|
3
|
+
* replaces errno, errMsg or data)
|
|
4
|
+
* @param fields The fields of the envelope.
|
|
5
|
+
* @param extra The extra fields, ignored when they are not an object.
|
|
6
|
+
*/
|
|
7
|
+
export declare function envelopeOf(fields: Record<string, unknown>, extra: unknown): Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* The extra fields of an error response: none when the function giving them fails, an error being answered anyway
|
|
10
|
+
* @param extra The function giving the extra fields.
|
|
11
|
+
* @param request The request answered.
|
|
12
|
+
*/
|
|
13
|
+
export declare function errorExtraOf<Request>(extra: ((request: Request) => Record<string, unknown>) | undefined, request: Request): unknown;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.envelopeOf = envelopeOf;
|
|
4
|
+
exports.errorExtraOf = errorExtraOf;
|
|
5
|
+
/**
|
|
6
|
+
* The envelope of a response: its fields, then the extra fields not named as one of them (an extra field never
|
|
7
|
+
* replaces errno, errMsg or data)
|
|
8
|
+
* @param fields The fields of the envelope.
|
|
9
|
+
* @param extra The extra fields, ignored when they are not an object.
|
|
10
|
+
*/
|
|
11
|
+
function envelopeOf(fields, extra) {
|
|
12
|
+
const envelope = { ...fields };
|
|
13
|
+
if (extra === null || typeof extra !== 'object')
|
|
14
|
+
return envelope;
|
|
15
|
+
for (const [key, value] of Object.entries(extra))
|
|
16
|
+
if (!Object.prototype.hasOwnProperty.call(envelope, key))
|
|
17
|
+
envelope[key] = value;
|
|
18
|
+
return envelope;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The extra fields of an error response: none when the function giving them fails, an error being answered anyway
|
|
22
|
+
* @param extra The function giving the extra fields.
|
|
23
|
+
* @param request The request answered.
|
|
24
|
+
*/
|
|
25
|
+
function errorExtraOf(extra, request) {
|
|
26
|
+
try {
|
|
27
|
+
return extra?.(request);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -5,6 +5,7 @@ import { HTTPContext } from '../../context/HTTPContext.js';
|
|
|
5
5
|
import { HTTPChannel } from '../../channel/BuiltinChannels.js';
|
|
6
6
|
import { EventsResult, FileResult, StatusResult } from '../../lib/Result.js';
|
|
7
7
|
import { RouteNotFoundException } from '../../exceptions/RouteNotFoundException.js';
|
|
8
|
+
import { trackConnections } from '../../lib/Connections.js';
|
|
8
9
|
import { JSONResponder } from './Responders.js';
|
|
9
10
|
/**
|
|
10
11
|
* The first segments of the routes
|
|
@@ -50,7 +51,8 @@ function eventFrame(data, event, id) {
|
|
|
50
51
|
*
|
|
51
52
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
52
53
|
* entrypoint stops, the server stops accepting connections, the event streams end, and the requests in progress are
|
|
53
|
-
* given up to one second after they settle to send their responses before the remaining connections are closed
|
|
54
|
+
* given up to one second after they settle to send their responses before the remaining connections are closed — the
|
|
55
|
+
* connections taken over by an upgrade (the WebSockets of the application) too, which would hold the stop forever.
|
|
54
56
|
* @param options The adapter options; by default it listens on port 80 of all the interfaces with a new express
|
|
55
57
|
* application.
|
|
56
58
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
@@ -101,6 +103,7 @@ export function expressHTTPEntrypoint(options = {}) {
|
|
|
101
103
|
const input = options.input ?? ((request) => ({ ...request.query, ...(request.body && typeof request.body === 'object' ? request.body : {}) }));
|
|
102
104
|
const streams = new Set();
|
|
103
105
|
let server;
|
|
106
|
+
let destroyConnections;
|
|
104
107
|
let closed;
|
|
105
108
|
let stopping = false;
|
|
106
109
|
//The responses of the actions not sent yet
|
|
@@ -239,6 +242,7 @@ export function expressHTTPEntrypoint(options = {}) {
|
|
|
239
242
|
start: async (runtime) => {
|
|
240
243
|
const app = options.app ?? express();
|
|
241
244
|
server = createServer(options.server ?? {}, app);
|
|
245
|
+
destroyConnections = trackConnections(server);
|
|
242
246
|
const routes = new Map();
|
|
243
247
|
for (const action of runtime.actions)
|
|
244
248
|
routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(action.address.method));
|
|
@@ -288,6 +292,8 @@ export function expressHTTPEntrypoint(options = {}) {
|
|
|
288
292
|
})]);
|
|
289
293
|
clearTimeout(timer);
|
|
290
294
|
server?.closeAllConnections();
|
|
295
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
296
|
+
destroyConnections?.();
|
|
291
297
|
await closed;
|
|
292
298
|
await options.stop?.();
|
|
293
299
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
|
|
2
|
+
import { envelopeOf, errorExtraOf } from '../../lib/Envelope.js';
|
|
2
3
|
export { describeError, statusOfError };
|
|
3
4
|
/**
|
|
4
5
|
* The default responder of {@link expressHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
@@ -21,7 +22,8 @@ export const JSONResponder = {
|
|
|
21
22
|
* Create a responder for {@link expressHTTPEntrypoint} that wraps every response in the envelope
|
|
22
23
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
23
24
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
24
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
25
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
26
|
+
* fields to both.
|
|
25
27
|
* @param options The envelope options.
|
|
26
28
|
* @returns The responder, to give to the `respond` option of `expressHTTPEntrypoint`.
|
|
27
29
|
* @example
|
|
@@ -46,8 +48,15 @@ export const JSONResponder = {
|
|
|
46
48
|
* entrypoint: buildEntrypoints({
|
|
47
49
|
* controllers: [PingController],
|
|
48
50
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
49
|
-
* //found and the errors of the express middlewares with their status (404, 401…)
|
|
50
|
-
*
|
|
51
|
+
* //found and the errors of the express middlewares with their status (404, 401…); every envelope with
|
|
52
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
53
|
+
* http: expressHTTPEntrypoint({
|
|
54
|
+
* port: 8080,
|
|
55
|
+
* respond: jsonEnvelope({
|
|
56
|
+
* actionErrorStatus: 200,
|
|
57
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
58
|
+
* })
|
|
59
|
+
* })
|
|
51
60
|
* })
|
|
52
61
|
* }
|
|
53
62
|
* })
|
|
@@ -55,12 +64,12 @@ export const JSONResponder = {
|
|
|
55
64
|
*/
|
|
56
65
|
export function jsonEnvelope(options = {}) {
|
|
57
66
|
return {
|
|
58
|
-
success: (response, data) => {
|
|
59
|
-
response.json({ errno: null, errMsg: null, data: data === undefined ? null : data });
|
|
67
|
+
success: (response, data, request) => {
|
|
68
|
+
response.json(envelopeOf({ errno: null, errMsg: null, data: data === undefined ? null : data }, options.extra?.(request)));
|
|
60
69
|
},
|
|
61
|
-
error: (response, error,
|
|
70
|
+
error: (response, error, request, origin) => {
|
|
62
71
|
const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
|
|
63
|
-
response.status(status ?? statusOfError(error)).json({ ...describeError(error), data: null });
|
|
72
|
+
response.status(status ?? statusOfError(error)).json(envelopeOf({ ...describeError(error), data: null }, errorExtraOf(options.extra, request)));
|
|
64
73
|
}
|
|
65
74
|
};
|
|
66
75
|
}
|
|
@@ -5,6 +5,7 @@ import { HTTPContext } from '../../context/HTTPContext.js';
|
|
|
5
5
|
import { HTTPChannel } from '../../channel/BuiltinChannels.js';
|
|
6
6
|
import { EventsResult, FileResult, StatusResult } from '../../lib/Result.js';
|
|
7
7
|
import { RouteNotFoundException } from '../../exceptions/RouteNotFoundException.js';
|
|
8
|
+
import { trackConnections } from '../../lib/Connections.js';
|
|
8
9
|
import { FastifyJSONResponder } from './Responders.js';
|
|
9
10
|
/**
|
|
10
11
|
* The content types of the common file extensions
|
|
@@ -79,7 +80,8 @@ function isFastifyInstance(value) {
|
|
|
79
80
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
80
81
|
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
81
82
|
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
82
|
-
* remaining connections are closed
|
|
83
|
+
* remaining connections are closed — the connections taken over by an upgrade (the WebSockets of the application) too,
|
|
84
|
+
* which would hold the stop forever.
|
|
83
85
|
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
84
86
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
85
87
|
* @example
|
|
@@ -131,6 +133,7 @@ export function fastifyHTTPEntrypoint(options = {}) {
|
|
|
131
133
|
}));
|
|
132
134
|
const streams = new Set();
|
|
133
135
|
let app;
|
|
136
|
+
let destroyConnections;
|
|
134
137
|
let closed;
|
|
135
138
|
let stopping = false;
|
|
136
139
|
//The responses not sent yet
|
|
@@ -260,6 +263,7 @@ export function fastifyHTTPEntrypoint(options = {}) {
|
|
|
260
263
|
start: async (runtime) => {
|
|
261
264
|
const fastify = isFastifyInstance(options.fastify) ? options.fastify : Fastify(options.fastify ?? {});
|
|
262
265
|
app = fastify;
|
|
266
|
+
destroyConnections = trackConnections(fastify.server);
|
|
263
267
|
const routes = new Map();
|
|
264
268
|
for (const action of runtime.actions)
|
|
265
269
|
routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(String(action.address.method).toUpperCase()));
|
|
@@ -326,6 +330,8 @@ export function fastifyHTTPEntrypoint(options = {}) {
|
|
|
326
330
|
})]);
|
|
327
331
|
clearTimeout(timer);
|
|
328
332
|
app?.server.closeAllConnections();
|
|
333
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
334
|
+
destroyConnections?.();
|
|
329
335
|
await closed;
|
|
330
336
|
await options.stop?.();
|
|
331
337
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
|
|
2
|
+
import { envelopeOf, errorExtraOf } from '../../lib/Envelope.js';
|
|
2
3
|
/**
|
|
3
4
|
* Send JSON
|
|
4
5
|
* @param reply
|
|
@@ -29,7 +30,8 @@ export const FastifyJSONResponder = {
|
|
|
29
30
|
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
30
31
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
31
32
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
32
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
33
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
34
|
+
* fields to both.
|
|
33
35
|
* @param options The envelope options.
|
|
34
36
|
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
35
37
|
* @example
|
|
@@ -54,8 +56,15 @@ export const FastifyJSONResponder = {
|
|
|
54
56
|
* entrypoint: buildEntrypoints({
|
|
55
57
|
* controllers: [PingController],
|
|
56
58
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
57
|
-
* //found and the errors of the hooks with their status (404, 401…)
|
|
58
|
-
*
|
|
59
|
+
* //found and the errors of the hooks with their status (404, 401…); every envelope with
|
|
60
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
61
|
+
* http: fastifyHTTPEntrypoint({
|
|
62
|
+
* port: 8080,
|
|
63
|
+
* respond: fastifyJSONEnvelope({
|
|
64
|
+
* actionErrorStatus: 200,
|
|
65
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
66
|
+
* })
|
|
67
|
+
* })
|
|
59
68
|
* })
|
|
60
69
|
* }
|
|
61
70
|
* })
|
|
@@ -63,11 +72,11 @@ export const FastifyJSONResponder = {
|
|
|
63
72
|
*/
|
|
64
73
|
export function fastifyJSONEnvelope(options = {}) {
|
|
65
74
|
return {
|
|
66
|
-
success: (reply, data) => sendJSON(reply, { errno: null, errMsg: null, data: data === undefined ? null : data }),
|
|
67
|
-
error: (reply, error,
|
|
75
|
+
success: (reply, data, request) => sendJSON(reply, envelopeOf({ errno: null, errMsg: null, data: data === undefined ? null : data }, options.extra?.(request))),
|
|
76
|
+
error: (reply, error, request, origin) => {
|
|
68
77
|
const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
|
|
69
78
|
reply.code(status ?? statusOfError(error));
|
|
70
|
-
sendJSON(reply, { ...describeError(error), data: null });
|
|
79
|
+
sendJSON(reply, envelopeOf({ ...describeError(error), data: null }, errorExtraOf(options.extra, request)));
|
|
71
80
|
}
|
|
72
81
|
};
|
|
73
82
|
}
|
|
@@ -4,6 +4,7 @@ import { ServiceContext } from '../../context/ServiceContext.js';
|
|
|
4
4
|
import { ServiceChannel } from '../../channel/BuiltinChannels.js';
|
|
5
5
|
import { ServiceResponseCodec } from '../../lib/ServiceResponseCodec.js';
|
|
6
6
|
import { EntrypointStoppedException } from '../../exceptions/EntrypointStoppedException.js';
|
|
7
|
+
import { trackConnections } from '../../lib/Connections.js';
|
|
7
8
|
/**
|
|
8
9
|
* Get the HTTP request carrying a service action's request, when {@link httpServiceEntrypoint} serves it (to read its
|
|
9
10
|
* headers, its remote address…).
|
|
@@ -88,7 +89,8 @@ async function readBody(request, limit) {
|
|
|
88
89
|
*
|
|
89
90
|
* When a client disconnects before its response, the action's abort signal fires. When the entrypoint stops, the new
|
|
90
91
|
* requests are answered with an `EntrypointStoppedException` error; once the requests in progress end, the event
|
|
91
|
-
* streams are closed and the server shuts down
|
|
92
|
+
* streams are closed and the server shuts down, closing its remaining connections (the ones taken over by an upgrade of
|
|
93
|
+
* the application too).
|
|
92
94
|
* @param options The adapter options; by default it listens on port 3000 of all the interfaces, on the path `/`.
|
|
93
95
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
94
96
|
* @example
|
|
@@ -131,6 +133,7 @@ export function httpServiceEntrypoint(options = {}) {
|
|
|
131
133
|
const eventsPath = `${base}/events`;
|
|
132
134
|
const limit = options.limit ?? 10 * 1024 * 1024;
|
|
133
135
|
let server;
|
|
136
|
+
let destroyConnections;
|
|
134
137
|
let stopping = false;
|
|
135
138
|
let keepAlive;
|
|
136
139
|
//The event streams of each client (a client may listen several times)
|
|
@@ -218,6 +221,7 @@ export function httpServiceEntrypoint(options = {}) {
|
|
|
218
221
|
//The event streams kept open by the proxies
|
|
219
222
|
keepAlive = setInterval(() => clients.forEach((streams) => streams.forEach((stream) => stream.write(': keep-alive\n\n'))), 15000);
|
|
220
223
|
keepAlive.unref();
|
|
224
|
+
destroyConnections = trackConnections(server);
|
|
221
225
|
await new Promise((resolve, reject) => {
|
|
222
226
|
server.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
223
227
|
server.off('error', reject);
|
|
@@ -238,6 +242,8 @@ export function httpServiceEntrypoint(options = {}) {
|
|
|
238
242
|
return;
|
|
239
243
|
const closing = new Promise((resolve) => void server.close(() => resolve()));
|
|
240
244
|
server.closeAllConnections();
|
|
245
|
+
//The connections taken over by an upgrade (a WebSocket) are not closed by closeAllConnections() and hold the close of the server
|
|
246
|
+
destroyConnections?.();
|
|
241
247
|
await closing;
|
|
242
248
|
},
|
|
243
249
|
notify: (clientId, event, args) => {
|
|
@@ -3,6 +3,7 @@ import { createServer } from 'node:http';
|
|
|
3
3
|
import { ServiceChannel } from '../../channel/BuiltinChannels.js';
|
|
4
4
|
import { JSONRPCErrorCode, JSONRPCProtocol } from './JSONRPCProtocol.js';
|
|
5
5
|
import { applicationOf } from '../../lib/ApplicationOf.js';
|
|
6
|
+
import { trackConnections } from '../../lib/Connections.js';
|
|
6
7
|
/**
|
|
7
8
|
* The headers of the responses of HTTP: the tools of the browsers may call the adapter
|
|
8
9
|
*/
|
|
@@ -137,7 +138,8 @@ function readMessages(input, framing, limit, receive, ended, oversized) {
|
|
|
137
138
|
*
|
|
138
139
|
* When an HTTP client disconnects before its response, the action's abort signal fires. When the entrypoint stops,
|
|
139
140
|
* stdio stops reading, the new requests are answered with `STOPPING`, and once the requests in progress are answered
|
|
140
|
-
* the WebSocket connections are closed (code 1001) and the
|
|
141
|
+
* the WebSocket connections are closed (code 1001; the ones not answering within a second are terminated) and the
|
|
142
|
+
* server shuts down, closing its remaining connections (the ones taken over by an upgrade of the application too).
|
|
141
143
|
* @param options The adapter options; by default it serves HTTP on port 3000 of all the interfaces, on the path `/`.
|
|
142
144
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
143
145
|
* @example
|
|
@@ -192,6 +194,7 @@ export function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
192
194
|
const path = (options.path ?? '/').replace(/(.)\/+$/, '$1');
|
|
193
195
|
let stopping = false;
|
|
194
196
|
let server;
|
|
197
|
+
let destroyConnections;
|
|
195
198
|
let webSocketServer;
|
|
196
199
|
let stopReading;
|
|
197
200
|
let heartbeat;
|
|
@@ -227,6 +230,7 @@ export function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
227
230
|
return handled;
|
|
228
231
|
};
|
|
229
232
|
const listen = async (target) => {
|
|
233
|
+
destroyConnections = trackConnections(target);
|
|
230
234
|
await new Promise((resolve, reject) => {
|
|
231
235
|
target.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
232
236
|
target.off('error', reject);
|
|
@@ -339,13 +343,25 @@ export function jsonRPCServiceEntrypoint(options = {}) {
|
|
|
339
343
|
await Promise.allSettled([...inProgress]);
|
|
340
344
|
clients.clear();
|
|
341
345
|
if (webSocketServer) {
|
|
342
|
-
|
|
343
|
-
|
|
346
|
+
//The WebSockets are given a second to answer their close, then the ones not answering are terminated (the
|
|
347
|
+
//close of the server waits for them all)
|
|
348
|
+
const sockets = webSocketServer.clients;
|
|
349
|
+
const closedSockets = new Promise((resolve) => webSocketServer.close(() => resolve()));
|
|
350
|
+
sockets.forEach((socket) => socket.close(1001, 'The service stops'));
|
|
351
|
+
let timer;
|
|
352
|
+
await Promise.race([closedSockets, new Promise((resolve) => {
|
|
353
|
+
timer = setTimeout(resolve, 1000);
|
|
354
|
+
})]);
|
|
355
|
+
clearTimeout(timer);
|
|
356
|
+
sockets.forEach((socket) => socket.terminate());
|
|
357
|
+
await closedSockets;
|
|
344
358
|
}
|
|
345
359
|
if (!server)
|
|
346
360
|
return;
|
|
347
361
|
const closing = new Promise((resolve) => void server.close(() => resolve()));
|
|
348
362
|
server.closeAllConnections();
|
|
363
|
+
//The WebSockets not answering their close, and the connections taken over by an upgrade of the application, hold the close of the server
|
|
364
|
+
destroyConnections?.();
|
|
349
365
|
await closing;
|
|
350
366
|
},
|
|
351
367
|
notify: (clientId, event, args) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track the connections of a server, to destroy the ones its `closeAllConnections()` leaves open: the connections taken
|
|
3
|
+
* over by an upgrade (a WebSocket: socket.io, ws, the hot reload of a development server) or by a CONNECT request,
|
|
4
|
+
* which hold the close of the server forever. Unlike a listener of 'upgrade', it does not change how the server answers
|
|
5
|
+
* the requests (without one, an upgrade request is answered as any request).
|
|
6
|
+
* @param server The server whose connections are tracked, before it listens.
|
|
7
|
+
* @returns A function destroying the connections still open.
|
|
8
|
+
*/
|
|
9
|
+
export function trackConnections(server) {
|
|
10
|
+
const sockets = new Set();
|
|
11
|
+
server.on('connection', (socket) => {
|
|
12
|
+
sockets.add(socket);
|
|
13
|
+
socket.once('close', () => sockets.delete(socket));
|
|
14
|
+
});
|
|
15
|
+
return () => sockets.forEach((socket) => void socket.destroy());
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The envelope of a response: its fields, then the extra fields not named as one of them (an extra field never
|
|
3
|
+
* replaces errno, errMsg or data)
|
|
4
|
+
* @param fields The fields of the envelope.
|
|
5
|
+
* @param extra The extra fields, ignored when they are not an object.
|
|
6
|
+
*/
|
|
7
|
+
export function envelopeOf(fields, extra) {
|
|
8
|
+
const envelope = { ...fields };
|
|
9
|
+
if (extra === null || typeof extra !== 'object')
|
|
10
|
+
return envelope;
|
|
11
|
+
for (const [key, value] of Object.entries(extra))
|
|
12
|
+
if (!Object.prototype.hasOwnProperty.call(envelope, key))
|
|
13
|
+
envelope[key] = value;
|
|
14
|
+
return envelope;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The extra fields of an error response: none when the function giving them fails, an error being answered anyway
|
|
18
|
+
* @param extra The function giving the extra fields.
|
|
19
|
+
* @param request The request answered.
|
|
20
|
+
*/
|
|
21
|
+
export function errorExtraOf(extra, request) {
|
|
22
|
+
try {
|
|
23
|
+
return extra?.(request);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -123,7 +123,8 @@ export type ExpressHTTPEntrypointOptions = {
|
|
|
123
123
|
*
|
|
124
124
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
125
125
|
* entrypoint stops, the server stops accepting connections, the event streams end, and the requests in progress are
|
|
126
|
-
* given up to one second after they settle to send their responses before the remaining connections are closed
|
|
126
|
+
* given up to one second after they settle to send their responses before the remaining connections are closed — the
|
|
127
|
+
* connections taken over by an upgrade (the WebSockets of the application) too, which would hold the stop forever.
|
|
127
128
|
* @param options The adapter options; by default it listens on port 80 of all the interfaces with a new express
|
|
128
129
|
* application.
|
|
129
130
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
@@ -49,12 +49,23 @@ export type JSONEnvelopeOptions = {
|
|
|
49
49
|
* @default `errorStatus`
|
|
50
50
|
*/
|
|
51
51
|
actionErrorStatus?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The extra fields of the envelope, such as the `timestamp`, `nonce` or `requestId` of the envelopes of the
|
|
54
|
+
* applications of lakutata 2.x: called for each response (a result or an error), its fields added after `errno`,
|
|
55
|
+
* `errMsg` and `data`, which they never replace. When it throws, a result is answered as an error, and an error
|
|
56
|
+
* without the extra fields.
|
|
57
|
+
* @param request The express request being answered.
|
|
58
|
+
* @returns The fields to add to the envelope.
|
|
59
|
+
* @default no extra field
|
|
60
|
+
*/
|
|
61
|
+
extra?: (request: Request) => Record<string, unknown>;
|
|
52
62
|
};
|
|
53
63
|
/**
|
|
54
64
|
* Create a responder for {@link expressHTTPEntrypoint} that wraps every response in the envelope
|
|
55
65
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
56
66
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
57
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
67
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
68
|
+
* fields to both.
|
|
58
69
|
* @param options The envelope options.
|
|
59
70
|
* @returns The responder, to give to the `respond` option of `expressHTTPEntrypoint`.
|
|
60
71
|
* @example
|
|
@@ -79,8 +90,15 @@ export type JSONEnvelopeOptions = {
|
|
|
79
90
|
* entrypoint: buildEntrypoints({
|
|
80
91
|
* controllers: [PingController],
|
|
81
92
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
82
|
-
* //found and the errors of the express middlewares with their status (404, 401…)
|
|
83
|
-
*
|
|
93
|
+
* //found and the errors of the express middlewares with their status (404, 401…); every envelope with
|
|
94
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
95
|
+
* http: expressHTTPEntrypoint({
|
|
96
|
+
* port: 8080,
|
|
97
|
+
* respond: jsonEnvelope({
|
|
98
|
+
* actionErrorStatus: 200,
|
|
99
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
100
|
+
* })
|
|
101
|
+
* })
|
|
84
102
|
* })
|
|
85
103
|
* }
|
|
86
104
|
* })
|
|
@@ -109,7 +109,8 @@ export type FastifyHTTPEntrypointOptions = {
|
|
|
109
109
|
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
110
110
|
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
111
111
|
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
112
|
-
* remaining connections are closed
|
|
112
|
+
* remaining connections are closed — the connections taken over by an upgrade (the WebSockets of the application) too,
|
|
113
|
+
* which would hold the stop forever.
|
|
113
114
|
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
114
115
|
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
115
116
|
* @example
|
|
@@ -48,12 +48,23 @@ export type FastifyJSONEnvelopeOptions = {
|
|
|
48
48
|
* @default `errorStatus`
|
|
49
49
|
*/
|
|
50
50
|
actionErrorStatus?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The extra fields of the envelope, such as the `timestamp`, `nonce` or `requestId` of the envelopes of the
|
|
53
|
+
* applications of lakutata 2.x: called for each response (a result or an error), its fields added after `errno`,
|
|
54
|
+
* `errMsg` and `data`, which they never replace. When it throws, a result is answered as an error, and an error
|
|
55
|
+
* without the extra fields.
|
|
56
|
+
* @param request The fastify request being answered.
|
|
57
|
+
* @returns The fields to add to the envelope.
|
|
58
|
+
* @default no extra field
|
|
59
|
+
*/
|
|
60
|
+
extra?: (request: FastifyRequest) => Record<string, unknown>;
|
|
51
61
|
};
|
|
52
62
|
/**
|
|
53
63
|
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
54
64
|
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
55
65
|
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
56
|
-
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option
|
|
66
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option; the `extra` option adds
|
|
67
|
+
* fields to both.
|
|
57
68
|
* @param options The envelope options.
|
|
58
69
|
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
59
70
|
* @example
|
|
@@ -78,8 +89,15 @@ export type FastifyJSONEnvelopeOptions = {
|
|
|
78
89
|
* entrypoint: buildEntrypoints({
|
|
79
90
|
* controllers: [PingController],
|
|
80
91
|
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
81
|
-
* //found and the errors of the hooks with their status (404, 401…)
|
|
82
|
-
*
|
|
92
|
+
* //found and the errors of the hooks with their status (404, 401…); every envelope with
|
|
93
|
+
* //its timestamp ({"errno": null, "errMsg": null, "data": "pong", "timestamp": 1767225600000})
|
|
94
|
+
* http: fastifyHTTPEntrypoint({
|
|
95
|
+
* port: 8080,
|
|
96
|
+
* respond: fastifyJSONEnvelope({
|
|
97
|
+
* actionErrorStatus: 200,
|
|
98
|
+
* extra: (): Record<string, unknown> => ({timestamp: Date.now()})
|
|
99
|
+
* })
|
|
100
|
+
* })
|
|
83
101
|
* })
|
|
84
102
|
* }
|
|
85
103
|
* })
|
|
@@ -77,7 +77,8 @@ export declare function httpRequestOfContext(context: ServiceContext): IncomingM
|
|
|
77
77
|
*
|
|
78
78
|
* When a client disconnects before its response, the action's abort signal fires. When the entrypoint stops, the new
|
|
79
79
|
* requests are answered with an `EntrypointStoppedException` error; once the requests in progress end, the event
|
|
80
|
-
* streams are closed and the server shuts down
|
|
80
|
+
* streams are closed and the server shuts down, closing its remaining connections (the ones taken over by an upgrade of
|
|
81
|
+
* the application too).
|
|
81
82
|
* @param options The adapter options; by default it listens on port 3000 of all the interfaces, on the path `/`.
|
|
82
83
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
83
84
|
* @example
|
|
@@ -103,7 +103,8 @@ export type JSONRPCServiceEntrypointOptions = JSONRPCMappingOptions & {
|
|
|
103
103
|
*
|
|
104
104
|
* When an HTTP client disconnects before its response, the action's abort signal fires. When the entrypoint stops,
|
|
105
105
|
* stdio stops reading, the new requests are answered with `STOPPING`, and once the requests in progress are answered
|
|
106
|
-
* the WebSocket connections are closed (code 1001) and the
|
|
106
|
+
* the WebSocket connections are closed (code 1001; the ones not answering within a second are terminated) and the
|
|
107
|
+
* server shuts down, closing its remaining connections (the ones taken over by an upgrade of the application too).
|
|
107
108
|
* @param options The adapter options; by default it serves HTTP on port 3000 of all the interfaces, on the path `/`.
|
|
108
109
|
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
109
110
|
* @example
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Track the connections of a server, to destroy the ones its `closeAllConnections()` leaves open: the connections taken
|
|
4
|
+
* over by an upgrade (a WebSocket: socket.io, ws, the hot reload of a development server) or by a CONNECT request,
|
|
5
|
+
* which hold the close of the server forever. Unlike a listener of 'upgrade', it does not change how the server answers
|
|
6
|
+
* the requests (without one, an upgrade request is answered as any request).
|
|
7
|
+
* @param server The server whose connections are tracked, before it listens.
|
|
8
|
+
* @returns A function destroying the connections still open.
|
|
9
|
+
*/
|
|
10
|
+
export declare function trackConnections(server: Server): () => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The envelope of a response: its fields, then the extra fields not named as one of them (an extra field never
|
|
3
|
+
* replaces errno, errMsg or data)
|
|
4
|
+
* @param fields The fields of the envelope.
|
|
5
|
+
* @param extra The extra fields, ignored when they are not an object.
|
|
6
|
+
*/
|
|
7
|
+
export declare function envelopeOf(fields: Record<string, unknown>, extra: unknown): Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* The extra fields of an error response: none when the function giving them fails, an error being answered anyway
|
|
10
|
+
* @param extra The function giving the extra fields.
|
|
11
|
+
* @param request The request answered.
|
|
12
|
+
*/
|
|
13
|
+
export declare function errorExtraOf<Request>(extra: ((request: Request) => Record<string, unknown>) | undefined, request: Request): unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/entrypoint",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.3",
|
|
4
4
|
"description": "The entrypoint component of the lakutata framework: controllers and the dispatch of the CLI, HTTP and service actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/exports/Entrypoint.js",
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
},
|
|
111
111
|
"peerDependencies": {
|
|
112
112
|
"@inquirer/prompts": "^7.0.0 || ^8.0.0",
|
|
113
|
-
"@lakutata/core": "3.0.0-beta.
|
|
114
|
-
"@lakutata/utils": "3.0.0-beta.
|
|
113
|
+
"@lakutata/core": "3.0.0-beta.3",
|
|
114
|
+
"@lakutata/utils": "3.0.0-beta.3",
|
|
115
115
|
"commander": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
|
|
116
116
|
"express": "^4.21.0 || ^5.0.0",
|
|
117
117
|
"fastify": "^5.0.0",
|