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