@jaypie/express 1.2.4-rc9 → 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.
@@ -195,21 +224,87 @@ class LambdaResponseBuffered extends node_stream.Writable {
195
224
  // Internal state exposed for direct manipulation by safe response methods
196
225
  // that need to bypass dd-trace interception of stream methods
197
226
  this._chunks = [];
227
+ this._ended = false; // Track ended state since writableEnded is lost after prototype change
198
228
  this._headers = new Map();
199
229
  this._headersSent = false;
200
230
  this._resolve = null;
231
+ // Mark as Lambda mock response for identification in expressHandler
232
+ this[JAYPIE_LAMBDA_MOCK] = true;
201
233
  // Initialize Node's internal kOutHeaders for dd-trace compatibility.
202
234
  // dd-trace patches HTTP methods and expects this internal state.
203
235
  if (kOutHeaders$1) {
204
236
  this[kOutHeaders$1] = Object.create(null);
205
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
+ }
206
300
  }
207
301
  //
208
302
  // Promise-based API for getting final result
209
303
  //
210
304
  getResult() {
211
305
  return new Promise((resolve) => {
212
- if (this.writableEnded) {
306
+ // Use _ended instead of writableEnded since Express's setPrototypeOf breaks the getter
307
+ if (this._ended) {
213
308
  resolve(this.buildResult());
214
309
  }
215
310
  else {
@@ -267,36 +362,40 @@ class LambdaResponseBuffered extends node_stream.Writable {
267
362
  /**
268
363
  * Proxy for direct header access (e.g., res.headers['content-type']).
269
364
  * Required for compatibility with middleware like helmet that access headers directly.
365
+ * Uses direct _headers access to bypass dd-trace interception.
270
366
  */
271
367
  get headers() {
272
368
  return new Proxy({}, {
273
369
  deleteProperty: (_target, prop) => {
274
- this.removeHeader(String(prop));
370
+ this._headers.delete(String(prop).toLowerCase());
275
371
  return true;
276
372
  },
277
373
  get: (_target, prop) => {
278
374
  if (typeof prop === "symbol")
279
375
  return undefined;
280
- return this.getHeader(String(prop));
376
+ return this._headers.get(String(prop).toLowerCase());
281
377
  },
282
378
  getOwnPropertyDescriptor: (_target, prop) => {
283
- if (this.hasHeader(String(prop))) {
379
+ const lowerProp = String(prop).toLowerCase();
380
+ if (this._headers.has(lowerProp)) {
284
381
  return {
285
382
  configurable: true,
286
383
  enumerable: true,
287
- value: this.getHeader(String(prop)),
384
+ value: this._headers.get(lowerProp),
288
385
  };
289
386
  }
290
387
  return undefined;
291
388
  },
292
389
  has: (_target, prop) => {
293
- return this.hasHeader(String(prop));
390
+ return this._headers.has(String(prop).toLowerCase());
294
391
  },
295
392
  ownKeys: () => {
296
- return this.getHeaderNames();
393
+ return Array.from(this._headers.keys());
297
394
  },
298
395
  set: (_target, prop, value) => {
299
- this.setHeader(String(prop), value);
396
+ if (!this._headersSent) {
397
+ this._headers.set(String(prop).toLowerCase(), value);
398
+ }
300
399
  return true;
301
400
  },
302
401
  });
@@ -313,9 +412,10 @@ class LambdaResponseBuffered extends node_stream.Writable {
313
412
  headersToSet = statusMessageOrHeaders;
314
413
  }
315
414
  if (headersToSet) {
415
+ // Use direct _headers access to bypass dd-trace interception
316
416
  for (const [key, value] of Object.entries(headersToSet)) {
317
417
  if (value !== undefined) {
318
- this.setHeader(key, value);
418
+ this._headers.set(key.toLowerCase(), String(value));
319
419
  }
320
420
  }
321
421
  }
@@ -352,7 +452,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
352
452
  return this;
353
453
  }
354
454
  json(data) {
355
- this.setHeader("content-type", "application/json");
455
+ // Use direct _headers access to bypass dd-trace interception
456
+ this._headers.set("content-type", "application/json");
356
457
  this.end(JSON.stringify(data));
357
458
  return this;
358
459
  }
@@ -366,11 +467,12 @@ class LambdaResponseBuffered extends node_stream.Writable {
366
467
  /**
367
468
  * Add a field to the Vary response header.
368
469
  * Used by CORS middleware to indicate response varies by Origin.
470
+ * Uses direct _headers access to bypass dd-trace interception.
369
471
  */
370
472
  vary(field) {
371
- const existing = this.getHeader("vary");
473
+ const existing = this._headers.get("vary");
372
474
  if (!existing) {
373
- this.setHeader("vary", field);
475
+ this._headers.set("vary", field);
374
476
  }
375
477
  else {
376
478
  // Append to existing Vary header if field not already present
@@ -378,7 +480,7 @@ class LambdaResponseBuffered extends node_stream.Writable {
378
480
  .split(",")
379
481
  .map((f) => f.trim().toLowerCase());
380
482
  if (!fields.includes(field.toLowerCase())) {
381
- this.setHeader("vary", `${existing}, ${field}`);
483
+ this._headers.set("vary", `${existing}, ${field}`);
382
484
  }
383
485
  }
384
486
  return this;
@@ -396,6 +498,7 @@ class LambdaResponseBuffered extends node_stream.Writable {
396
498
  callback();
397
499
  }
398
500
  _final(callback) {
501
+ this._ended = true;
399
502
  if (this._resolve) {
400
503
  this._resolve(this.buildResult());
401
504
  }
@@ -406,7 +509,8 @@ class LambdaResponseBuffered extends node_stream.Writable {
406
509
  //
407
510
  buildResult() {
408
511
  const body = Buffer.concat(this._chunks);
409
- const contentType = this.getHeader("content-type") || "";
512
+ // Use direct _headers access to bypass dd-trace interception
513
+ const contentType = this._headers.get("content-type") || "";
410
514
  // Determine if response should be base64 encoded
411
515
  const isBase64Encoded = this.isBinaryContentType(contentType);
412
516
  // Build headers object
@@ -468,6 +572,8 @@ class LambdaResponseStreaming extends node_stream.Writable {
468
572
  this.socket = {
469
573
  remoteAddress: "127.0.0.1",
470
574
  };
575
+ // Internal state exposed for direct manipulation by safe response methods
576
+ // that need to bypass dd-trace interception
471
577
  this._headers = new Map();
472
578
  this._headersSent = false;
473
579
  this._pendingWrites = [];
@@ -478,6 +584,65 @@ class LambdaResponseStreaming extends node_stream.Writable {
478
584
  if (kOutHeaders) {
479
585
  this[kOutHeaders] = Object.create(null);
480
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
+ }
481
646
  }
482
647
  //
483
648
  // Header management
@@ -532,36 +697,42 @@ class LambdaResponseStreaming extends node_stream.Writable {
532
697
  /**
533
698
  * Proxy for direct header access (e.g., res.headers['content-type']).
534
699
  * Required for compatibility with middleware like helmet that access headers directly.
700
+ * Uses direct _headers access to bypass dd-trace interception.
535
701
  */
536
702
  get headers() {
537
703
  return new Proxy({}, {
538
704
  deleteProperty: (_target, prop) => {
539
- this.removeHeader(String(prop));
705
+ if (!this._headersSent) {
706
+ this._headers.delete(String(prop).toLowerCase());
707
+ }
540
708
  return true;
541
709
  },
542
710
  get: (_target, prop) => {
543
711
  if (typeof prop === "symbol")
544
712
  return undefined;
545
- return this.getHeader(String(prop));
713
+ return this._headers.get(String(prop).toLowerCase());
546
714
  },
547
715
  getOwnPropertyDescriptor: (_target, prop) => {
548
- if (this.hasHeader(String(prop))) {
716
+ const lowerProp = String(prop).toLowerCase();
717
+ if (this._headers.has(lowerProp)) {
549
718
  return {
550
719
  configurable: true,
551
720
  enumerable: true,
552
- value: this.getHeader(String(prop)),
721
+ value: this._headers.get(lowerProp),
553
722
  };
554
723
  }
555
724
  return undefined;
556
725
  },
557
726
  has: (_target, prop) => {
558
- return this.hasHeader(String(prop));
727
+ return this._headers.has(String(prop).toLowerCase());
559
728
  },
560
729
  ownKeys: () => {
561
- return this.getHeaderNames();
730
+ return Array.from(this._headers.keys());
562
731
  },
563
732
  set: (_target, prop, value) => {
564
- this.setHeader(String(prop), value);
733
+ if (!this._headersSent) {
734
+ this._headers.set(String(prop).toLowerCase(), value);
735
+ }
565
736
  return true;
566
737
  },
567
738
  });
@@ -581,9 +752,10 @@ class LambdaResponseStreaming extends node_stream.Writable {
581
752
  headersToSet = statusMessageOrHeaders;
582
753
  }
583
754
  if (headersToSet) {
755
+ // Use direct _headers access to bypass dd-trace interception
584
756
  for (const [key, value] of Object.entries(headersToSet)) {
585
757
  if (value !== undefined) {
586
- this.setHeader(key, value);
758
+ this._headers.set(key.toLowerCase(), String(value));
587
759
  }
588
760
  }
589
761
  }
@@ -641,7 +813,8 @@ class LambdaResponseStreaming extends node_stream.Writable {
641
813
  return this;
642
814
  }
643
815
  json(data) {
644
- this.setHeader("content-type", "application/json");
816
+ // Use direct _headers access to bypass dd-trace interception
817
+ this._headers.set("content-type", "application/json");
645
818
  this.end(JSON.stringify(data));
646
819
  return this;
647
820
  }
@@ -655,11 +828,12 @@ class LambdaResponseStreaming extends node_stream.Writable {
655
828
  /**
656
829
  * Add a field to the Vary response header.
657
830
  * Used by CORS middleware to indicate response varies by Origin.
831
+ * Uses direct _headers access to bypass dd-trace interception.
658
832
  */
659
833
  vary(field) {
660
- const existing = this.getHeader("vary");
834
+ const existing = this._headers.get("vary");
661
835
  if (!existing) {
662
- this.setHeader("vary", field);
836
+ this._headers.set("vary", field);
663
837
  }
664
838
  else {
665
839
  // Append to existing Vary header if field not already present
@@ -667,7 +841,7 @@ class LambdaResponseStreaming extends node_stream.Writable {
667
841
  .split(",")
668
842
  .map((f) => f.trim().toLowerCase());
669
843
  if (!fields.includes(field.toLowerCase())) {
670
- this.setHeader("vary", `${existing}, ${field}`);
844
+ this._headers.set("vary", `${existing}, ${field}`);
671
845
  }
672
846
  }
673
847
  return this;
@@ -765,6 +939,7 @@ function runExpressApp(app, req, res) {
765
939
  */
766
940
  function createLambdaHandler(app, _options) {
767
941
  return async (event, context) => {
942
+ let result;
768
943
  try {
769
944
  // Set current invoke for getCurrentInvokeUuid
770
945
  setCurrentInvoke(event, context);
@@ -774,8 +949,38 @@ function createLambdaHandler(app, _options) {
774
949
  const res = new LambdaResponseBuffered();
775
950
  // Run Express app
776
951
  await runExpressApp(app, req, res);
777
- // Return Lambda response
778
- 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
+ };
779
984
  }
780
985
  finally {
781
986
  // Clear current invoke context
@@ -913,7 +1118,7 @@ const corsHelper = (config = {}) => {
913
1118
  };
914
1119
  return expressCors(options);
915
1120
  };
916
- var cors = (config) => {
1121
+ var cors_helper = (config) => {
917
1122
  const cors = corsHelper(config);
918
1123
  return (req, res, next) => {
919
1124
  cors(req, res, (error) => {
@@ -928,154 +1133,10 @@ var cors = (config) => {
928
1133
  };
929
1134
  };
930
1135
 
931
- //
932
- //
933
- // Constants
934
- //
935
- const DEFAULT_PORT = 8080;
936
- //
937
- //
938
- // Main
939
- //
940
- /**
941
- * Creates and starts an Express server with standard Jaypie middleware.
942
- *
943
- * Features:
944
- * - CORS handling (configurable)
945
- * - JSON body parsing
946
- * - Listens on PORT env var (default 8080)
947
- *
948
- * Usage:
949
- * ```ts
950
- * import express from "express";
951
- * import { createServer, expressHandler } from "@jaypie/express";
952
- *
953
- * const app = express();
954
- *
955
- * app.get("/", expressHandler(async (req, res) => {
956
- * return { message: "Hello World" };
957
- * }));
958
- *
959
- * const { server, port } = await createServer(app);
960
- * console.log(`Server running on port ${port}`);
961
- * ```
962
- *
963
- * @param app - Express application instance
964
- * @param options - Server configuration options
965
- * @returns Promise resolving to server instance and port
966
- */
967
- async function createServer(app, options = {}) {
968
- const { cors: corsConfig, jsonLimit = "1mb", middleware = [], port: portOption, } = options;
969
- // Determine port
970
- const port = typeof portOption === "string"
971
- ? parseInt(portOption, 10)
972
- : (portOption ?? parseInt(process.env.PORT || String(DEFAULT_PORT), 10));
973
- // Apply CORS middleware (unless explicitly disabled)
974
- if (corsConfig !== false) {
975
- app.use(cors(corsConfig));
976
- }
977
- // Apply JSON body parser
978
- // Note: We use dynamic import to avoid requiring express as a direct dependency
979
- const express = await import('express');
980
- app.use(express.json({ limit: jsonLimit }));
981
- // Apply additional middleware
982
- for (const mw of middleware) {
983
- app.use(mw);
984
- }
985
- // Start server
986
- return new Promise((resolve, reject) => {
987
- try {
988
- const server = app.listen(port, () => {
989
- // Get the actual port (important when port 0 is passed to get an ephemeral port)
990
- const address = server.address();
991
- const actualPort = address?.port ?? port;
992
- logger$2.log.info(`Server listening on port ${actualPort}`);
993
- resolve({ port: actualPort, server });
994
- });
995
- server.on("error", (error) => {
996
- logger$2.log.error("Server error", { error });
997
- reject(error);
998
- });
999
- }
1000
- catch (error) {
1001
- reject(error);
1002
- }
1003
- });
1004
- }
1005
-
1006
- //
1007
- //
1008
- // Constants
1009
- //
1010
- const HEADER_AMZN_REQUEST_ID$1 = "x-amzn-request-id";
1011
- const ENV_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID";
1012
- //
1013
- //
1014
- // Helper Functions
1015
- //
1016
- /**
1017
- * Extract request ID from X-Ray trace ID environment variable
1018
- * Format: Root=1-5e6b4a90-example;Parent=example;Sampled=1
1019
- * We extract the trace ID from the Root segment
1020
- */
1021
- function parseTraceId(traceId) {
1022
- if (!traceId)
1023
- return undefined;
1024
- // Extract the Root segment (format: Root=1-{timestamp}-{uuid})
1025
- const rootMatch = traceId.match(/Root=([^;]+)/);
1026
- if (rootMatch && rootMatch[1]) {
1027
- return rootMatch[1];
1028
- }
1029
- return undefined;
1030
- }
1031
- //
1032
- //
1033
- // Main
1034
- //
1035
- /**
1036
- * Get the current invoke UUID from Lambda Web Adapter context.
1037
- * This function extracts the request ID from either:
1038
- * 1. The x-amzn-request-id header (set by Lambda Web Adapter)
1039
- * 2. The _X_AMZN_TRACE_ID environment variable (set by Lambda runtime)
1040
- *
1041
- * @param req - Optional Express request object to extract headers from
1042
- * @returns The AWS request ID or undefined if not in Lambda context
1043
- */
1044
- function getWebAdapterUuid(req) {
1045
- // First, try to get from request headers
1046
- if (req && req.headers) {
1047
- const headerValue = req.headers[HEADER_AMZN_REQUEST_ID$1];
1048
- if (headerValue) {
1049
- return Array.isArray(headerValue) ? headerValue[0] : headerValue;
1050
- }
1051
- }
1052
- // Fall back to environment variable (X-Ray trace ID)
1053
- const traceId = process.env[ENV_AMZN_TRACE_ID];
1054
- if (traceId) {
1055
- return parseTraceId(traceId);
1056
- }
1057
- return undefined;
1058
- }
1059
-
1060
- //
1061
- //
1062
- // Constants
1063
- //
1064
- const HEADER_AMZN_REQUEST_ID = "x-amzn-request-id";
1065
1136
  //
1066
1137
  //
1067
1138
  // Helper Functions
1068
1139
  //
1069
- /**
1070
- * Detect if we're running in Lambda Web Adapter mode.
1071
- * Web Adapter sets the x-amzn-request-id header on requests.
1072
- */
1073
- function isWebAdapterMode(req) {
1074
- if (req && req.headers && req.headers[HEADER_AMZN_REQUEST_ID]) {
1075
- return true;
1076
- }
1077
- return false;
1078
- }
1079
1140
  /**
1080
1141
  * Get UUID from Jaypie Lambda adapter context.
1081
1142
  * This is set by createLambdaHandler/createLambdaStreamHandler.
@@ -1092,8 +1153,12 @@ function getJaypieAdapterUuid() {
1092
1153
  * The Jaypie adapter attaches _lambdaContext to the request.
1093
1154
  */
1094
1155
  function getRequestContextUuid(req) {
1095
- if (req && req._lambdaContext?.awsRequestId) {
1096
- return req._lambdaContext.awsRequestId;
1156
+ if (req && req._lambdaContext) {
1157
+ const lambdaContext = req
1158
+ ._lambdaContext;
1159
+ if (lambdaContext.awsRequestId) {
1160
+ return lambdaContext.awsRequestId;
1161
+ }
1097
1162
  }
1098
1163
  return undefined;
1099
1164
  }
@@ -1103,29 +1168,20 @@ function getRequestContextUuid(req) {
1103
1168
  //
1104
1169
  /**
1105
1170
  * Get the current invoke UUID from Lambda context.
1106
- * Works with Jaypie Lambda adapter and Lambda Web Adapter mode.
1171
+ * Works with Jaypie Lambda adapter (createLambdaHandler/createLambdaStreamHandler).
1107
1172
  *
1108
1173
  * @param req - Optional Express request object. Used to extract context
1109
- * from Web Adapter headers or Jaypie adapter's _lambdaContext.
1174
+ * from Jaypie adapter's _lambdaContext.
1110
1175
  * @returns The AWS request ID or undefined if not in Lambda context
1111
1176
  */
1112
1177
  function getCurrentInvokeUuid(req) {
1113
- // Priority 1: Web Adapter mode (header-based)
1114
- if (isWebAdapterMode(req)) {
1115
- return getWebAdapterUuid(req);
1116
- }
1117
- // Priority 2: Request has Lambda context attached (Jaypie adapter)
1178
+ // Priority 1: Request has Lambda context attached (Jaypie adapter)
1118
1179
  const requestContextUuid = getRequestContextUuid(req);
1119
1180
  if (requestContextUuid) {
1120
1181
  return requestContextUuid;
1121
1182
  }
1122
- // Priority 3: Global context from Jaypie adapter
1123
- const jaypieAdapterUuid = getJaypieAdapterUuid();
1124
- if (jaypieAdapterUuid) {
1125
- return jaypieAdapterUuid;
1126
- }
1127
- // Fallback: Web Adapter env var
1128
- return getWebAdapterUuid();
1183
+ // Priority 2: Global context from Jaypie adapter
1184
+ return getJaypieAdapterUuid();
1129
1185
  }
1130
1186
 
1131
1187
  //
@@ -1139,7 +1195,11 @@ function getCurrentInvokeUuid(req) {
1139
1195
  */
1140
1196
  function safeGetHeader(res, name) {
1141
1197
  try {
1142
- // 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)
1143
1203
  if (res._headers instanceof Map) {
1144
1204
  const value = res._headers.get(name.toLowerCase());
1145
1205
  return value ? String(value) : undefined;
@@ -1167,7 +1227,12 @@ function safeGetHeader(res, name) {
1167
1227
  */
1168
1228
  function safeSetHeader(res, name, value) {
1169
1229
  try {
1170
- // 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)
1171
1236
  if (res._headers instanceof Map) {
1172
1237
  res._headers.set(name.toLowerCase(), value);
1173
1238
  return;
@@ -1298,12 +1363,10 @@ const logger$1 = logger$2.log;
1298
1363
  //
1299
1364
  /**
1300
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.
1301
1367
  */
1302
1368
  function isLambdaMockResponse(res) {
1303
- const mock = res;
1304
- return (mock._headers instanceof Map &&
1305
- Array.isArray(mock._chunks) &&
1306
- typeof mock.buildResult === "function");
1369
+ return res[JAYPIE_LAMBDA_MOCK] === true;
1307
1370
  }
1308
1371
  /**
1309
1372
  * Safely send a JSON response, avoiding dd-trace interception.
@@ -1312,17 +1375,28 @@ function isLambdaMockResponse(res) {
1312
1375
  */
1313
1376
  function safeSendJson(res, statusCode, data) {
1314
1377
  if (isLambdaMockResponse(res)) {
1315
- // Direct internal state manipulation - bypasses dd-trace completely
1316
- 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
+ }
1317
1386
  res.statusCode = statusCode;
1318
1387
  // Directly push to chunks array instead of using stream write/end
1319
1388
  const chunk = Buffer.from(JSON.stringify(data));
1320
1389
  res._chunks.push(chunk);
1321
1390
  res._headersSent = true;
1391
+ // Mark as ended so getResult() resolves immediately
1392
+ res._ended = true;
1322
1393
  // Signal completion if a promise is waiting
1323
1394
  if (res._resolve) {
1324
1395
  res._resolve(res.buildResult());
1325
1396
  }
1397
+ // Emit "finish" event so runExpressApp's promise resolves
1398
+ console.log("[safeSendJson] Emitting finish event");
1399
+ res.emit("finish");
1326
1400
  return;
1327
1401
  }
1328
1402
  // Fall back to standard Express methods for real responses
@@ -1342,10 +1416,15 @@ function safeSend(res, statusCode, body) {
1342
1416
  res._chunks.push(chunk);
1343
1417
  }
1344
1418
  res._headersSent = true;
1419
+ // Mark as ended so getResult() resolves immediately
1420
+ res._ended = true;
1345
1421
  // Signal completion if a promise is waiting
1346
1422
  if (res._resolve) {
1347
1423
  res._resolve(res.buildResult());
1348
1424
  }
1425
+ // Emit "finish" event so runExpressApp's promise resolves
1426
+ console.log("[safeSend] Emitting finish event");
1427
+ res.emit("finish");
1349
1428
  return;
1350
1429
  }
1351
1430
  // Fall back to standard Express methods for real responses
@@ -1612,7 +1691,18 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
1612
1691
  }
1613
1692
  }
1614
1693
  catch (error) {
1615
- 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}`);
1616
1706
  log.var({ responseError: error });
1617
1707
  }
1618
1708
  // Log response
@@ -1712,14 +1802,13 @@ const logger = logger$2.log;
1712
1802
  // Helper
1713
1803
  //
1714
1804
  /**
1715
- * Format an error as an SSE error event
1805
+ * Get error body from an error
1716
1806
  */
1717
- function formatErrorSSE(error) {
1807
+ function getErrorBody(error) {
1718
1808
  const isJaypieError = error.isProjectError;
1719
- const body = isJaypieError
1809
+ return isJaypieError
1720
1810
  ? (error.body?.() ?? { error: error.message })
1721
1811
  : new errors.UnhandledError().body();
1722
- return `event: error\ndata: ${JSON.stringify(body)}\n\n`;
1723
1812
  }
1724
1813
  function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1725
1814
  /* eslint-enable no-redeclare */
@@ -1739,7 +1828,8 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1739
1828
  //
1740
1829
  // Validate
1741
1830
  //
1742
- 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;
1743
1833
  if (typeof handler !== "function") {
1744
1834
  throw new errors.BadRequestError(`Argument "${handler}" doesn't match type "function"`);
1745
1835
  }
@@ -1877,9 +1967,10 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
1877
1967
  log.fatal("Caught unhandled error in stream handler");
1878
1968
  log.info.var({ unhandledError: error.message });
1879
1969
  }
1880
- // Write error as SSE event
1970
+ // Write error in the appropriate format
1881
1971
  try {
1882
- res.write(formatErrorSSE(error));
1972
+ const errorBody = getErrorBody(error);
1973
+ res.write(aws.formatStreamError(errorBody, format));
1883
1974
  }
1884
1975
  catch {
1885
1976
  // Response may already be closed
@@ -1993,10 +2084,9 @@ exports.LambdaRequest = LambdaRequest;
1993
2084
  exports.LambdaResponseBuffered = LambdaResponseBuffered;
1994
2085
  exports.LambdaResponseStreaming = LambdaResponseStreaming;
1995
2086
  exports.badRequestRoute = badRequestRoute;
1996
- exports.cors = cors;
2087
+ exports.cors = cors_helper;
1997
2088
  exports.createLambdaHandler = createLambdaHandler;
1998
2089
  exports.createLambdaStreamHandler = createLambdaStreamHandler;
1999
- exports.createServer = createServer;
2000
2090
  exports.echoRoute = echoRoute;
2001
2091
  exports.expressHandler = expressHandler;
2002
2092
  exports.expressHttpCodeHandler = httpHandler;