@jaypie/express 1.2.4-rc1 → 1.2.4-rc11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/adapter/LambdaResponseBuffered.d.ts +31 -5
- package/dist/cjs/adapter/LambdaResponseStreaming.d.ts +28 -2
- package/dist/cjs/index.cjs +444 -20
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/adapter/LambdaResponseBuffered.d.ts +31 -5
- package/dist/esm/adapter/LambdaResponseStreaming.d.ts +28 -2
- package/dist/esm/index.js +444 -20
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -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');
|
|
@@ -161,6 +162,10 @@ function createLambdaRequest(event, context) {
|
|
|
161
162
|
//
|
|
162
163
|
// Constants
|
|
163
164
|
//
|
|
165
|
+
// Get Node's internal kOutHeaders symbol from ServerResponse prototype.
|
|
166
|
+
// This is needed for compatibility with Datadog dd-trace instrumentation,
|
|
167
|
+
// which patches HTTP methods and expects this internal state to exist.
|
|
168
|
+
const kOutHeaders$1 = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
|
|
164
169
|
const BINARY_CONTENT_TYPE_PATTERNS = [
|
|
165
170
|
/^application\/octet-stream$/,
|
|
166
171
|
/^application\/pdf$/,
|
|
@@ -187,10 +192,53 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
187
192
|
this.socket = {
|
|
188
193
|
remoteAddress: "127.0.0.1",
|
|
189
194
|
};
|
|
195
|
+
// Internal state exposed for direct manipulation by safe response methods
|
|
196
|
+
// that need to bypass dd-trace interception of stream methods
|
|
190
197
|
this._chunks = [];
|
|
191
198
|
this._headers = new Map();
|
|
192
199
|
this._headersSent = false;
|
|
193
200
|
this._resolve = null;
|
|
201
|
+
// Initialize Node's internal kOutHeaders for dd-trace compatibility.
|
|
202
|
+
// dd-trace patches HTTP methods and expects this internal state.
|
|
203
|
+
if (kOutHeaders$1) {
|
|
204
|
+
this[kOutHeaders$1] = Object.create(null);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//
|
|
208
|
+
// Internal bypass methods - completely avoid prototype chain lookup
|
|
209
|
+
// These directly access _headers Map, safe from dd-trace interception
|
|
210
|
+
//
|
|
211
|
+
_internalGetHeader(name) {
|
|
212
|
+
const value = this._headers.get(name.toLowerCase());
|
|
213
|
+
return value ? String(value) : undefined;
|
|
214
|
+
}
|
|
215
|
+
_internalSetHeader(name, value) {
|
|
216
|
+
if (!this._headersSent) {
|
|
217
|
+
const lowerName = name.toLowerCase();
|
|
218
|
+
this._headers.set(lowerName, value);
|
|
219
|
+
// Also sync kOutHeaders for any code that expects it
|
|
220
|
+
if (kOutHeaders$1) {
|
|
221
|
+
const outHeaders = this[kOutHeaders$1];
|
|
222
|
+
if (outHeaders) {
|
|
223
|
+
outHeaders[lowerName] = [name, value];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
_internalHasHeader(name) {
|
|
229
|
+
return this._headers.has(name.toLowerCase());
|
|
230
|
+
}
|
|
231
|
+
_internalRemoveHeader(name) {
|
|
232
|
+
if (!this._headersSent) {
|
|
233
|
+
const lowerName = name.toLowerCase();
|
|
234
|
+
this._headers.delete(lowerName);
|
|
235
|
+
if (kOutHeaders$1) {
|
|
236
|
+
const outHeaders = this[kOutHeaders$1];
|
|
237
|
+
if (outHeaders) {
|
|
238
|
+
delete outHeaders[lowerName];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
194
242
|
}
|
|
195
243
|
//
|
|
196
244
|
// Promise-based API for getting final result
|
|
@@ -213,14 +261,31 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
213
261
|
// In production, log warning but don't throw to match Express behavior
|
|
214
262
|
return this;
|
|
215
263
|
}
|
|
216
|
-
|
|
264
|
+
const lowerName = name.toLowerCase();
|
|
265
|
+
this._headers.set(lowerName, String(value));
|
|
266
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
267
|
+
// Node stores as { 'header-name': ['Header-Name', value] }
|
|
268
|
+
if (kOutHeaders$1) {
|
|
269
|
+
const outHeaders = this[kOutHeaders$1];
|
|
270
|
+
if (outHeaders) {
|
|
271
|
+
outHeaders[lowerName] = [name, String(value)];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
217
274
|
return this;
|
|
218
275
|
}
|
|
219
276
|
getHeader(name) {
|
|
220
277
|
return this._headers.get(name.toLowerCase());
|
|
221
278
|
}
|
|
222
279
|
removeHeader(name) {
|
|
223
|
-
|
|
280
|
+
const lowerName = name.toLowerCase();
|
|
281
|
+
this._headers.delete(lowerName);
|
|
282
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
283
|
+
if (kOutHeaders$1) {
|
|
284
|
+
const outHeaders = this[kOutHeaders$1];
|
|
285
|
+
if (outHeaders) {
|
|
286
|
+
delete outHeaders[lowerName];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
224
289
|
}
|
|
225
290
|
getHeaders() {
|
|
226
291
|
const headers = {};
|
|
@@ -235,6 +300,43 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
235
300
|
getHeaderNames() {
|
|
236
301
|
return Array.from(this._headers.keys());
|
|
237
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Proxy for direct header access (e.g., res.headers['content-type']).
|
|
305
|
+
* Required for compatibility with middleware like helmet that access headers directly.
|
|
306
|
+
*/
|
|
307
|
+
get headers() {
|
|
308
|
+
return new Proxy({}, {
|
|
309
|
+
deleteProperty: (_target, prop) => {
|
|
310
|
+
this.removeHeader(String(prop));
|
|
311
|
+
return true;
|
|
312
|
+
},
|
|
313
|
+
get: (_target, prop) => {
|
|
314
|
+
if (typeof prop === "symbol")
|
|
315
|
+
return undefined;
|
|
316
|
+
return this.getHeader(String(prop));
|
|
317
|
+
},
|
|
318
|
+
getOwnPropertyDescriptor: (_target, prop) => {
|
|
319
|
+
if (this.hasHeader(String(prop))) {
|
|
320
|
+
return {
|
|
321
|
+
configurable: true,
|
|
322
|
+
enumerable: true,
|
|
323
|
+
value: this.getHeader(String(prop)),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
},
|
|
328
|
+
has: (_target, prop) => {
|
|
329
|
+
return this.hasHeader(String(prop));
|
|
330
|
+
},
|
|
331
|
+
ownKeys: () => {
|
|
332
|
+
return this.getHeaderNames();
|
|
333
|
+
},
|
|
334
|
+
set: (_target, prop, value) => {
|
|
335
|
+
this.setHeader(String(prop), value);
|
|
336
|
+
return true;
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
238
340
|
writeHead(statusCode, statusMessageOrHeaders, headers) {
|
|
239
341
|
this.statusCode = statusCode;
|
|
240
342
|
let headersToSet;
|
|
@@ -262,6 +364,25 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
262
364
|
//
|
|
263
365
|
// Express compatibility methods
|
|
264
366
|
//
|
|
367
|
+
/**
|
|
368
|
+
* Express-style alias for getHeader().
|
|
369
|
+
* Used by middleware like decorateResponse that use res.get().
|
|
370
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
371
|
+
*/
|
|
372
|
+
get(name) {
|
|
373
|
+
return this._headers.get(name.toLowerCase());
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Express-style alias for setHeader().
|
|
377
|
+
* Used by middleware like decorateResponse that use res.set().
|
|
378
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
379
|
+
*/
|
|
380
|
+
set(name, value) {
|
|
381
|
+
if (!this._headersSent) {
|
|
382
|
+
this._headers.set(name.toLowerCase(), String(value));
|
|
383
|
+
}
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
265
386
|
status(code) {
|
|
266
387
|
this.statusCode = code;
|
|
267
388
|
return this;
|
|
@@ -278,6 +399,26 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
278
399
|
this.end(body);
|
|
279
400
|
return this;
|
|
280
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Add a field to the Vary response header.
|
|
404
|
+
* Used by CORS middleware to indicate response varies by Origin.
|
|
405
|
+
*/
|
|
406
|
+
vary(field) {
|
|
407
|
+
const existing = this.getHeader("vary");
|
|
408
|
+
if (!existing) {
|
|
409
|
+
this.setHeader("vary", field);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
// Append to existing Vary header if field not already present
|
|
413
|
+
const fields = String(existing)
|
|
414
|
+
.split(",")
|
|
415
|
+
.map((f) => f.trim().toLowerCase());
|
|
416
|
+
if (!fields.includes(field.toLowerCase())) {
|
|
417
|
+
this.setHeader("vary", `${existing}, ${field}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
281
422
|
//
|
|
282
423
|
// Writable stream implementation
|
|
283
424
|
//
|
|
@@ -301,7 +442,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
301
442
|
//
|
|
302
443
|
buildResult() {
|
|
303
444
|
const body = Buffer.concat(this._chunks);
|
|
304
|
-
|
|
445
|
+
// Use direct _headers access to bypass dd-trace interception
|
|
446
|
+
const contentType = this._headers.get("content-type") || "";
|
|
305
447
|
// Determine if response should be base64 encoded
|
|
306
448
|
const isBase64Encoded = this.isBinaryContentType(contentType);
|
|
307
449
|
// Build headers object
|
|
@@ -338,6 +480,14 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
338
480
|
}
|
|
339
481
|
}
|
|
340
482
|
|
|
483
|
+
//
|
|
484
|
+
//
|
|
485
|
+
// Constants
|
|
486
|
+
//
|
|
487
|
+
// Get Node's internal kOutHeaders symbol from ServerResponse prototype.
|
|
488
|
+
// This is needed for compatibility with Datadog dd-trace instrumentation,
|
|
489
|
+
// which patches HTTP methods and expects this internal state to exist.
|
|
490
|
+
const kOutHeaders = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
|
|
341
491
|
//
|
|
342
492
|
//
|
|
343
493
|
// LambdaResponseStreaming Class
|
|
@@ -355,11 +505,54 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
355
505
|
this.socket = {
|
|
356
506
|
remoteAddress: "127.0.0.1",
|
|
357
507
|
};
|
|
508
|
+
// Internal state exposed for direct manipulation by safe response methods
|
|
509
|
+
// that need to bypass dd-trace interception
|
|
358
510
|
this._headers = new Map();
|
|
359
511
|
this._headersSent = false;
|
|
360
512
|
this._pendingWrites = [];
|
|
361
513
|
this._wrappedStream = null;
|
|
362
514
|
this._responseStream = responseStream;
|
|
515
|
+
// Initialize Node's internal kOutHeaders for dd-trace compatibility.
|
|
516
|
+
// dd-trace patches HTTP methods and expects this internal state.
|
|
517
|
+
if (kOutHeaders) {
|
|
518
|
+
this[kOutHeaders] = Object.create(null);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
//
|
|
522
|
+
// Internal bypass methods - completely avoid prototype chain lookup
|
|
523
|
+
// These directly access _headers Map, safe from dd-trace interception
|
|
524
|
+
//
|
|
525
|
+
_internalGetHeader(name) {
|
|
526
|
+
const value = this._headers.get(name.toLowerCase());
|
|
527
|
+
return value ? String(value) : undefined;
|
|
528
|
+
}
|
|
529
|
+
_internalSetHeader(name, value) {
|
|
530
|
+
if (!this._headersSent) {
|
|
531
|
+
const lowerName = name.toLowerCase();
|
|
532
|
+
this._headers.set(lowerName, value);
|
|
533
|
+
// Also sync kOutHeaders for any code that expects it
|
|
534
|
+
if (kOutHeaders) {
|
|
535
|
+
const outHeaders = this[kOutHeaders];
|
|
536
|
+
if (outHeaders) {
|
|
537
|
+
outHeaders[lowerName] = [name, value];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
_internalHasHeader(name) {
|
|
543
|
+
return this._headers.has(name.toLowerCase());
|
|
544
|
+
}
|
|
545
|
+
_internalRemoveHeader(name) {
|
|
546
|
+
if (!this._headersSent) {
|
|
547
|
+
const lowerName = name.toLowerCase();
|
|
548
|
+
this._headers.delete(lowerName);
|
|
549
|
+
if (kOutHeaders) {
|
|
550
|
+
const outHeaders = this[kOutHeaders];
|
|
551
|
+
if (outHeaders) {
|
|
552
|
+
delete outHeaders[lowerName];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
363
556
|
}
|
|
364
557
|
//
|
|
365
558
|
// Header management
|
|
@@ -370,7 +563,16 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
370
563
|
// Headers cannot be changed after body starts
|
|
371
564
|
return this;
|
|
372
565
|
}
|
|
373
|
-
|
|
566
|
+
const lowerName = name.toLowerCase();
|
|
567
|
+
this._headers.set(lowerName, String(value));
|
|
568
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
569
|
+
// Node stores as { 'header-name': ['Header-Name', value] }
|
|
570
|
+
if (kOutHeaders) {
|
|
571
|
+
const outHeaders = this[kOutHeaders];
|
|
572
|
+
if (outHeaders) {
|
|
573
|
+
outHeaders[lowerName] = [name, String(value)];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
374
576
|
return this;
|
|
375
577
|
}
|
|
376
578
|
getHeader(name) {
|
|
@@ -378,7 +580,15 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
378
580
|
}
|
|
379
581
|
removeHeader(name) {
|
|
380
582
|
if (!this._headersSent) {
|
|
381
|
-
|
|
583
|
+
const lowerName = name.toLowerCase();
|
|
584
|
+
this._headers.delete(lowerName);
|
|
585
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
586
|
+
if (kOutHeaders) {
|
|
587
|
+
const outHeaders = this[kOutHeaders];
|
|
588
|
+
if (outHeaders) {
|
|
589
|
+
delete outHeaders[lowerName];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
382
592
|
}
|
|
383
593
|
}
|
|
384
594
|
getHeaders() {
|
|
@@ -394,6 +604,43 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
394
604
|
getHeaderNames() {
|
|
395
605
|
return Array.from(this._headers.keys());
|
|
396
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Proxy for direct header access (e.g., res.headers['content-type']).
|
|
609
|
+
* Required for compatibility with middleware like helmet that access headers directly.
|
|
610
|
+
*/
|
|
611
|
+
get headers() {
|
|
612
|
+
return new Proxy({}, {
|
|
613
|
+
deleteProperty: (_target, prop) => {
|
|
614
|
+
this.removeHeader(String(prop));
|
|
615
|
+
return true;
|
|
616
|
+
},
|
|
617
|
+
get: (_target, prop) => {
|
|
618
|
+
if (typeof prop === "symbol")
|
|
619
|
+
return undefined;
|
|
620
|
+
return this.getHeader(String(prop));
|
|
621
|
+
},
|
|
622
|
+
getOwnPropertyDescriptor: (_target, prop) => {
|
|
623
|
+
if (this.hasHeader(String(prop))) {
|
|
624
|
+
return {
|
|
625
|
+
configurable: true,
|
|
626
|
+
enumerable: true,
|
|
627
|
+
value: this.getHeader(String(prop)),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return undefined;
|
|
631
|
+
},
|
|
632
|
+
has: (_target, prop) => {
|
|
633
|
+
return this.hasHeader(String(prop));
|
|
634
|
+
},
|
|
635
|
+
ownKeys: () => {
|
|
636
|
+
return this.getHeaderNames();
|
|
637
|
+
},
|
|
638
|
+
set: (_target, prop, value) => {
|
|
639
|
+
this.setHeader(String(prop), value);
|
|
640
|
+
return true;
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
}
|
|
397
644
|
writeHead(statusCode, statusMessageOrHeaders, headers) {
|
|
398
645
|
if (this._headersSent) {
|
|
399
646
|
return this;
|
|
@@ -445,6 +692,25 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
445
692
|
//
|
|
446
693
|
// Express compatibility methods
|
|
447
694
|
//
|
|
695
|
+
/**
|
|
696
|
+
* Express-style alias for getHeader().
|
|
697
|
+
* Used by middleware like decorateResponse that use res.get().
|
|
698
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
699
|
+
*/
|
|
700
|
+
get(name) {
|
|
701
|
+
return this._headers.get(name.toLowerCase());
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Express-style alias for setHeader().
|
|
705
|
+
* Used by middleware like decorateResponse that use res.set().
|
|
706
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
707
|
+
*/
|
|
708
|
+
set(name, value) {
|
|
709
|
+
if (!this._headersSent) {
|
|
710
|
+
this._headers.set(name.toLowerCase(), String(value));
|
|
711
|
+
}
|
|
712
|
+
return this;
|
|
713
|
+
}
|
|
448
714
|
status(code) {
|
|
449
715
|
this.statusCode = code;
|
|
450
716
|
return this;
|
|
@@ -461,6 +727,26 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
461
727
|
this.end(body);
|
|
462
728
|
return this;
|
|
463
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Add a field to the Vary response header.
|
|
732
|
+
* Used by CORS middleware to indicate response varies by Origin.
|
|
733
|
+
*/
|
|
734
|
+
vary(field) {
|
|
735
|
+
const existing = this.getHeader("vary");
|
|
736
|
+
if (!existing) {
|
|
737
|
+
this.setHeader("vary", field);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
// Append to existing Vary header if field not already present
|
|
741
|
+
const fields = String(existing)
|
|
742
|
+
.split(",")
|
|
743
|
+
.map((f) => f.trim().toLowerCase());
|
|
744
|
+
if (!fields.includes(field.toLowerCase())) {
|
|
745
|
+
this.setHeader("vary", `${existing}, ${field}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return this;
|
|
749
|
+
}
|
|
464
750
|
//
|
|
465
751
|
// Writable stream implementation
|
|
466
752
|
//
|
|
@@ -917,6 +1203,73 @@ function getCurrentInvokeUuid(req) {
|
|
|
917
1203
|
return getWebAdapterUuid();
|
|
918
1204
|
}
|
|
919
1205
|
|
|
1206
|
+
//
|
|
1207
|
+
//
|
|
1208
|
+
// Helpers
|
|
1209
|
+
//
|
|
1210
|
+
/**
|
|
1211
|
+
* Safely get a header value from response.
|
|
1212
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1213
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1214
|
+
*/
|
|
1215
|
+
function safeGetHeader(res, name) {
|
|
1216
|
+
try {
|
|
1217
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1218
|
+
if (typeof res._internalGetHeader === "function") {
|
|
1219
|
+
return res._internalGetHeader(name);
|
|
1220
|
+
}
|
|
1221
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1222
|
+
if (res._headers instanceof Map) {
|
|
1223
|
+
const value = res._headers.get(name.toLowerCase());
|
|
1224
|
+
return value ? String(value) : undefined;
|
|
1225
|
+
}
|
|
1226
|
+
// Fall back to getHeader (more standard than get)
|
|
1227
|
+
if (typeof res.getHeader === "function") {
|
|
1228
|
+
const value = res.getHeader(name);
|
|
1229
|
+
return value ? String(value) : undefined;
|
|
1230
|
+
}
|
|
1231
|
+
// Last resort: try get
|
|
1232
|
+
if (typeof res.get === "function") {
|
|
1233
|
+
const value = res.get(name);
|
|
1234
|
+
return value ? String(value) : undefined;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
catch {
|
|
1238
|
+
// Silently fail - caller will handle missing value
|
|
1239
|
+
}
|
|
1240
|
+
return undefined;
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Safely set a header value on response.
|
|
1244
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1245
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1246
|
+
*/
|
|
1247
|
+
function safeSetHeader(res, name, value) {
|
|
1248
|
+
try {
|
|
1249
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1250
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1251
|
+
res._internalSetHeader(name, value);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1255
|
+
if (res._headers instanceof Map) {
|
|
1256
|
+
res._headers.set(name.toLowerCase(), value);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
// Fall back to setHeader (more standard than set)
|
|
1260
|
+
if (typeof res.setHeader === "function") {
|
|
1261
|
+
res.setHeader(name, value);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
// Last resort: try set
|
|
1265
|
+
if (typeof res.set === "function") {
|
|
1266
|
+
res.set(name, value);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
catch {
|
|
1270
|
+
// Silently fail - header just won't be set
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
920
1273
|
//
|
|
921
1274
|
//
|
|
922
1275
|
// Main
|
|
@@ -933,36 +1286,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
|
|
|
933
1286
|
log.warn("decorateResponse called but response is not an object");
|
|
934
1287
|
return;
|
|
935
1288
|
}
|
|
1289
|
+
const extRes = res;
|
|
936
1290
|
try {
|
|
937
1291
|
//
|
|
938
1292
|
//
|
|
939
1293
|
// Decorate Headers
|
|
940
1294
|
//
|
|
941
1295
|
// X-Powered-By, override "Express" but nothing else
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1296
|
+
const currentPoweredBy = safeGetHeader(extRes, kit.HTTP.HEADER.POWERED_BY);
|
|
1297
|
+
if (!currentPoweredBy || currentPoweredBy === "Express") {
|
|
1298
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
|
|
945
1299
|
}
|
|
946
1300
|
// X-Project-Environment
|
|
947
1301
|
if (process.env.PROJECT_ENV) {
|
|
948
|
-
|
|
1302
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
|
|
949
1303
|
}
|
|
950
1304
|
// X-Project-Handler
|
|
951
1305
|
if (handler) {
|
|
952
|
-
|
|
1306
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.HANDLER, handler);
|
|
953
1307
|
}
|
|
954
1308
|
// X-Project-Invocation
|
|
955
1309
|
const currentInvoke = getCurrentInvokeUuid();
|
|
956
1310
|
if (currentInvoke) {
|
|
957
|
-
|
|
1311
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
|
|
958
1312
|
}
|
|
959
1313
|
// X-Project-Key
|
|
960
1314
|
if (process.env.PROJECT_KEY) {
|
|
961
|
-
|
|
1315
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
|
|
962
1316
|
}
|
|
963
1317
|
// X-Project-Version
|
|
964
1318
|
if (version) {
|
|
965
|
-
|
|
1319
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.VERSION, version);
|
|
966
1320
|
}
|
|
967
1321
|
//
|
|
968
1322
|
//
|
|
@@ -1022,6 +1376,76 @@ function summarizeResponse(res, extras) {
|
|
|
1022
1376
|
|
|
1023
1377
|
// Cast logger to extended interface for runtime features not in type definitions
|
|
1024
1378
|
const logger$1 = logger$2.log;
|
|
1379
|
+
//
|
|
1380
|
+
//
|
|
1381
|
+
// Helpers - Safe response methods to bypass dd-trace interception
|
|
1382
|
+
//
|
|
1383
|
+
/**
|
|
1384
|
+
* Check if response is a Lambda mock response with direct internal access.
|
|
1385
|
+
*/
|
|
1386
|
+
function isLambdaMockResponse(res) {
|
|
1387
|
+
const mock = res;
|
|
1388
|
+
return (mock._headers instanceof Map &&
|
|
1389
|
+
Array.isArray(mock._chunks) &&
|
|
1390
|
+
typeof mock.buildResult === "function");
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Safely send a JSON response, avoiding dd-trace interception.
|
|
1394
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1395
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1396
|
+
*/
|
|
1397
|
+
function safeSendJson(res, statusCode, data) {
|
|
1398
|
+
if (isLambdaMockResponse(res)) {
|
|
1399
|
+
// Use internal method to set header (completely bypasses dd-trace)
|
|
1400
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1401
|
+
res._internalSetHeader("content-type", "application/json");
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
// Fall back to direct _headers manipulation
|
|
1405
|
+
res._headers.set("content-type", "application/json");
|
|
1406
|
+
}
|
|
1407
|
+
res.statusCode = statusCode;
|
|
1408
|
+
// Directly push to chunks array instead of using stream write/end
|
|
1409
|
+
const chunk = Buffer.from(JSON.stringify(data));
|
|
1410
|
+
res._chunks.push(chunk);
|
|
1411
|
+
res._headersSent = true;
|
|
1412
|
+
// Signal completion if a promise is waiting
|
|
1413
|
+
if (res._resolve) {
|
|
1414
|
+
res._resolve(res.buildResult());
|
|
1415
|
+
}
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
// Fall back to standard Express methods for real responses
|
|
1419
|
+
res.status(statusCode).json(data);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Safely send a response body, avoiding dd-trace interception.
|
|
1423
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1424
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1425
|
+
*/
|
|
1426
|
+
function safeSend(res, statusCode, body) {
|
|
1427
|
+
if (isLambdaMockResponse(res)) {
|
|
1428
|
+
// Direct internal state manipulation - bypasses dd-trace completely
|
|
1429
|
+
res.statusCode = statusCode;
|
|
1430
|
+
if (body !== undefined) {
|
|
1431
|
+
const chunk = Buffer.from(body);
|
|
1432
|
+
res._chunks.push(chunk);
|
|
1433
|
+
}
|
|
1434
|
+
res._headersSent = true;
|
|
1435
|
+
// Signal completion if a promise is waiting
|
|
1436
|
+
if (res._resolve) {
|
|
1437
|
+
res._resolve(res.buildResult());
|
|
1438
|
+
}
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
// Fall back to standard Express methods for real responses
|
|
1442
|
+
if (body !== undefined) {
|
|
1443
|
+
res.status(statusCode).send(body);
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
res.status(statusCode).send();
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1025
1449
|
function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
1026
1450
|
/* eslint-enable no-redeclare */
|
|
1027
1451
|
let handler;
|
|
@@ -1238,30 +1662,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
|
1238
1662
|
if (typeof response === "object") {
|
|
1239
1663
|
if (typeof response.json ===
|
|
1240
1664
|
"function") {
|
|
1241
|
-
res
|
|
1665
|
+
safeSendJson(res, status, response.json());
|
|
1242
1666
|
}
|
|
1243
1667
|
else {
|
|
1244
|
-
res
|
|
1668
|
+
safeSendJson(res, status, response);
|
|
1245
1669
|
}
|
|
1246
1670
|
}
|
|
1247
1671
|
else if (typeof response === "string") {
|
|
1248
1672
|
try {
|
|
1249
|
-
res
|
|
1673
|
+
safeSendJson(res, status, JSON.parse(response));
|
|
1250
1674
|
}
|
|
1251
1675
|
catch {
|
|
1252
|
-
res
|
|
1676
|
+
safeSend(res, status, response);
|
|
1253
1677
|
}
|
|
1254
1678
|
}
|
|
1255
1679
|
else if (response === true) {
|
|
1256
|
-
res
|
|
1680
|
+
safeSend(res, kit.HTTP.CODE.CREATED);
|
|
1257
1681
|
}
|
|
1258
1682
|
else {
|
|
1259
|
-
res
|
|
1683
|
+
safeSend(res, status, response);
|
|
1260
1684
|
}
|
|
1261
1685
|
}
|
|
1262
1686
|
else {
|
|
1263
1687
|
// No response
|
|
1264
|
-
res
|
|
1688
|
+
safeSend(res, kit.HTTP.CODE.NO_CONTENT);
|
|
1265
1689
|
}
|
|
1266
1690
|
}
|
|
1267
1691
|
else {
|