@lakutata/entrypoint 3.0.0 → 3.1.0
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 +39 -26
- package/README.zh-CN.md +182 -0
- package/dist/cjs/Entrypoint.d.ts +13 -7
- package/dist/cjs/Entrypoint.js +12 -6
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.d.ts +3 -2
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.js +3 -2
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.js +2 -1
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +3 -2
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +8 -2
- package/dist/cjs/adapter/express/Responders.d.ts +2 -1
- package/dist/cjs/adapter/express/Responders.js +4 -2
- package/dist/cjs/adapter/express/StaticUI.d.ts +2 -1
- package/dist/cjs/adapter/express/StaticUI.js +2 -1
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +4 -1
- package/dist/cjs/adapter/fastify/Responders.d.ts +2 -1
- package/dist/cjs/adapter/fastify/Responders.js +4 -2
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +4 -3
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +10 -22
- package/dist/cjs/adapter/http/HTTPServiceProxy.d.ts +190 -0
- package/dist/cjs/adapter/http/HTTPServiceProxy.js +371 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +11 -7
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +15 -26
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.js +12 -5
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.js +2 -1
- package/dist/cjs/channel/BuiltinChannels.js +4 -2
- package/dist/cjs/channel/EntrypointAdapter.d.ts +2 -2
- package/dist/cjs/decorators/HTTPAction.d.ts +5 -5
- package/dist/cjs/decorators/Schedule.d.ts +4 -2
- package/dist/cjs/decorators/Schedule.js +3 -2
- package/dist/cjs/decorators/ServiceAction.d.ts +12 -8
- package/dist/cjs/decorators/http/DELETE.d.ts +5 -5
- package/dist/cjs/decorators/http/GET.d.ts +7 -7
- package/dist/cjs/decorators/http/HEAD.d.ts +9 -9
- package/dist/cjs/decorators/http/OPTIONS.d.ts +8 -7
- package/dist/cjs/decorators/http/PATCH.d.ts +5 -5
- package/dist/cjs/decorators/http/POST.d.ts +5 -5
- package/dist/cjs/decorators/http/PUT.d.ts +7 -7
- package/dist/cjs/exceptions/AccessDeniedException.d.ts +7 -1
- package/dist/cjs/exceptions/AccessDeniedException.js +8 -3
- package/dist/cjs/exceptions/EntrypointStoppedException.d.ts +2 -1
- package/dist/cjs/exceptions/EntrypointStoppedException.js +2 -1
- package/dist/cjs/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.js +4 -0
- package/dist/cjs/exceptions/ServiceUnavailableException.d.ts +15 -0
- package/dist/cjs/exceptions/ServiceUnavailableException.js +18 -0
- package/dist/cjs/exports/Entrypoint.d.ts +3 -0
- package/dist/cjs/exports/Entrypoint.js +6 -1
- package/dist/cjs/lib/AccessControl.d.ts +2 -1
- package/dist/cjs/lib/AccessControl.js +2 -1
- package/dist/cjs/lib/AccessControlRule.d.ts +2 -1
- package/dist/cjs/lib/AccessControlRule.js +2 -1
- package/dist/cjs/lib/ExplicitStatus.d.ts +11 -0
- package/dist/cjs/lib/ExplicitStatus.js +22 -0
- package/dist/cjs/lib/Middleware.d.ts +2 -1
- package/dist/cjs/lib/Middleware.js +2 -1
- package/dist/cjs/lib/Preflight.d.ts +8 -0
- package/dist/cjs/lib/Preflight.js +13 -0
- package/dist/cjs/lib/ReadBody.d.ts +9 -0
- package/dist/cjs/lib/ReadBody.js +25 -0
- package/dist/cjs/lib/pattern/FlattenPattern.d.ts +14 -0
- package/dist/cjs/lib/pattern/FlattenPattern.js +59 -0
- package/dist/esm/Entrypoint.js +12 -6
- package/dist/esm/adapter/commander/CommanderCLIEntrypoint.js +3 -2
- package/dist/esm/adapter/electron/ElectronIPCServiceEntrypoint.js +2 -1
- package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +8 -2
- package/dist/esm/adapter/express/Responders.js +4 -2
- package/dist/esm/adapter/express/StaticUI.js +2 -1
- package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +4 -1
- package/dist/esm/adapter/fastify/Responders.js +4 -2
- package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +9 -21
- package/dist/esm/adapter/http/HTTPServiceProxy.js +366 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +14 -25
- package/dist/esm/adapter/schedule/ScheduleEntrypoint.js +12 -5
- package/dist/esm/adapter/socket.io/SocketIOServiceEntrypoint.js +2 -1
- package/dist/esm/channel/BuiltinChannels.js +4 -2
- package/dist/esm/decorators/Schedule.js +3 -2
- package/dist/esm/exceptions/AccessDeniedException.js +8 -3
- package/dist/esm/exceptions/EntrypointStoppedException.js +2 -1
- package/dist/esm/exceptions/InvalidActionGroupException.js +4 -0
- package/dist/esm/exceptions/ServiceUnavailableException.js +14 -0
- package/dist/esm/exports/Entrypoint.js +3 -0
- package/dist/esm/lib/AccessControl.js +2 -1
- package/dist/esm/lib/AccessControlRule.js +2 -1
- package/dist/esm/lib/ExplicitStatus.js +18 -0
- package/dist/esm/lib/Middleware.js +2 -1
- package/dist/esm/lib/Preflight.js +10 -0
- package/dist/esm/lib/ReadBody.js +22 -0
- package/dist/esm/lib/pattern/FlattenPattern.js +55 -0
- package/dist/types/Entrypoint.d.ts +13 -7
- package/dist/types/adapter/commander/CommanderCLIEntrypoint.d.ts +3 -2
- package/dist/types/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +2 -1
- package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +3 -2
- package/dist/types/adapter/express/Responders.d.ts +2 -1
- package/dist/types/adapter/express/StaticUI.d.ts +2 -1
- package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/types/adapter/fastify/Responders.d.ts +2 -1
- package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +4 -3
- package/dist/types/adapter/http/HTTPServiceProxy.d.ts +190 -0
- package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +11 -7
- package/dist/types/adapter/schedule/ScheduleEntrypoint.d.ts +2 -1
- package/dist/types/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +2 -1
- package/dist/types/channel/EntrypointAdapter.d.ts +2 -2
- package/dist/types/decorators/HTTPAction.d.ts +5 -5
- package/dist/types/decorators/Schedule.d.ts +4 -2
- package/dist/types/decorators/ServiceAction.d.ts +12 -8
- package/dist/types/decorators/http/DELETE.d.ts +5 -5
- package/dist/types/decorators/http/GET.d.ts +7 -7
- package/dist/types/decorators/http/HEAD.d.ts +9 -9
- package/dist/types/decorators/http/OPTIONS.d.ts +8 -7
- package/dist/types/decorators/http/PATCH.d.ts +5 -5
- package/dist/types/decorators/http/POST.d.ts +5 -5
- package/dist/types/decorators/http/PUT.d.ts +7 -7
- package/dist/types/exceptions/AccessDeniedException.d.ts +7 -1
- package/dist/types/exceptions/EntrypointStoppedException.d.ts +2 -1
- package/dist/types/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/types/exceptions/ServiceUnavailableException.d.ts +15 -0
- package/dist/types/exports/Entrypoint.d.ts +3 -0
- package/dist/types/lib/AccessControl.d.ts +2 -1
- package/dist/types/lib/AccessControlRule.d.ts +2 -1
- package/dist/types/lib/ExplicitStatus.d.ts +11 -0
- package/dist/types/lib/Middleware.d.ts +2 -1
- package/dist/types/lib/Preflight.d.ts +8 -0
- package/dist/types/lib/ReadBody.d.ts +9 -0
- package/dist/types/lib/pattern/FlattenPattern.d.ts +14 -0
- package/package.json +35 -4
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenPattern = flattenPattern;
|
|
4
|
+
exports.flattenSubject = flattenSubject;
|
|
5
|
+
/**
|
|
6
|
+
* The nesting levels of a subject flattened: deeper than the patterns may be (2 levels)
|
|
7
|
+
*/
|
|
8
|
+
const MAX_DEPTH = 3;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a value is a plain object, whose properties a pattern compares one by one
|
|
11
|
+
* @param value
|
|
12
|
+
*/
|
|
13
|
+
function isPlainObject(value) {
|
|
14
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
15
|
+
return false;
|
|
16
|
+
const prototype = Object.getPrototypeOf(value);
|
|
17
|
+
return prototype === Object.prototype || prototype === null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A pattern with the properties of its nested objects as keys joined by dots (`{res: {name: 'user'}}` gives
|
|
21
|
+
* `{'res.name': 'user'}`): their values are compared one by one, not as the text of their object. An empty nested
|
|
22
|
+
* object stays a value.
|
|
23
|
+
* @param pattern
|
|
24
|
+
* @param prefix
|
|
25
|
+
*/
|
|
26
|
+
function flattenPattern(pattern, prefix = '') {
|
|
27
|
+
const flat = {};
|
|
28
|
+
for (const [key, value] of Object.entries(pattern)) {
|
|
29
|
+
if (isPlainObject(value) && Object.keys(value).length) {
|
|
30
|
+
Object.assign(flat, flattenPattern(value, `${prefix}${key}.`));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
flat[`${prefix}${key}`] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return flat;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A subject matched by the flattened patterns: its own properties, with the ones of its nested objects as keys joined
|
|
40
|
+
* by dots
|
|
41
|
+
* @param subject
|
|
42
|
+
*/
|
|
43
|
+
function flattenSubject(subject) {
|
|
44
|
+
if (!isPlainObject(subject))
|
|
45
|
+
return subject;
|
|
46
|
+
const flat = { ...subject };
|
|
47
|
+
const add = (object, prefix, depth) => {
|
|
48
|
+
for (const [key, value] of Object.entries(object)) {
|
|
49
|
+
if (!isPlainObject(value))
|
|
50
|
+
continue;
|
|
51
|
+
for (const [nestedKey, nestedValue] of Object.entries(value))
|
|
52
|
+
flat[`${prefix}${key}.${nestedKey}`] = nestedValue;
|
|
53
|
+
if (depth < MAX_DEPTH)
|
|
54
|
+
add(value, `${prefix}${key}.`, depth + 1);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
add(subject, '', 1);
|
|
58
|
+
return flat;
|
|
59
|
+
}
|
package/dist/esm/Entrypoint.js
CHANGED
|
@@ -58,7 +58,8 @@ export { ScheduleContext } from './context/ScheduleContext.js';
|
|
|
58
58
|
* if (cliMap.has(command)) console.log(await handler(new CLIContext({command: command, data: {}})))
|
|
59
59
|
* })
|
|
60
60
|
* })
|
|
61
|
-
* }
|
|
61
|
+
* },
|
|
62
|
+
* bootstrap: ['entrypoint']
|
|
62
63
|
* }))
|
|
63
64
|
* ```
|
|
64
65
|
*/
|
|
@@ -90,8 +91,9 @@ export const buildCLIEntrypoint = (entrypoint) => entrypoint;
|
|
|
90
91
|
* controllers: [HealthController],
|
|
91
92
|
* http: buildHTTPEntrypoint((module, routeMap, handler, registerDestroy): void => {
|
|
92
93
|
* const server: Server = createServer(async (request, response): Promise<void> => {
|
|
94
|
+
* //The route map holds the routes and the methods as declared: '/health', 'get'
|
|
93
95
|
* const route: string = (request.url ?? '/').split('?')[0]
|
|
94
|
-
* const method: string = request.method ?? 'GET'
|
|
96
|
+
* const method: string = (request.method ?? 'GET').toLowerCase()
|
|
95
97
|
* if (!routeMap.get(route)?.has(method)) {
|
|
96
98
|
* response.writeHead(404).end()
|
|
97
99
|
* return
|
|
@@ -108,7 +110,8 @@ export const buildCLIEntrypoint = (entrypoint) => entrypoint;
|
|
|
108
110
|
* registerDestroy((): Promise<void> => new Promise((resolve): void => void server.close((): void => resolve())))
|
|
109
111
|
* })
|
|
110
112
|
* })
|
|
111
|
-
* }
|
|
113
|
+
* },
|
|
114
|
+
* bootstrap: ['entrypoint']
|
|
112
115
|
* }))
|
|
113
116
|
* ```
|
|
114
117
|
*/
|
|
@@ -149,7 +152,8 @@ export const buildHTTPEntrypoint = (entrypoint) => entrypoint;
|
|
|
149
152
|
* registerDestroy((): void => void bus.off('request', listener))
|
|
150
153
|
* })
|
|
151
154
|
* })
|
|
152
|
-
* }
|
|
155
|
+
* },
|
|
156
|
+
* bootstrap: ['entrypoint']
|
|
153
157
|
* }))
|
|
154
158
|
* ```
|
|
155
159
|
*/
|
|
@@ -199,7 +203,8 @@ export const buildServiceEntrypoint = (entrypoint) => entrypoint;
|
|
|
199
203
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
200
204
|
* schedule: scheduleEntrypoint()
|
|
201
205
|
* })
|
|
202
|
-
* }
|
|
206
|
+
* },
|
|
207
|
+
* bootstrap: ['entrypoint']
|
|
203
208
|
* }))
|
|
204
209
|
* ```
|
|
205
210
|
*/
|
|
@@ -269,7 +274,8 @@ const LEGACY_CHANNELS = new Set(['cli', 'http', 'service']);
|
|
|
269
274
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
270
275
|
* service: socketIOServiceEntrypoint({port: 3001})
|
|
271
276
|
* })
|
|
272
|
-
* }
|
|
277
|
+
* },
|
|
278
|
+
* bootstrap: ['entrypoint']
|
|
273
279
|
* }))
|
|
274
280
|
* .onLaunched(async (app: Application): Promise<void> => {
|
|
275
281
|
* const entrypoint: Entrypoint = await app.getObject<Entrypoint>('entrypoint')
|
|
@@ -190,7 +190,7 @@ function errorMessage(error) {
|
|
|
190
190
|
* @CLIAction('greet <name>', GreetDTO)
|
|
191
191
|
* public async greet(input: GreetDTO): Promise<string> {
|
|
192
192
|
* const text: string = `Hello, ${input.name}`
|
|
193
|
-
* return (input.loud ? text.toUpperCase() : text).
|
|
193
|
+
* return Array(input.times ?? 1).fill(input.loud ? text.toUpperCase() : text).join('\n')
|
|
194
194
|
* }
|
|
195
195
|
* }
|
|
196
196
|
*
|
|
@@ -202,7 +202,8 @@ function errorMessage(error) {
|
|
|
202
202
|
* controllers: [GreetController],
|
|
203
203
|
* cli: commanderCLIEntrypoint({bin: 'tool', description: 'A greeting tool', version: '1.0.0'})
|
|
204
204
|
* })
|
|
205
|
-
* }
|
|
205
|
+
* },
|
|
206
|
+
* bootstrap: ['entrypoint']
|
|
206
207
|
* })
|
|
207
208
|
* ```
|
|
208
209
|
*/
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { trackConnections } from '../../lib/Connections.js';
|
|
9
|
+
import { markExplicitStatus } from '../../lib/ExplicitStatus.js';
|
|
9
10
|
import { JSONResponder } from './Responders.js';
|
|
10
11
|
/**
|
|
11
12
|
* The first segments of the routes
|
|
@@ -94,7 +95,8 @@ function eventFrame(data, event, id) {
|
|
|
94
95
|
* fallback: staticUI('./ui/dist')
|
|
95
96
|
* })
|
|
96
97
|
* })
|
|
97
|
-
* }
|
|
98
|
+
* },
|
|
99
|
+
* bootstrap: ['entrypoint']
|
|
98
100
|
* })
|
|
99
101
|
* ```
|
|
100
102
|
*/
|
|
@@ -193,6 +195,7 @@ export function expressHTTPEntrypoint(options = {}) {
|
|
|
193
195
|
return await sendFile(result, request, response);
|
|
194
196
|
if (result instanceof StatusResult) {
|
|
195
197
|
response.status(result.status).set(result.headers);
|
|
198
|
+
markExplicitStatus(response);
|
|
196
199
|
return respond.success(response, result.data, request);
|
|
197
200
|
}
|
|
198
201
|
respond.success(response, result, request);
|
|
@@ -251,7 +254,10 @@ export function expressHTTPEntrypoint(options = {}) {
|
|
|
251
254
|
await options.before?.(app, { module: runtime.module, server: server, routes: routes, actionPrefixes: actionPrefixes, isActionPath: isActionPath });
|
|
252
255
|
if (options.body !== false)
|
|
253
256
|
app.use(express.json({ limit: options.body?.limit ?? '1mb' }), express.urlencoded({ extended: true, limit: options.body?.limit ?? '1mb' }));
|
|
254
|
-
|
|
257
|
+
//The HEAD actions first: express answers a HEAD request with the first GET route of its path when no HEAD route
|
|
258
|
+
//comes before it
|
|
259
|
+
const isHead = (action) => String(action.address.method).toLowerCase() === 'head';
|
|
260
|
+
for (const action of [...runtime.actions.filter(isHead), ...runtime.actions.filter((action) => !isHead(action))]) {
|
|
255
261
|
const method = String(action.address.method).toLowerCase();
|
|
256
262
|
const register = app[method];
|
|
257
263
|
if (typeof register !== 'function')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
|
|
2
2
|
import { envelopeOf, errorExtraOf } from '../../lib/Envelope.js';
|
|
3
|
+
import { hasExplicitStatus } from '../../lib/ExplicitStatus.js';
|
|
3
4
|
export { describeError, statusOfError };
|
|
4
5
|
/**
|
|
5
6
|
* The default responder of {@link expressHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
@@ -9,7 +10,7 @@ export { describeError, statusOfError };
|
|
|
9
10
|
export const JSONResponder = {
|
|
10
11
|
success: (response, data) => {
|
|
11
12
|
if (data === undefined) {
|
|
12
|
-
response.status(response.statusCode === 200 ? 204 : response.statusCode).end();
|
|
13
|
+
response.status(response.statusCode === 200 && !hasExplicitStatus(response) ? 204 : response.statusCode).end();
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
16
|
response.json(data);
|
|
@@ -58,7 +59,8 @@ export const JSONResponder = {
|
|
|
58
59
|
* })
|
|
59
60
|
* })
|
|
60
61
|
* })
|
|
61
|
-
* }
|
|
62
|
+
* },
|
|
63
|
+
* bootstrap: ['entrypoint']
|
|
62
64
|
* })
|
|
63
65
|
* ```
|
|
64
66
|
*/
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { trackConnections } from '../../lib/Connections.js';
|
|
9
|
+
import { markExplicitStatus } from '../../lib/ExplicitStatus.js';
|
|
9
10
|
import { FastifyJSONResponder } from './Responders.js';
|
|
10
11
|
/**
|
|
11
12
|
* The content types of the common file extensions
|
|
@@ -121,7 +122,8 @@ function isFastifyInstance(value) {
|
|
|
121
122
|
* }
|
|
122
123
|
* })
|
|
123
124
|
* })
|
|
124
|
-
* }
|
|
125
|
+
* },
|
|
126
|
+
* bootstrap: ['entrypoint']
|
|
125
127
|
* })
|
|
126
128
|
* ```
|
|
127
129
|
*/
|
|
@@ -216,6 +218,7 @@ export function fastifyHTTPEntrypoint(options = {}) {
|
|
|
216
218
|
return await sendFile(result, request, reply);
|
|
217
219
|
if (result instanceof StatusResult) {
|
|
218
220
|
reply.code(result.status).headers(result.headers);
|
|
221
|
+
markExplicitStatus(reply.raw);
|
|
219
222
|
return respond.success(reply, result.data, request);
|
|
220
223
|
}
|
|
221
224
|
respond.success(reply, result, request);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
|
|
2
2
|
import { envelopeOf, errorExtraOf } from '../../lib/Envelope.js';
|
|
3
|
+
import { hasExplicitStatus } from '../../lib/ExplicitStatus.js';
|
|
3
4
|
/**
|
|
4
5
|
* Send JSON
|
|
5
6
|
* @param reply
|
|
@@ -16,7 +17,7 @@ function sendJSON(reply, data) {
|
|
|
16
17
|
export const FastifyJSONResponder = {
|
|
17
18
|
success: (reply, data) => {
|
|
18
19
|
if (data === undefined) {
|
|
19
|
-
reply.code(reply.statusCode === 200 ? 204 : reply.statusCode).send();
|
|
20
|
+
reply.code(reply.statusCode === 200 && !hasExplicitStatus(reply.raw) ? 204 : reply.statusCode).send();
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
sendJSON(reply, data);
|
|
@@ -66,7 +67,8 @@ export const FastifyJSONResponder = {
|
|
|
66
67
|
* })
|
|
67
68
|
* })
|
|
68
69
|
* })
|
|
69
|
-
* }
|
|
70
|
+
* },
|
|
71
|
+
* bootstrap: ['entrypoint']
|
|
70
72
|
* })
|
|
71
73
|
* ```
|
|
72
74
|
*/
|
|
@@ -5,6 +5,8 @@ import { ServiceChannel } from '../../channel/BuiltinChannels.js';
|
|
|
5
5
|
import { ServiceResponseCodec } from '../../lib/ServiceResponseCodec.js';
|
|
6
6
|
import { EntrypointStoppedException } from '../../exceptions/EntrypointStoppedException.js';
|
|
7
7
|
import { trackConnections } from '../../lib/Connections.js';
|
|
8
|
+
import { readBody } from '../../lib/ReadBody.js';
|
|
9
|
+
import { preflightHeaders } from '../../lib/Preflight.js';
|
|
8
10
|
/**
|
|
9
11
|
* Get the HTTP request carrying a service action's request, when {@link httpServiceEntrypoint} serves it (to read its
|
|
10
12
|
* headers, its remote address…).
|
|
@@ -27,7 +29,8 @@ export function httpRequestOfContext(context) {
|
|
|
27
29
|
return context.request;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
|
-
* The headers of the responses: the tools of the browsers may call the adapter (the development of the applications)
|
|
32
|
+
* The headers of the responses: the tools of the browsers may call the adapter (the development of the applications);
|
|
33
|
+
* a preflight is allowed the headers it asks for
|
|
31
34
|
*/
|
|
32
35
|
const CORS_HEADERS = {
|
|
33
36
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -55,22 +58,6 @@ function reply(response, status, body) {
|
|
|
55
58
|
return;
|
|
56
59
|
response.writeHead(status, { ...CORS_HEADERS, 'Content-Type': 'application/json; charset=utf-8' }).end(JSON.stringify(body));
|
|
57
60
|
}
|
|
58
|
-
/**
|
|
59
|
-
* The body of a request, up to a limit
|
|
60
|
-
* @param request
|
|
61
|
-
* @param limit
|
|
62
|
-
*/
|
|
63
|
-
async function readBody(request, limit) {
|
|
64
|
-
const chunks = [];
|
|
65
|
-
let size = 0;
|
|
66
|
-
for await (const chunk of request) {
|
|
67
|
-
size += chunk.length;
|
|
68
|
-
if (size > limit)
|
|
69
|
-
throw Object.assign(new Error(`The body of the request is larger than ${limit} bytes`), { statusCode: 413 });
|
|
70
|
-
chunks.push(chunk);
|
|
71
|
-
}
|
|
72
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
73
|
-
}
|
|
74
61
|
/**
|
|
75
62
|
* Create a service adapter of an entrypoint served over plain HTTP, handy to call the service actions from tools such
|
|
76
63
|
* as curl or Postman during development. It needs no optional dependency.
|
|
@@ -78,8 +65,8 @@ async function readBody(request, limit) {
|
|
|
78
65
|
* A request is a `POST` to the path whose JSON body holds the action's pattern and input (`{"cmd": "echo", "text":
|
|
79
66
|
* "hi"}`). The response is always 200 with the body `{success, error, payload}` (see `ServiceResponseCodec`): `payload`
|
|
80
67
|
* is the result, or `error` the whole error object when the action failed (no matching action, validation…). A body that
|
|
81
|
-
* is not JSON
|
|
82
|
-
* headers allow any origin.
|
|
68
|
+
* is not JSON is answered with 400 and an error body, a body over `limit` with 413; any other method or path with 404.
|
|
69
|
+
* The CORS headers allow any origin, and the headers a preflight asks for (`Authorization`…).
|
|
83
70
|
*
|
|
84
71
|
* The events pushed to the clients (`Entrypoint.notify`, `Entrypoint.broadcast`) are streamed as Server-Sent Events by
|
|
85
72
|
* `GET <path>/events`: each event's name is the SSE event, its arguments the JSON array of its data. A client gives its
|
|
@@ -114,7 +101,8 @@ async function readBody(request, limit) {
|
|
|
114
101
|
* controllers: [EchoController],
|
|
115
102
|
* service: httpServiceEntrypoint({port: 3000, path: '/rpc'})
|
|
116
103
|
* })
|
|
117
|
-
* }
|
|
104
|
+
* },
|
|
105
|
+
* bootstrap: ['entrypoint']
|
|
118
106
|
* })
|
|
119
107
|
*
|
|
120
108
|
* //A client: the response is {"success": true, "error": null, "payload": "hi"}
|
|
@@ -211,7 +199,7 @@ export function httpServiceEntrypoint(options = {}) {
|
|
|
211
199
|
server = createServer((request, response) => {
|
|
212
200
|
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
213
201
|
if (request.method === 'OPTIONS')
|
|
214
|
-
return void response.writeHead(204, CORS_HEADERS).end();
|
|
202
|
+
return void response.writeHead(204, preflightHeaders(request, CORS_HEADERS)).end();
|
|
215
203
|
if (request.method === 'GET' && url.pathname === eventsPath)
|
|
216
204
|
return listen(request, response, url);
|
|
217
205
|
if (request.method === 'POST' && url.pathname === requestPath)
|