@jaypie/express 1.2.4-rc8 → 1.2.4

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.
@@ -5,8 +5,8 @@ var node_http = require('node:http');
5
5
  var errors = require('@jaypie/errors');
6
6
  var kit = require('@jaypie/kit');
7
7
  var expressCors = require('cors');
8
- var logger$2 = require('@jaypie/logger');
9
8
  var aws = require('@jaypie/aws');
9
+ var logger$2 = require('@jaypie/logger');
10
10
  var datadog = require('@jaypie/datadog');
11
11
 
12
12
  //
@@ -34,6 +34,15 @@ class LambdaRequest extends node_stream.Readable {
34
34
  this.path = options.url.split("?")[0];
35
35
  this.headers = this.normalizeHeaders(options.headers);
36
36
  this.bodyBuffer = options.body ?? null;
37
+ // Parse query string from URL
38
+ const queryIndex = options.url.indexOf("?");
39
+ if (queryIndex !== -1) {
40
+ const queryString = options.url.slice(queryIndex + 1);
41
+ const params = new URLSearchParams(queryString);
42
+ for (const [key, value] of params) {
43
+ this.query[key] = value;
44
+ }
45
+ }
37
46
  // Store Lambda context
38
47
  this._lambdaContext = options.lambdaContext;
39
48
  this._lambdaEvent = options.lambdaEvent;
@@ -44,6 +53,18 @@ class LambdaRequest extends node_stream.Readable {
44
53
  remoteAddress: options.remoteAddress,
45
54
  };
46
55
  this.connection = this.socket;
56
+ // Schedule body push for next tick to ensure stream is ready
57
+ // This is needed for body parsers that consume the stream
58
+ if (this.bodyBuffer && this.bodyBuffer.length > 0) {
59
+ process.nextTick(() => {
60
+ if (!this.bodyPushed) {
61
+ this.push(this.bodyBuffer);
62
+ this.push(null);
63
+ this.bodyPushed = true;
64
+ this.complete = true;
65
+ }
66
+ });
67
+ }
47
68
  }
48
69
  //
49
70
  // Readable stream implementation
@@ -145,6 +166,11 @@ function createLambdaRequest(event, context) {
145
166
  body = event.isBase64Encoded
146
167
  ? Buffer.from(event.body, "base64")
147
168
  : Buffer.from(event.body, "utf8");
169
+ // Add content-length header if not present (required for body parsers)
170
+ const hasContentLength = Object.keys(headers).some((k) => k.toLowerCase() === "content-length");
171
+ if (!hasContentLength) {
172
+ headers["content-length"] = String(body.length);
173
+ }
148
174
  }
149
175
  return new LambdaRequest({
150
176
  body,
@@ -162,6 +188,9 @@ function createLambdaRequest(event, context) {
162
188
  //
163
189
  // Constants
164
190
  //
191
+ // Symbol to identify Lambda mock responses. Uses Symbol.for() to ensure
192
+ // the same symbol is used across bundles/realms. Survives prototype manipulation.
193
+ const JAYPIE_LAMBDA_MOCK = Symbol.for("@jaypie/express/LambdaMock");
165
194
  // Get Node's internal kOutHeaders symbol from ServerResponse prototype.
166
195
  // This is needed for compatibility with Datadog dd-trace instrumentation,
167
196
  // which patches HTTP methods and expects this internal state to exist.
@@ -192,22 +221,90 @@ class LambdaResponseBuffered extends node_stream.Writable {
192
221
  this.socket = {
193
222
  remoteAddress: "127.0.0.1",
194
223
  };
224
+ // Internal state exposed for direct manipulation by safe response methods
225
+ // that need to bypass dd-trace interception of stream methods
195
226
  this._chunks = [];
227
+ this._ended = false; // Track ended state since writableEnded is lost after prototype change
196
228
  this._headers = new Map();
197
229
  this._headersSent = false;
198
230
  this._resolve = null;
231
+ // Mark as Lambda mock response for identification in expressHandler
232
+ this[JAYPIE_LAMBDA_MOCK] = true;
199
233
  // Initialize Node's internal kOutHeaders for dd-trace compatibility.
200
234
  // dd-trace patches HTTP methods and expects this internal state.
201
235
  if (kOutHeaders$1) {
202
236
  this[kOutHeaders$1] = Object.create(null);
203
237
  }
238
+ // CRITICAL: Define key methods as instance properties to survive Express's
239
+ // setPrototypeOf(res, app.response) in middleware/init.js which would
240
+ // otherwise replace our prototype with ServerResponse.prototype.
241
+ // Instance properties take precedence over prototype properties.
242
+ this.getHeader = this.getHeader.bind(this);
243
+ this.setHeader = this.setHeader.bind(this);
244
+ this.removeHeader = this.removeHeader.bind(this);
245
+ this.hasHeader = this.hasHeader.bind(this);
246
+ this.getHeaders = this.getHeaders.bind(this);
247
+ this.getHeaderNames = this.getHeaderNames.bind(this);
248
+ this.writeHead = this.writeHead.bind(this);
249
+ this.get = this.get.bind(this);
250
+ this.set = this.set.bind(this);
251
+ this.status = this.status.bind(this);
252
+ this.json = this.json.bind(this);
253
+ this.send = this.send.bind(this);
254
+ this.vary = this.vary.bind(this);
255
+ this.end = this.end.bind(this);
256
+ this.write = this.write.bind(this);
257
+ // Also bind internal Writable methods that are called via prototype chain
258
+ this._write = this._write.bind(this);
259
+ this._final = this._final.bind(this);
260
+ // Bind result-building methods
261
+ this.getResult = this.getResult.bind(this);
262
+ this.buildResult = this.buildResult.bind(this);
263
+ this.isBinaryContentType = this.isBinaryContentType.bind(this);
264
+ }
265
+ //
266
+ // Internal bypass methods - completely avoid prototype chain lookup
267
+ // These directly access _headers Map, safe from dd-trace interception
268
+ //
269
+ _internalGetHeader(name) {
270
+ const value = this._headers.get(name.toLowerCase());
271
+ return value ? String(value) : undefined;
272
+ }
273
+ _internalSetHeader(name, value) {
274
+ if (!this._headersSent) {
275
+ const lowerName = name.toLowerCase();
276
+ this._headers.set(lowerName, value);
277
+ // Also sync kOutHeaders for any code that expects it
278
+ if (kOutHeaders$1) {
279
+ const outHeaders = this[kOutHeaders$1];
280
+ if (outHeaders) {
281
+ outHeaders[lowerName] = [name, value];
282
+ }
283
+ }
284
+ }
285
+ }
286
+ _internalHasHeader(name) {
287
+ return this._headers.has(name.toLowerCase());
288
+ }
289
+ _internalRemoveHeader(name) {
290
+ if (!this._headersSent) {
291
+ const lowerName = name.toLowerCase();
292
+ this._headers.delete(lowerName);
293
+ if (kOutHeaders$1) {
294
+ const outHeaders = this[kOutHeaders$1];
295
+ if (outHeaders) {
296
+ delete outHeaders[lowerName];
297
+ }
298
+ }
299
+ }
204
300
  }
205
301
  //
206
302
  // Promise-based API for getting final result
207
303
  //
208
304
  getResult() {
209
305
  return new Promise((resolve) => {
210
- if (this.writableEnded) {
306
+ // Use _ended instead of writableEnded since Express's setPrototypeOf breaks the getter
307
+ if (this._ended) {
211
308
  resolve(this.buildResult());
212
309
  }
213
310
  else {
@@ -265,36 +362,40 @@ class LambdaResponseBuffered extends node_stream.Writable {
265
362
  /**
266
363
  * Proxy for direct header access (e.g., res.headers['content-type']).
267
364
  * Required for compatibility with middleware like helmet that access headers directly.
365
+ * Uses direct _headers access to bypass dd-trace interception.
268
366
  */
269
367
  get headers() {
270
368
  return new Proxy({}, {
271
369
  deleteProperty: (_target, prop) => {
272
- this.removeHeader(String(prop));
370
+ this._headers.delete(String(prop).toLowerCase());
273
371
  return true;
274
372
  },
275
373
  get: (_target, prop) => {
276
374
  if (typeof prop === "symbol")
277
375
  return undefined;
278
- return this.getHeader(String(prop));
376
+ return this._headers.get(String(prop).toLowerCase());
279
377
  },
280
378
  getOwnPropertyDescriptor: (_target, prop) => {
281
- if (this.hasHeader(String(prop))) {
379
+ const lowerProp = String(prop).toLowerCase();
380
+ if (this._headers.has(lowerProp)) {
282
381
  return {
283
382
  configurable: true,
284
383
  enumerable: true,
285
- value: this.getHeader(String(prop)),
384
+ value: this._headers.get(lowerProp),
286
385
  };
287
386
  }
288
387
  return undefined;
289
388
  },
290
389
  has: (_target, prop) => {
291
- return this.hasHeader(String(prop));
390
+ return this._headers.has(String(prop).toLowerCase());
292
391
  },
293
392
  ownKeys: () => {
294
- return this.getHeaderNames();
393
+ return Array.from(this._headers.keys());
295
394
  },
296
395
  set: (_target, prop, value) => {
297
- this.setHeader(String(prop), value);
396
+ if (!this._headersSent) {
397
+ this._headers.set(String(prop).toLowerCase(), value);
398
+ }
298
399
  return true;
299
400
  },
300
401
  });
@@ -311,9 +412,10 @@ class LambdaResponseBuffered extends node_stream.Writable {
311
412
  headersToSet = statusMessageOrHeaders;
312
413
  }
313
414
  if (headersToSet) {
415
+ // Use direct _headers access to bypass dd-trace interception
314
416
  for (const [key, value] of Object.entries(headersToSet)) {
315
417
  if (value !== undefined) {
316
- this.setHeader(key, value);
418
+ this._headers.set(key.toLowerCase(), String(value));
317
419
  }
318
420
  }
319
421
  }
@@ -350,7 +452,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
350
452
  return this;
351
453
  }
352
454
  json(data) {
353
- this.setHeader("content-type", "application/json");
455
+ // Use direct _headers access to bypass dd-trace interception
456
+ this._headers.set("content-type", "application/json");
354
457
  this.end(JSON.stringify(data));
355
458
  return this;
356
459
  }
@@ -364,11 +467,12 @@ class LambdaResponseBuffered extends node_stream.Writable {
364
467
  /**
365
468
  * Add a field to the Vary response header.
366
469
  * Used by CORS middleware to indicate response varies by Origin.
470
+ * Uses direct _headers access to bypass dd-trace interception.
367
471
  */
368
472
  vary(field) {
369
- const existing = this.getHeader("vary");
473
+ const existing = this._headers.get("vary");
370
474
  if (!existing) {
371
- this.setHeader("vary", field);
475
+ this._headers.set("vary", field);
372
476
  }
373
477
  else {
374
478
  // Append to existing Vary header if field not already present
@@ -376,7 +480,7 @@ class LambdaResponseBuffered extends node_stream.Writable {
376
480
  .split(",")
377
481
  .map((f) => f.trim().toLowerCase());
378
482
  if (!fields.includes(field.toLowerCase())) {
379
- this.setHeader("vary", `${existing}, ${field}`);
483
+ this._headers.set("vary", `${existing}, ${field}`);
380
484
  }
381
485
  }
382
486
  return this;
@@ -394,6 +498,7 @@ class LambdaResponseBuffered extends node_stream.Writable {
394
498
  callback();
395
499
  }
396
500
  _final(callback) {
501
+ this._ended = true;
397
502
  if (this._resolve) {
398
503
  this._resolve(this.buildResult());
399
504
  }
@@ -404,7 +509,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
404
509
  //
405
510
  buildResult() {
406
511
  const body = Buffer.concat(this._chunks);
407
- const contentType = this.getHeader("content-type") || "";
512
+ // Use direct _headers access to bypass dd-trace interception
513
+ const contentType = this._headers.get("content-type") || "";
408
514
  // Determine if response should be base64 encoded
409
515
  const isBase64Encoded = this.isBinaryContentType(contentType);
410
516
  // Build headers object
@@ -466,6 +572,8 @@ class LambdaResponseStreaming extends node_stream.Writable {
466
572
  this.socket = {
467
573
  remoteAddress: "127.0.0.1",
468
574
  };
575
+ // Internal state exposed for direct manipulation by safe response methods
576
+ // that need to bypass dd-trace interception
469
577
  this._headers = new Map();
470
578
  this._headersSent = false;
471
579
  this._pendingWrites = [];
@@ -476,6 +584,65 @@ class LambdaResponseStreaming extends node_stream.Writable {
476
584
  if (kOutHeaders) {
477
585
  this[kOutHeaders] = Object.create(null);
478
586
  }
587
+ // CRITICAL: Define key methods as instance properties to survive Express's
588
+ // setPrototypeOf(res, app.response) in middleware/init.js which would
589
+ // otherwise replace our prototype with ServerResponse.prototype.
590
+ // Instance properties take precedence over prototype properties.
591
+ this.getHeader = this.getHeader.bind(this);
592
+ this.setHeader = this.setHeader.bind(this);
593
+ this.removeHeader = this.removeHeader.bind(this);
594
+ this.hasHeader = this.hasHeader.bind(this);
595
+ this.getHeaders = this.getHeaders.bind(this);
596
+ this.getHeaderNames = this.getHeaderNames.bind(this);
597
+ this.writeHead = this.writeHead.bind(this);
598
+ this.flushHeaders = this.flushHeaders.bind(this);
599
+ this.get = this.get.bind(this);
600
+ this.set = this.set.bind(this);
601
+ this.status = this.status.bind(this);
602
+ this.json = this.json.bind(this);
603
+ this.send = this.send.bind(this);
604
+ this.vary = this.vary.bind(this);
605
+ this.end = this.end.bind(this);
606
+ this.write = this.write.bind(this);
607
+ // Also bind internal Writable methods that are called via prototype chain
608
+ this._write = this._write.bind(this);
609
+ this._final = this._final.bind(this);
610
+ }
611
+ //
612
+ // Internal bypass methods - completely avoid prototype chain lookup
613
+ // These directly access _headers Map, safe from dd-trace interception
614
+ //
615
+ _internalGetHeader(name) {
616
+ const value = this._headers.get(name.toLowerCase());
617
+ return value ? String(value) : undefined;
618
+ }
619
+ _internalSetHeader(name, value) {
620
+ if (!this._headersSent) {
621
+ const lowerName = name.toLowerCase();
622
+ this._headers.set(lowerName, value);
623
+ // Also sync kOutHeaders for any code that expects it
624
+ if (kOutHeaders) {
625
+ const outHeaders = this[kOutHeaders];
626
+ if (outHeaders) {
627
+ outHeaders[lowerName] = [name, value];
628
+ }
629
+ }
630
+ }
631
+ }
632
+ _internalHasHeader(name) {
633
+ return this._headers.has(name.toLowerCase());
634
+ }
635
+ _internalRemoveHeader(name) {
636
+ if (!this._headersSent) {
637
+ const lowerName = name.toLowerCase();
638
+ this._headers.delete(lowerName);
639
+ if (kOutHeaders) {
640
+ const outHeaders = this[kOutHeaders];
641
+ if (outHeaders) {
642
+ delete outHeaders[lowerName];
643
+ }
644
+ }
645
+ }
479
646
  }
480
647
  //
481
648
  // Header management
@@ -530,36 +697,42 @@ class LambdaResponseStreaming extends node_stream.Writable {
530
697
  /**
531
698
  * Proxy for direct header access (e.g., res.headers['content-type']).
532
699
  * Required for compatibility with middleware like helmet that access headers directly.
700
+ * Uses direct _headers access to bypass dd-trace interception.
533
701
  */
534
702
  get headers() {
535
703
  return new Proxy({}, {
536
704
  deleteProperty: (_target, prop) => {
537
- this.removeHeader(String(prop));
705
+ if (!this._headersSent) {
706
+ this._headers.delete(String(prop).toLowerCase());
707
+ }
538
708
  return true;
539
709
  },
540
710
  get: (_target, prop) => {
541
711
  if (typeof prop === "symbol")
542
712
  return undefined;
543
- return this.getHeader(String(prop));
713
+ return this._headers.get(String(prop).toLowerCase());
544
714
  },
545
715
  getOwnPropertyDescriptor: (_target, prop) => {
546
- if (this.hasHeader(String(prop))) {
716
+ const lowerProp = String(prop).toLowerCase();
717
+ if (this._headers.has(lowerProp)) {
547
718
  return {
548
719
  configurable: true,
549
720
  enumerable: true,
550
- value: this.getHeader(String(prop)),
721
+ value: this._headers.get(lowerProp),
551
722
  };
552
723
  }
553
724
  return undefined;
554
725
  },
555
726
  has: (_target, prop) => {
556
- return this.hasHeader(String(prop));
727
+ return this._headers.has(String(prop).toLowerCase());
557
728
  },
558
729
  ownKeys: () => {
559
- return this.getHeaderNames();
730
+ return Array.from(this._headers.keys());
560
731
  },
561
732
  set: (_target, prop, value) => {
562
- this.setHeader(String(prop), value);
733
+ if (!this._headersSent) {
734
+ this._headers.set(String(prop).toLowerCase(), value);
735
+ }
563
736
  return true;
564
737
  },
565
738
  });
@@ -579,9 +752,10 @@ class LambdaResponseStreaming extends node_stream.Writable {
579
752
  headersToSet = statusMessageOrHeaders;
580
753
  }
581
754
  if (headersToSet) {
755
+ // Use direct _headers access to bypass dd-trace interception
582
756
  for (const [key, value] of Object.entries(headersToSet)) {
583
757
  if (value !== undefined) {
584
- this.setHeader(key, value);
758
+ this._headers.set(key.toLowerCase(), String(value));
585
759
  }
586
760
  }
587
761
  }
@@ -639,7 +813,8 @@ class LambdaResponseStreaming extends node_stream.Writable {
639
813
  return this;
640
814
  }
641
815
  json(data) {
642
- this.setHeader("content-type", "application/json");
816
+ // Use direct _headers access to bypass dd-trace interception
817
+ this._headers.set("content-type", "application/json");
643
818
  this.end(JSON.stringify(data));
644
819
  return this;
645
820
  }
@@ -653,11 +828,12 @@ class LambdaResponseStreaming extends node_stream.Writable {
653
828
  /**
654
829
  * Add a field to the Vary response header.
655
830
  * Used by CORS middleware to indicate response varies by Origin.
831
+ * Uses direct _headers access to bypass dd-trace interception.
656
832
  */
657
833
  vary(field) {
658
- const existing = this.getHeader("vary");
834
+ const existing = this._headers.get("vary");
659
835
  if (!existing) {
660
- this.setHeader("vary", field);
836
+ this._headers.set("vary", field);
661
837
  }
662
838
  else {
663
839
  // Append to existing Vary header if field not already present
@@ -665,7 +841,7 @@ class LambdaResponseStreaming extends node_stream.Writable {
665
841
  .split(",")
666
842
  .map((f) => f.trim().toLowerCase());
667
843
  if (!fields.includes(field.toLowerCase())) {
668
- this.setHeader("vary", `${existing}, ${field}`);
844
+ this._headers.set("vary", `${existing}, ${field}`);
669
845
  }
670
846
  }
671
847
  return this;
@@ -763,6 +939,7 @@ function runExpressApp(app, req, res) {
763
939
  */
764
940
  function createLambdaHandler(app, _options) {
765
941
  return async (event, context) => {
942
+ let result;
766
943
  try {
767
944
  // Set current invoke for getCurrentInvokeUuid
768
945
  setCurrentInvoke(event, context);
@@ -772,8 +949,38 @@ function createLambdaHandler(app, _options) {
772
949
  const res = new LambdaResponseBuffered();
773
950
  // Run Express app
774
951
  await runExpressApp(app, req, res);
775
- // Return Lambda response
776
- return res.getResult();
952
+ // Get Lambda response - await explicitly to ensure we have the result
953
+ result = await res.getResult();
954
+ // Debug: Log the response before returning
955
+ console.log("[createLambdaHandler] Returning response:", JSON.stringify({
956
+ statusCode: result.statusCode,
957
+ headers: result.headers,
958
+ bodyLength: result.body?.length,
959
+ isBase64Encoded: result.isBase64Encoded,
960
+ }));
961
+ return result;
962
+ }
963
+ catch (error) {
964
+ // Log any unhandled errors
965
+ console.error("[createLambdaHandler] Unhandled error:", error);
966
+ if (error instanceof Error) {
967
+ console.error("[createLambdaHandler] Stack:", error.stack);
968
+ }
969
+ // Return a proper error response instead of throwing
970
+ return {
971
+ statusCode: 500,
972
+ headers: { "content-type": "application/json" },
973
+ body: JSON.stringify({
974
+ errors: [
975
+ {
976
+ status: 500,
977
+ title: "Internal Server Error",
978
+ detail: error instanceof Error ? error.message : "Unknown error occurred",
979
+ },
980
+ ],
981
+ }),
982
+ isBase64Encoded: false,
983
+ };
777
984
  }
778
985
  finally {
779
986
  // Clear current invoke context
@@ -911,7 +1118,7 @@ const corsHelper = (config = {}) => {
911
1118
  };
912
1119
  return expressCors(options);
913
1120
  };
914
- var cors = (config) => {
1121
+ var cors_helper = (config) => {
915
1122
  const cors = corsHelper(config);
916
1123
  return (req, res, next) => {
917
1124
  cors(req, res, (error) => {
@@ -926,154 +1133,10 @@ var cors = (config) => {
926
1133
  };
927
1134
  };
928
1135
 
929
- //
930
- //
931
- // Constants
932
- //
933
- const DEFAULT_PORT = 8080;
934
- //
935
- //
936
- // Main
937
- //
938
- /**
939
- * Creates and starts an Express server with standard Jaypie middleware.
940
- *
941
- * Features:
942
- * - CORS handling (configurable)
943
- * - JSON body parsing
944
- * - Listens on PORT env var (default 8080)
945
- *
946
- * Usage:
947
- * ```ts
948
- * import express from "express";
949
- * import { createServer, expressHandler } from "@jaypie/express";
950
- *
951
- * const app = express();
952
- *
953
- * app.get("/", expressHandler(async (req, res) => {
954
- * return { message: "Hello World" };
955
- * }));
956
- *
957
- * const { server, port } = await createServer(app);
958
- * console.log(`Server running on port ${port}`);
959
- * ```
960
- *
961
- * @param app - Express application instance
962
- * @param options - Server configuration options
963
- * @returns Promise resolving to server instance and port
964
- */
965
- async function createServer(app, options = {}) {
966
- const { cors: corsConfig, jsonLimit = "1mb", middleware = [], port: portOption, } = options;
967
- // Determine port
968
- const port = typeof portOption === "string"
969
- ? parseInt(portOption, 10)
970
- : (portOption ?? parseInt(process.env.PORT || String(DEFAULT_PORT), 10));
971
- // Apply CORS middleware (unless explicitly disabled)
972
- if (corsConfig !== false) {
973
- app.use(cors(corsConfig));
974
- }
975
- // Apply JSON body parser
976
- // Note: We use dynamic import to avoid requiring express as a direct dependency
977
- const express = await import('express');
978
- app.use(express.json({ limit: jsonLimit }));
979
- // Apply additional middleware
980
- for (const mw of middleware) {
981
- app.use(mw);
982
- }
983
- // Start server
984
- return new Promise((resolve, reject) => {
985
- try {
986
- const server = app.listen(port, () => {
987
- // Get the actual port (important when port 0 is passed to get an ephemeral port)
988
- const address = server.address();
989
- const actualPort = address?.port ?? port;
990
- logger$2.log.info(`Server listening on port ${actualPort}`);
991
- resolve({ port: actualPort, server });
992
- });
993
- server.on("error", (error) => {
994
- logger$2.log.error("Server error", { error });
995
- reject(error);
996
- });
997
- }
998
- catch (error) {
999
- reject(error);
1000
- }
1001
- });
1002
- }
1003
-
1004
- //
1005
- //
1006
- // Constants
1007
- //
1008
- const HEADER_AMZN_REQUEST_ID$1 = "x-amzn-request-id";
1009
- const ENV_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID";
1010
- //
1011
- //
1012
- // Helper Functions
1013
- //
1014
- /**
1015
- * Extract request ID from X-Ray trace ID environment variable
1016
- * Format: Root=1-5e6b4a90-example;Parent=example;Sampled=1
1017
- * We extract the trace ID from the Root segment
1018
- */
1019
- function parseTraceId(traceId) {
1020
- if (!traceId)
1021
- return undefined;
1022
- // Extract the Root segment (format: Root=1-{timestamp}-{uuid})
1023
- const rootMatch = traceId.match(/Root=([^;]+)/);
1024
- if (rootMatch && rootMatch[1]) {
1025
- return rootMatch[1];
1026
- }
1027
- return undefined;
1028
- }
1029
- //
1030
- //
1031
- // Main
1032
- //
1033
- /**
1034
- * Get the current invoke UUID from Lambda Web Adapter context.
1035
- * This function extracts the request ID from either:
1036
- * 1. The x-amzn-request-id header (set by Lambda Web Adapter)
1037
- * 2. The _X_AMZN_TRACE_ID environment variable (set by Lambda runtime)
1038
- *
1039
- * @param req - Optional Express request object to extract headers from
1040
- * @returns The AWS request ID or undefined if not in Lambda context
1041
- */
1042
- function getWebAdapterUuid(req) {
1043
- // First, try to get from request headers
1044
- if (req && req.headers) {
1045
- const headerValue = req.headers[HEADER_AMZN_REQUEST_ID$1];
1046
- if (headerValue) {
1047
- return Array.isArray(headerValue) ? headerValue[0] : headerValue;
1048
- }
1049
- }
1050
- // Fall back to environment variable (X-Ray trace ID)
1051
- const traceId = process.env[ENV_AMZN_TRACE_ID];
1052
- if (traceId) {
1053
- return parseTraceId(traceId);
1054
- }
1055
- return undefined;
1056
- }
1057
-
1058
- //
1059
- //
1060
- // Constants
1061
- //
1062
- const HEADER_AMZN_REQUEST_ID = "x-amzn-request-id";
1063
1136
  //
1064
1137
  //
1065
1138
  // Helper Functions
1066
1139
  //
1067
- /**
1068
- * Detect if we're running in Lambda Web Adapter mode.
1069
- * Web Adapter sets the x-amzn-request-id header on requests.
1070
- */
1071
- function isWebAdapterMode(req) {
1072
- if (req && req.headers && req.headers[HEADER_AMZN_REQUEST_ID]) {
1073
- return true;
1074
- }
1075
- return false;
1076
- }
1077
1140
  /**
1078
1141
  * Get UUID from Jaypie Lambda adapter context.
1079
1142
  * This is set by createLambdaHandler/createLambdaStreamHandler.
@@ -1090,8 +1153,12 @@ function getJaypieAdapterUuid() {
1090
1153
  * The Jaypie adapter attaches _lambdaContext to the request.
1091
1154
  */
1092
1155
  function getRequestContextUuid(req) {
1093
- if (req && req._lambdaContext?.awsRequestId) {
1094
- return req._lambdaContext.awsRequestId;
1156
+ if (req && req._lambdaContext) {
1157
+ const lambdaContext = req
1158
+ ._lambdaContext;
1159
+ if (lambdaContext.awsRequestId) {
1160
+ return lambdaContext.awsRequestId;
1161
+ }
1095
1162
  }
1096
1163
  return undefined;
1097
1164
  }
@@ -1101,29 +1168,20 @@ function getRequestContextUuid(req) {
1101
1168
  //
1102
1169
  /**
1103
1170
  * Get the current invoke UUID from Lambda context.
1104
- * Works with Jaypie Lambda adapter and Lambda Web Adapter mode.
1171
+ * Works with Jaypie Lambda adapter (createLambdaHandler/createLambdaStreamHandler).
1105
1172
  *
1106
1173
  * @param req - Optional Express request object. Used to extract context
1107
- * from Web Adapter headers or Jaypie adapter's _lambdaContext.
1174
+ * from Jaypie adapter's _lambdaContext.
1108
1175
  * @returns The AWS request ID or undefined if not in Lambda context
1109
1176
  */
1110
1177
  function getCurrentInvokeUuid(req) {
1111
- // Priority 1: Web Adapter mode (header-based)
1112
- if (isWebAdapterMode(req)) {
1113
- return getWebAdapterUuid(req);
1114
- }
1115
- // Priority 2: Request has Lambda context attached (Jaypie adapter)
1178
+ // Priority 1: Request has Lambda context attached (Jaypie adapter)
1116
1179
  const requestContextUuid = getRequestContextUuid(req);
1117
1180
  if (requestContextUuid) {
1118
1181
  return requestContextUuid;
1119
1182
  }
1120
- // Priority 3: Global context from Jaypie adapter
1121
- const jaypieAdapterUuid = getJaypieAdapterUuid();
1122
- if (jaypieAdapterUuid) {
1123
- return jaypieAdapterUuid;
1124
- }
1125
- // Fallback: Web Adapter env var
1126
- return getWebAdapterUuid();
1183
+ // Priority 2: Global context from Jaypie adapter
1184
+ return getJaypieAdapterUuid();
1127
1185
  }
1128
1186
 
1129
1187
  //
@@ -1137,7 +1195,11 @@ function getCurrentInvokeUuid(req) {
1137
1195
  */
1138
1196
  function safeGetHeader(res, name) {
1139
1197
  try {
1140
- // Try direct _headers access first (Lambda adapter, avoids dd-trace)
1198
+ // Try internal method first (completely bypasses dd-trace)
1199
+ if (typeof res._internalGetHeader === "function") {
1200
+ return res._internalGetHeader(name);
1201
+ }
1202
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1141
1203
  if (res._headers instanceof Map) {
1142
1204
  const value = res._headers.get(name.toLowerCase());
1143
1205
  return value ? String(value) : undefined;
@@ -1165,7 +1227,12 @@ function safeGetHeader(res, name) {
1165
1227
  */
1166
1228
  function safeSetHeader(res, name, value) {
1167
1229
  try {
1168
- // Try direct _headers access first (Lambda adapter, avoids dd-trace)
1230
+ // Try internal method first (completely bypasses dd-trace)
1231
+ if (typeof res._internalSetHeader === "function") {
1232
+ res._internalSetHeader(name, value);
1233
+ return;
1234
+ }
1235
+ // Fall back to _headers Map access (Lambda adapter, avoids dd-trace)
1169
1236
  if (res._headers instanceof Map) {
1170
1237
  res._headers.set(name.toLowerCase(), value);
1171
1238
  return;
@@ -1295,21 +1362,41 @@ const logger$1 = logger$2.log;
1295
1362
  // Helpers - Safe response methods to bypass dd-trace interception
1296
1363
  //
1297
1364
  /**
1298
- * Check if response is a Lambda mock response with direct _headers access.
1365
+ * Check if response is a Lambda mock response with direct internal access.
1366
+ * Uses Symbol marker to survive prototype chain modifications from Express and dd-trace.
1299
1367
  */
1300
1368
  function isLambdaMockResponse(res) {
1301
- return res._headers instanceof Map;
1369
+ return res[JAYPIE_LAMBDA_MOCK] === true;
1302
1370
  }
1303
1371
  /**
1304
1372
  * Safely send a JSON response, avoiding dd-trace interception.
1305
- * For Lambda mock responses, directly sets headers and writes to stream.
1373
+ * For Lambda mock responses, directly manipulates internal state instead of
1374
+ * using stream methods (write/end) which dd-trace intercepts.
1306
1375
  */
1307
1376
  function safeSendJson(res, statusCode, data) {
1308
1377
  if (isLambdaMockResponse(res)) {
1309
- // Direct access for Lambda mock responses - bypasses dd-trace
1310
- res._headers.set("content-type", "application/json");
1378
+ // Use internal method to set header (completely bypasses dd-trace)
1379
+ if (typeof res._internalSetHeader === "function") {
1380
+ res._internalSetHeader("content-type", "application/json");
1381
+ }
1382
+ else {
1383
+ // Fall back to direct _headers manipulation
1384
+ res._headers.set("content-type", "application/json");
1385
+ }
1311
1386
  res.statusCode = statusCode;
1312
- res.end(JSON.stringify(data));
1387
+ // Directly push to chunks array instead of using stream write/end
1388
+ const chunk = Buffer.from(JSON.stringify(data));
1389
+ res._chunks.push(chunk);
1390
+ res._headersSent = true;
1391
+ // Mark as ended so getResult() resolves immediately
1392
+ res._ended = true;
1393
+ // Signal completion if a promise is waiting
1394
+ if (res._resolve) {
1395
+ res._resolve(res.buildResult());
1396
+ }
1397
+ // Emit "finish" event so runExpressApp's promise resolves
1398
+ console.log("[safeSendJson] Emitting finish event");
1399
+ res.emit("finish");
1313
1400
  return;
1314
1401
  }
1315
1402
  // Fall back to standard Express methods for real responses
@@ -1317,18 +1404,27 @@ function safeSendJson(res, statusCode, data) {
1317
1404
  }
1318
1405
  /**
1319
1406
  * Safely send a response body, avoiding dd-trace interception.
1320
- * For Lambda mock responses, directly writes to stream.
1407
+ * For Lambda mock responses, directly manipulates internal state instead of
1408
+ * using stream methods (write/end) which dd-trace intercepts.
1321
1409
  */
1322
1410
  function safeSend(res, statusCode, body) {
1323
1411
  if (isLambdaMockResponse(res)) {
1324
- // Direct access for Lambda mock responses - bypasses dd-trace
1412
+ // Direct internal state manipulation - bypasses dd-trace completely
1325
1413
  res.statusCode = statusCode;
1326
1414
  if (body !== undefined) {
1327
- res.end(body);
1328
- }
1329
- else {
1330
- res.end();
1331
- }
1415
+ const chunk = Buffer.from(body);
1416
+ res._chunks.push(chunk);
1417
+ }
1418
+ res._headersSent = true;
1419
+ // Mark as ended so getResult() resolves immediately
1420
+ res._ended = true;
1421
+ // Signal completion if a promise is waiting
1422
+ if (res._resolve) {
1423
+ res._resolve(res.buildResult());
1424
+ }
1425
+ // Emit "finish" event so runExpressApp's promise resolves
1426
+ console.log("[safeSend] Emitting finish event");
1427
+ res.emit("finish");
1332
1428
  return;
1333
1429
  }
1334
1430
  // Fall back to standard Express methods for real responses
@@ -1595,7 +1691,18 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1595
1691
  }
1596
1692
  }
1597
1693
  catch (error) {
1598
- log.fatal("Express encountered an error while sending the response");
1694
+ // Use console.error for raw stack trace to ensure it appears in CloudWatch
1695
+ // Handle both Error objects and plain thrown values
1696
+ const errorMessage = error instanceof Error
1697
+ ? error.message
1698
+ : typeof error === "object" && error !== null
1699
+ ? JSON.stringify(error)
1700
+ : String(error);
1701
+ const errorStack = error instanceof Error
1702
+ ? error.stack
1703
+ : new Error("Stack trace").stack?.replace("Error: Stack trace", `Error: ${errorMessage}`);
1704
+ console.error("Express response error stack trace:", errorStack);
1705
+ log.fatal(`Express encountered an error while sending the response: ${errorMessage}`);
1599
1706
  log.var({ responseError: error });
1600
1707
  }
1601
1708
  // Log response
@@ -1695,14 +1802,13 @@ const logger = logger$2.log;
1695
1802
  // Helper
1696
1803
  //
1697
1804
  /**
1698
- * Format an error as an SSE error event
1805
+ * Get error body from an error
1699
1806
  */
1700
- function formatErrorSSE(error) {
1807
+ function getErrorBody(error) {
1701
1808
  const isJaypieError = error.isProjectError;
1702
- const body = isJaypieError
1809
+ return isJaypieError
1703
1810
  ? (error.body?.() ?? { error: error.message })
1704
1811
  : new errors.UnhandledError().body();
1705
- return `event: error\ndata: ${JSON.stringify(body)}\n\n`;
1706
1812
  }
1707
1813
  function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1708
1814
  /* eslint-enable no-redeclare */
@@ -1722,7 +1828,8 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1722
1828
  //
1723
1829
  // Validate
1724
1830
  //
1725
- let { chaos, contentType = "text/event-stream", locals, name, secrets, setup = [], teardown = [], unavailable, validate, } = options;
1831
+ const format = options.format ?? "sse";
1832
+ let { chaos, contentType = aws.getContentTypeForFormat(format), locals, name, secrets, setup = [], teardown = [], unavailable, validate, } = options;
1726
1833
  if (typeof handler !== "function") {
1727
1834
  throw new errors.BadRequestError(`Argument "${handler}" doesn't match type "function"`);
1728
1835
  }
@@ -1860,9 +1967,10 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1860
1967
  log.fatal("Caught unhandled error in stream handler");
1861
1968
  log.info.var({ unhandledError: error.message });
1862
1969
  }
1863
- // Write error as SSE event
1970
+ // Write error in the appropriate format
1864
1971
  try {
1865
- res.write(formatErrorSSE(error));
1972
+ const errorBody = getErrorBody(error);
1973
+ res.write(aws.formatStreamError(errorBody, format));
1866
1974
  }
1867
1975
  catch {
1868
1976
  // Response may already be closed
@@ -1976,10 +2084,9 @@ exports.LambdaRequest = LambdaRequest;
1976
2084
  exports.LambdaResponseBuffered = LambdaResponseBuffered;
1977
2085
  exports.LambdaResponseStreaming = LambdaResponseStreaming;
1978
2086
  exports.badRequestRoute = badRequestRoute;
1979
- exports.cors = cors;
2087
+ exports.cors = cors_helper;
1980
2088
  exports.createLambdaHandler = createLambdaHandler;
1981
2089
  exports.createLambdaStreamHandler = createLambdaStreamHandler;
1982
- exports.createServer = createServer;
1983
2090
  exports.echoRoute = echoRoute;
1984
2091
  exports.expressHandler = expressHandler;
1985
2092
  exports.expressHttpCodeHandler = httpHandler;