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