@jaypie/express 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,390 @@
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
- // Export
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 = true;
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;
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
+ // Send response
323
+ try {
324
+ // Status
325
+ if (status && !originalRes.statusSent) {
326
+ res.status(status);
327
+ }
328
+
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.json(response);
337
+ }
338
+ } else if (typeof response === "string") {
339
+ try {
340
+ res.json(JSON.parse(response));
341
+ } catch (error) {
342
+ res.send(response);
343
+ }
344
+ } else if (response === true) {
345
+ res.status(core.HTTP.CODE.CREATED);
346
+ res.send();
347
+ } else {
348
+ res.send(response);
349
+ }
350
+ } else {
351
+ // No response
352
+ res.status(core.HTTP.CODE.NO_CONTENT).send();
353
+ }
354
+ } else {
355
+ // Resolve illegal call to res.end(), res.json(), or res.send()
356
+ log.debug("[jaypie] Resolving illegal call to res");
357
+ log.var({
358
+ attemptedCall: {
359
+ name: originalRes.attemptedCall.name,
360
+ params: originalRes.attemptedParams,
361
+ },
362
+ });
363
+ // Call the original function with the original parameters and the original `this` (res)
364
+ originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
365
+ }
366
+ } catch (error) {
367
+ log.fatal("Express encountered an error while sending the response");
368
+ log.var({ responseError: error });
369
+ }
370
+
371
+ // Log response
372
+ const extras = {};
373
+ if (response) extras.body = response;
374
+ log.info.var({
375
+ res: summarizeResponse(res, extras),
376
+ });
377
+
378
+ // Clean up the public logger
379
+ core.log.untag("handler");
380
+
381
+ //
382
+ //
383
+ // Return
384
+ //
7
385
 
8
- var index = {};
386
+ return response;
387
+ };
388
+ };
9
389
 
10
- module.exports = index;
390
+ exports.expressHandler = expressHandler;
@@ -1,8 +1,388 @@
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
- // Export
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 = true;
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;
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
+ // Send response
321
+ try {
322
+ // Status
323
+ if (status && !originalRes.statusSent) {
324
+ res.status(status);
325
+ }
326
+
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.json(response);
335
+ }
336
+ } else if (typeof response === "string") {
337
+ try {
338
+ res.json(JSON.parse(response));
339
+ } catch (error) {
340
+ res.send(response);
341
+ }
342
+ } else if (response === true) {
343
+ res.status(HTTP.CODE.CREATED);
344
+ res.send();
345
+ } else {
346
+ res.send(response);
347
+ }
348
+ } else {
349
+ // No response
350
+ res.status(HTTP.CODE.NO_CONTENT).send();
351
+ }
352
+ } else {
353
+ // Resolve illegal call to res.end(), res.json(), or res.send()
354
+ log$1.debug("[jaypie] Resolving illegal call to res");
355
+ log$1.var({
356
+ attemptedCall: {
357
+ name: originalRes.attemptedCall.name,
358
+ params: originalRes.attemptedParams,
359
+ },
360
+ });
361
+ // Call the original function with the original parameters and the original `this` (res)
362
+ originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
363
+ }
364
+ } catch (error) {
365
+ log$1.fatal("Express encountered an error while sending the response");
366
+ log$1.var({ responseError: error });
367
+ }
368
+
369
+ // Log response
370
+ const extras = {};
371
+ if (response) extras.body = response;
372
+ log$1.info.var({
373
+ res: summarizeResponse(res, extras),
374
+ });
375
+
376
+ // Clean up the public logger
377
+ log.untag("handler");
378
+
379
+ //
380
+ //
381
+ // Return
382
+ //
5
383
 
6
- var index = {};
384
+ return response;
385
+ };
386
+ };
7
387
 
8
- export { index as default };
388
+ export { expressHandler };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaypie/express",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "author": "Finlayson Studio",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.js CHANGED
@@ -1,6 +1 @@
1
- //
2
- //
3
- // Export
4
- //
5
-
6
- export default {};
1
+ export { default as expressHandler } from "./expressHandler.js";