@jaypie/express 1.2.4-rc1 → 1.2.4-rc10
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 +442 -19
- 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 +442 -19
- 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
|
//
|
|
@@ -338,6 +479,14 @@ class LambdaResponseBuffered extends node_stream.Writable {
|
|
|
338
479
|
}
|
|
339
480
|
}
|
|
340
481
|
|
|
482
|
+
//
|
|
483
|
+
//
|
|
484
|
+
// Constants
|
|
485
|
+
//
|
|
486
|
+
// Get Node's internal kOutHeaders symbol from ServerResponse prototype.
|
|
487
|
+
// This is needed for compatibility with Datadog dd-trace instrumentation,
|
|
488
|
+
// which patches HTTP methods and expects this internal state to exist.
|
|
489
|
+
const kOutHeaders = Object.getOwnPropertySymbols(node_http.ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
|
|
341
490
|
//
|
|
342
491
|
//
|
|
343
492
|
// LambdaResponseStreaming Class
|
|
@@ -355,11 +504,54 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
355
504
|
this.socket = {
|
|
356
505
|
remoteAddress: "127.0.0.1",
|
|
357
506
|
};
|
|
507
|
+
// Internal state exposed for direct manipulation by safe response methods
|
|
508
|
+
// that need to bypass dd-trace interception
|
|
358
509
|
this._headers = new Map();
|
|
359
510
|
this._headersSent = false;
|
|
360
511
|
this._pendingWrites = [];
|
|
361
512
|
this._wrappedStream = null;
|
|
362
513
|
this._responseStream = responseStream;
|
|
514
|
+
// Initialize Node's internal kOutHeaders for dd-trace compatibility.
|
|
515
|
+
// dd-trace patches HTTP methods and expects this internal state.
|
|
516
|
+
if (kOutHeaders) {
|
|
517
|
+
this[kOutHeaders] = Object.create(null);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
//
|
|
521
|
+
// Internal bypass methods - completely avoid prototype chain lookup
|
|
522
|
+
// These directly access _headers Map, safe from dd-trace interception
|
|
523
|
+
//
|
|
524
|
+
_internalGetHeader(name) {
|
|
525
|
+
const value = this._headers.get(name.toLowerCase());
|
|
526
|
+
return value ? String(value) : undefined;
|
|
527
|
+
}
|
|
528
|
+
_internalSetHeader(name, value) {
|
|
529
|
+
if (!this._headersSent) {
|
|
530
|
+
const lowerName = name.toLowerCase();
|
|
531
|
+
this._headers.set(lowerName, value);
|
|
532
|
+
// Also sync kOutHeaders for any code that expects it
|
|
533
|
+
if (kOutHeaders) {
|
|
534
|
+
const outHeaders = this[kOutHeaders];
|
|
535
|
+
if (outHeaders) {
|
|
536
|
+
outHeaders[lowerName] = [name, value];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
_internalHasHeader(name) {
|
|
542
|
+
return this._headers.has(name.toLowerCase());
|
|
543
|
+
}
|
|
544
|
+
_internalRemoveHeader(name) {
|
|
545
|
+
if (!this._headersSent) {
|
|
546
|
+
const lowerName = name.toLowerCase();
|
|
547
|
+
this._headers.delete(lowerName);
|
|
548
|
+
if (kOutHeaders) {
|
|
549
|
+
const outHeaders = this[kOutHeaders];
|
|
550
|
+
if (outHeaders) {
|
|
551
|
+
delete outHeaders[lowerName];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
363
555
|
}
|
|
364
556
|
//
|
|
365
557
|
// Header management
|
|
@@ -370,7 +562,16 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
370
562
|
// Headers cannot be changed after body starts
|
|
371
563
|
return this;
|
|
372
564
|
}
|
|
373
|
-
|
|
565
|
+
const lowerName = name.toLowerCase();
|
|
566
|
+
this._headers.set(lowerName, String(value));
|
|
567
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
568
|
+
// Node stores as { 'header-name': ['Header-Name', value] }
|
|
569
|
+
if (kOutHeaders) {
|
|
570
|
+
const outHeaders = this[kOutHeaders];
|
|
571
|
+
if (outHeaders) {
|
|
572
|
+
outHeaders[lowerName] = [name, String(value)];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
374
575
|
return this;
|
|
375
576
|
}
|
|
376
577
|
getHeader(name) {
|
|
@@ -378,7 +579,15 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
378
579
|
}
|
|
379
580
|
removeHeader(name) {
|
|
380
581
|
if (!this._headersSent) {
|
|
381
|
-
|
|
582
|
+
const lowerName = name.toLowerCase();
|
|
583
|
+
this._headers.delete(lowerName);
|
|
584
|
+
// Sync with kOutHeaders for dd-trace compatibility
|
|
585
|
+
if (kOutHeaders) {
|
|
586
|
+
const outHeaders = this[kOutHeaders];
|
|
587
|
+
if (outHeaders) {
|
|
588
|
+
delete outHeaders[lowerName];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
382
591
|
}
|
|
383
592
|
}
|
|
384
593
|
getHeaders() {
|
|
@@ -394,6 +603,43 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
394
603
|
getHeaderNames() {
|
|
395
604
|
return Array.from(this._headers.keys());
|
|
396
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Proxy for direct header access (e.g., res.headers['content-type']).
|
|
608
|
+
* Required for compatibility with middleware like helmet that access headers directly.
|
|
609
|
+
*/
|
|
610
|
+
get headers() {
|
|
611
|
+
return new Proxy({}, {
|
|
612
|
+
deleteProperty: (_target, prop) => {
|
|
613
|
+
this.removeHeader(String(prop));
|
|
614
|
+
return true;
|
|
615
|
+
},
|
|
616
|
+
get: (_target, prop) => {
|
|
617
|
+
if (typeof prop === "symbol")
|
|
618
|
+
return undefined;
|
|
619
|
+
return this.getHeader(String(prop));
|
|
620
|
+
},
|
|
621
|
+
getOwnPropertyDescriptor: (_target, prop) => {
|
|
622
|
+
if (this.hasHeader(String(prop))) {
|
|
623
|
+
return {
|
|
624
|
+
configurable: true,
|
|
625
|
+
enumerable: true,
|
|
626
|
+
value: this.getHeader(String(prop)),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return undefined;
|
|
630
|
+
},
|
|
631
|
+
has: (_target, prop) => {
|
|
632
|
+
return this.hasHeader(String(prop));
|
|
633
|
+
},
|
|
634
|
+
ownKeys: () => {
|
|
635
|
+
return this.getHeaderNames();
|
|
636
|
+
},
|
|
637
|
+
set: (_target, prop, value) => {
|
|
638
|
+
this.setHeader(String(prop), value);
|
|
639
|
+
return true;
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
}
|
|
397
643
|
writeHead(statusCode, statusMessageOrHeaders, headers) {
|
|
398
644
|
if (this._headersSent) {
|
|
399
645
|
return this;
|
|
@@ -445,6 +691,25 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
445
691
|
//
|
|
446
692
|
// Express compatibility methods
|
|
447
693
|
//
|
|
694
|
+
/**
|
|
695
|
+
* Express-style alias for getHeader().
|
|
696
|
+
* Used by middleware like decorateResponse that use res.get().
|
|
697
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
698
|
+
*/
|
|
699
|
+
get(name) {
|
|
700
|
+
return this._headers.get(name.toLowerCase());
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Express-style alias for setHeader().
|
|
704
|
+
* Used by middleware like decorateResponse that use res.set().
|
|
705
|
+
* Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
|
|
706
|
+
*/
|
|
707
|
+
set(name, value) {
|
|
708
|
+
if (!this._headersSent) {
|
|
709
|
+
this._headers.set(name.toLowerCase(), String(value));
|
|
710
|
+
}
|
|
711
|
+
return this;
|
|
712
|
+
}
|
|
448
713
|
status(code) {
|
|
449
714
|
this.statusCode = code;
|
|
450
715
|
return this;
|
|
@@ -461,6 +726,26 @@ class LambdaResponseStreaming extends node_stream.Writable {
|
|
|
461
726
|
this.end(body);
|
|
462
727
|
return this;
|
|
463
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Add a field to the Vary response header.
|
|
731
|
+
* Used by CORS middleware to indicate response varies by Origin.
|
|
732
|
+
*/
|
|
733
|
+
vary(field) {
|
|
734
|
+
const existing = this.getHeader("vary");
|
|
735
|
+
if (!existing) {
|
|
736
|
+
this.setHeader("vary", field);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
// Append to existing Vary header if field not already present
|
|
740
|
+
const fields = String(existing)
|
|
741
|
+
.split(",")
|
|
742
|
+
.map((f) => f.trim().toLowerCase());
|
|
743
|
+
if (!fields.includes(field.toLowerCase())) {
|
|
744
|
+
this.setHeader("vary", `${existing}, ${field}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
464
749
|
//
|
|
465
750
|
// Writable stream implementation
|
|
466
751
|
//
|
|
@@ -917,6 +1202,73 @@ function getCurrentInvokeUuid(req) {
|
|
|
917
1202
|
return getWebAdapterUuid();
|
|
918
1203
|
}
|
|
919
1204
|
|
|
1205
|
+
//
|
|
1206
|
+
//
|
|
1207
|
+
// Helpers
|
|
1208
|
+
//
|
|
1209
|
+
/**
|
|
1210
|
+
* Safely get a header value from response.
|
|
1211
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1212
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1213
|
+
*/
|
|
1214
|
+
function safeGetHeader(res, name) {
|
|
1215
|
+
try {
|
|
1216
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1217
|
+
if (typeof res._internalGetHeader === "function") {
|
|
1218
|
+
return res._internalGetHeader(name);
|
|
1219
|
+
}
|
|
1220
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1221
|
+
if (res._headers instanceof Map) {
|
|
1222
|
+
const value = res._headers.get(name.toLowerCase());
|
|
1223
|
+
return value ? String(value) : undefined;
|
|
1224
|
+
}
|
|
1225
|
+
// Fall back to getHeader (more standard than get)
|
|
1226
|
+
if (typeof res.getHeader === "function") {
|
|
1227
|
+
const value = res.getHeader(name);
|
|
1228
|
+
return value ? String(value) : undefined;
|
|
1229
|
+
}
|
|
1230
|
+
// Last resort: try get
|
|
1231
|
+
if (typeof res.get === "function") {
|
|
1232
|
+
const value = res.get(name);
|
|
1233
|
+
return value ? String(value) : undefined;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
catch {
|
|
1237
|
+
// Silently fail - caller will handle missing value
|
|
1238
|
+
}
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Safely set a header value on response.
|
|
1243
|
+
* Handles both Express Response and Lambda adapter responses.
|
|
1244
|
+
* Defensive against dd-trace instrumentation issues.
|
|
1245
|
+
*/
|
|
1246
|
+
function safeSetHeader(res, name, value) {
|
|
1247
|
+
try {
|
|
1248
|
+
// Try internal method first (completely bypasses dd-trace)
|
|
1249
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1250
|
+
res._internalSetHeader(name, value);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
// Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
|
|
1254
|
+
if (res._headers instanceof Map) {
|
|
1255
|
+
res._headers.set(name.toLowerCase(), value);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
// Fall back to setHeader (more standard than set)
|
|
1259
|
+
if (typeof res.setHeader === "function") {
|
|
1260
|
+
res.setHeader(name, value);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
// Last resort: try set
|
|
1264
|
+
if (typeof res.set === "function") {
|
|
1265
|
+
res.set(name, value);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
catch {
|
|
1269
|
+
// Silently fail - header just won't be set
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
920
1272
|
//
|
|
921
1273
|
//
|
|
922
1274
|
// Main
|
|
@@ -933,36 +1285,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
|
|
|
933
1285
|
log.warn("decorateResponse called but response is not an object");
|
|
934
1286
|
return;
|
|
935
1287
|
}
|
|
1288
|
+
const extRes = res;
|
|
936
1289
|
try {
|
|
937
1290
|
//
|
|
938
1291
|
//
|
|
939
1292
|
// Decorate Headers
|
|
940
1293
|
//
|
|
941
1294
|
// X-Powered-By, override "Express" but nothing else
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1295
|
+
const currentPoweredBy = safeGetHeader(extRes, kit.HTTP.HEADER.POWERED_BY);
|
|
1296
|
+
if (!currentPoweredBy || currentPoweredBy === "Express") {
|
|
1297
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
|
|
945
1298
|
}
|
|
946
1299
|
// X-Project-Environment
|
|
947
1300
|
if (process.env.PROJECT_ENV) {
|
|
948
|
-
|
|
1301
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
|
|
949
1302
|
}
|
|
950
1303
|
// X-Project-Handler
|
|
951
1304
|
if (handler) {
|
|
952
|
-
|
|
1305
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.HANDLER, handler);
|
|
953
1306
|
}
|
|
954
1307
|
// X-Project-Invocation
|
|
955
1308
|
const currentInvoke = getCurrentInvokeUuid();
|
|
956
1309
|
if (currentInvoke) {
|
|
957
|
-
|
|
1310
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
|
|
958
1311
|
}
|
|
959
1312
|
// X-Project-Key
|
|
960
1313
|
if (process.env.PROJECT_KEY) {
|
|
961
|
-
|
|
1314
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
|
|
962
1315
|
}
|
|
963
1316
|
// X-Project-Version
|
|
964
1317
|
if (version) {
|
|
965
|
-
|
|
1318
|
+
safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.VERSION, version);
|
|
966
1319
|
}
|
|
967
1320
|
//
|
|
968
1321
|
//
|
|
@@ -1022,6 +1375,76 @@ function summarizeResponse(res, extras) {
|
|
|
1022
1375
|
|
|
1023
1376
|
// Cast logger to extended interface for runtime features not in type definitions
|
|
1024
1377
|
const logger$1 = logger$2.log;
|
|
1378
|
+
//
|
|
1379
|
+
//
|
|
1380
|
+
// Helpers - Safe response methods to bypass dd-trace interception
|
|
1381
|
+
//
|
|
1382
|
+
/**
|
|
1383
|
+
* Check if response is a Lambda mock response with direct internal access.
|
|
1384
|
+
*/
|
|
1385
|
+
function isLambdaMockResponse(res) {
|
|
1386
|
+
const mock = res;
|
|
1387
|
+
return (mock._headers instanceof Map &&
|
|
1388
|
+
Array.isArray(mock._chunks) &&
|
|
1389
|
+
typeof mock.buildResult === "function");
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Safely send a JSON response, avoiding dd-trace interception.
|
|
1393
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1394
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1395
|
+
*/
|
|
1396
|
+
function safeSendJson(res, statusCode, data) {
|
|
1397
|
+
if (isLambdaMockResponse(res)) {
|
|
1398
|
+
// Use internal method to set header (completely bypasses dd-trace)
|
|
1399
|
+
if (typeof res._internalSetHeader === "function") {
|
|
1400
|
+
res._internalSetHeader("content-type", "application/json");
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
// Fall back to direct _headers manipulation
|
|
1404
|
+
res._headers.set("content-type", "application/json");
|
|
1405
|
+
}
|
|
1406
|
+
res.statusCode = statusCode;
|
|
1407
|
+
// Directly push to chunks array instead of using stream write/end
|
|
1408
|
+
const chunk = Buffer.from(JSON.stringify(data));
|
|
1409
|
+
res._chunks.push(chunk);
|
|
1410
|
+
res._headersSent = true;
|
|
1411
|
+
// Signal completion if a promise is waiting
|
|
1412
|
+
if (res._resolve) {
|
|
1413
|
+
res._resolve(res.buildResult());
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
// Fall back to standard Express methods for real responses
|
|
1418
|
+
res.status(statusCode).json(data);
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Safely send a response body, avoiding dd-trace interception.
|
|
1422
|
+
* For Lambda mock responses, directly manipulates internal state instead of
|
|
1423
|
+
* using stream methods (write/end) which dd-trace intercepts.
|
|
1424
|
+
*/
|
|
1425
|
+
function safeSend(res, statusCode, body) {
|
|
1426
|
+
if (isLambdaMockResponse(res)) {
|
|
1427
|
+
// Direct internal state manipulation - bypasses dd-trace completely
|
|
1428
|
+
res.statusCode = statusCode;
|
|
1429
|
+
if (body !== undefined) {
|
|
1430
|
+
const chunk = Buffer.from(body);
|
|
1431
|
+
res._chunks.push(chunk);
|
|
1432
|
+
}
|
|
1433
|
+
res._headersSent = true;
|
|
1434
|
+
// Signal completion if a promise is waiting
|
|
1435
|
+
if (res._resolve) {
|
|
1436
|
+
res._resolve(res.buildResult());
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
// Fall back to standard Express methods for real responses
|
|
1441
|
+
if (body !== undefined) {
|
|
1442
|
+
res.status(statusCode).send(body);
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
res.status(statusCode).send();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1025
1448
|
function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
1026
1449
|
/* eslint-enable no-redeclare */
|
|
1027
1450
|
let handler;
|
|
@@ -1238,30 +1661,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
|
|
|
1238
1661
|
if (typeof response === "object") {
|
|
1239
1662
|
if (typeof response.json ===
|
|
1240
1663
|
"function") {
|
|
1241
|
-
res
|
|
1664
|
+
safeSendJson(res, status, response.json());
|
|
1242
1665
|
}
|
|
1243
1666
|
else {
|
|
1244
|
-
res
|
|
1667
|
+
safeSendJson(res, status, response);
|
|
1245
1668
|
}
|
|
1246
1669
|
}
|
|
1247
1670
|
else if (typeof response === "string") {
|
|
1248
1671
|
try {
|
|
1249
|
-
res
|
|
1672
|
+
safeSendJson(res, status, JSON.parse(response));
|
|
1250
1673
|
}
|
|
1251
1674
|
catch {
|
|
1252
|
-
res
|
|
1675
|
+
safeSend(res, status, response);
|
|
1253
1676
|
}
|
|
1254
1677
|
}
|
|
1255
1678
|
else if (response === true) {
|
|
1256
|
-
res
|
|
1679
|
+
safeSend(res, kit.HTTP.CODE.CREATED);
|
|
1257
1680
|
}
|
|
1258
1681
|
else {
|
|
1259
|
-
res
|
|
1682
|
+
safeSend(res, status, response);
|
|
1260
1683
|
}
|
|
1261
1684
|
}
|
|
1262
1685
|
else {
|
|
1263
1686
|
// No response
|
|
1264
|
-
res
|
|
1687
|
+
safeSend(res, kit.HTTP.CODE.NO_CONTENT);
|
|
1265
1688
|
}
|
|
1266
1689
|
}
|
|
1267
1690
|
else {
|