@jaypie/express 1.2.4-rc0 → 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/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';
@@ -79,16 +80,63 @@ class LambdaRequest extends Readable {
79
80
  }
80
81
  //
81
82
  //
83
+ // Type Guards
84
+ //
85
+ /**
86
+ * Check if event is a Function URL / HTTP API v2 event.
87
+ */
88
+ function isFunctionUrlEvent(event) {
89
+ return "requestContext" in event && "http" in event.requestContext;
90
+ }
91
+ /**
92
+ * Check if event is an API Gateway REST API v1 event.
93
+ */
94
+ function isApiGatewayV1Event(event) {
95
+ return "httpMethod" in event;
96
+ }
97
+ //
98
+ //
82
99
  // Factory Function
83
100
  //
84
101
  /**
85
- * Create a LambdaRequest from a Function URL event.
102
+ * Create a LambdaRequest from a Lambda event (Function URL, HTTP API v2, or REST API v1).
86
103
  */
87
104
  function createLambdaRequest(event, context) {
88
- // Build URL with query string
89
- const url = event.rawQueryString
90
- ? `${event.rawPath}?${event.rawQueryString}`
91
- : event.rawPath;
105
+ let url;
106
+ let method;
107
+ let protocol;
108
+ let remoteAddress;
109
+ const headers = { ...event.headers };
110
+ if (isFunctionUrlEvent(event)) {
111
+ // Function URL / HTTP API v2 format
112
+ url = event.rawQueryString
113
+ ? `${event.rawPath}?${event.rawQueryString}`
114
+ : event.rawPath;
115
+ method = event.requestContext.http.method;
116
+ protocol = event.requestContext.http.protocol.split("/")[0].toLowerCase();
117
+ remoteAddress = event.requestContext.http.sourceIp;
118
+ // Normalize cookies into Cookie header if not already present
119
+ if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
120
+ headers.cookie = event.cookies.join("; ");
121
+ }
122
+ }
123
+ else if (isApiGatewayV1Event(event)) {
124
+ // API Gateway REST API v1 format
125
+ const queryParams = event.queryStringParameters;
126
+ if (queryParams && Object.keys(queryParams).length > 0) {
127
+ const queryString = new URLSearchParams(queryParams).toString();
128
+ url = `${event.path}?${queryString}`;
129
+ }
130
+ else {
131
+ url = event.path;
132
+ }
133
+ method = event.httpMethod;
134
+ protocol = event.requestContext.protocol.split("/")[0].toLowerCase();
135
+ remoteAddress = event.requestContext.identity.sourceIp;
136
+ }
137
+ else {
138
+ throw new Error("Unsupported Lambda event format. Expected Function URL, HTTP API v2, or REST API v1 event.");
139
+ }
92
140
  // Decode body if present
93
141
  let body = null;
94
142
  if (event.body) {
@@ -96,19 +144,14 @@ function createLambdaRequest(event, context) {
96
144
  ? Buffer.from(event.body, "base64")
97
145
  : Buffer.from(event.body, "utf8");
98
146
  }
99
- // Normalize cookies into Cookie header if not already present
100
- const headers = { ...event.headers };
101
- if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
102
- headers.cookie = event.cookies.join("; ");
103
- }
104
147
  return new LambdaRequest({
105
148
  body,
106
149
  headers,
107
150
  lambdaContext: context,
108
151
  lambdaEvent: event,
109
- method: event.requestContext.http.method,
110
- protocol: event.requestContext.http.protocol.split("/")[0].toLowerCase(),
111
- remoteAddress: event.requestContext.http.sourceIp,
152
+ method,
153
+ protocol,
154
+ remoteAddress,
112
155
  url,
113
156
  });
114
157
  }
@@ -117,6 +160,10 @@ function createLambdaRequest(event, context) {
117
160
  //
118
161
  // Constants
119
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)");
120
167
  const BINARY_CONTENT_TYPE_PATTERNS = [
121
168
  /^application\/octet-stream$/,
122
169
  /^application\/pdf$/,
@@ -141,16 +188,55 @@ class LambdaResponseBuffered extends Writable {
141
188
  this.statusMessage = "OK";
142
189
  // Mock socket to satisfy Express/finalhandler checks
143
190
  this.socket = {
144
- cork: () => { },
145
- destroy: () => { },
146
191
  remoteAddress: "127.0.0.1",
147
- uncork: () => { },
148
- writable: true,
149
192
  };
193
+ // Internal state exposed for direct manipulation by safe response methods
194
+ // that need to bypass dd-trace interception of stream methods
150
195
  this._chunks = [];
151
196
  this._headers = new Map();
152
197
  this._headersSent = false;
153
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
+ }
154
240
  }
155
241
  //
156
242
  // Promise-based API for getting final result
@@ -173,14 +259,31 @@ class LambdaResponseBuffered extends Writable {
173
259
  // In production, log warning but don't throw to match Express behavior
174
260
  return this;
175
261
  }
176
- this._headers.set(name.toLowerCase(), String(value));
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
+ }
177
272
  return this;
178
273
  }
179
274
  getHeader(name) {
180
275
  return this._headers.get(name.toLowerCase());
181
276
  }
182
277
  removeHeader(name) {
183
- this._headers.delete(name.toLowerCase());
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
+ }
184
287
  }
185
288
  getHeaders() {
186
289
  const headers = {};
@@ -195,6 +298,43 @@ class LambdaResponseBuffered extends Writable {
195
298
  getHeaderNames() {
196
299
  return Array.from(this._headers.keys());
197
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
+ }
198
338
  writeHead(statusCode, statusMessageOrHeaders, headers) {
199
339
  this.statusCode = statusCode;
200
340
  let headersToSet;
@@ -222,6 +362,25 @@ class LambdaResponseBuffered extends Writable {
222
362
  //
223
363
  // Express compatibility methods
224
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
+ }
225
384
  status(code) {
226
385
  this.statusCode = code;
227
386
  return this;
@@ -238,6 +397,26 @@ class LambdaResponseBuffered extends Writable {
238
397
  this.end(body);
239
398
  return this;
240
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
+ }
241
420
  //
242
421
  // Writable stream implementation
243
422
  //
@@ -298,6 +477,14 @@ class LambdaResponseBuffered extends Writable {
298
477
  }
299
478
  }
300
479
 
480
+ //
481
+ //
482
+ // Constants
483
+ //
484
+ // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
485
+ // This is needed for compatibility with Datadog dd-trace instrumentation,
486
+ // which patches HTTP methods and expects this internal state to exist.
487
+ const kOutHeaders = Object.getOwnPropertySymbols(ServerResponse.prototype).find((s) => s.toString() === "Symbol(kOutHeaders)");
301
488
  //
302
489
  //
303
490
  // LambdaResponseStreaming Class
@@ -313,17 +500,56 @@ class LambdaResponseStreaming extends Writable {
313
500
  this.statusMessage = "OK";
314
501
  // Mock socket to satisfy Express/finalhandler checks
315
502
  this.socket = {
316
- cork: () => { },
317
- destroy: () => { },
318
503
  remoteAddress: "127.0.0.1",
319
- uncork: () => { },
320
- writable: true,
321
504
  };
505
+ // Internal state exposed for direct manipulation by safe response methods
506
+ // that need to bypass dd-trace interception
322
507
  this._headers = new Map();
323
508
  this._headersSent = false;
324
509
  this._pendingWrites = [];
325
510
  this._wrappedStream = null;
326
511
  this._responseStream = responseStream;
512
+ // Initialize Node's internal kOutHeaders for dd-trace compatibility.
513
+ // dd-trace patches HTTP methods and expects this internal state.
514
+ if (kOutHeaders) {
515
+ this[kOutHeaders] = Object.create(null);
516
+ }
517
+ }
518
+ //
519
+ // Internal bypass methods - completely avoid prototype chain lookup
520
+ // These directly access _headers Map, safe from dd-trace interception
521
+ //
522
+ _internalGetHeader(name) {
523
+ const value = this._headers.get(name.toLowerCase());
524
+ return value ? String(value) : undefined;
525
+ }
526
+ _internalSetHeader(name, value) {
527
+ if (!this._headersSent) {
528
+ const lowerName = name.toLowerCase();
529
+ this._headers.set(lowerName, value);
530
+ // Also sync kOutHeaders for any code that expects it
531
+ if (kOutHeaders) {
532
+ const outHeaders = this[kOutHeaders];
533
+ if (outHeaders) {
534
+ outHeaders[lowerName] = [name, value];
535
+ }
536
+ }
537
+ }
538
+ }
539
+ _internalHasHeader(name) {
540
+ return this._headers.has(name.toLowerCase());
541
+ }
542
+ _internalRemoveHeader(name) {
543
+ if (!this._headersSent) {
544
+ const lowerName = name.toLowerCase();
545
+ this._headers.delete(lowerName);
546
+ if (kOutHeaders) {
547
+ const outHeaders = this[kOutHeaders];
548
+ if (outHeaders) {
549
+ delete outHeaders[lowerName];
550
+ }
551
+ }
552
+ }
327
553
  }
328
554
  //
329
555
  // Header management
@@ -334,7 +560,16 @@ class LambdaResponseStreaming extends Writable {
334
560
  // Headers cannot be changed after body starts
335
561
  return this;
336
562
  }
337
- this._headers.set(name.toLowerCase(), String(value));
563
+ const lowerName = name.toLowerCase();
564
+ this._headers.set(lowerName, String(value));
565
+ // Sync with kOutHeaders for dd-trace compatibility
566
+ // Node stores as { 'header-name': ['Header-Name', value] }
567
+ if (kOutHeaders) {
568
+ const outHeaders = this[kOutHeaders];
569
+ if (outHeaders) {
570
+ outHeaders[lowerName] = [name, String(value)];
571
+ }
572
+ }
338
573
  return this;
339
574
  }
340
575
  getHeader(name) {
@@ -342,7 +577,15 @@ class LambdaResponseStreaming extends Writable {
342
577
  }
343
578
  removeHeader(name) {
344
579
  if (!this._headersSent) {
345
- this._headers.delete(name.toLowerCase());
580
+ const lowerName = name.toLowerCase();
581
+ this._headers.delete(lowerName);
582
+ // Sync with kOutHeaders for dd-trace compatibility
583
+ if (kOutHeaders) {
584
+ const outHeaders = this[kOutHeaders];
585
+ if (outHeaders) {
586
+ delete outHeaders[lowerName];
587
+ }
588
+ }
346
589
  }
347
590
  }
348
591
  getHeaders() {
@@ -358,6 +601,43 @@ class LambdaResponseStreaming extends Writable {
358
601
  getHeaderNames() {
359
602
  return Array.from(this._headers.keys());
360
603
  }
604
+ /**
605
+ * Proxy for direct header access (e.g., res.headers['content-type']).
606
+ * Required for compatibility with middleware like helmet that access headers directly.
607
+ */
608
+ get headers() {
609
+ return new Proxy({}, {
610
+ deleteProperty: (_target, prop) => {
611
+ this.removeHeader(String(prop));
612
+ return true;
613
+ },
614
+ get: (_target, prop) => {
615
+ if (typeof prop === "symbol")
616
+ return undefined;
617
+ return this.getHeader(String(prop));
618
+ },
619
+ getOwnPropertyDescriptor: (_target, prop) => {
620
+ if (this.hasHeader(String(prop))) {
621
+ return {
622
+ configurable: true,
623
+ enumerable: true,
624
+ value: this.getHeader(String(prop)),
625
+ };
626
+ }
627
+ return undefined;
628
+ },
629
+ has: (_target, prop) => {
630
+ return this.hasHeader(String(prop));
631
+ },
632
+ ownKeys: () => {
633
+ return this.getHeaderNames();
634
+ },
635
+ set: (_target, prop, value) => {
636
+ this.setHeader(String(prop), value);
637
+ return true;
638
+ },
639
+ });
640
+ }
361
641
  writeHead(statusCode, statusMessageOrHeaders, headers) {
362
642
  if (this._headersSent) {
363
643
  return this;
@@ -409,6 +689,25 @@ class LambdaResponseStreaming extends Writable {
409
689
  //
410
690
  // Express compatibility methods
411
691
  //
692
+ /**
693
+ * Express-style alias for getHeader().
694
+ * Used by middleware like decorateResponse that use res.get().
695
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
696
+ */
697
+ get(name) {
698
+ return this._headers.get(name.toLowerCase());
699
+ }
700
+ /**
701
+ * Express-style alias for setHeader().
702
+ * Used by middleware like decorateResponse that use res.set().
703
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
704
+ */
705
+ set(name, value) {
706
+ if (!this._headersSent) {
707
+ this._headers.set(name.toLowerCase(), String(value));
708
+ }
709
+ return this;
710
+ }
412
711
  status(code) {
413
712
  this.statusCode = code;
414
713
  return this;
@@ -425,6 +724,26 @@ class LambdaResponseStreaming extends Writable {
425
724
  this.end(body);
426
725
  return this;
427
726
  }
727
+ /**
728
+ * Add a field to the Vary response header.
729
+ * Used by CORS middleware to indicate response varies by Origin.
730
+ */
731
+ vary(field) {
732
+ const existing = this.getHeader("vary");
733
+ if (!existing) {
734
+ this.setHeader("vary", field);
735
+ }
736
+ else {
737
+ // Append to existing Vary header if field not already present
738
+ const fields = String(existing)
739
+ .split(",")
740
+ .map((f) => f.trim().toLowerCase());
741
+ if (!fields.includes(field.toLowerCase())) {
742
+ this.setHeader("vary", `${existing}, ${field}`);
743
+ }
744
+ }
745
+ return this;
746
+ }
428
747
  //
429
748
  // Writable stream implementation
430
749
  //
@@ -881,6 +1200,73 @@ function getCurrentInvokeUuid(req) {
881
1200
  return getWebAdapterUuid();
882
1201
  }
883
1202
 
1203
+ //
1204
+ //
1205
+ // Helpers
1206
+ //
1207
+ /**
1208
+ * Safely get a header value from response.
1209
+ * Handles both Express Response and Lambda adapter responses.
1210
+ * Defensive against dd-trace instrumentation issues.
1211
+ */
1212
+ function safeGetHeader(res, name) {
1213
+ try {
1214
+ // Try internal method first (completely bypasses dd-trace)
1215
+ if (typeof res._internalGetHeader === "function") {
1216
+ return res._internalGetHeader(name);
1217
+ }
1218
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1219
+ if (res._headers instanceof Map) {
1220
+ const value = res._headers.get(name.toLowerCase());
1221
+ return value ? String(value) : undefined;
1222
+ }
1223
+ // Fall back to getHeader (more standard than get)
1224
+ if (typeof res.getHeader === "function") {
1225
+ const value = res.getHeader(name);
1226
+ return value ? String(value) : undefined;
1227
+ }
1228
+ // Last resort: try get
1229
+ if (typeof res.get === "function") {
1230
+ const value = res.get(name);
1231
+ return value ? String(value) : undefined;
1232
+ }
1233
+ }
1234
+ catch {
1235
+ // Silently fail - caller will handle missing value
1236
+ }
1237
+ return undefined;
1238
+ }
1239
+ /**
1240
+ * Safely set a header value on response.
1241
+ * Handles both Express Response and Lambda adapter responses.
1242
+ * Defensive against dd-trace instrumentation issues.
1243
+ */
1244
+ function safeSetHeader(res, name, value) {
1245
+ try {
1246
+ // Try internal method first (completely bypasses dd-trace)
1247
+ if (typeof res._internalSetHeader === "function") {
1248
+ res._internalSetHeader(name, value);
1249
+ return;
1250
+ }
1251
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1252
+ if (res._headers instanceof Map) {
1253
+ res._headers.set(name.toLowerCase(), value);
1254
+ return;
1255
+ }
1256
+ // Fall back to setHeader (more standard than set)
1257
+ if (typeof res.setHeader === "function") {
1258
+ res.setHeader(name, value);
1259
+ return;
1260
+ }
1261
+ // Last resort: try set
1262
+ if (typeof res.set === "function") {
1263
+ res.set(name, value);
1264
+ }
1265
+ }
1266
+ catch {
1267
+ // Silently fail - header just won't be set
1268
+ }
1269
+ }
884
1270
  //
885
1271
  //
886
1272
  // Main
@@ -897,36 +1283,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
897
1283
  log$1.warn("decorateResponse called but response is not an object");
898
1284
  return;
899
1285
  }
1286
+ const extRes = res;
900
1287
  try {
901
1288
  //
902
1289
  //
903
1290
  // Decorate Headers
904
1291
  //
905
1292
  // X-Powered-By, override "Express" but nothing else
906
- if (!res.get(HTTP.HEADER.POWERED_BY) ||
907
- res.get(HTTP.HEADER.POWERED_BY) === "Express") {
908
- res.set(HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
1293
+ const currentPoweredBy = safeGetHeader(extRes, HTTP.HEADER.POWERED_BY);
1294
+ if (!currentPoweredBy || currentPoweredBy === "Express") {
1295
+ safeSetHeader(extRes, HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
909
1296
  }
910
1297
  // X-Project-Environment
911
1298
  if (process.env.PROJECT_ENV) {
912
- res.set(HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
1299
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
913
1300
  }
914
1301
  // X-Project-Handler
915
1302
  if (handler) {
916
- res.set(HTTP.HEADER.PROJECT.HANDLER, handler);
1303
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.HANDLER, handler);
917
1304
  }
918
1305
  // X-Project-Invocation
919
1306
  const currentInvoke = getCurrentInvokeUuid();
920
1307
  if (currentInvoke) {
921
- res.set(HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
1308
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
922
1309
  }
923
1310
  // X-Project-Key
924
1311
  if (process.env.PROJECT_KEY) {
925
- res.set(HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
1312
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
926
1313
  }
927
1314
  // X-Project-Version
928
1315
  if (version) {
929
- res.set(HTTP.HEADER.PROJECT.VERSION, version);
1316
+ safeSetHeader(extRes, HTTP.HEADER.PROJECT.VERSION, version);
930
1317
  }
931
1318
  //
932
1319
  //
@@ -986,6 +1373,76 @@ function summarizeResponse(res, extras) {
986
1373
 
987
1374
  // Cast logger to extended interface for runtime features not in type definitions
988
1375
  const logger$1 = log;
1376
+ //
1377
+ //
1378
+ // Helpers - Safe response methods to bypass dd-trace interception
1379
+ //
1380
+ /**
1381
+ * Check if response is a Lambda mock response with direct internal access.
1382
+ */
1383
+ function isLambdaMockResponse(res) {
1384
+ const mock = res;
1385
+ return (mock._headers instanceof Map &&
1386
+ Array.isArray(mock._chunks) &&
1387
+ typeof mock.buildResult === "function");
1388
+ }
1389
+ /**
1390
+ * Safely send a JSON response, avoiding dd-trace interception.
1391
+ * For Lambda mock responses, directly manipulates internal state instead of
1392
+ * using stream methods (write/end) which dd-trace intercepts.
1393
+ */
1394
+ function safeSendJson(res, statusCode, data) {
1395
+ if (isLambdaMockResponse(res)) {
1396
+ // Use internal method to set header (completely bypasses dd-trace)
1397
+ if (typeof res._internalSetHeader === "function") {
1398
+ res._internalSetHeader("content-type", "application/json");
1399
+ }
1400
+ else {
1401
+ // Fall back to direct _headers manipulation
1402
+ res._headers.set("content-type", "application/json");
1403
+ }
1404
+ res.statusCode = statusCode;
1405
+ // Directly push to chunks array instead of using stream write/end
1406
+ const chunk = Buffer.from(JSON.stringify(data));
1407
+ res._chunks.push(chunk);
1408
+ res._headersSent = true;
1409
+ // Signal completion if a promise is waiting
1410
+ if (res._resolve) {
1411
+ res._resolve(res.buildResult());
1412
+ }
1413
+ return;
1414
+ }
1415
+ // Fall back to standard Express methods for real responses
1416
+ res.status(statusCode).json(data);
1417
+ }
1418
+ /**
1419
+ * Safely send a response body, avoiding dd-trace interception.
1420
+ * For Lambda mock responses, directly manipulates internal state instead of
1421
+ * using stream methods (write/end) which dd-trace intercepts.
1422
+ */
1423
+ function safeSend(res, statusCode, body) {
1424
+ if (isLambdaMockResponse(res)) {
1425
+ // Direct internal state manipulation - bypasses dd-trace completely
1426
+ res.statusCode = statusCode;
1427
+ if (body !== undefined) {
1428
+ const chunk = Buffer.from(body);
1429
+ res._chunks.push(chunk);
1430
+ }
1431
+ res._headersSent = true;
1432
+ // Signal completion if a promise is waiting
1433
+ if (res._resolve) {
1434
+ res._resolve(res.buildResult());
1435
+ }
1436
+ return;
1437
+ }
1438
+ // Fall back to standard Express methods for real responses
1439
+ if (body !== undefined) {
1440
+ res.status(statusCode).send(body);
1441
+ }
1442
+ else {
1443
+ res.status(statusCode).send();
1444
+ }
1445
+ }
989
1446
  function expressHandler(handlerOrOptions, optionsOrHandler) {
990
1447
  /* eslint-enable no-redeclare */
991
1448
  let handler;
@@ -1202,30 +1659,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1202
1659
  if (typeof response === "object") {
1203
1660
  if (typeof response.json ===
1204
1661
  "function") {
1205
- res.json(response.json());
1662
+ safeSendJson(res, status, response.json());
1206
1663
  }
1207
1664
  else {
1208
- res.status(status).json(response);
1665
+ safeSendJson(res, status, response);
1209
1666
  }
1210
1667
  }
1211
1668
  else if (typeof response === "string") {
1212
1669
  try {
1213
- res.status(status).json(JSON.parse(response));
1670
+ safeSendJson(res, status, JSON.parse(response));
1214
1671
  }
1215
1672
  catch {
1216
- res.status(status).send(response);
1673
+ safeSend(res, status, response);
1217
1674
  }
1218
1675
  }
1219
1676
  else if (response === true) {
1220
- res.status(HTTP.CODE.CREATED).send();
1677
+ safeSend(res, HTTP.CODE.CREATED);
1221
1678
  }
1222
1679
  else {
1223
- res.status(status).send(response);
1680
+ safeSend(res, status, response);
1224
1681
  }
1225
1682
  }
1226
1683
  else {
1227
1684
  // No response
1228
- res.status(HTTP.CODE.NO_CONTENT).send();
1685
+ safeSend(res, HTTP.CODE.NO_CONTENT);
1229
1686
  }
1230
1687
  }
1231
1688
  else {