@jaypie/express 1.2.4-rc2 → 1.2.4-rc20

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,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var node_stream = require('node:stream');
4
+ var node_http = require('node:http');
4
5
  var errors = require('@jaypie/errors');
5
6
  var kit = require('@jaypie/kit');
6
7
  var expressCors = require('cors');
@@ -33,6 +34,15 @@ class LambdaRequest extends node_stream.Readable {
33
34
  this.path = options.url.split("?")[0];
34
35
  this.headers = this.normalizeHeaders(options.headers);
35
36
  this.bodyBuffer = options.body ?? null;
37
+ // Parse query string from URL
38
+ const queryIndex = options.url.indexOf("?");
39
+ if (queryIndex !== -1) {
40
+ const queryString = options.url.slice(queryIndex + 1);
41
+ const params = new URLSearchParams(queryString);
42
+ for (const [key, value] of params) {
43
+ this.query[key] = value;
44
+ }
45
+ }
36
46
  // Store Lambda context
37
47
  this._lambdaContext = options.lambdaContext;
38
48
  this._lambdaEvent = options.lambdaEvent;
@@ -43,6 +53,18 @@ class LambdaRequest extends node_stream.Readable {
43
53
  remoteAddress: options.remoteAddress,
44
54
  };
45
55
  this.connection = this.socket;
56
+ // Schedule body push for next tick to ensure stream is ready
57
+ // This is needed for body parsers that consume the stream
58
+ if (this.bodyBuffer && this.bodyBuffer.length > 0) {
59
+ process.nextTick(() => {
60
+ if (!this.bodyPushed) {
61
+ this.push(this.bodyBuffer);
62
+ this.push(null);
63
+ this.bodyPushed = true;
64
+ this.complete = true;
65
+ }
66
+ });
67
+ }
46
68
  }
47
69
  //
48
70
  // Readable stream implementation
@@ -144,6 +166,11 @@ function createLambdaRequest(event, context) {
144
166
  body = event.isBase64Encoded
145
167
  ? Buffer.from(event.body, "base64")
146
168
  : Buffer.from(event.body, "utf8");
169
+ // Add content-length header if not present (required for body parsers)
170
+ const hasContentLength = Object.keys(headers).some((k) => k.toLowerCase() === "content-length");
171
+ if (!hasContentLength) {
172
+ headers["content-length"] = String(body.length);
173
+ }
147
174
  }
148
175
  return new LambdaRequest({
149
176
  body,
@@ -161,6 +188,10 @@ function createLambdaRequest(event, context) {
161
188
  //
162
189
  // Constants
163
190
  //
191
+ // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
192
+ // This is needed for compatibility with Datadog dd-trace instrumentation,
193
+ // which patches HTTP methods and expects this internal state to exist.
194
+ const kOutHeaders$1 = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
164
195
  const BINARY_CONTENT_TYPE_PATTERNS = [
165
196
  /^application\/octet-stream$/,
166
197
  /^application\/pdf$/,
@@ -187,17 +218,88 @@ class LambdaResponseBuffered extends node_stream.Writable {
187
218
  this.socket = {
188
219
  remoteAddress: "127.0.0.1",
189
220
  };
221
+ // Internal state exposed for direct manipulation by safe response methods
222
+ // that need to bypass dd-trace interception of stream methods
190
223
  this._chunks = [];
224
+ this._ended = false; // Track ended state since writableEnded is lost after prototype change
191
225
  this._headers = new Map();
192
226
  this._headersSent = false;
193
227
  this._resolve = null;
228
+ // Initialize Node's internal kOutHeaders for dd-trace compatibility.
229
+ // dd-trace patches HTTP methods and expects this internal state.
230
+ if (kOutHeaders$1) {
231
+ this[kOutHeaders$1] = Object.create(null);
232
+ }
233
+ // CRITICAL: Define key methods as instance properties to survive Express's
234
+ // setPrototypeOf(res, app.response) in middleware/init.js which would
235
+ // otherwise replace our prototype with ServerResponse.prototype.
236
+ // Instance properties take precedence over prototype properties.
237
+ this.getHeader = this.getHeader.bind(this);
238
+ this.setHeader = this.setHeader.bind(this);
239
+ this.removeHeader = this.removeHeader.bind(this);
240
+ this.hasHeader = this.hasHeader.bind(this);
241
+ this.getHeaders = this.getHeaders.bind(this);
242
+ this.getHeaderNames = this.getHeaderNames.bind(this);
243
+ this.writeHead = this.writeHead.bind(this);
244
+ this.get = this.get.bind(this);
245
+ this.set = this.set.bind(this);
246
+ this.status = this.status.bind(this);
247
+ this.json = this.json.bind(this);
248
+ this.send = this.send.bind(this);
249
+ this.vary = this.vary.bind(this);
250
+ this.end = this.end.bind(this);
251
+ this.write = this.write.bind(this);
252
+ // Also bind internal Writable methods that are called via prototype chain
253
+ this._write = this._write.bind(this);
254
+ this._final = this._final.bind(this);
255
+ // Bind result-building methods
256
+ this.getResult = this.getResult.bind(this);
257
+ this.buildResult = this.buildResult.bind(this);
258
+ this.isBinaryContentType = this.isBinaryContentType.bind(this);
259
+ }
260
+ //
261
+ // Internal bypass methods - completely avoid prototype chain lookup
262
+ // These directly access _headers Map, safe from dd-trace interception
263
+ //
264
+ _internalGetHeader(name) {
265
+ const value = this._headers.get(name.toLowerCase());
266
+ return value ? String(value) : undefined;
267
+ }
268
+ _internalSetHeader(name, value) {
269
+ if (!this._headersSent) {
270
+ const lowerName = name.toLowerCase();
271
+ this._headers.set(lowerName, value);
272
+ // Also sync kOutHeaders for any code that expects it
273
+ if (kOutHeaders$1) {
274
+ const outHeaders = this[kOutHeaders$1];
275
+ if (outHeaders) {
276
+ outHeaders[lowerName] = [name, value];
277
+ }
278
+ }
279
+ }
280
+ }
281
+ _internalHasHeader(name) {
282
+ return this._headers.has(name.toLowerCase());
283
+ }
284
+ _internalRemoveHeader(name) {
285
+ if (!this._headersSent) {
286
+ const lowerName = name.toLowerCase();
287
+ this._headers.delete(lowerName);
288
+ if (kOutHeaders$1) {
289
+ const outHeaders = this[kOutHeaders$1];
290
+ if (outHeaders) {
291
+ delete outHeaders[lowerName];
292
+ }
293
+ }
294
+ }
194
295
  }
195
296
  //
196
297
  // Promise-based API for getting final result
197
298
  //
198
299
  getResult() {
199
300
  return new Promise((resolve) => {
200
- if (this.writableEnded) {
301
+ // Use _ended instead of writableEnded since Express's setPrototypeOf breaks the getter
302
+ if (this._ended) {
201
303
  resolve(this.buildResult());
202
304
  }
203
305
  else {
@@ -213,14 +315,31 @@ class LambdaResponseBuffered extends node_stream.Writable {
213
315
  // In production, log warning but don't throw to match Express behavior
214
316
  return this;
215
317
  }
216
- this._headers.set(name.toLowerCase(), String(value));
318
+ const lowerName = name.toLowerCase();
319
+ this._headers.set(lowerName, String(value));
320
+ // Sync with kOutHeaders for dd-trace compatibility
321
+ // Node stores as { 'header-name': ['Header-Name', value] }
322
+ if (kOutHeaders$1) {
323
+ const outHeaders = this[kOutHeaders$1];
324
+ if (outHeaders) {
325
+ outHeaders[lowerName] = [name, String(value)];
326
+ }
327
+ }
217
328
  return this;
218
329
  }
219
330
  getHeader(name) {
220
331
  return this._headers.get(name.toLowerCase());
221
332
  }
222
333
  removeHeader(name) {
223
- this._headers.delete(name.toLowerCase());
334
+ const lowerName = name.toLowerCase();
335
+ this._headers.delete(lowerName);
336
+ // Sync with kOutHeaders for dd-trace compatibility
337
+ if (kOutHeaders$1) {
338
+ const outHeaders = this[kOutHeaders$1];
339
+ if (outHeaders) {
340
+ delete outHeaders[lowerName];
341
+ }
342
+ }
224
343
  }
225
344
  getHeaders() {
226
345
  const headers = {};
@@ -235,6 +354,47 @@ class LambdaResponseBuffered extends node_stream.Writable {
235
354
  getHeaderNames() {
236
355
  return Array.from(this._headers.keys());
237
356
  }
357
+ /**
358
+ * Proxy for direct header access (e.g., res.headers['content-type']).
359
+ * Required for compatibility with middleware like helmet that access headers directly.
360
+ * Uses direct _headers access to bypass dd-trace interception.
361
+ */
362
+ get headers() {
363
+ return new Proxy({}, {
364
+ deleteProperty: (_target, prop) => {
365
+ this._headers.delete(String(prop).toLowerCase());
366
+ return true;
367
+ },
368
+ get: (_target, prop) => {
369
+ if (typeof prop === "symbol")
370
+ return undefined;
371
+ return this._headers.get(String(prop).toLowerCase());
372
+ },
373
+ getOwnPropertyDescriptor: (_target, prop) => {
374
+ const lowerProp = String(prop).toLowerCase();
375
+ if (this._headers.has(lowerProp)) {
376
+ return {
377
+ configurable: true,
378
+ enumerable: true,
379
+ value: this._headers.get(lowerProp),
380
+ };
381
+ }
382
+ return undefined;
383
+ },
384
+ has: (_target, prop) => {
385
+ return this._headers.has(String(prop).toLowerCase());
386
+ },
387
+ ownKeys: () => {
388
+ return Array.from(this._headers.keys());
389
+ },
390
+ set: (_target, prop, value) => {
391
+ if (!this._headersSent) {
392
+ this._headers.set(String(prop).toLowerCase(), value);
393
+ }
394
+ return true;
395
+ },
396
+ });
397
+ }
238
398
  writeHead(statusCode, statusMessageOrHeaders, headers) {
239
399
  this.statusCode = statusCode;
240
400
  let headersToSet;
@@ -247,9 +407,10 @@ class LambdaResponseBuffered extends node_stream.Writable {
247
407
  headersToSet = statusMessageOrHeaders;
248
408
  }
249
409
  if (headersToSet) {
410
+ // Use direct _headers access to bypass dd-trace interception
250
411
  for (const [key, value] of Object.entries(headersToSet)) {
251
412
  if (value !== undefined) {
252
- this.setHeader(key, value);
413
+ this._headers.set(key.toLowerCase(), String(value));
253
414
  }
254
415
  }
255
416
  }
@@ -262,12 +423,32 @@ class LambdaResponseBuffered extends node_stream.Writable {
262
423
  //
263
424
  // Express compatibility methods
264
425
  //
426
+ /**
427
+ * Express-style alias for getHeader().
428
+ * Used by middleware like decorateResponse that use res.get().
429
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
430
+ */
431
+ get(name) {
432
+ return this._headers.get(name.toLowerCase());
433
+ }
434
+ /**
435
+ * Express-style alias for setHeader().
436
+ * Used by middleware like decorateResponse that use res.set().
437
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
438
+ */
439
+ set(name, value) {
440
+ if (!this._headersSent) {
441
+ this._headers.set(name.toLowerCase(), String(value));
442
+ }
443
+ return this;
444
+ }
265
445
  status(code) {
266
446
  this.statusCode = code;
267
447
  return this;
268
448
  }
269
449
  json(data) {
270
- this.setHeader("content-type", "application/json");
450
+ // Use direct _headers access to bypass dd-trace interception
451
+ this._headers.set("content-type", "application/json");
271
452
  this.end(JSON.stringify(data));
272
453
  return this;
273
454
  }
@@ -278,6 +459,27 @@ class LambdaResponseBuffered extends node_stream.Writable {
278
459
  this.end(body);
279
460
  return this;
280
461
  }
462
+ /**
463
+ * Add a field to the Vary response header.
464
+ * Used by CORS middleware to indicate response varies by Origin.
465
+ * Uses direct _headers access to bypass dd-trace interception.
466
+ */
467
+ vary(field) {
468
+ const existing = this._headers.get("vary");
469
+ if (!existing) {
470
+ this._headers.set("vary", field);
471
+ }
472
+ else {
473
+ // Append to existing Vary header if field not already present
474
+ const fields = String(existing)
475
+ .split(",")
476
+ .map((f) => f.trim().toLowerCase());
477
+ if (!fields.includes(field.toLowerCase())) {
478
+ this._headers.set("vary", `${existing}, ${field}`);
479
+ }
480
+ }
481
+ return this;
482
+ }
281
483
  //
282
484
  // Writable stream implementation
283
485
  //
@@ -291,6 +493,7 @@ class LambdaResponseBuffered extends node_stream.Writable {
291
493
  callback();
292
494
  }
293
495
  _final(callback) {
496
+ this._ended = true;
294
497
  if (this._resolve) {
295
498
  this._resolve(this.buildResult());
296
499
  }
@@ -301,7 +504,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
301
504
  //
302
505
  buildResult() {
303
506
  const body = Buffer.concat(this._chunks);
304
- const contentType = this.getHeader("content-type") || "";
507
+ // Use direct _headers access to bypass dd-trace interception
508
+ const contentType = this._headers.get("content-type") || "";
305
509
  // Determine if response should be base64 encoded
306
510
  const isBase64Encoded = this.isBinaryContentType(contentType);
307
511
  // Build headers object
@@ -338,6 +542,14 @@ class LambdaResponseBuffered extends node_stream.Writable {
338
542
  }
339
543
  }
340
544
 
545
+ //
546
+ //
547
+ // Constants
548
+ //
549
+ // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
550
+ // This is needed for compatibility with Datadog dd-trace instrumentation,
551
+ // which patches HTTP methods and expects this internal state to exist.
552
+ const kOutHeaders = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
341
553
  //
342
554
  //
343
555
  // LambdaResponseStreaming Class
@@ -355,11 +567,77 @@ class LambdaResponseStreaming extends node_stream.Writable {
355
567
  this.socket = {
356
568
  remoteAddress: "127.0.0.1",
357
569
  };
570
+ // Internal state exposed for direct manipulation by safe response methods
571
+ // that need to bypass dd-trace interception
358
572
  this._headers = new Map();
359
573
  this._headersSent = false;
360
574
  this._pendingWrites = [];
361
575
  this._wrappedStream = null;
362
576
  this._responseStream = responseStream;
577
+ // Initialize Node's internal kOutHeaders for dd-trace compatibility.
578
+ // dd-trace patches HTTP methods and expects this internal state.
579
+ if (kOutHeaders) {
580
+ this[kOutHeaders] = Object.create(null);
581
+ }
582
+ // CRITICAL: Define key methods as instance properties to survive Express's
583
+ // setPrototypeOf(res, app.response) in middleware/init.js which would
584
+ // otherwise replace our prototype with ServerResponse.prototype.
585
+ // Instance properties take precedence over prototype properties.
586
+ this.getHeader = this.getHeader.bind(this);
587
+ this.setHeader = this.setHeader.bind(this);
588
+ this.removeHeader = this.removeHeader.bind(this);
589
+ this.hasHeader = this.hasHeader.bind(this);
590
+ this.getHeaders = this.getHeaders.bind(this);
591
+ this.getHeaderNames = this.getHeaderNames.bind(this);
592
+ this.writeHead = this.writeHead.bind(this);
593
+ this.flushHeaders = this.flushHeaders.bind(this);
594
+ this.get = this.get.bind(this);
595
+ this.set = this.set.bind(this);
596
+ this.status = this.status.bind(this);
597
+ this.json = this.json.bind(this);
598
+ this.send = this.send.bind(this);
599
+ this.vary = this.vary.bind(this);
600
+ this.end = this.end.bind(this);
601
+ this.write = this.write.bind(this);
602
+ // Also bind internal Writable methods that are called via prototype chain
603
+ this._write = this._write.bind(this);
604
+ this._final = this._final.bind(this);
605
+ }
606
+ //
607
+ // Internal bypass methods - completely avoid prototype chain lookup
608
+ // These directly access _headers Map, safe from dd-trace interception
609
+ //
610
+ _internalGetHeader(name) {
611
+ const value = this._headers.get(name.toLowerCase());
612
+ return value ? String(value) : undefined;
613
+ }
614
+ _internalSetHeader(name, value) {
615
+ if (!this._headersSent) {
616
+ const lowerName = name.toLowerCase();
617
+ this._headers.set(lowerName, value);
618
+ // Also sync kOutHeaders for any code that expects it
619
+ if (kOutHeaders) {
620
+ const outHeaders = this[kOutHeaders];
621
+ if (outHeaders) {
622
+ outHeaders[lowerName] = [name, value];
623
+ }
624
+ }
625
+ }
626
+ }
627
+ _internalHasHeader(name) {
628
+ return this._headers.has(name.toLowerCase());
629
+ }
630
+ _internalRemoveHeader(name) {
631
+ if (!this._headersSent) {
632
+ const lowerName = name.toLowerCase();
633
+ this._headers.delete(lowerName);
634
+ if (kOutHeaders) {
635
+ const outHeaders = this[kOutHeaders];
636
+ if (outHeaders) {
637
+ delete outHeaders[lowerName];
638
+ }
639
+ }
640
+ }
363
641
  }
364
642
  //
365
643
  // Header management
@@ -370,7 +648,16 @@ class LambdaResponseStreaming extends node_stream.Writable {
370
648
  // Headers cannot be changed after body starts
371
649
  return this;
372
650
  }
373
- this._headers.set(name.toLowerCase(), String(value));
651
+ const lowerName = name.toLowerCase();
652
+ this._headers.set(lowerName, String(value));
653
+ // Sync with kOutHeaders for dd-trace compatibility
654
+ // Node stores as { 'header-name': ['Header-Name', value] }
655
+ if (kOutHeaders) {
656
+ const outHeaders = this[kOutHeaders];
657
+ if (outHeaders) {
658
+ outHeaders[lowerName] = [name, String(value)];
659
+ }
660
+ }
374
661
  return this;
375
662
  }
376
663
  getHeader(name) {
@@ -378,7 +665,15 @@ class LambdaResponseStreaming extends node_stream.Writable {
378
665
  }
379
666
  removeHeader(name) {
380
667
  if (!this._headersSent) {
381
- this._headers.delete(name.toLowerCase());
668
+ const lowerName = name.toLowerCase();
669
+ this._headers.delete(lowerName);
670
+ // Sync with kOutHeaders for dd-trace compatibility
671
+ if (kOutHeaders) {
672
+ const outHeaders = this[kOutHeaders];
673
+ if (outHeaders) {
674
+ delete outHeaders[lowerName];
675
+ }
676
+ }
382
677
  }
383
678
  }
384
679
  getHeaders() {
@@ -394,6 +689,49 @@ class LambdaResponseStreaming extends node_stream.Writable {
394
689
  getHeaderNames() {
395
690
  return Array.from(this._headers.keys());
396
691
  }
692
+ /**
693
+ * Proxy for direct header access (e.g., res.headers['content-type']).
694
+ * Required for compatibility with middleware like helmet that access headers directly.
695
+ * Uses direct _headers access to bypass dd-trace interception.
696
+ */
697
+ get headers() {
698
+ return new Proxy({}, {
699
+ deleteProperty: (_target, prop) => {
700
+ if (!this._headersSent) {
701
+ this._headers.delete(String(prop).toLowerCase());
702
+ }
703
+ return true;
704
+ },
705
+ get: (_target, prop) => {
706
+ if (typeof prop === "symbol")
707
+ return undefined;
708
+ return this._headers.get(String(prop).toLowerCase());
709
+ },
710
+ getOwnPropertyDescriptor: (_target, prop) => {
711
+ const lowerProp = String(prop).toLowerCase();
712
+ if (this._headers.has(lowerProp)) {
713
+ return {
714
+ configurable: true,
715
+ enumerable: true,
716
+ value: this._headers.get(lowerProp),
717
+ };
718
+ }
719
+ return undefined;
720
+ },
721
+ has: (_target, prop) => {
722
+ return this._headers.has(String(prop).toLowerCase());
723
+ },
724
+ ownKeys: () => {
725
+ return Array.from(this._headers.keys());
726
+ },
727
+ set: (_target, prop, value) => {
728
+ if (!this._headersSent) {
729
+ this._headers.set(String(prop).toLowerCase(), value);
730
+ }
731
+ return true;
732
+ },
733
+ });
734
+ }
397
735
  writeHead(statusCode, statusMessageOrHeaders, headers) {
398
736
  if (this._headersSent) {
399
737
  return this;
@@ -409,9 +747,10 @@ class LambdaResponseStreaming extends node_stream.Writable {
409
747
  headersToSet = statusMessageOrHeaders;
410
748
  }
411
749
  if (headersToSet) {
750
+ // Use direct _headers access to bypass dd-trace interception
412
751
  for (const [key, value] of Object.entries(headersToSet)) {
413
752
  if (value !== undefined) {
414
- this.setHeader(key, value);
753
+ this._headers.set(key.toLowerCase(), String(value));
415
754
  }
416
755
  }
417
756
  }
@@ -445,12 +784,32 @@ class LambdaResponseStreaming extends node_stream.Writable {
445
784
  //
446
785
  // Express compatibility methods
447
786
  //
787
+ /**
788
+ * Express-style alias for getHeader().
789
+ * Used by middleware like decorateResponse that use res.get().
790
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
791
+ */
792
+ get(name) {
793
+ return this._headers.get(name.toLowerCase());
794
+ }
795
+ /**
796
+ * Express-style alias for setHeader().
797
+ * Used by middleware like decorateResponse that use res.set().
798
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
799
+ */
800
+ set(name, value) {
801
+ if (!this._headersSent) {
802
+ this._headers.set(name.toLowerCase(), String(value));
803
+ }
804
+ return this;
805
+ }
448
806
  status(code) {
449
807
  this.statusCode = code;
450
808
  return this;
451
809
  }
452
810
  json(data) {
453
- this.setHeader("content-type", "application/json");
811
+ // Use direct _headers access to bypass dd-trace interception
812
+ this._headers.set("content-type", "application/json");
454
813
  this.end(JSON.stringify(data));
455
814
  return this;
456
815
  }
@@ -461,6 +820,27 @@ class LambdaResponseStreaming extends node_stream.Writable {
461
820
  this.end(body);
462
821
  return this;
463
822
  }
823
+ /**
824
+ * Add a field to the Vary response header.
825
+ * Used by CORS middleware to indicate response varies by Origin.
826
+ * Uses direct _headers access to bypass dd-trace interception.
827
+ */
828
+ vary(field) {
829
+ const existing = this._headers.get("vary");
830
+ if (!existing) {
831
+ this._headers.set("vary", field);
832
+ }
833
+ else {
834
+ // Append to existing Vary header if field not already present
835
+ const fields = String(existing)
836
+ .split(",")
837
+ .map((f) => f.trim().toLowerCase());
838
+ if (!fields.includes(field.toLowerCase())) {
839
+ this._headers.set("vary", `${existing}, ${field}`);
840
+ }
841
+ }
842
+ return this;
843
+ }
464
844
  //
465
845
  // Writable stream implementation
466
846
  //
@@ -554,6 +934,7 @@ function runExpressApp(app, req, res) {
554
934
  */
555
935
  function createLambdaHandler(app, _options) {
556
936
  return async (event, context) => {
937
+ let result;
557
938
  try {
558
939
  // Set current invoke for getCurrentInvokeUuid
559
940
  setCurrentInvoke(event, context);
@@ -563,8 +944,38 @@ function createLambdaHandler(app, _options) {
563
944
  const res = new LambdaResponseBuffered();
564
945
  // Run Express app
565
946
  await runExpressApp(app, req, res);
566
- // Return Lambda response
567
- return res.getResult();
947
+ // Get Lambda response - await explicitly to ensure we have the result
948
+ result = await res.getResult();
949
+ // Debug: Log the response before returning
950
+ console.log("[createLambdaHandler] Returning response:", JSON.stringify({
951
+ statusCode: result.statusCode,
952
+ headers: result.headers,
953
+ bodyLength: result.body?.length,
954
+ isBase64Encoded: result.isBase64Encoded,
955
+ }));
956
+ return result;
957
+ }
958
+ catch (error) {
959
+ // Log any unhandled errors
960
+ console.error("[createLambdaHandler] Unhandled error:", error);
961
+ if (error instanceof Error) {
962
+ console.error("[createLambdaHandler] Stack:", error.stack);
963
+ }
964
+ // Return a proper error response instead of throwing
965
+ return {
966
+ statusCode: 500,
967
+ headers: { "content-type": "application/json" },
968
+ body: JSON.stringify({
969
+ errors: [
970
+ {
971
+ status: 500,
972
+ title: "Internal Server Error",
973
+ detail: error instanceof Error ? error.message : "Unknown error occurred",
974
+ },
975
+ ],
976
+ }),
977
+ isBase64Encoded: false,
978
+ };
568
979
  }
569
980
  finally {
570
981
  // Clear current invoke context
@@ -917,6 +1328,73 @@ function getCurrentInvokeUuid(req) {
917
1328
  return getWebAdapterUuid();
918
1329
  }
919
1330
 
1331
+ //
1332
+ //
1333
+ // Helpers
1334
+ //
1335
+ /**
1336
+ * Safely get a header value from response.
1337
+ * Handles both Express Response and Lambda adapter responses.
1338
+ * Defensive against dd-trace instrumentation issues.
1339
+ */
1340
+ function safeGetHeader(res, name) {
1341
+ try {
1342
+ // Try internal method first (completely bypasses dd-trace)
1343
+ if (typeof res._internalGetHeader === "function") {
1344
+ return res._internalGetHeader(name);
1345
+ }
1346
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1347
+ if (res._headers instanceof Map) {
1348
+ const value = res._headers.get(name.toLowerCase());
1349
+ return value ? String(value) : undefined;
1350
+ }
1351
+ // Fall back to getHeader (more standard than get)
1352
+ if (typeof res.getHeader === "function") {
1353
+ const value = res.getHeader(name);
1354
+ return value ? String(value) : undefined;
1355
+ }
1356
+ // Last resort: try get
1357
+ if (typeof res.get === "function") {
1358
+ const value = res.get(name);
1359
+ return value ? String(value) : undefined;
1360
+ }
1361
+ }
1362
+ catch {
1363
+ // Silently fail - caller will handle missing value
1364
+ }
1365
+ return undefined;
1366
+ }
1367
+ /**
1368
+ * Safely set a header value on response.
1369
+ * Handles both Express Response and Lambda adapter responses.
1370
+ * Defensive against dd-trace instrumentation issues.
1371
+ */
1372
+ function safeSetHeader(res, name, value) {
1373
+ try {
1374
+ // Try internal method first (completely bypasses dd-trace)
1375
+ if (typeof res._internalSetHeader === "function") {
1376
+ res._internalSetHeader(name, value);
1377
+ return;
1378
+ }
1379
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1380
+ if (res._headers instanceof Map) {
1381
+ res._headers.set(name.toLowerCase(), value);
1382
+ return;
1383
+ }
1384
+ // Fall back to setHeader (more standard than set)
1385
+ if (typeof res.setHeader === "function") {
1386
+ res.setHeader(name, value);
1387
+ return;
1388
+ }
1389
+ // Last resort: try set
1390
+ if (typeof res.set === "function") {
1391
+ res.set(name, value);
1392
+ }
1393
+ }
1394
+ catch {
1395
+ // Silently fail - header just won't be set
1396
+ }
1397
+ }
920
1398
  //
921
1399
  //
922
1400
  // Main
@@ -933,36 +1411,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
933
1411
  log.warn("decorateResponse called but response is not an object");
934
1412
  return;
935
1413
  }
1414
+ const extRes = res;
936
1415
  try {
937
1416
  //
938
1417
  //
939
1418
  // Decorate Headers
940
1419
  //
941
1420
  // X-Powered-By, override "Express" but nothing else
942
- if (!res.get(kit.HTTP.HEADER.POWERED_BY) ||
943
- res.get(kit.HTTP.HEADER.POWERED_BY) === "Express") {
944
- res.set(kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
1421
+ const currentPoweredBy = safeGetHeader(extRes, kit.HTTP.HEADER.POWERED_BY);
1422
+ if (!currentPoweredBy || currentPoweredBy === "Express") {
1423
+ safeSetHeader(extRes, kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
945
1424
  }
946
1425
  // X-Project-Environment
947
1426
  if (process.env.PROJECT_ENV) {
948
- res.set(kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
1427
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
949
1428
  }
950
1429
  // X-Project-Handler
951
1430
  if (handler) {
952
- res.set(kit.HTTP.HEADER.PROJECT.HANDLER, handler);
1431
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.HANDLER, handler);
953
1432
  }
954
1433
  // X-Project-Invocation
955
1434
  const currentInvoke = getCurrentInvokeUuid();
956
1435
  if (currentInvoke) {
957
- res.set(kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
1436
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
958
1437
  }
959
1438
  // X-Project-Key
960
1439
  if (process.env.PROJECT_KEY) {
961
- res.set(kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
1440
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
962
1441
  }
963
1442
  // X-Project-Version
964
1443
  if (version) {
965
- res.set(kit.HTTP.HEADER.PROJECT.VERSION, version);
1444
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.VERSION, version);
966
1445
  }
967
1446
  //
968
1447
  //
@@ -1022,6 +1501,80 @@ function summarizeResponse(res, extras) {
1022
1501
 
1023
1502
  // Cast logger to extended interface for runtime features not in type definitions
1024
1503
  const logger$1 = logger$2.log;
1504
+ //
1505
+ //
1506
+ // Helpers - Safe response methods to bypass dd-trace interception
1507
+ //
1508
+ /**
1509
+ * Check if response is a Lambda mock response with direct internal access.
1510
+ */
1511
+ function isLambdaMockResponse(res) {
1512
+ const mock = res;
1513
+ return (mock._headers instanceof Map &&
1514
+ Array.isArray(mock._chunks) &&
1515
+ typeof mock.buildResult === "function");
1516
+ }
1517
+ /**
1518
+ * Safely send a JSON response, avoiding dd-trace interception.
1519
+ * For Lambda mock responses, directly manipulates internal state instead of
1520
+ * using stream methods (write/end) which dd-trace intercepts.
1521
+ */
1522
+ function safeSendJson(res, statusCode, data) {
1523
+ if (isLambdaMockResponse(res)) {
1524
+ // Use internal method to set header (completely bypasses dd-trace)
1525
+ if (typeof res._internalSetHeader === "function") {
1526
+ res._internalSetHeader("content-type", "application/json");
1527
+ }
1528
+ else {
1529
+ // Fall back to direct _headers manipulation
1530
+ res._headers.set("content-type", "application/json");
1531
+ }
1532
+ res.statusCode = statusCode;
1533
+ // Directly push to chunks array instead of using stream write/end
1534
+ const chunk = Buffer.from(JSON.stringify(data));
1535
+ res._chunks.push(chunk);
1536
+ res._headersSent = true;
1537
+ // Signal completion if a promise is waiting
1538
+ if (res._resolve) {
1539
+ res._resolve(res.buildResult());
1540
+ }
1541
+ // Emit "finish" event so runExpressApp's promise resolves
1542
+ res.emit("finish");
1543
+ return;
1544
+ }
1545
+ // Fall back to standard Express methods for real responses
1546
+ res.status(statusCode).json(data);
1547
+ }
1548
+ /**
1549
+ * Safely send a response body, avoiding dd-trace interception.
1550
+ * For Lambda mock responses, directly manipulates internal state instead of
1551
+ * using stream methods (write/end) which dd-trace intercepts.
1552
+ */
1553
+ function safeSend(res, statusCode, body) {
1554
+ if (isLambdaMockResponse(res)) {
1555
+ // Direct internal state manipulation - bypasses dd-trace completely
1556
+ res.statusCode = statusCode;
1557
+ if (body !== undefined) {
1558
+ const chunk = Buffer.from(body);
1559
+ res._chunks.push(chunk);
1560
+ }
1561
+ res._headersSent = true;
1562
+ // Signal completion if a promise is waiting
1563
+ if (res._resolve) {
1564
+ res._resolve(res.buildResult());
1565
+ }
1566
+ // Emit "finish" event so runExpressApp's promise resolves
1567
+ res.emit("finish");
1568
+ return;
1569
+ }
1570
+ // Fall back to standard Express methods for real responses
1571
+ if (body !== undefined) {
1572
+ res.status(statusCode).send(body);
1573
+ }
1574
+ else {
1575
+ res.status(statusCode).send();
1576
+ }
1577
+ }
1025
1578
  function expressHandler(handlerOrOptions, optionsOrHandler) {
1026
1579
  /* eslint-enable no-redeclare */
1027
1580
  let handler;
@@ -1238,30 +1791,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1238
1791
  if (typeof response === "object") {
1239
1792
  if (typeof response.json ===
1240
1793
  "function") {
1241
- res.json(response.json());
1794
+ safeSendJson(res, status, response.json());
1242
1795
  }
1243
1796
  else {
1244
- res.status(status).json(response);
1797
+ safeSendJson(res, status, response);
1245
1798
  }
1246
1799
  }
1247
1800
  else if (typeof response === "string") {
1248
1801
  try {
1249
- res.status(status).json(JSON.parse(response));
1802
+ safeSendJson(res, status, JSON.parse(response));
1250
1803
  }
1251
1804
  catch {
1252
- res.status(status).send(response);
1805
+ safeSend(res, status, response);
1253
1806
  }
1254
1807
  }
1255
1808
  else if (response === true) {
1256
- res.status(kit.HTTP.CODE.CREATED).send();
1809
+ safeSend(res, kit.HTTP.CODE.CREATED);
1257
1810
  }
1258
1811
  else {
1259
- res.status(status).send(response);
1812
+ safeSend(res, status, response);
1260
1813
  }
1261
1814
  }
1262
1815
  else {
1263
1816
  // No response
1264
- res.status(kit.HTTP.CODE.NO_CONTENT).send();
1817
+ safeSend(res, kit.HTTP.CODE.NO_CONTENT);
1265
1818
  }
1266
1819
  }
1267
1820
  else {
@@ -1278,7 +1831,11 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1278
1831
  }
1279
1832
  }
1280
1833
  catch (error) {
1281
- log.fatal("Express encountered an error while sending the response");
1834
+ // Use console.error for raw stack trace to ensure it appears in CloudWatch
1835
+ if (error instanceof Error) {
1836
+ console.error("Express response error stack trace:", error.stack);
1837
+ }
1838
+ log.fatal(`Express encountered an error while sending the response: ${error instanceof Error ? error.message : String(error)}`);
1282
1839
  log.var({ responseError: error });
1283
1840
  }
1284
1841
  // Log response