@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.
@@ -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');
@@ -81,16 +82,63 @@ class LambdaRequest extends node_stream.Readable {
81
82
  }
82
83
  //
83
84
  //
85
+ // Type Guards
86
+ //
87
+ /**
88
+ * Check if event is a Function URL / HTTP API v2 event.
89
+ */
90
+ function isFunctionUrlEvent(event) {
91
+ return "requestContext" in event && "http" in event.requestContext;
92
+ }
93
+ /**
94
+ * Check if event is an API Gateway REST API v1 event.
95
+ */
96
+ function isApiGatewayV1Event(event) {
97
+ return "httpMethod" in event;
98
+ }
99
+ //
100
+ //
84
101
  // Factory Function
85
102
  //
86
103
  /**
87
- * Create a LambdaRequest from a Function URL event.
104
+ * Create a LambdaRequest from a Lambda event (Function URL, HTTP API v2, or REST API v1).
88
105
  */
89
106
  function createLambdaRequest(event, context) {
90
- // Build URL with query string
91
- const url = event.rawQueryString
92
- ? `${event.rawPath}?${event.rawQueryString}`
93
- : event.rawPath;
107
+ let url;
108
+ let method;
109
+ let protocol;
110
+ let remoteAddress;
111
+ const headers = { ...event.headers };
112
+ if (isFunctionUrlEvent(event)) {
113
+ // Function URL / HTTP API v2 format
114
+ url = event.rawQueryString
115
+ ? `${event.rawPath}?${event.rawQueryString}`
116
+ : event.rawPath;
117
+ method = event.requestContext.http.method;
118
+ protocol = event.requestContext.http.protocol.split("/")[0].toLowerCase();
119
+ remoteAddress = event.requestContext.http.sourceIp;
120
+ // Normalize cookies into Cookie header if not already present
121
+ if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
122
+ headers.cookie = event.cookies.join("; ");
123
+ }
124
+ }
125
+ else if (isApiGatewayV1Event(event)) {
126
+ // API Gateway REST API v1 format
127
+ const queryParams = event.queryStringParameters;
128
+ if (queryParams && Object.keys(queryParams).length > 0) {
129
+ const queryString = new URLSearchParams(queryParams).toString();
130
+ url = `${event.path}?${queryString}`;
131
+ }
132
+ else {
133
+ url = event.path;
134
+ }
135
+ method = event.httpMethod;
136
+ protocol = event.requestContext.protocol.split("/")[0].toLowerCase();
137
+ remoteAddress = event.requestContext.identity.sourceIp;
138
+ }
139
+ else {
140
+ throw new Error("Unsupported Lambda event format. Expected Function URL, HTTP API v2, or REST API v1 event.");
141
+ }
94
142
  // Decode body if present
95
143
  let body = null;
96
144
  if (event.body) {
@@ -98,19 +146,14 @@ function createLambdaRequest(event, context) {
98
146
  ? Buffer.from(event.body, "base64")
99
147
  : Buffer.from(event.body, "utf8");
100
148
  }
101
- // Normalize cookies into Cookie header if not already present
102
- const headers = { ...event.headers };
103
- if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
104
- headers.cookie = event.cookies.join("; ");
105
- }
106
149
  return new LambdaRequest({
107
150
  body,
108
151
  headers,
109
152
  lambdaContext: context,
110
153
  lambdaEvent: event,
111
- method: event.requestContext.http.method,
112
- protocol: event.requestContext.http.protocol.split("/")[0].toLowerCase(),
113
- remoteAddress: event.requestContext.http.sourceIp,
154
+ method,
155
+ protocol,
156
+ remoteAddress,
114
157
  url,
115
158
  });
116
159
  }
@@ -119,6 +162,10 @@ function createLambdaRequest(event, context) {
119
162
  //
120
163
  // Constants
121
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)");
122
169
  const BINARY_CONTENT_TYPE_PATTERNS = [
123
170
  /^application\/octet-stream$/,
124
171
  /^application\/pdf$/,
@@ -143,16 +190,55 @@ class LambdaResponseBuffered extends node_stream.Writable {
143
190
  this.statusMessage = "OK";
144
191
  // Mock socket to satisfy Express/finalhandler checks
145
192
  this.socket = {
146
- cork: () => { },
147
- destroy: () => { },
148
193
  remoteAddress: "127.0.0.1",
149
- uncork: () => { },
150
- writable: true,
151
194
  };
195
+ // Internal state exposed for direct manipulation by safe response methods
196
+ // that need to bypass dd-trace interception of stream methods
152
197
  this._chunks = [];
153
198
  this._headers = new Map();
154
199
  this._headersSent = false;
155
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
+ }
156
242
  }
157
243
  //
158
244
  // Promise-based API for getting final result
@@ -175,14 +261,31 @@ class LambdaResponseBuffered extends node_stream.Writable {
175
261
  // In production, log warning but don't throw to match Express behavior
176
262
  return this;
177
263
  }
178
- this._headers.set(name.toLowerCase(), String(value));
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
+ }
179
274
  return this;
180
275
  }
181
276
  getHeader(name) {
182
277
  return this._headers.get(name.toLowerCase());
183
278
  }
184
279
  removeHeader(name) {
185
- this._headers.delete(name.toLowerCase());
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
+ }
186
289
  }
187
290
  getHeaders() {
188
291
  const headers = {};
@@ -197,6 +300,43 @@ class LambdaResponseBuffered extends node_stream.Writable {
197
300
  getHeaderNames() {
198
301
  return Array.from(this._headers.keys());
199
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
+ }
200
340
  writeHead(statusCode, statusMessageOrHeaders, headers) {
201
341
  this.statusCode = statusCode;
202
342
  let headersToSet;
@@ -224,6 +364,25 @@ class LambdaResponseBuffered extends node_stream.Writable {
224
364
  //
225
365
  // Express compatibility methods
226
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
+ }
227
386
  status(code) {
228
387
  this.statusCode = code;
229
388
  return this;
@@ -240,6 +399,26 @@ class LambdaResponseBuffered extends node_stream.Writable {
240
399
  this.end(body);
241
400
  return this;
242
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
+ }
243
422
  //
244
423
  // Writable stream implementation
245
424
  //
@@ -300,6 +479,14 @@ class LambdaResponseBuffered extends node_stream.Writable {
300
479
  }
301
480
  }
302
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)");
303
490
  //
304
491
  //
305
492
  // LambdaResponseStreaming Class
@@ -315,17 +502,56 @@ class LambdaResponseStreaming extends node_stream.Writable {
315
502
  this.statusMessage = "OK";
316
503
  // Mock socket to satisfy Express/finalhandler checks
317
504
  this.socket = {
318
- cork: () => { },
319
- destroy: () => { },
320
505
  remoteAddress: "127.0.0.1",
321
- uncork: () => { },
322
- writable: true,
323
506
  };
507
+ // Internal state exposed for direct manipulation by safe response methods
508
+ // that need to bypass dd-trace interception
324
509
  this._headers = new Map();
325
510
  this._headersSent = false;
326
511
  this._pendingWrites = [];
327
512
  this._wrappedStream = null;
328
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
+ }
329
555
  }
330
556
  //
331
557
  // Header management
@@ -336,7 +562,16 @@ class LambdaResponseStreaming extends node_stream.Writable {
336
562
  // Headers cannot be changed after body starts
337
563
  return this;
338
564
  }
339
- this._headers.set(name.toLowerCase(), String(value));
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
+ }
340
575
  return this;
341
576
  }
342
577
  getHeader(name) {
@@ -344,7 +579,15 @@ class LambdaResponseStreaming extends node_stream.Writable {
344
579
  }
345
580
  removeHeader(name) {
346
581
  if (!this._headersSent) {
347
- this._headers.delete(name.toLowerCase());
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
+ }
348
591
  }
349
592
  }
350
593
  getHeaders() {
@@ -360,6 +603,43 @@ class LambdaResponseStreaming extends node_stream.Writable {
360
603
  getHeaderNames() {
361
604
  return Array.from(this._headers.keys());
362
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
+ }
363
643
  writeHead(statusCode, statusMessageOrHeaders, headers) {
364
644
  if (this._headersSent) {
365
645
  return this;
@@ -411,6 +691,25 @@ class LambdaResponseStreaming extends node_stream.Writable {
411
691
  //
412
692
  // Express compatibility methods
413
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
+ }
414
713
  status(code) {
415
714
  this.statusCode = code;
416
715
  return this;
@@ -427,6 +726,26 @@ class LambdaResponseStreaming extends node_stream.Writable {
427
726
  this.end(body);
428
727
  return this;
429
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
+ }
430
749
  //
431
750
  // Writable stream implementation
432
751
  //
@@ -883,6 +1202,73 @@ function getCurrentInvokeUuid(req) {
883
1202
  return getWebAdapterUuid();
884
1203
  }
885
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
+ }
886
1272
  //
887
1273
  //
888
1274
  // Main
@@ -899,36 +1285,37 @@ const decorateResponse = (res, { handler = "", version = process.env.PROJECT_VER
899
1285
  log.warn("decorateResponse called but response is not an object");
900
1286
  return;
901
1287
  }
1288
+ const extRes = res;
902
1289
  try {
903
1290
  //
904
1291
  //
905
1292
  // Decorate Headers
906
1293
  //
907
1294
  // X-Powered-By, override "Express" but nothing else
908
- if (!res.get(kit.HTTP.HEADER.POWERED_BY) ||
909
- res.get(kit.HTTP.HEADER.POWERED_BY) === "Express") {
910
- res.set(kit.HTTP.HEADER.POWERED_BY, kit.JAYPIE.LIB.EXPRESS);
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);
911
1298
  }
912
1299
  // X-Project-Environment
913
1300
  if (process.env.PROJECT_ENV) {
914
- res.set(kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
1301
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
915
1302
  }
916
1303
  // X-Project-Handler
917
1304
  if (handler) {
918
- res.set(kit.HTTP.HEADER.PROJECT.HANDLER, handler);
1305
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.HANDLER, handler);
919
1306
  }
920
1307
  // X-Project-Invocation
921
1308
  const currentInvoke = getCurrentInvokeUuid();
922
1309
  if (currentInvoke) {
923
- res.set(kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
1310
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
924
1311
  }
925
1312
  // X-Project-Key
926
1313
  if (process.env.PROJECT_KEY) {
927
- res.set(kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
1314
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
928
1315
  }
929
1316
  // X-Project-Version
930
1317
  if (version) {
931
- res.set(kit.HTTP.HEADER.PROJECT.VERSION, version);
1318
+ safeSetHeader(extRes, kit.HTTP.HEADER.PROJECT.VERSION, version);
932
1319
  }
933
1320
  //
934
1321
  //
@@ -988,6 +1375,76 @@ function summarizeResponse(res, extras) {
988
1375
 
989
1376
  // Cast logger to extended interface for runtime features not in type definitions
990
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
+ }
991
1448
  function expressHandler(handlerOrOptions, optionsOrHandler) {
992
1449
  /* eslint-enable no-redeclare */
993
1450
  let handler;
@@ -1204,30 +1661,30 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1204
1661
  if (typeof response === "object") {
1205
1662
  if (typeof response.json ===
1206
1663
  "function") {
1207
- res.json(response.json());
1664
+ safeSendJson(res, status, response.json());
1208
1665
  }
1209
1666
  else {
1210
- res.status(status).json(response);
1667
+ safeSendJson(res, status, response);
1211
1668
  }
1212
1669
  }
1213
1670
  else if (typeof response === "string") {
1214
1671
  try {
1215
- res.status(status).json(JSON.parse(response));
1672
+ safeSendJson(res, status, JSON.parse(response));
1216
1673
  }
1217
1674
  catch {
1218
- res.status(status).send(response);
1675
+ safeSend(res, status, response);
1219
1676
  }
1220
1677
  }
1221
1678
  else if (response === true) {
1222
- res.status(kit.HTTP.CODE.CREATED).send();
1679
+ safeSend(res, kit.HTTP.CODE.CREATED);
1223
1680
  }
1224
1681
  else {
1225
- res.status(status).send(response);
1682
+ safeSend(res, status, response);
1226
1683
  }
1227
1684
  }
1228
1685
  else {
1229
1686
  // No response
1230
- res.status(kit.HTTP.CODE.NO_CONTENT).send();
1687
+ safeSend(res, kit.HTTP.CODE.NO_CONTENT);
1231
1688
  }
1232
1689
  }
1233
1690
  else {