@jaypie/express 0.1.1 → 0.1.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/dist/module.cjs.js +382 -3
- package/dist/module.esm.js +382 -3
- package/package.json +3 -1
- package/src/echo.handler.js +32 -0
- package/src/expressHandler.js +12 -13
- package/src/http.handler.js +73 -0
- package/src/index.js +1 -6
package/dist/module.cjs.js
CHANGED
|
@@ -1,10 +1,389 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var core = require('@jaypie/core');
|
|
4
|
+
var serverlessExpress = require('@codegenie/serverless-express');
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
//
|
|
8
|
+
// Helper Functions
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
// Adapter for the "@codegenie/serverless-express" uuid
|
|
12
|
+
function getServerlessExpressUuid() {
|
|
13
|
+
const currentInvoke = serverlessExpress.getCurrentInvoke();
|
|
14
|
+
if (
|
|
15
|
+
currentInvoke &&
|
|
16
|
+
currentInvoke.context &&
|
|
17
|
+
currentInvoke.context.awsRequestId
|
|
18
|
+
) {
|
|
19
|
+
return currentInvoke.context.awsRequestId;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
//
|
|
26
|
+
// Main
|
|
27
|
+
//
|
|
28
|
+
|
|
29
|
+
const getCurrentInvokeUuid = () => getServerlessExpressUuid();
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
//
|
|
33
|
+
// Main
|
|
34
|
+
//
|
|
35
|
+
|
|
36
|
+
const decorateResponse = (
|
|
37
|
+
res,
|
|
38
|
+
{ handler = "", version = process.env.PROJECT_VERSION } = {},
|
|
39
|
+
) => {
|
|
40
|
+
const log = core.log.lib({
|
|
41
|
+
lib: core.JAYPIE.LIB.EXPRESS,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
//
|
|
45
|
+
//
|
|
46
|
+
// Validate
|
|
47
|
+
//
|
|
48
|
+
if (typeof res !== "object" || res === null) {
|
|
49
|
+
log.warn("decorateResponse called but response is not an object");
|
|
50
|
+
return; // eslint-disable-line no-useless-return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
//
|
|
55
|
+
//
|
|
56
|
+
// Decorate Headers
|
|
57
|
+
//
|
|
58
|
+
|
|
59
|
+
// X-Powered-By, override "Express" but nothing else
|
|
60
|
+
if (
|
|
61
|
+
!res.get(core.HTTP.HEADER.POWERED_BY) ||
|
|
62
|
+
res.get(core.HTTP.HEADER.POWERED_BY) === "Express"
|
|
63
|
+
) {
|
|
64
|
+
res.set(core.HTTP.HEADER.POWERED_BY, core.JAYPIE.LIB.EXPRESS);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// X-Project-Environment
|
|
68
|
+
if (process.env.PROJECT_ENV) {
|
|
69
|
+
res.setHeader(core.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// X-Project-Handler
|
|
73
|
+
if (handler) {
|
|
74
|
+
res.setHeader(core.HTTP.HEADER.PROJECT.HANDLER, handler);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// X-Project-Invocation
|
|
78
|
+
const currentInvoke = getCurrentInvokeUuid();
|
|
79
|
+
if (currentInvoke) {
|
|
80
|
+
res.setHeader(core.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// X-Project-Key
|
|
84
|
+
if (process.env.PROJECT_KEY) {
|
|
85
|
+
res.setHeader(core.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// X-Project-Version
|
|
89
|
+
if (version) {
|
|
90
|
+
res.setHeader(core.HTTP.HEADER.PROJECT.VERSION, version);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//
|
|
94
|
+
//
|
|
95
|
+
// Error Handling
|
|
96
|
+
//
|
|
97
|
+
} catch (error) {
|
|
98
|
+
log.warn("decorateResponse caught an internal error");
|
|
99
|
+
log.var({ error });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
//
|
|
3
104
|
//
|
|
105
|
+
// Footnotes
|
|
4
106
|
//
|
|
5
|
-
|
|
107
|
+
|
|
108
|
+
// This is a "utility" function but it needs a lot of "context"
|
|
109
|
+
// about the environment's secret parameters, the special adapter,
|
|
110
|
+
// HTTP, etc. There must be a better way to organize this
|
|
111
|
+
|
|
112
|
+
//
|
|
113
|
+
//
|
|
114
|
+
// Function Definition
|
|
115
|
+
//
|
|
116
|
+
|
|
117
|
+
function summarizeRequest(req) {
|
|
118
|
+
// If body is a buffer, convert it to a string
|
|
119
|
+
let { body } = req;
|
|
120
|
+
if (Buffer.isBuffer(body)) {
|
|
121
|
+
body = body.toString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
baseUrl: req.baseUrl,
|
|
126
|
+
body,
|
|
127
|
+
headers: req.headers,
|
|
128
|
+
method: req.method,
|
|
129
|
+
query: req.query,
|
|
130
|
+
url: req.url,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
6
134
|
//
|
|
135
|
+
//
|
|
136
|
+
// Function Definition
|
|
137
|
+
//
|
|
138
|
+
|
|
139
|
+
function summarizeResponse(res, extras) {
|
|
140
|
+
const response = {
|
|
141
|
+
statusCode: res.statusCode,
|
|
142
|
+
statusMessage: res.statusMessage,
|
|
143
|
+
};
|
|
144
|
+
if (typeof res.getHeaders === "function") {
|
|
145
|
+
response.headers = res.getHeaders();
|
|
146
|
+
}
|
|
147
|
+
if (typeof extras === "object" && extras !== null) {
|
|
148
|
+
Object.assign(response, extras);
|
|
149
|
+
}
|
|
150
|
+
return response;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//
|
|
154
|
+
//
|
|
155
|
+
// Main
|
|
156
|
+
//
|
|
157
|
+
|
|
158
|
+
const expressHandler = (
|
|
159
|
+
handler,
|
|
160
|
+
{ locals, name, setup = [], teardown = [], unavailable, validate } = {},
|
|
161
|
+
) => {
|
|
162
|
+
//
|
|
163
|
+
//
|
|
164
|
+
// Validate
|
|
165
|
+
//
|
|
166
|
+
core.validate.function(handler);
|
|
167
|
+
core.validate.optional.object(locals);
|
|
168
|
+
setup = core.force.array(setup); // allows a single item
|
|
169
|
+
teardown = core.force.array(teardown); // allows a single item
|
|
170
|
+
|
|
171
|
+
//
|
|
172
|
+
//
|
|
173
|
+
// Setup
|
|
174
|
+
//
|
|
175
|
+
|
|
176
|
+
let jaypieFunction;
|
|
177
|
+
|
|
178
|
+
return async (req, res, ...params) => {
|
|
179
|
+
// * This is the first line of code that runs when a request is received
|
|
180
|
+
|
|
181
|
+
// Update the public logger with the request ID
|
|
182
|
+
core.log.tag({ invoke: getCurrentInvokeUuid() });
|
|
183
|
+
|
|
184
|
+
// Very low-level, internal sub-trace details
|
|
185
|
+
const libLogger = core.log.lib({
|
|
186
|
+
lib: core.JAYPIE.LIB.EXPRESS,
|
|
187
|
+
});
|
|
188
|
+
libLogger.trace("[jaypie] Express init");
|
|
189
|
+
|
|
190
|
+
// Top-level, important details that run at the same level as the main logger
|
|
191
|
+
const log = core.log.lib({
|
|
192
|
+
level: core.log.level,
|
|
193
|
+
lib: core.JAYPIE.LIB.EXPRESS,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Set req.locals if it doesn't exist
|
|
197
|
+
if (!req.locals) req.locals = {};
|
|
198
|
+
if (!req.locals._jaypie) req.locals._jaypie = {};
|
|
199
|
+
|
|
200
|
+
// Set res.locals if it doesn't exist
|
|
201
|
+
if (!res.locals) res.locals = {};
|
|
202
|
+
if (!res.locals._jaypie) res.locals._jaypie = {};
|
|
203
|
+
|
|
204
|
+
const originalRes = {
|
|
205
|
+
attemptedCall: undefined,
|
|
206
|
+
attemptedParams: undefined,
|
|
207
|
+
end: res.end,
|
|
208
|
+
json: res.json,
|
|
209
|
+
send: res.send,
|
|
210
|
+
status: res.status,
|
|
211
|
+
statusSent: false,
|
|
212
|
+
};
|
|
213
|
+
res.end = (...params) => {
|
|
214
|
+
originalRes.attemptedCall = originalRes.end;
|
|
215
|
+
originalRes.attemptedParams = params;
|
|
216
|
+
log.warn(
|
|
217
|
+
"[jaypie] Illegal call to res.end(); prefer Jaypie response conventions",
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
res.json = (...params) => {
|
|
221
|
+
originalRes.attemptedCall = originalRes.json;
|
|
222
|
+
originalRes.attemptedParams = params;
|
|
223
|
+
log.warn(
|
|
224
|
+
"[jaypie] Illegal call to res.json(); prefer Jaypie response conventions",
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
res.send = (...params) => {
|
|
228
|
+
originalRes.attemptedCall = originalRes.send;
|
|
229
|
+
originalRes.attemptedParams = params;
|
|
230
|
+
log.warn(
|
|
231
|
+
"[jaypie] Illegal call to res.send(); prefer Jaypie response conventions",
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
res.status = (...params) => {
|
|
235
|
+
originalRes.statusSent = params;
|
|
236
|
+
return originalRes.status(...params);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
//
|
|
240
|
+
//
|
|
241
|
+
// Preprocess
|
|
242
|
+
//
|
|
243
|
+
|
|
244
|
+
if (locals) {
|
|
245
|
+
// Locals
|
|
246
|
+
const keys = Object.keys(locals);
|
|
247
|
+
if (keys.length > 0) {
|
|
248
|
+
const localsSetup = async () => {
|
|
249
|
+
libLogger.trace("[jaypie] Locals");
|
|
250
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
251
|
+
const key = keys[i];
|
|
252
|
+
if (typeof locals[key] === "function") {
|
|
253
|
+
// eslint-disable-next-line no-await-in-loop
|
|
254
|
+
req.locals[key] = await locals[key](req, res);
|
|
255
|
+
} else {
|
|
256
|
+
req.locals[key] = locals[key];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
setup.push(localsSetup);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!jaypieFunction) {
|
|
265
|
+
// Initialize after logging is set up
|
|
266
|
+
jaypieFunction = core.jaypieHandler(handler, {
|
|
267
|
+
name,
|
|
268
|
+
setup,
|
|
269
|
+
teardown,
|
|
270
|
+
unavailable,
|
|
271
|
+
validate,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let response;
|
|
276
|
+
let status = core.HTTP.CODE.OK;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
libLogger.trace("[jaypie] Lambda execution");
|
|
280
|
+
log.info.var({ req: summarizeRequest(req) });
|
|
281
|
+
|
|
282
|
+
//
|
|
283
|
+
//
|
|
284
|
+
// Process
|
|
285
|
+
//
|
|
286
|
+
|
|
287
|
+
response = await jaypieFunction(req, res, ...params);
|
|
288
|
+
|
|
289
|
+
//
|
|
290
|
+
//
|
|
291
|
+
// Error Handling
|
|
292
|
+
//
|
|
293
|
+
} catch (error) {
|
|
294
|
+
// In theory jaypieFunction has handled all errors
|
|
295
|
+
if (error.status) {
|
|
296
|
+
status = error.status;
|
|
297
|
+
}
|
|
298
|
+
if (typeof error.json === "function") {
|
|
299
|
+
response = error.json();
|
|
300
|
+
} else {
|
|
301
|
+
// This should never happen
|
|
302
|
+
const unhandledError = new core.UnhandledError();
|
|
303
|
+
response = unhandledError.json();
|
|
304
|
+
status = unhandledError.status;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//
|
|
309
|
+
//
|
|
310
|
+
// Postprocess
|
|
311
|
+
//
|
|
312
|
+
|
|
313
|
+
// Restore original res functions
|
|
314
|
+
res.end = originalRes.end;
|
|
315
|
+
res.json = originalRes.json;
|
|
316
|
+
res.send = originalRes.send;
|
|
317
|
+
res.status = originalRes.status;
|
|
318
|
+
|
|
319
|
+
// Decorate response
|
|
320
|
+
decorateResponse(res, { handler: name });
|
|
321
|
+
|
|
322
|
+
// Allow the sent status to override the status in the response
|
|
323
|
+
if (originalRes.statusSent) {
|
|
324
|
+
status = originalRes.statusSent;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Send response
|
|
328
|
+
try {
|
|
329
|
+
if (!originalRes.attemptedCall) {
|
|
330
|
+
// Body
|
|
331
|
+
if (response) {
|
|
332
|
+
if (typeof response === "object") {
|
|
333
|
+
if (typeof response.json === "function") {
|
|
334
|
+
res.json(response.json());
|
|
335
|
+
} else {
|
|
336
|
+
res.status(status).json(response);
|
|
337
|
+
}
|
|
338
|
+
} else if (typeof response === "string") {
|
|
339
|
+
try {
|
|
340
|
+
res.status(status).json(JSON.parse(response));
|
|
341
|
+
} catch (error) {
|
|
342
|
+
res.status(status).send(response);
|
|
343
|
+
}
|
|
344
|
+
} else if (response === true) {
|
|
345
|
+
res.status(core.HTTP.CODE.CREATED).send();
|
|
346
|
+
} else {
|
|
347
|
+
res.status(status).send(response);
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// No response
|
|
351
|
+
res.status(core.HTTP.CODE.NO_CONTENT).send();
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
// Resolve illegal call to res.end(), res.json(), or res.send()
|
|
355
|
+
log.debug("[jaypie] Resolving illegal call to res");
|
|
356
|
+
log.var({
|
|
357
|
+
attemptedCall: {
|
|
358
|
+
name: originalRes.attemptedCall.name,
|
|
359
|
+
params: originalRes.attemptedParams,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
// Call the original function with the original parameters and the original `this` (res)
|
|
363
|
+
originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
log.fatal("Express encountered an error while sending the response");
|
|
367
|
+
log.var({ responseError: error });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Log response
|
|
371
|
+
const extras = {};
|
|
372
|
+
if (response) extras.body = response;
|
|
373
|
+
log.info.var({
|
|
374
|
+
res: summarizeResponse(res, extras),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Clean up the public logger
|
|
378
|
+
core.log.untag("handler");
|
|
379
|
+
|
|
380
|
+
//
|
|
381
|
+
//
|
|
382
|
+
// Return
|
|
383
|
+
//
|
|
7
384
|
|
|
8
|
-
|
|
385
|
+
return response;
|
|
386
|
+
};
|
|
387
|
+
};
|
|
9
388
|
|
|
10
|
-
|
|
389
|
+
exports.expressHandler = expressHandler;
|
package/dist/module.esm.js
CHANGED
|
@@ -1,8 +1,387 @@
|
|
|
1
|
+
import { log, JAYPIE, HTTP, validate, force, jaypieHandler, UnhandledError } from '@jaypie/core';
|
|
2
|
+
import { getCurrentInvoke } from '@codegenie/serverless-express';
|
|
3
|
+
|
|
4
|
+
//
|
|
5
|
+
//
|
|
6
|
+
// Helper Functions
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
// Adapter for the "@codegenie/serverless-express" uuid
|
|
10
|
+
function getServerlessExpressUuid() {
|
|
11
|
+
const currentInvoke = getCurrentInvoke();
|
|
12
|
+
if (
|
|
13
|
+
currentInvoke &&
|
|
14
|
+
currentInvoke.context &&
|
|
15
|
+
currentInvoke.context.awsRequestId
|
|
16
|
+
) {
|
|
17
|
+
return currentInvoke.context.awsRequestId;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//
|
|
23
|
+
//
|
|
24
|
+
// Main
|
|
25
|
+
//
|
|
26
|
+
|
|
27
|
+
const getCurrentInvokeUuid = () => getServerlessExpressUuid();
|
|
28
|
+
|
|
29
|
+
//
|
|
30
|
+
//
|
|
31
|
+
// Main
|
|
32
|
+
//
|
|
33
|
+
|
|
34
|
+
const decorateResponse = (
|
|
35
|
+
res,
|
|
36
|
+
{ handler = "", version = process.env.PROJECT_VERSION } = {},
|
|
37
|
+
) => {
|
|
38
|
+
const log$1 = log.lib({
|
|
39
|
+
lib: JAYPIE.LIB.EXPRESS,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
//
|
|
43
|
+
//
|
|
44
|
+
// Validate
|
|
45
|
+
//
|
|
46
|
+
if (typeof res !== "object" || res === null) {
|
|
47
|
+
log$1.warn("decorateResponse called but response is not an object");
|
|
48
|
+
return; // eslint-disable-line no-useless-return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
//
|
|
53
|
+
//
|
|
54
|
+
// Decorate Headers
|
|
55
|
+
//
|
|
56
|
+
|
|
57
|
+
// X-Powered-By, override "Express" but nothing else
|
|
58
|
+
if (
|
|
59
|
+
!res.get(HTTP.HEADER.POWERED_BY) ||
|
|
60
|
+
res.get(HTTP.HEADER.POWERED_BY) === "Express"
|
|
61
|
+
) {
|
|
62
|
+
res.set(HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// X-Project-Environment
|
|
66
|
+
if (process.env.PROJECT_ENV) {
|
|
67
|
+
res.setHeader(HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// X-Project-Handler
|
|
71
|
+
if (handler) {
|
|
72
|
+
res.setHeader(HTTP.HEADER.PROJECT.HANDLER, handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// X-Project-Invocation
|
|
76
|
+
const currentInvoke = getCurrentInvokeUuid();
|
|
77
|
+
if (currentInvoke) {
|
|
78
|
+
res.setHeader(HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// X-Project-Key
|
|
82
|
+
if (process.env.PROJECT_KEY) {
|
|
83
|
+
res.setHeader(HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// X-Project-Version
|
|
87
|
+
if (version) {
|
|
88
|
+
res.setHeader(HTTP.HEADER.PROJECT.VERSION, version);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//
|
|
92
|
+
//
|
|
93
|
+
// Error Handling
|
|
94
|
+
//
|
|
95
|
+
} catch (error) {
|
|
96
|
+
log$1.warn("decorateResponse caught an internal error");
|
|
97
|
+
log$1.var({ error });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
//
|
|
1
102
|
//
|
|
103
|
+
// Footnotes
|
|
2
104
|
//
|
|
3
|
-
|
|
105
|
+
|
|
106
|
+
// This is a "utility" function but it needs a lot of "context"
|
|
107
|
+
// about the environment's secret parameters, the special adapter,
|
|
108
|
+
// HTTP, etc. There must be a better way to organize this
|
|
109
|
+
|
|
110
|
+
//
|
|
111
|
+
//
|
|
112
|
+
// Function Definition
|
|
113
|
+
//
|
|
114
|
+
|
|
115
|
+
function summarizeRequest(req) {
|
|
116
|
+
// If body is a buffer, convert it to a string
|
|
117
|
+
let { body } = req;
|
|
118
|
+
if (Buffer.isBuffer(body)) {
|
|
119
|
+
body = body.toString();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
baseUrl: req.baseUrl,
|
|
124
|
+
body,
|
|
125
|
+
headers: req.headers,
|
|
126
|
+
method: req.method,
|
|
127
|
+
query: req.query,
|
|
128
|
+
url: req.url,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
4
132
|
//
|
|
133
|
+
//
|
|
134
|
+
// Function Definition
|
|
135
|
+
//
|
|
136
|
+
|
|
137
|
+
function summarizeResponse(res, extras) {
|
|
138
|
+
const response = {
|
|
139
|
+
statusCode: res.statusCode,
|
|
140
|
+
statusMessage: res.statusMessage,
|
|
141
|
+
};
|
|
142
|
+
if (typeof res.getHeaders === "function") {
|
|
143
|
+
response.headers = res.getHeaders();
|
|
144
|
+
}
|
|
145
|
+
if (typeof extras === "object" && extras !== null) {
|
|
146
|
+
Object.assign(response, extras);
|
|
147
|
+
}
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
//
|
|
152
|
+
//
|
|
153
|
+
// Main
|
|
154
|
+
//
|
|
155
|
+
|
|
156
|
+
const expressHandler = (
|
|
157
|
+
handler,
|
|
158
|
+
{ locals, name, setup = [], teardown = [], unavailable, validate: validate$1 } = {},
|
|
159
|
+
) => {
|
|
160
|
+
//
|
|
161
|
+
//
|
|
162
|
+
// Validate
|
|
163
|
+
//
|
|
164
|
+
validate.function(handler);
|
|
165
|
+
validate.optional.object(locals);
|
|
166
|
+
setup = force.array(setup); // allows a single item
|
|
167
|
+
teardown = force.array(teardown); // allows a single item
|
|
168
|
+
|
|
169
|
+
//
|
|
170
|
+
//
|
|
171
|
+
// Setup
|
|
172
|
+
//
|
|
173
|
+
|
|
174
|
+
let jaypieFunction;
|
|
175
|
+
|
|
176
|
+
return async (req, res, ...params) => {
|
|
177
|
+
// * This is the first line of code that runs when a request is received
|
|
178
|
+
|
|
179
|
+
// Update the public logger with the request ID
|
|
180
|
+
log.tag({ invoke: getCurrentInvokeUuid() });
|
|
181
|
+
|
|
182
|
+
// Very low-level, internal sub-trace details
|
|
183
|
+
const libLogger = log.lib({
|
|
184
|
+
lib: JAYPIE.LIB.EXPRESS,
|
|
185
|
+
});
|
|
186
|
+
libLogger.trace("[jaypie] Express init");
|
|
187
|
+
|
|
188
|
+
// Top-level, important details that run at the same level as the main logger
|
|
189
|
+
const log$1 = log.lib({
|
|
190
|
+
level: log.level,
|
|
191
|
+
lib: JAYPIE.LIB.EXPRESS,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Set req.locals if it doesn't exist
|
|
195
|
+
if (!req.locals) req.locals = {};
|
|
196
|
+
if (!req.locals._jaypie) req.locals._jaypie = {};
|
|
197
|
+
|
|
198
|
+
// Set res.locals if it doesn't exist
|
|
199
|
+
if (!res.locals) res.locals = {};
|
|
200
|
+
if (!res.locals._jaypie) res.locals._jaypie = {};
|
|
201
|
+
|
|
202
|
+
const originalRes = {
|
|
203
|
+
attemptedCall: undefined,
|
|
204
|
+
attemptedParams: undefined,
|
|
205
|
+
end: res.end,
|
|
206
|
+
json: res.json,
|
|
207
|
+
send: res.send,
|
|
208
|
+
status: res.status,
|
|
209
|
+
statusSent: false,
|
|
210
|
+
};
|
|
211
|
+
res.end = (...params) => {
|
|
212
|
+
originalRes.attemptedCall = originalRes.end;
|
|
213
|
+
originalRes.attemptedParams = params;
|
|
214
|
+
log$1.warn(
|
|
215
|
+
"[jaypie] Illegal call to res.end(); prefer Jaypie response conventions",
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
res.json = (...params) => {
|
|
219
|
+
originalRes.attemptedCall = originalRes.json;
|
|
220
|
+
originalRes.attemptedParams = params;
|
|
221
|
+
log$1.warn(
|
|
222
|
+
"[jaypie] Illegal call to res.json(); prefer Jaypie response conventions",
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
res.send = (...params) => {
|
|
226
|
+
originalRes.attemptedCall = originalRes.send;
|
|
227
|
+
originalRes.attemptedParams = params;
|
|
228
|
+
log$1.warn(
|
|
229
|
+
"[jaypie] Illegal call to res.send(); prefer Jaypie response conventions",
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
res.status = (...params) => {
|
|
233
|
+
originalRes.statusSent = params;
|
|
234
|
+
return originalRes.status(...params);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
//
|
|
238
|
+
//
|
|
239
|
+
// Preprocess
|
|
240
|
+
//
|
|
241
|
+
|
|
242
|
+
if (locals) {
|
|
243
|
+
// Locals
|
|
244
|
+
const keys = Object.keys(locals);
|
|
245
|
+
if (keys.length > 0) {
|
|
246
|
+
const localsSetup = async () => {
|
|
247
|
+
libLogger.trace("[jaypie] Locals");
|
|
248
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
249
|
+
const key = keys[i];
|
|
250
|
+
if (typeof locals[key] === "function") {
|
|
251
|
+
// eslint-disable-next-line no-await-in-loop
|
|
252
|
+
req.locals[key] = await locals[key](req, res);
|
|
253
|
+
} else {
|
|
254
|
+
req.locals[key] = locals[key];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
setup.push(localsSetup);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!jaypieFunction) {
|
|
263
|
+
// Initialize after logging is set up
|
|
264
|
+
jaypieFunction = jaypieHandler(handler, {
|
|
265
|
+
name,
|
|
266
|
+
setup,
|
|
267
|
+
teardown,
|
|
268
|
+
unavailable,
|
|
269
|
+
validate: validate$1,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let response;
|
|
274
|
+
let status = HTTP.CODE.OK;
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
libLogger.trace("[jaypie] Lambda execution");
|
|
278
|
+
log$1.info.var({ req: summarizeRequest(req) });
|
|
279
|
+
|
|
280
|
+
//
|
|
281
|
+
//
|
|
282
|
+
// Process
|
|
283
|
+
//
|
|
284
|
+
|
|
285
|
+
response = await jaypieFunction(req, res, ...params);
|
|
286
|
+
|
|
287
|
+
//
|
|
288
|
+
//
|
|
289
|
+
// Error Handling
|
|
290
|
+
//
|
|
291
|
+
} catch (error) {
|
|
292
|
+
// In theory jaypieFunction has handled all errors
|
|
293
|
+
if (error.status) {
|
|
294
|
+
status = error.status;
|
|
295
|
+
}
|
|
296
|
+
if (typeof error.json === "function") {
|
|
297
|
+
response = error.json();
|
|
298
|
+
} else {
|
|
299
|
+
// This should never happen
|
|
300
|
+
const unhandledError = new UnhandledError();
|
|
301
|
+
response = unhandledError.json();
|
|
302
|
+
status = unhandledError.status;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
//
|
|
307
|
+
//
|
|
308
|
+
// Postprocess
|
|
309
|
+
//
|
|
310
|
+
|
|
311
|
+
// Restore original res functions
|
|
312
|
+
res.end = originalRes.end;
|
|
313
|
+
res.json = originalRes.json;
|
|
314
|
+
res.send = originalRes.send;
|
|
315
|
+
res.status = originalRes.status;
|
|
316
|
+
|
|
317
|
+
// Decorate response
|
|
318
|
+
decorateResponse(res, { handler: name });
|
|
319
|
+
|
|
320
|
+
// Allow the sent status to override the status in the response
|
|
321
|
+
if (originalRes.statusSent) {
|
|
322
|
+
status = originalRes.statusSent;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Send response
|
|
326
|
+
try {
|
|
327
|
+
if (!originalRes.attemptedCall) {
|
|
328
|
+
// Body
|
|
329
|
+
if (response) {
|
|
330
|
+
if (typeof response === "object") {
|
|
331
|
+
if (typeof response.json === "function") {
|
|
332
|
+
res.json(response.json());
|
|
333
|
+
} else {
|
|
334
|
+
res.status(status).json(response);
|
|
335
|
+
}
|
|
336
|
+
} else if (typeof response === "string") {
|
|
337
|
+
try {
|
|
338
|
+
res.status(status).json(JSON.parse(response));
|
|
339
|
+
} catch (error) {
|
|
340
|
+
res.status(status).send(response);
|
|
341
|
+
}
|
|
342
|
+
} else if (response === true) {
|
|
343
|
+
res.status(HTTP.CODE.CREATED).send();
|
|
344
|
+
} else {
|
|
345
|
+
res.status(status).send(response);
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
// No response
|
|
349
|
+
res.status(HTTP.CODE.NO_CONTENT).send();
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// Resolve illegal call to res.end(), res.json(), or res.send()
|
|
353
|
+
log$1.debug("[jaypie] Resolving illegal call to res");
|
|
354
|
+
log$1.var({
|
|
355
|
+
attemptedCall: {
|
|
356
|
+
name: originalRes.attemptedCall.name,
|
|
357
|
+
params: originalRes.attemptedParams,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
// Call the original function with the original parameters and the original `this` (res)
|
|
361
|
+
originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
log$1.fatal("Express encountered an error while sending the response");
|
|
365
|
+
log$1.var({ responseError: error });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Log response
|
|
369
|
+
const extras = {};
|
|
370
|
+
if (response) extras.body = response;
|
|
371
|
+
log$1.info.var({
|
|
372
|
+
res: summarizeResponse(res, extras),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Clean up the public logger
|
|
376
|
+
log.untag("handler");
|
|
377
|
+
|
|
378
|
+
//
|
|
379
|
+
//
|
|
380
|
+
// Return
|
|
381
|
+
//
|
|
5
382
|
|
|
6
|
-
|
|
383
|
+
return response;
|
|
384
|
+
};
|
|
385
|
+
};
|
|
7
386
|
|
|
8
|
-
export {
|
|
387
|
+
export { expressHandler };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaypie/express",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"author": "Finlayson Studio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,8 +29,10 @@
|
|
|
29
29
|
"prepublish": "npm run build",
|
|
30
30
|
"test": "vitest",
|
|
31
31
|
"test:spec:decorateResponse.helper": "vitest run ./src/__tests__/decorateResponse.helper.spec.js",
|
|
32
|
+
"test:spec:echo.handler": "vitest run ./src/__tests__/echo.handler.spec.js",
|
|
32
33
|
"test:spec:expressHandler": "vitest run ./src/__tests__/expressHandler.spec.js",
|
|
33
34
|
"test:spec:getCurrentInvokeUuid.adapter": "vitest run ./src/__tests__/getCurrentInvokeUuid.adapter.spec.js",
|
|
35
|
+
"test:spec:http.handler": "vitest run ./src/__tests__/http.handler.spec.js",
|
|
34
36
|
"test:spec:index": "vitest run ./src/__tests__/index.spec.js",
|
|
35
37
|
"test:spec:summarizeRequest.helper": "vitest run ./src/__tests__/summarizeRequest.helper.spec.js",
|
|
36
38
|
"test:spec:summarizeResponse.helper": "vitest run ./src/__tests__/summarizeResponse.helper.spec.js",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { validate } from "@jaypie/core";
|
|
2
|
+
|
|
3
|
+
import expressHandler from "./expressHandler.js";
|
|
4
|
+
import summarizeRequest from "./summarizeRequest.helper.js";
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
//
|
|
8
|
+
// Main
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
const echo = (context = {}) => {
|
|
12
|
+
validate.object(context);
|
|
13
|
+
// Give a default name if there isn't one
|
|
14
|
+
if (!context.name) {
|
|
15
|
+
context.name = "_echo";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Return a function that will be used as an express route
|
|
19
|
+
return expressHandler(async (req) => {
|
|
20
|
+
console.log("req.body :>> ", req.body);
|
|
21
|
+
return {
|
|
22
|
+
req: summarizeRequest(req),
|
|
23
|
+
};
|
|
24
|
+
}, context);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//
|
|
28
|
+
//
|
|
29
|
+
// Export
|
|
30
|
+
//
|
|
31
|
+
|
|
32
|
+
export default echo;
|
package/src/expressHandler.js
CHANGED
|
@@ -95,7 +95,7 @@ const expressHandler = (
|
|
|
95
95
|
);
|
|
96
96
|
};
|
|
97
97
|
res.status = (...params) => {
|
|
98
|
-
originalRes.statusSent =
|
|
98
|
+
originalRes.statusSent = params;
|
|
99
99
|
return originalRes.status(...params);
|
|
100
100
|
};
|
|
101
101
|
|
|
@@ -136,7 +136,7 @@ const expressHandler = (
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
let response;
|
|
139
|
-
let status;
|
|
139
|
+
let status = HTTP.CODE.OK;
|
|
140
140
|
|
|
141
141
|
try {
|
|
142
142
|
libLogger.trace("[jaypie] Lambda execution");
|
|
@@ -182,13 +182,13 @@ const expressHandler = (
|
|
|
182
182
|
// Decorate response
|
|
183
183
|
decorateResponse(res, { handler: name });
|
|
184
184
|
|
|
185
|
+
// Allow the sent status to override the status in the response
|
|
186
|
+
if (originalRes.statusSent) {
|
|
187
|
+
status = originalRes.statusSent;
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
// Send response
|
|
186
191
|
try {
|
|
187
|
-
// Status
|
|
188
|
-
if (status && !originalRes.statusSent) {
|
|
189
|
-
res.status(status);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
192
|
if (!originalRes.attemptedCall) {
|
|
193
193
|
// Body
|
|
194
194
|
if (response) {
|
|
@@ -196,19 +196,18 @@ const expressHandler = (
|
|
|
196
196
|
if (typeof response.json === "function") {
|
|
197
197
|
res.json(response.json());
|
|
198
198
|
} else {
|
|
199
|
-
res.json(response);
|
|
199
|
+
res.status(status).json(response);
|
|
200
200
|
}
|
|
201
201
|
} else if (typeof response === "string") {
|
|
202
202
|
try {
|
|
203
|
-
res.json(JSON.parse(response));
|
|
203
|
+
res.status(status).json(JSON.parse(response));
|
|
204
204
|
} catch (error) {
|
|
205
|
-
res.send(response);
|
|
205
|
+
res.status(status).send(response);
|
|
206
206
|
}
|
|
207
207
|
} else if (response === true) {
|
|
208
|
-
res.status(HTTP.CODE.CREATED);
|
|
209
|
-
res.send();
|
|
208
|
+
res.status(HTTP.CODE.CREATED).send();
|
|
210
209
|
} else {
|
|
211
|
-
res.send(response);
|
|
210
|
+
res.status(status).send(response);
|
|
212
211
|
}
|
|
213
212
|
} else {
|
|
214
213
|
// No response
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadGatewayError,
|
|
3
|
+
BadRequestError,
|
|
4
|
+
ForbiddenError,
|
|
5
|
+
GatewayTimeoutError,
|
|
6
|
+
GoneError,
|
|
7
|
+
HTTP,
|
|
8
|
+
InternalError,
|
|
9
|
+
log,
|
|
10
|
+
MethodNotAllowedError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
TeapotError,
|
|
13
|
+
UnauthorizedError,
|
|
14
|
+
UnavailableError,
|
|
15
|
+
} from "@jaypie/core";
|
|
16
|
+
|
|
17
|
+
import expressHandler from "./expressHandler.js";
|
|
18
|
+
|
|
19
|
+
//
|
|
20
|
+
//
|
|
21
|
+
// Main
|
|
22
|
+
//
|
|
23
|
+
|
|
24
|
+
const http = (statusCode = HTTP.CODE.OK, context = {}) => {
|
|
25
|
+
// Give a default name if there isn't one
|
|
26
|
+
if (!context.name) {
|
|
27
|
+
context.name = "_http";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Return a function that will be used as an express route
|
|
31
|
+
return expressHandler(async (req, res) => {
|
|
32
|
+
// Map the most throwable status codes to errors and throw them!
|
|
33
|
+
const error = {
|
|
34
|
+
[HTTP.CODE.BAD_REQUEST]: BadRequestError,
|
|
35
|
+
[HTTP.CODE.UNAUTHORIZED]: UnauthorizedError,
|
|
36
|
+
[HTTP.CODE.FORBIDDEN]: ForbiddenError,
|
|
37
|
+
[HTTP.CODE.NOT_FOUND]: NotFoundError,
|
|
38
|
+
[HTTP.CODE.METHOD_NOT_ALLOWED]: MethodNotAllowedError,
|
|
39
|
+
[HTTP.CODE.GONE]: GoneError,
|
|
40
|
+
[HTTP.CODE.TEAPOT]: TeapotError,
|
|
41
|
+
[HTTP.CODE.INTERNAL_ERROR]: InternalError,
|
|
42
|
+
[HTTP.CODE.BAD_GATEWAY]: BadGatewayError,
|
|
43
|
+
[HTTP.CODE.UNAVAILABLE]: UnavailableError,
|
|
44
|
+
[HTTP.CODE.GATEWAY_TIMEOUT]: GatewayTimeoutError,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// If this maps to an error, throw it
|
|
48
|
+
if (error[statusCode]) {
|
|
49
|
+
log.trace(
|
|
50
|
+
`@knowdev/express: gracefully throwing ${statusCode} up to projectHandler`,
|
|
51
|
+
);
|
|
52
|
+
throw new error[statusCode]();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If this is an error and we didn't get thrown, log a warning
|
|
56
|
+
if (statusCode >= 400) {
|
|
57
|
+
log.warn(
|
|
58
|
+
`@knowdev/express: status code ${statusCode} not mapped as throwable`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Send the response
|
|
63
|
+
res.status(statusCode);
|
|
64
|
+
return statusCode === HTTP.CODE.NO_CONTENT ? null : {};
|
|
65
|
+
}, context);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
//
|
|
69
|
+
//
|
|
70
|
+
// Export
|
|
71
|
+
//
|
|
72
|
+
|
|
73
|
+
export default http;
|