@jaypie/express 1.1.18-rc.1 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +3 -2
  2. package/dist/cjs/__tests__/constants.spec.d.ts +0 -1
  3. package/dist/cjs/__tests__/cors.helper.spec.d.ts +0 -1
  4. package/dist/cjs/__tests__/cors.helper.supertest.spec.d.ts +0 -1
  5. package/dist/cjs/__tests__/decorateResponse.helper.spec.d.ts +0 -1
  6. package/dist/cjs/__tests__/echo.handler.spec.d.ts +0 -1
  7. package/dist/cjs/__tests__/expressHandler.spec.d.ts +0 -1
  8. package/dist/cjs/__tests__/getCurrentInvokeUuid.adapter.spec.d.ts +0 -1
  9. package/dist/cjs/__tests__/http.handler.spec.d.ts +0 -1
  10. package/dist/cjs/__tests__/index.spec.d.ts +0 -1
  11. package/dist/cjs/__tests__/routes.spec.d.ts +0 -1
  12. package/dist/cjs/__tests__/summarizeRequest.helper.spec.d.ts +0 -1
  13. package/dist/cjs/__tests__/summarizeResponse.helper.spec.d.ts +0 -1
  14. package/dist/cjs/__tests__/supertest.spec.d.ts +0 -1
  15. package/dist/esm/__tests__/constants.spec.d.ts +0 -1
  16. package/dist/esm/__tests__/cors.helper.spec.d.ts +0 -1
  17. package/dist/esm/__tests__/cors.helper.supertest.spec.d.ts +0 -1
  18. package/dist/esm/__tests__/decorateResponse.helper.spec.d.ts +0 -1
  19. package/dist/esm/__tests__/echo.handler.spec.d.ts +0 -1
  20. package/dist/esm/__tests__/expressHandler.spec.d.ts +0 -1
  21. package/dist/esm/__tests__/getCurrentInvokeUuid.adapter.spec.d.ts +0 -1
  22. package/dist/esm/__tests__/http.handler.spec.d.ts +0 -1
  23. package/dist/esm/__tests__/index.spec.d.ts +0 -1
  24. package/dist/esm/__tests__/routes.spec.d.ts +0 -1
  25. package/dist/esm/__tests__/summarizeRequest.helper.spec.d.ts +0 -1
  26. package/dist/esm/__tests__/summarizeResponse.helper.spec.d.ts +0 -1
  27. package/dist/esm/__tests__/supertest.spec.d.ts +0 -1
  28. package/dist/module.cjs +0 -718
  29. package/dist/module.esm.js +0 -705
package/dist/module.cjs DELETED
@@ -1,718 +0,0 @@
1
- 'use strict';
2
-
3
- var errors = require('@jaypie/errors');
4
- var core = require('@jaypie/core');
5
- var expressCors = require('cors');
6
- var datadog = require('@jaypie/datadog');
7
- var serverlessExpress = require('@codegenie/serverless-express');
8
-
9
- //
10
- //
11
- // Constants
12
- //
13
-
14
- const EXPRESS = {
15
- PATH: {
16
- ANY: "*",
17
- ID: "/:id",
18
- ROOT: /^\/?$/,
19
- },
20
- };
21
-
22
- //
23
- //
24
- // Constants
25
- //
26
-
27
- const HTTP_PROTOCOL = "http://";
28
- const HTTPS_PROTOCOL = "https://";
29
- const SANDBOX_ENV = "sandbox";
30
-
31
- //
32
- //
33
- // Helper Functions
34
- //
35
-
36
- const ensureProtocol = (url) => {
37
- if (!url) return url;
38
- if (url.startsWith(HTTP_PROTOCOL) || url.startsWith(HTTPS_PROTOCOL))
39
- return url;
40
- return HTTPS_PROTOCOL + url;
41
- };
42
-
43
- const dynamicOriginCallbackHandler = (origin) => {
44
- return (requestOrigin, callback) => {
45
- // Handle wildcard origin
46
- if (origin === "*") {
47
- callback(null, true);
48
- return;
49
- }
50
-
51
- // Allow requests with no origin (like mobile apps, curl, etc)
52
- if (!requestOrigin) {
53
- callback(null, true);
54
- return;
55
- }
56
-
57
- const allowedOrigins = [];
58
- if (process.env.BASE_URL) {
59
- allowedOrigins.push(ensureProtocol(process.env.BASE_URL));
60
- }
61
- if (process.env.PROJECT_BASE_URL) {
62
- allowedOrigins.push(ensureProtocol(process.env.PROJECT_BASE_URL));
63
- }
64
- if (origin) {
65
- const additionalOrigins = core.force.array(origin);
66
- allowedOrigins.push(...additionalOrigins);
67
- }
68
-
69
- // Add localhost origins in sandbox
70
- if (
71
- process.env.PROJECT_ENV === SANDBOX_ENV ||
72
- core.envBoolean("PROJECT_SANDBOX_MODE")
73
- ) {
74
- allowedOrigins.push("http://localhost");
75
- allowedOrigins.push(/^http:\/\/localhost:\d+$/);
76
- }
77
-
78
- const isAllowed = allowedOrigins.some((allowed) => {
79
- if (allowed instanceof RegExp) {
80
- return allowed.test(requestOrigin);
81
- }
82
- return requestOrigin.includes(allowed);
83
- });
84
-
85
- if (isAllowed) {
86
- callback(null, true);
87
- } else {
88
- callback(new errors.CorsError());
89
- }
90
- };
91
- };
92
-
93
- //
94
- //
95
- // Main
96
- //
97
-
98
- const corsHelper = (config = {}) => {
99
- const { origin, overrides = {} } = config;
100
-
101
- const options = {
102
- origin: dynamicOriginCallbackHandler(origin),
103
- // * The default behavior is to allow any headers and methods so they are not included here
104
- ...overrides,
105
- };
106
-
107
- return expressCors(options);
108
- };
109
-
110
- //
111
- //
112
- // Export
113
- //
114
-
115
- var cors_helper = (config) => {
116
- const cors = corsHelper(config);
117
- return (req, res, next) => {
118
- cors(req, res, (error) => {
119
- if (error) {
120
- res.status(error.status);
121
- res.setHeader("Content-Type", "application/json");
122
- return res.json(error.body());
123
- }
124
- next();
125
- });
126
- };
127
- };
128
-
129
- //
130
- //
131
- // Helper Functions
132
- //
133
-
134
- // Adapter for the "@codegenie/serverless-express" uuid
135
- function getServerlessExpressUuid() {
136
- const currentInvoke = serverlessExpress.getCurrentInvoke();
137
- if (
138
- currentInvoke &&
139
- currentInvoke.context &&
140
- currentInvoke.context.awsRequestId
141
- ) {
142
- return currentInvoke.context.awsRequestId;
143
- }
144
- return undefined;
145
- }
146
-
147
- //
148
- //
149
- // Main
150
- //
151
-
152
- const getCurrentInvokeUuid = () => getServerlessExpressUuid();
153
-
154
- //
155
- //
156
- // Main
157
- //
158
-
159
- const decorateResponse = (
160
- res,
161
- { handler = "", version = process.env.PROJECT_VERSION } = {},
162
- ) => {
163
- const log = core.log.lib({
164
- lib: core.JAYPIE.LIB.EXPRESS,
165
- });
166
-
167
- //
168
- //
169
- // Validate
170
- //
171
- if (typeof res !== "object" || res === null) {
172
- log.warn("decorateResponse called but response is not an object");
173
- return;
174
- }
175
-
176
- try {
177
- //
178
- //
179
- // Decorate Headers
180
- //
181
-
182
- // X-Powered-By, override "Express" but nothing else
183
- if (
184
- !res.get(core.HTTP.HEADER.POWERED_BY) ||
185
- res.get(core.HTTP.HEADER.POWERED_BY) === "Express"
186
- ) {
187
- res.set(core.HTTP.HEADER.POWERED_BY, core.JAYPIE.LIB.EXPRESS);
188
- }
189
-
190
- // X-Project-Environment
191
- if (process.env.PROJECT_ENV) {
192
- res.set(core.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
193
- }
194
-
195
- // X-Project-Handler
196
- if (handler) {
197
- res.set(core.HTTP.HEADER.PROJECT.HANDLER, handler);
198
- }
199
-
200
- // X-Project-Invocation
201
- const currentInvoke = getCurrentInvokeUuid();
202
- if (currentInvoke) {
203
- res.set(core.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
204
- }
205
-
206
- // X-Project-Key
207
- if (process.env.PROJECT_KEY) {
208
- res.set(core.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
209
- }
210
-
211
- // X-Project-Version
212
- if (version) {
213
- res.set(core.HTTP.HEADER.PROJECT.VERSION, version);
214
- }
215
-
216
- //
217
- //
218
- // Error Handling
219
- //
220
- } catch (error) {
221
- log.warn("decorateResponse caught an internal error");
222
- log.var({ error });
223
- }
224
- };
225
-
226
- //
227
- //
228
- // Footnotes
229
- //
230
-
231
- // This is a "utility" function but it needs a lot of "context"
232
- // about the environment's secret parameters, the special adapter,
233
- // HTTP, etc. There must be a better way to organize this
234
-
235
- //
236
- //
237
- // Function Definition
238
- //
239
-
240
- function summarizeRequest(req) {
241
- // If body is a buffer, convert it to a string
242
- let { body } = req;
243
- if (Buffer.isBuffer(body)) {
244
- body = body.toString();
245
- }
246
-
247
- return {
248
- baseUrl: req.baseUrl,
249
- body,
250
- headers: req.headers,
251
- method: req.method,
252
- query: req.query,
253
- url: req.url,
254
- };
255
- }
256
-
257
- //
258
- //
259
- // Function Definition
260
- //
261
-
262
- function summarizeResponse(res, extras) {
263
- const response = {
264
- statusCode: res.statusCode,
265
- statusMessage: res.statusMessage,
266
- };
267
- if (typeof res.getHeaders === "function") {
268
- response.headers = res.getHeaders();
269
- }
270
- if (typeof extras === "object" && extras !== null) {
271
- Object.assign(response, extras);
272
- }
273
- return response;
274
- }
275
-
276
- //
277
- //
278
- // Main
279
- //
280
-
281
- const expressHandler = (handler, options = {}) => {
282
- // If handler is an object and options is a function, swap them
283
- if (typeof handler === "object" && typeof options === "function") {
284
- const temp = handler;
285
- handler = options;
286
- options = temp;
287
- }
288
-
289
- //
290
- //
291
- // Validate
292
- //
293
- let {
294
- chaos,
295
- locals,
296
- name,
297
- setup = [],
298
- teardown = [],
299
- unavailable,
300
- validate,
301
- } = options;
302
- core.validate.function(handler);
303
- core.validate.optional.object(locals);
304
- setup = core.force.array(setup); // allows a single item
305
- teardown = core.force.array(teardown); // allows a single item
306
-
307
- //
308
- //
309
- // Setup
310
- //
311
-
312
- let jaypieFunction;
313
-
314
- return async (req, res, ...params) => {
315
- // * This is the first line of code that runs when a request is received
316
-
317
- // Set default chaos value
318
- if (chaos === undefined) {
319
- chaos =
320
- process.env.PROJECT_CHAOS ||
321
- core.getHeaderFrom(core.HTTP.HEADER.PROJECT.CHAOS, req);
322
- }
323
-
324
- // Re-init the logger
325
- core.log.init();
326
- // Very low-level, internal sub-trace details
327
- const libLogger = core.log.lib({
328
- lib: core.JAYPIE.LIB.EXPRESS,
329
- });
330
- // Top-level, important details that run at the same level as the main logger
331
- const log = core.log.lib({
332
- level: core.log.level,
333
- lib: core.JAYPIE.LIB.EXPRESS,
334
- });
335
-
336
- // Update the public logger with the request ID
337
- const invokeUuid = getCurrentInvokeUuid();
338
- if (invokeUuid) {
339
- core.log.tag({ invoke: invokeUuid });
340
- core.log.tag({ shortInvoke: invokeUuid.slice(0, 8) });
341
- // TODO: in theory this is redundant
342
- libLogger.tag({ invoke: invokeUuid });
343
- libLogger.tag({ shortInvoke: invokeUuid.slice(0, 8) });
344
- log.tag({ invoke: invokeUuid });
345
- log.tag({ shortInvoke: invokeUuid.slice(0, 8) });
346
- }
347
-
348
- if (!name) {
349
- // If handler has a name, use it
350
- if (handler.name) {
351
- name = handler.name;
352
- } else {
353
- name = core.JAYPIE.UNKNOWN;
354
- }
355
- }
356
- core.log.tag({ handler: name });
357
- // TODO: in theory this is redundant
358
- libLogger.tag({ handler: name });
359
- log.tag({ handler: name });
360
-
361
- libLogger.trace("[jaypie] Express init");
362
-
363
- // Set req.locals if it doesn't exist
364
- if (!req.locals) req.locals = {};
365
- if (!req.locals._jaypie) req.locals._jaypie = {};
366
-
367
- // Set res.locals if it doesn't exist
368
- if (!res.locals) res.locals = {};
369
- if (!res.locals._jaypie) res.locals._jaypie = {};
370
-
371
- const originalRes = {
372
- attemptedCall: undefined,
373
- attemptedParams: undefined,
374
- end: res.end,
375
- json: res.json,
376
- send: res.send,
377
- status: res.status,
378
- statusSent: false,
379
- };
380
-
381
- res.end = (...params) => {
382
- originalRes.attemptedCall = originalRes.end;
383
- originalRes.attemptedParams = params;
384
- log.warn(
385
- "[jaypie] Illegal call to res.end(); prefer Jaypie response conventions",
386
- );
387
- };
388
-
389
- res.json = (...params) => {
390
- originalRes.attemptedCall = originalRes.json;
391
- originalRes.attemptedParams = params;
392
- log.warn(
393
- "[jaypie] Illegal call to res.json(); prefer Jaypie response conventions",
394
- );
395
- };
396
-
397
- res.send = (...params) => {
398
- originalRes.attemptedCall = originalRes.send;
399
- originalRes.attemptedParams = params;
400
- log.warn(
401
- "[jaypie] Illegal call to res.send(); prefer Jaypie response conventions",
402
- );
403
- };
404
-
405
- res.status = (...params) => {
406
- originalRes.statusSent = params[0];
407
- return originalRes.status(...params);
408
- };
409
-
410
- //
411
- //
412
- // Preprocess
413
- //
414
-
415
- if (locals) {
416
- // Locals
417
- const keys = Object.keys(locals);
418
- if (keys.length > 0) {
419
- const localsSetup = async (localsReq, localsRes) => {
420
- for (let i = 0; i < keys.length; i += 1) {
421
- const key = keys[i];
422
- libLogger.trace(`[jaypie] Locals: ${key}`);
423
- if (typeof locals[key] === "function") {
424
- localsReq.locals[key] = await locals[key](localsReq, localsRes);
425
- } else {
426
- localsReq.locals[key] = locals[key];
427
- }
428
- }
429
- };
430
- setup.push(localsSetup);
431
- }
432
- }
433
-
434
- let response;
435
- let status = core.HTTP.CODE.OK;
436
-
437
- try {
438
- log.info.var({ req: summarizeRequest(req) });
439
-
440
- // Initialize after logging is set up
441
- jaypieFunction = core.jaypieHandler(handler, {
442
- chaos,
443
- name,
444
- setup,
445
- teardown,
446
- unavailable,
447
- validate,
448
- });
449
-
450
- libLogger.trace("[jaypie] Express execution");
451
-
452
- //
453
- //
454
- // Process
455
- //
456
-
457
- response = await jaypieFunction(req, res, ...params);
458
-
459
- //
460
- //
461
- // Error Handling
462
- //
463
- } catch (error) {
464
- // In theory jaypieFunction has handled all errors
465
- if (error.status) {
466
- status = error.status;
467
- }
468
- if (typeof error.json === "function") {
469
- response = error.json();
470
- } else {
471
- // This should never happen
472
- const unhandledError = new core.UnhandledError();
473
- response = unhandledError.json();
474
- status = unhandledError.status;
475
- }
476
- }
477
-
478
- //
479
- //
480
- // Postprocess
481
- //
482
-
483
- // Restore original res functions
484
- res.end = originalRes.end;
485
- res.json = originalRes.json;
486
- res.send = originalRes.send;
487
- res.status = originalRes.status;
488
-
489
- // Decorate response
490
- decorateResponse(res, { handler: name });
491
-
492
- // Allow the sent status to override the status in the response
493
- if (originalRes.statusSent) {
494
- status = originalRes.statusSent;
495
- }
496
-
497
- // Send response
498
- try {
499
- if (!originalRes.attemptedCall) {
500
- // Body
501
- if (response) {
502
- if (typeof response === "object") {
503
- if (typeof response.json === "function") {
504
- res.json(response.json());
505
- } else {
506
- res.status(status).json(response);
507
- }
508
- } else if (typeof response === "string") {
509
- try {
510
- res.status(status).json(JSON.parse(response));
511
- // eslint-disable-next-line no-unused-vars
512
- } catch (error) {
513
- res.status(status).send(response);
514
- }
515
- } else if (response === true) {
516
- res.status(core.HTTP.CODE.CREATED).send();
517
- } else {
518
- res.status(status).send(response);
519
- }
520
- } else {
521
- // No response
522
- res.status(core.HTTP.CODE.NO_CONTENT).send();
523
- }
524
- } else {
525
- // Resolve illegal call to res.end(), res.json(), or res.send()
526
- log.debug("[jaypie] Resolving illegal call to res");
527
- log.var({
528
- attemptedCall: {
529
- name: originalRes.attemptedCall.name,
530
- params: originalRes.attemptedParams,
531
- },
532
- });
533
- // Call the original function with the original parameters and the original `this` (res)
534
- originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
535
- }
536
- } catch (error) {
537
- log.fatal("Express encountered an error while sending the response");
538
- log.var({ responseError: error });
539
- }
540
-
541
- // Log response
542
- const extras = {};
543
- if (response) extras.body = response;
544
- log.info.var({
545
- res: summarizeResponse(res, extras),
546
- });
547
-
548
- // Submit metric if Datadog is configured
549
- if (datadog.hasDatadogEnv()) {
550
- // Construct path from baseUrl and url
551
- let path = (req.baseUrl || "") + (req.url || "");
552
- // Ensure path starts with /
553
- if (!path.startsWith("/")) {
554
- path = "/" + path;
555
- }
556
- // Remove trailing slash unless it's the root path
557
- if (path.length > 1 && path.endsWith("/")) {
558
- path = path.slice(0, -1);
559
- }
560
-
561
- // Replace UUIDs with :id for better aggregation
562
- // Matches standard UUID v4 format (8-4-4-4-12 hex characters)
563
- path = path.replace(
564
- /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,
565
- ":id",
566
- );
567
-
568
- // Determine metric name based on environment variables
569
- let metricPrefix = "project";
570
- if (process.env.PROJECT_SPONSOR) {
571
- metricPrefix = process.env.PROJECT_SPONSOR;
572
- } else if (process.env.PROJECT_KEY) {
573
- metricPrefix = `project.${process.env.PROJECT_KEY}`;
574
- }
575
-
576
- await datadog.submitMetric({
577
- name: `${metricPrefix}.api.response`,
578
- type: datadog.DATADOG.METRIC.TYPE.COUNT,
579
- value: 1,
580
- tags: {
581
- status: res.statusCode,
582
- path,
583
- },
584
- });
585
- }
586
-
587
- // Clean up the public logger
588
- core.log.untag("handler");
589
-
590
- //
591
- //
592
- // Return
593
- //
594
-
595
- return response;
596
- };
597
- };
598
-
599
- //
600
- //
601
- // Main
602
- //
603
-
604
- const httpHandler = (statusCode = core.HTTP.CODE.OK, context = {}) => {
605
- // Give a default name if there isn't one
606
- if (!context.name) {
607
- context.name = "_http";
608
- }
609
-
610
- // Return a function that will be used as an express route
611
- return expressHandler(async (req, res) => {
612
- // Map the most throwable status codes to errors and throw them!
613
- const error = {
614
- [core.HTTP.CODE.BAD_REQUEST]: core.BadRequestError,
615
- [core.HTTP.CODE.UNAUTHORIZED]: core.UnauthorizedError,
616
- [core.HTTP.CODE.FORBIDDEN]: core.ForbiddenError,
617
- [core.HTTP.CODE.NOT_FOUND]: core.NotFoundError,
618
- [core.HTTP.CODE.METHOD_NOT_ALLOWED]: core.MethodNotAllowedError,
619
- [core.HTTP.CODE.GONE]: core.GoneError,
620
- [core.HTTP.CODE.TEAPOT]: core.TeapotError,
621
- [core.HTTP.CODE.INTERNAL_ERROR]: core.InternalError,
622
- [core.HTTP.CODE.BAD_GATEWAY]: core.BadGatewayError,
623
- [core.HTTP.CODE.UNAVAILABLE]: core.UnavailableError,
624
- [core.HTTP.CODE.GATEWAY_TIMEOUT]: core.GatewayTimeoutError,
625
- };
626
-
627
- // If this maps to an error, throw it
628
- if (error[statusCode]) {
629
- core.log.trace(
630
- `@knowdev/express: gracefully throwing ${statusCode} up to projectHandler`,
631
- );
632
- throw new error[statusCode]();
633
- }
634
-
635
- // If this is an error and we didn't get thrown, log a warning
636
- if (statusCode >= 400) {
637
- core.log.warn(
638
- `@knowdev/express: status code ${statusCode} not mapped as throwable`,
639
- );
640
- }
641
-
642
- // Send the response
643
- res.status(statusCode);
644
- return statusCode === core.HTTP.CODE.NO_CONTENT ? null : {};
645
- }, context);
646
- };
647
-
648
- //
649
- //
650
- // Main
651
- //
652
-
653
- const echoHandler = (context = {}) => {
654
- core.validate.object(context);
655
- // Give a default name if there isn't one
656
- if (!context.name) {
657
- context.name = "_echo";
658
- }
659
-
660
- // Return a function that will be used as an express route
661
- return expressHandler(async (req) => {
662
- return {
663
- req: summarizeRequest(req),
664
- };
665
- }, context);
666
- };
667
-
668
- //
669
- //
670
- // Functions
671
- //
672
-
673
- const routes = {
674
- badRequestRoute: httpHandler(core.HTTP.CODE.BAD_REQUEST, { name: "_badRequest" }),
675
- echoRoute: echoHandler(),
676
- forbiddenRoute: httpHandler(core.HTTP.CODE.FORBIDDEN, { name: "_forbidden" }),
677
- goneRoute: httpHandler(core.HTTP.CODE.GONE, { name: "_gone" }),
678
- methodNotAllowedRoute: httpHandler(core.HTTP.CODE.METHOD_NOT_ALLOWED, {
679
- name: "_methodNotAllowed",
680
- }),
681
- noContentRoute: httpHandler(core.HTTP.CODE.NO_CONTENT, { name: "_noContent" }),
682
- notFoundRoute: httpHandler(core.HTTP.CODE.NOT_FOUND, { name: "_notFound" }),
683
- notImplementedRoute: expressHandler(
684
- () => {
685
- throw new core.NotImplementedError();
686
- },
687
- { name: "_notImplemented" },
688
- ),
689
- };
690
-
691
- //
692
- //
693
- // Export
694
- //
695
-
696
- const {
697
- badRequestRoute,
698
- echoRoute,
699
- forbiddenRoute,
700
- goneRoute,
701
- methodNotAllowedRoute,
702
- noContentRoute,
703
- notFoundRoute,
704
- notImplementedRoute,
705
- } = routes;
706
-
707
- exports.EXPRESS = EXPRESS;
708
- exports.badRequestRoute = badRequestRoute;
709
- exports.cors = cors_helper;
710
- exports.echoRoute = echoRoute;
711
- exports.expressHandler = expressHandler;
712
- exports.expressHttpCodeHandler = httpHandler;
713
- exports.forbiddenRoute = forbiddenRoute;
714
- exports.goneRoute = goneRoute;
715
- exports.methodNotAllowedRoute = methodNotAllowedRoute;
716
- exports.noContentRoute = noContentRoute;
717
- exports.notFoundRoute = notFoundRoute;
718
- exports.notImplementedRoute = notImplementedRoute;