@jaypie/express 1.2.2 → 1.2.4-rc0

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.
Files changed (31) hide show
  1. package/dist/cjs/adapter/LambdaRequest.d.ts +53 -0
  2. package/dist/cjs/adapter/LambdaResponseBuffered.d.ts +41 -0
  3. package/dist/cjs/adapter/LambdaResponseStreaming.d.ts +40 -0
  4. package/dist/cjs/adapter/__tests__/LambdaRequest.spec.d.ts +1 -0
  5. package/dist/cjs/adapter/__tests__/LambdaResponseBuffered.spec.d.ts +1 -0
  6. package/dist/cjs/adapter/__tests__/LambdaResponseStreaming.spec.d.ts +1 -0
  7. package/dist/cjs/adapter/__tests__/integration.spec.d.ts +1 -0
  8. package/dist/cjs/adapter/index.d.ts +50 -0
  9. package/dist/cjs/adapter/types.d.ts +75 -0
  10. package/dist/cjs/createServer.d.ts +60 -0
  11. package/dist/cjs/getCurrentInvokeUuid.adapter.d.ts +10 -1
  12. package/dist/cjs/getCurrentInvokeUuid.webadapter.d.ts +12 -0
  13. package/dist/cjs/index.cjs +815 -53
  14. package/dist/cjs/index.cjs.map +1 -1
  15. package/dist/cjs/index.d.ts +7 -2
  16. package/dist/esm/adapter/LambdaRequest.d.ts +53 -0
  17. package/dist/esm/adapter/LambdaResponseBuffered.d.ts +41 -0
  18. package/dist/esm/adapter/LambdaResponseStreaming.d.ts +40 -0
  19. package/dist/esm/adapter/__tests__/LambdaRequest.spec.d.ts +1 -0
  20. package/dist/esm/adapter/__tests__/LambdaResponseBuffered.spec.d.ts +1 -0
  21. package/dist/esm/adapter/__tests__/LambdaResponseStreaming.spec.d.ts +1 -0
  22. package/dist/esm/adapter/__tests__/integration.spec.d.ts +1 -0
  23. package/dist/esm/adapter/index.d.ts +50 -0
  24. package/dist/esm/adapter/types.d.ts +75 -0
  25. package/dist/esm/createServer.d.ts +60 -0
  26. package/dist/esm/getCurrentInvokeUuid.adapter.d.ts +10 -1
  27. package/dist/esm/getCurrentInvokeUuid.webadapter.d.ts +12 -0
  28. package/dist/esm/index.d.ts +7 -2
  29. package/dist/esm/index.js +806 -52
  30. package/dist/esm/index.js.map +1 -1
  31. package/package.json +4 -2
package/dist/esm/index.js CHANGED
@@ -1,10 +1,584 @@
1
+ import { Readable, Writable } from 'node:stream';
1
2
  import { CorsError, BadRequestError, UnhandledError, GatewayTimeoutError, UnavailableError, BadGatewayError, InternalError, TeapotError, GoneError, MethodNotAllowedError, NotFoundError, ForbiddenError, UnauthorizedError, NotImplementedError } from '@jaypie/errors';
2
3
  import { force, envBoolean, JAYPIE, HTTP, getHeaderFrom, jaypieHandler } from '@jaypie/kit';
3
4
  import expressCors from 'cors';
4
- import { loadEnvSecrets } from '@jaypie/aws';
5
5
  import { log } from '@jaypie/logger';
6
+ import { loadEnvSecrets } from '@jaypie/aws';
6
7
  import { hasDatadogEnv, submitMetric, DATADOG } from '@jaypie/datadog';
7
- import { getCurrentInvoke } from '@codegenie/serverless-express';
8
+
9
+ //
10
+ //
11
+ // LambdaRequest Class
12
+ //
13
+ /**
14
+ * Mock IncomingMessage that extends Readable stream.
15
+ * Provides Express-compatible request interface from Lambda Function URL events.
16
+ */
17
+ class LambdaRequest extends Readable {
18
+ constructor(options) {
19
+ super();
20
+ this.httpVersion = "1.1";
21
+ this.httpVersionMajor = 1;
22
+ this.httpVersionMinor = 1;
23
+ this.complete = false;
24
+ this.baseUrl = "";
25
+ this.params = {};
26
+ this.query = {};
27
+ this.bodyPushed = false;
28
+ this.method = options.method;
29
+ this.url = options.url;
30
+ this.originalUrl = options.url;
31
+ this.path = options.url.split("?")[0];
32
+ this.headers = this.normalizeHeaders(options.headers);
33
+ this.bodyBuffer = options.body ?? null;
34
+ // Store Lambda context
35
+ this._lambdaContext = options.lambdaContext;
36
+ this._lambdaEvent = options.lambdaEvent;
37
+ // Create mock socket
38
+ this.socket = {
39
+ destroy: () => { },
40
+ encrypted: options.protocol === "https",
41
+ remoteAddress: options.remoteAddress,
42
+ };
43
+ this.connection = this.socket;
44
+ }
45
+ //
46
+ // Readable stream implementation
47
+ //
48
+ _read() {
49
+ if (!this.bodyPushed) {
50
+ if (this.bodyBuffer && this.bodyBuffer.length > 0) {
51
+ this.push(this.bodyBuffer);
52
+ }
53
+ this.push(null); // Signal end of stream
54
+ this.bodyPushed = true;
55
+ this.complete = true;
56
+ }
57
+ }
58
+ //
59
+ // Express helper methods
60
+ //
61
+ get(headerName) {
62
+ const key = headerName.toLowerCase();
63
+ const value = this.headers[key];
64
+ return Array.isArray(value) ? value[0] : value;
65
+ }
66
+ header(headerName) {
67
+ return this.get(headerName);
68
+ }
69
+ //
70
+ // Private helpers
71
+ //
72
+ normalizeHeaders(headers) {
73
+ const normalized = {};
74
+ for (const [key, value] of Object.entries(headers)) {
75
+ normalized[key.toLowerCase()] = value;
76
+ }
77
+ return normalized;
78
+ }
79
+ }
80
+ //
81
+ //
82
+ // Factory Function
83
+ //
84
+ /**
85
+ * Create a LambdaRequest from a Function URL event.
86
+ */
87
+ function createLambdaRequest(event, context) {
88
+ // Build URL with query string
89
+ const url = event.rawQueryString
90
+ ? `${event.rawPath}?${event.rawQueryString}`
91
+ : event.rawPath;
92
+ // Decode body if present
93
+ let body = null;
94
+ if (event.body) {
95
+ body = event.isBase64Encoded
96
+ ? Buffer.from(event.body, "base64")
97
+ : Buffer.from(event.body, "utf8");
98
+ }
99
+ // Normalize cookies into Cookie header if not already present
100
+ const headers = { ...event.headers };
101
+ if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
102
+ headers.cookie = event.cookies.join("; ");
103
+ }
104
+ return new LambdaRequest({
105
+ body,
106
+ headers,
107
+ lambdaContext: context,
108
+ lambdaEvent: event,
109
+ method: event.requestContext.http.method,
110
+ protocol: event.requestContext.http.protocol.split("/")[0].toLowerCase(),
111
+ remoteAddress: event.requestContext.http.sourceIp,
112
+ url,
113
+ });
114
+ }
115
+
116
+ //
117
+ //
118
+ // Constants
119
+ //
120
+ const BINARY_CONTENT_TYPE_PATTERNS = [
121
+ /^application\/octet-stream$/,
122
+ /^application\/pdf$/,
123
+ /^application\/zip$/,
124
+ /^audio\//,
125
+ /^font\//,
126
+ /^image\//,
127
+ /^video\//,
128
+ ];
129
+ //
130
+ //
131
+ // LambdaResponseBuffered Class
132
+ //
133
+ /**
134
+ * Mock ServerResponse that buffers the response.
135
+ * Collects status, headers, and body chunks, then returns a Lambda response.
136
+ */
137
+ class LambdaResponseBuffered extends Writable {
138
+ constructor() {
139
+ super();
140
+ this.statusCode = 200;
141
+ this.statusMessage = "OK";
142
+ // Mock socket to satisfy Express/finalhandler checks
143
+ this.socket = {
144
+ cork: () => { },
145
+ destroy: () => { },
146
+ remoteAddress: "127.0.0.1",
147
+ uncork: () => { },
148
+ writable: true,
149
+ };
150
+ this._chunks = [];
151
+ this._headers = new Map();
152
+ this._headersSent = false;
153
+ this._resolve = null;
154
+ }
155
+ //
156
+ // Promise-based API for getting final result
157
+ //
158
+ getResult() {
159
+ return new Promise((resolve) => {
160
+ if (this.writableEnded) {
161
+ resolve(this.buildResult());
162
+ }
163
+ else {
164
+ this._resolve = resolve;
165
+ }
166
+ });
167
+ }
168
+ //
169
+ // Header management
170
+ //
171
+ setHeader(name, value) {
172
+ if (this._headersSent) {
173
+ // In production, log warning but don't throw to match Express behavior
174
+ return this;
175
+ }
176
+ this._headers.set(name.toLowerCase(), String(value));
177
+ return this;
178
+ }
179
+ getHeader(name) {
180
+ return this._headers.get(name.toLowerCase());
181
+ }
182
+ removeHeader(name) {
183
+ this._headers.delete(name.toLowerCase());
184
+ }
185
+ getHeaders() {
186
+ const headers = {};
187
+ for (const [key, value] of this._headers) {
188
+ headers[key] = value;
189
+ }
190
+ return headers;
191
+ }
192
+ hasHeader(name) {
193
+ return this._headers.has(name.toLowerCase());
194
+ }
195
+ getHeaderNames() {
196
+ return Array.from(this._headers.keys());
197
+ }
198
+ writeHead(statusCode, statusMessageOrHeaders, headers) {
199
+ this.statusCode = statusCode;
200
+ let headersToSet;
201
+ if (typeof statusMessageOrHeaders === "string") {
202
+ this.statusMessage = statusMessageOrHeaders;
203
+ headersToSet = headers;
204
+ }
205
+ else if (statusMessageOrHeaders &&
206
+ typeof statusMessageOrHeaders === "object") {
207
+ headersToSet = statusMessageOrHeaders;
208
+ }
209
+ if (headersToSet) {
210
+ for (const [key, value] of Object.entries(headersToSet)) {
211
+ if (value !== undefined) {
212
+ this.setHeader(key, value);
213
+ }
214
+ }
215
+ }
216
+ this._headersSent = true;
217
+ return this;
218
+ }
219
+ get headersSent() {
220
+ return this._headersSent;
221
+ }
222
+ //
223
+ // Express compatibility methods
224
+ //
225
+ status(code) {
226
+ this.statusCode = code;
227
+ return this;
228
+ }
229
+ json(data) {
230
+ this.setHeader("content-type", "application/json");
231
+ this.end(JSON.stringify(data));
232
+ return this;
233
+ }
234
+ send(body) {
235
+ if (typeof body === "object" && !Buffer.isBuffer(body)) {
236
+ return this.json(body);
237
+ }
238
+ this.end(body);
239
+ return this;
240
+ }
241
+ //
242
+ // Writable stream implementation
243
+ //
244
+ _write(chunk, encoding, // eslint-disable-line no-undef
245
+ callback) {
246
+ const buffer = Buffer.isBuffer(chunk)
247
+ ? chunk
248
+ : Buffer.from(chunk, encoding);
249
+ this._chunks.push(buffer);
250
+ this._headersSent = true;
251
+ callback();
252
+ }
253
+ _final(callback) {
254
+ if (this._resolve) {
255
+ this._resolve(this.buildResult());
256
+ }
257
+ callback();
258
+ }
259
+ //
260
+ // Private helpers
261
+ //
262
+ buildResult() {
263
+ const body = Buffer.concat(this._chunks);
264
+ const contentType = this.getHeader("content-type") || "";
265
+ // Determine if response should be base64 encoded
266
+ const isBase64Encoded = this.isBinaryContentType(contentType);
267
+ // Build headers object
268
+ const headers = {};
269
+ const cookies = [];
270
+ for (const [key, value] of this._headers) {
271
+ if (key === "set-cookie") {
272
+ // Collect Set-Cookie headers for v2 response format
273
+ if (Array.isArray(value)) {
274
+ cookies.push(...value);
275
+ }
276
+ else {
277
+ cookies.push(value);
278
+ }
279
+ }
280
+ else {
281
+ headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
282
+ }
283
+ }
284
+ const result = {
285
+ body: isBase64Encoded ? body.toString("base64") : body.toString("utf8"),
286
+ headers,
287
+ isBase64Encoded,
288
+ statusCode: this.statusCode,
289
+ };
290
+ // Only include cookies if present (v2 format)
291
+ if (cookies.length > 0) {
292
+ result.cookies = cookies;
293
+ }
294
+ return result;
295
+ }
296
+ isBinaryContentType(contentType) {
297
+ return BINARY_CONTENT_TYPE_PATTERNS.some((pattern) => pattern.test(contentType));
298
+ }
299
+ }
300
+
301
+ //
302
+ //
303
+ // LambdaResponseStreaming Class
304
+ //
305
+ /**
306
+ * Mock ServerResponse that streams directly to Lambda responseStream.
307
+ * Uses awslambda.HttpResponseStream.from() to set status and headers.
308
+ */
309
+ class LambdaResponseStreaming extends Writable {
310
+ constructor(responseStream) {
311
+ super();
312
+ this.statusCode = 200;
313
+ this.statusMessage = "OK";
314
+ // Mock socket to satisfy Express/finalhandler checks
315
+ this.socket = {
316
+ cork: () => { },
317
+ destroy: () => { },
318
+ remoteAddress: "127.0.0.1",
319
+ uncork: () => { },
320
+ writable: true,
321
+ };
322
+ this._headers = new Map();
323
+ this._headersSent = false;
324
+ this._pendingWrites = [];
325
+ this._wrappedStream = null;
326
+ this._responseStream = responseStream;
327
+ }
328
+ //
329
+ // Header management
330
+ //
331
+ setHeader(name, value) {
332
+ if (this._headersSent) {
333
+ // In streaming mode, log warning but don't throw
334
+ // Headers cannot be changed after body starts
335
+ return this;
336
+ }
337
+ this._headers.set(name.toLowerCase(), String(value));
338
+ return this;
339
+ }
340
+ getHeader(name) {
341
+ return this._headers.get(name.toLowerCase());
342
+ }
343
+ removeHeader(name) {
344
+ if (!this._headersSent) {
345
+ this._headers.delete(name.toLowerCase());
346
+ }
347
+ }
348
+ getHeaders() {
349
+ const headers = {};
350
+ for (const [key, value] of this._headers) {
351
+ headers[key] = value;
352
+ }
353
+ return headers;
354
+ }
355
+ hasHeader(name) {
356
+ return this._headers.has(name.toLowerCase());
357
+ }
358
+ getHeaderNames() {
359
+ return Array.from(this._headers.keys());
360
+ }
361
+ writeHead(statusCode, statusMessageOrHeaders, headers) {
362
+ if (this._headersSent) {
363
+ return this;
364
+ }
365
+ this.statusCode = statusCode;
366
+ let headersToSet;
367
+ if (typeof statusMessageOrHeaders === "string") {
368
+ this.statusMessage = statusMessageOrHeaders;
369
+ headersToSet = headers;
370
+ }
371
+ else if (statusMessageOrHeaders &&
372
+ typeof statusMessageOrHeaders === "object") {
373
+ headersToSet = statusMessageOrHeaders;
374
+ }
375
+ if (headersToSet) {
376
+ for (const [key, value] of Object.entries(headersToSet)) {
377
+ if (value !== undefined) {
378
+ this.setHeader(key, value);
379
+ }
380
+ }
381
+ }
382
+ this.flushHeaders();
383
+ return this;
384
+ }
385
+ get headersSent() {
386
+ return this._headersSent;
387
+ }
388
+ flushHeaders() {
389
+ if (this._headersSent)
390
+ return;
391
+ const headers = {};
392
+ for (const [key, value] of this._headers) {
393
+ headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
394
+ }
395
+ const metadata = {
396
+ headers,
397
+ statusCode: this.statusCode,
398
+ };
399
+ // Wrap the stream with metadata using awslambda global
400
+ this._wrappedStream = awslambda.HttpResponseStream.from(this._responseStream, metadata);
401
+ this._headersSent = true;
402
+ // Flush pending writes
403
+ for (const { callback, chunk } of this._pendingWrites) {
404
+ this._wrappedStream.write(chunk);
405
+ callback();
406
+ }
407
+ this._pendingWrites = [];
408
+ }
409
+ //
410
+ // Express compatibility methods
411
+ //
412
+ status(code) {
413
+ this.statusCode = code;
414
+ return this;
415
+ }
416
+ json(data) {
417
+ this.setHeader("content-type", "application/json");
418
+ this.end(JSON.stringify(data));
419
+ return this;
420
+ }
421
+ send(body) {
422
+ if (typeof body === "object" && !Buffer.isBuffer(body)) {
423
+ return this.json(body);
424
+ }
425
+ this.end(body);
426
+ return this;
427
+ }
428
+ //
429
+ // Writable stream implementation
430
+ //
431
+ _write(chunk, encoding, // eslint-disable-line no-undef
432
+ callback) {
433
+ const buffer = Buffer.isBuffer(chunk)
434
+ ? chunk
435
+ : Buffer.from(chunk, encoding);
436
+ if (!this._headersSent) {
437
+ // Buffer writes until headers are sent
438
+ this._pendingWrites.push({ callback: () => callback(), chunk: buffer });
439
+ // Auto-flush headers on first write
440
+ this.flushHeaders();
441
+ }
442
+ else {
443
+ this._wrappedStream.write(buffer);
444
+ callback();
445
+ }
446
+ }
447
+ _final(callback) {
448
+ if (!this._headersSent) {
449
+ this.flushHeaders();
450
+ }
451
+ this._wrappedStream?.end();
452
+ callback();
453
+ }
454
+ }
455
+
456
+ //
457
+ //
458
+ // Current Invoke Context
459
+ //
460
+ let currentInvoke = null;
461
+ /**
462
+ * Get the current Lambda invoke context.
463
+ * Used by getCurrentInvokeUuid adapter to get the request ID.
464
+ */
465
+ function getCurrentInvoke() {
466
+ return currentInvoke;
467
+ }
468
+ /**
469
+ * Set the current Lambda invoke context.
470
+ * Called at the start of each Lambda invocation.
471
+ */
472
+ function setCurrentInvoke(event, context) {
473
+ currentInvoke = { context, event };
474
+ }
475
+ /**
476
+ * Clear the current Lambda invoke context.
477
+ * Called at the end of each Lambda invocation.
478
+ */
479
+ function clearCurrentInvoke() {
480
+ currentInvoke = null;
481
+ }
482
+ //
483
+ //
484
+ // Express App Runner
485
+ //
486
+ /**
487
+ * Run Express app with mock request/response.
488
+ * Returns a promise that resolves when the response is complete.
489
+ */
490
+ function runExpressApp(app, req, res) {
491
+ return new Promise((resolve, reject) => {
492
+ // Listen for response completion
493
+ res.on("finish", resolve);
494
+ res.on("error", reject);
495
+ // Run the Express app
496
+ // Cast to Express types since our mocks implement the required interface
497
+ app(req, res);
498
+ });
499
+ }
500
+ //
501
+ //
502
+ // Buffered Handler Factory
503
+ //
504
+ /**
505
+ * Create a Lambda handler that buffers the Express response.
506
+ * Returns the complete response as a Lambda response object.
507
+ *
508
+ * @example
509
+ * ```typescript
510
+ * import express from "express";
511
+ * import { createLambdaHandler } from "@jaypie/express";
512
+ *
513
+ * const app = express();
514
+ * app.get("/", (req, res) => res.json({ message: "Hello" }));
515
+ *
516
+ * export const handler = createLambdaHandler(app);
517
+ * ```
518
+ */
519
+ function createLambdaHandler(app, _options) {
520
+ return async (event, context) => {
521
+ try {
522
+ // Set current invoke for getCurrentInvokeUuid
523
+ setCurrentInvoke(event, context);
524
+ // Create mock request from Lambda event
525
+ const req = createLambdaRequest(event, context);
526
+ // Create buffered response collector
527
+ const res = new LambdaResponseBuffered();
528
+ // Run Express app
529
+ await runExpressApp(app, req, res);
530
+ // Return Lambda response
531
+ return res.getResult();
532
+ }
533
+ finally {
534
+ // Clear current invoke context
535
+ clearCurrentInvoke();
536
+ }
537
+ };
538
+ }
539
+ //
540
+ //
541
+ // Streaming Handler Factory
542
+ //
543
+ /**
544
+ * Create a Lambda handler that streams the Express response.
545
+ * Uses awslambda.streamifyResponse() for Lambda response streaming.
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * import express from "express";
550
+ * import { createLambdaStreamHandler } from "@jaypie/express";
551
+ *
552
+ * const app = express();
553
+ * app.get("/stream", (req, res) => {
554
+ * res.setHeader("Content-Type", "text/event-stream");
555
+ * res.write("data: Hello\n\n");
556
+ * res.end();
557
+ * });
558
+ *
559
+ * export const handler = createLambdaStreamHandler(app);
560
+ * ```
561
+ */
562
+ function createLambdaStreamHandler(app, _options) {
563
+ // Wrap with awslambda.streamifyResponse for Lambda streaming
564
+ // @ts-expect-error awslambda is a Lambda runtime global
565
+ return awslambda.streamifyResponse(async (event, responseStream, context) => {
566
+ try {
567
+ // Set current invoke for getCurrentInvokeUuid
568
+ setCurrentInvoke(event, context);
569
+ // Create mock request from Lambda event
570
+ const req = createLambdaRequest(event, context);
571
+ // Create streaming response that pipes to Lambda responseStream
572
+ const res = new LambdaResponseStreaming(responseStream);
573
+ // Run Express app
574
+ await runExpressApp(app, req, res);
575
+ }
576
+ finally {
577
+ // Clear current invoke context
578
+ clearCurrentInvoke();
579
+ }
580
+ });
581
+ }
8
582
 
9
583
  //
10
584
  //
@@ -92,7 +666,7 @@ const corsHelper = (config = {}) => {
92
666
  };
93
667
  return expressCors(options);
94
668
  };
95
- var cors_helper = (config) => {
669
+ var cors = (config) => {
96
670
  const cors = corsHelper(config);
97
671
  return (req, res, next) => {
98
672
  cors(req, res, (error) => {
@@ -107,25 +681,205 @@ var cors_helper = (config) => {
107
681
  };
108
682
  };
109
683
 
684
+ //
685
+ //
686
+ // Constants
687
+ //
688
+ const DEFAULT_PORT = 8080;
689
+ //
690
+ //
691
+ // Main
692
+ //
693
+ /**
694
+ * Creates and starts an Express server with standard Jaypie middleware.
695
+ *
696
+ * Features:
697
+ * - CORS handling (configurable)
698
+ * - JSON body parsing
699
+ * - Listens on PORT env var (default 8080)
700
+ *
701
+ * Usage:
702
+ * ```ts
703
+ * import express from "express";
704
+ * import { createServer, expressHandler } from "@jaypie/express";
705
+ *
706
+ * const app = express();
707
+ *
708
+ * app.get("/", expressHandler(async (req, res) => {
709
+ * return { message: "Hello World" };
710
+ * }));
711
+ *
712
+ * const { server, port } = await createServer(app);
713
+ * console.log(`Server running on port ${port}`);
714
+ * ```
715
+ *
716
+ * @param app - Express application instance
717
+ * @param options - Server configuration options
718
+ * @returns Promise resolving to server instance and port
719
+ */
720
+ async function createServer(app, options = {}) {
721
+ const { cors: corsConfig, jsonLimit = "1mb", middleware = [], port: portOption, } = options;
722
+ // Determine port
723
+ const port = typeof portOption === "string"
724
+ ? parseInt(portOption, 10)
725
+ : (portOption ?? parseInt(process.env.PORT || String(DEFAULT_PORT), 10));
726
+ // Apply CORS middleware (unless explicitly disabled)
727
+ if (corsConfig !== false) {
728
+ app.use(cors(corsConfig));
729
+ }
730
+ // Apply JSON body parser
731
+ // Note: We use dynamic import to avoid requiring express as a direct dependency
732
+ const express = await import('express');
733
+ app.use(express.json({ limit: jsonLimit }));
734
+ // Apply additional middleware
735
+ for (const mw of middleware) {
736
+ app.use(mw);
737
+ }
738
+ // Start server
739
+ return new Promise((resolve, reject) => {
740
+ try {
741
+ const server = app.listen(port, () => {
742
+ // Get the actual port (important when port 0 is passed to get an ephemeral port)
743
+ const address = server.address();
744
+ const actualPort = address?.port ?? port;
745
+ log.info(`Server listening on port ${actualPort}`);
746
+ resolve({ port: actualPort, server });
747
+ });
748
+ server.on("error", (error) => {
749
+ log.error("Server error", { error });
750
+ reject(error);
751
+ });
752
+ }
753
+ catch (error) {
754
+ reject(error);
755
+ }
756
+ });
757
+ }
758
+
759
+ //
760
+ //
761
+ // Constants
762
+ //
763
+ const HEADER_AMZN_REQUEST_ID$1 = "x-amzn-request-id";
764
+ const ENV_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID";
110
765
  //
111
766
  //
112
767
  // Helper Functions
113
768
  //
114
- // Adapter for the "@codegenie/serverless-express" uuid
115
- function getServerlessExpressUuid() {
769
+ /**
770
+ * Extract request ID from X-Ray trace ID environment variable
771
+ * Format: Root=1-5e6b4a90-example;Parent=example;Sampled=1
772
+ * We extract the trace ID from the Root segment
773
+ */
774
+ function parseTraceId(traceId) {
775
+ if (!traceId)
776
+ return undefined;
777
+ // Extract the Root segment (format: Root=1-{timestamp}-{uuid})
778
+ const rootMatch = traceId.match(/Root=([^;]+)/);
779
+ if (rootMatch && rootMatch[1]) {
780
+ return rootMatch[1];
781
+ }
782
+ return undefined;
783
+ }
784
+ //
785
+ //
786
+ // Main
787
+ //
788
+ /**
789
+ * Get the current invoke UUID from Lambda Web Adapter context.
790
+ * This function extracts the request ID from either:
791
+ * 1. The x-amzn-request-id header (set by Lambda Web Adapter)
792
+ * 2. The _X_AMZN_TRACE_ID environment variable (set by Lambda runtime)
793
+ *
794
+ * @param req - Optional Express request object to extract headers from
795
+ * @returns The AWS request ID or undefined if not in Lambda context
796
+ */
797
+ function getWebAdapterUuid(req) {
798
+ // First, try to get from request headers
799
+ if (req && req.headers) {
800
+ const headerValue = req.headers[HEADER_AMZN_REQUEST_ID$1];
801
+ if (headerValue) {
802
+ return Array.isArray(headerValue) ? headerValue[0] : headerValue;
803
+ }
804
+ }
805
+ // Fall back to environment variable (X-Ray trace ID)
806
+ const traceId = process.env[ENV_AMZN_TRACE_ID];
807
+ if (traceId) {
808
+ return parseTraceId(traceId);
809
+ }
810
+ return undefined;
811
+ }
812
+
813
+ //
814
+ //
815
+ // Constants
816
+ //
817
+ const HEADER_AMZN_REQUEST_ID = "x-amzn-request-id";
818
+ //
819
+ //
820
+ // Helper Functions
821
+ //
822
+ /**
823
+ * Detect if we're running in Lambda Web Adapter mode.
824
+ * Web Adapter sets the x-amzn-request-id header on requests.
825
+ */
826
+ function isWebAdapterMode(req) {
827
+ if (req && req.headers && req.headers[HEADER_AMZN_REQUEST_ID]) {
828
+ return true;
829
+ }
830
+ return false;
831
+ }
832
+ /**
833
+ * Get UUID from Jaypie Lambda adapter context.
834
+ * This is set by createLambdaHandler/createLambdaStreamHandler.
835
+ */
836
+ function getJaypieAdapterUuid() {
116
837
  const currentInvoke = getCurrentInvoke();
117
- if (currentInvoke &&
118
- currentInvoke.context &&
119
- currentInvoke.context.awsRequestId) {
838
+ if (currentInvoke?.context?.awsRequestId) {
120
839
  return currentInvoke.context.awsRequestId;
121
840
  }
122
841
  return undefined;
123
842
  }
843
+ /**
844
+ * Get UUID from request object if it has Lambda context attached.
845
+ * The Jaypie adapter attaches _lambdaContext to the request.
846
+ */
847
+ function getRequestContextUuid(req) {
848
+ if (req && req._lambdaContext?.awsRequestId) {
849
+ return req._lambdaContext.awsRequestId;
850
+ }
851
+ return undefined;
852
+ }
124
853
  //
125
854
  //
126
855
  // Main
127
856
  //
128
- const getCurrentInvokeUuid = () => getServerlessExpressUuid();
857
+ /**
858
+ * Get the current invoke UUID from Lambda context.
859
+ * Works with Jaypie Lambda adapter and Lambda Web Adapter mode.
860
+ *
861
+ * @param req - Optional Express request object. Used to extract context
862
+ * from Web Adapter headers or Jaypie adapter's _lambdaContext.
863
+ * @returns The AWS request ID or undefined if not in Lambda context
864
+ */
865
+ function getCurrentInvokeUuid(req) {
866
+ // Priority 1: Web Adapter mode (header-based)
867
+ if (isWebAdapterMode(req)) {
868
+ return getWebAdapterUuid(req);
869
+ }
870
+ // Priority 2: Request has Lambda context attached (Jaypie adapter)
871
+ const requestContextUuid = getRequestContextUuid(req);
872
+ if (requestContextUuid) {
873
+ return requestContextUuid;
874
+ }
875
+ // Priority 3: Global context from Jaypie adapter
876
+ const jaypieAdapterUuid = getJaypieAdapterUuid();
877
+ if (jaypieAdapterUuid) {
878
+ return jaypieAdapterUuid;
879
+ }
880
+ // Fallback: Web Adapter env var
881
+ return getWebAdapterUuid();
882
+ }
129
883
 
130
884
  //
131
885
  //
@@ -287,7 +1041,7 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
287
1041
  lib: JAYPIE.LIB.EXPRESS,
288
1042
  });
289
1043
  // Update the public logger with the request ID
290
- const invokeUuid = getCurrentInvokeUuid();
1044
+ const invokeUuid = getCurrentInvokeUuid(req);
291
1045
  if (invokeUuid) {
292
1046
  logger$1.tag({ invoke: invokeUuid });
293
1047
  logger$1.tag({ shortInvoke: invokeUuid.slice(0, 8) });
@@ -541,6 +1295,46 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
541
1295
  };
542
1296
  }
543
1297
 
1298
+ //
1299
+ //
1300
+ // Main
1301
+ //
1302
+ const httpHandler = (statusCode = HTTP.CODE.OK, context = {}) => {
1303
+ // Give a default name if there isn't one
1304
+ if (!context.name) {
1305
+ context.name = "_http";
1306
+ }
1307
+ // Return a function that will be used as an express route
1308
+ return expressHandler(async (req, res) => {
1309
+ // Map the most throwable status codes to errors and throw them!
1310
+ const error = {
1311
+ [HTTP.CODE.BAD_REQUEST]: BadRequestError,
1312
+ [HTTP.CODE.UNAUTHORIZED]: UnauthorizedError,
1313
+ [HTTP.CODE.FORBIDDEN]: ForbiddenError,
1314
+ [HTTP.CODE.NOT_FOUND]: NotFoundError,
1315
+ [HTTP.CODE.METHOD_NOT_ALLOWED]: MethodNotAllowedError,
1316
+ [HTTP.CODE.GONE]: GoneError,
1317
+ [HTTP.CODE.TEAPOT]: TeapotError,
1318
+ [HTTP.CODE.INTERNAL_ERROR]: InternalError,
1319
+ [HTTP.CODE.BAD_GATEWAY]: BadGatewayError,
1320
+ [HTTP.CODE.UNAVAILABLE]: UnavailableError,
1321
+ [HTTP.CODE.GATEWAY_TIMEOUT]: GatewayTimeoutError,
1322
+ };
1323
+ // If this maps to an error, throw it
1324
+ if (error[statusCode]) {
1325
+ log.trace(`@knowdev/express: gracefully throwing ${statusCode} up to projectHandler`);
1326
+ throw new error[statusCode]();
1327
+ }
1328
+ // If this is an error and we didn't get thrown, log a warning
1329
+ if (statusCode >= 400) {
1330
+ log.warn(`@knowdev/express: status code ${statusCode} not mapped as throwable`);
1331
+ }
1332
+ // Send the response
1333
+ res.status(statusCode);
1334
+ return statusCode === HTTP.CODE.NO_CONTENT ? null : {};
1335
+ }, context);
1336
+ };
1337
+
544
1338
  // Cast logger to extended interface for runtime features not in type definitions
545
1339
  const logger = log;
546
1340
  //
@@ -609,7 +1403,7 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
609
1403
  lib: JAYPIE.LIB.EXPRESS,
610
1404
  });
611
1405
  // Update the public logger with the request ID
612
- const invokeUuid = getCurrentInvokeUuid();
1406
+ const invokeUuid = getCurrentInvokeUuid(req);
613
1407
  if (invokeUuid) {
614
1408
  logger.tag({ invoke: invokeUuid });
615
1409
  logger.tag({ shortInvoke: invokeUuid.slice(0, 8) });
@@ -771,46 +1565,6 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
771
1565
  };
772
1566
  }
773
1567
 
774
- //
775
- //
776
- // Main
777
- //
778
- const httpHandler = (statusCode = HTTP.CODE.OK, context = {}) => {
779
- // Give a default name if there isn't one
780
- if (!context.name) {
781
- context.name = "_http";
782
- }
783
- // Return a function that will be used as an express route
784
- return expressHandler(async (req, res) => {
785
- // Map the most throwable status codes to errors and throw them!
786
- const error = {
787
- [HTTP.CODE.BAD_REQUEST]: BadRequestError,
788
- [HTTP.CODE.UNAUTHORIZED]: UnauthorizedError,
789
- [HTTP.CODE.FORBIDDEN]: ForbiddenError,
790
- [HTTP.CODE.NOT_FOUND]: NotFoundError,
791
- [HTTP.CODE.METHOD_NOT_ALLOWED]: MethodNotAllowedError,
792
- [HTTP.CODE.GONE]: GoneError,
793
- [HTTP.CODE.TEAPOT]: TeapotError,
794
- [HTTP.CODE.INTERNAL_ERROR]: InternalError,
795
- [HTTP.CODE.BAD_GATEWAY]: BadGatewayError,
796
- [HTTP.CODE.UNAVAILABLE]: UnavailableError,
797
- [HTTP.CODE.GATEWAY_TIMEOUT]: GatewayTimeoutError,
798
- };
799
- // If this maps to an error, throw it
800
- if (error[statusCode]) {
801
- log.trace(`@knowdev/express: gracefully throwing ${statusCode} up to projectHandler`);
802
- throw new error[statusCode]();
803
- }
804
- // If this is an error and we didn't get thrown, log a warning
805
- if (statusCode >= 400) {
806
- log.warn(`@knowdev/express: status code ${statusCode} not mapped as throwable`);
807
- }
808
- // Send the response
809
- res.status(statusCode);
810
- return statusCode === HTTP.CODE.NO_CONTENT ? null : {};
811
- }, context);
812
- };
813
-
814
1568
  //
815
1569
  //
816
1570
  // Main
@@ -864,5 +1618,5 @@ const noContentRoute = routes.noContentRoute;
864
1618
  const notFoundRoute = routes.notFoundRoute;
865
1619
  const notImplementedRoute = routes.notImplementedRoute;
866
1620
 
867
- export { EXPRESS, badRequestRoute, cors_helper as cors, echoRoute, expressHandler, httpHandler as expressHttpCodeHandler, expressStreamHandler, forbiddenRoute, goneRoute, methodNotAllowedRoute, noContentRoute, notFoundRoute, notImplementedRoute };
1621
+ export { EXPRESS, LambdaRequest, LambdaResponseBuffered, LambdaResponseStreaming, badRequestRoute, cors, createLambdaHandler, createLambdaStreamHandler, createServer, echoRoute, expressHandler, httpHandler as expressHttpCodeHandler, expressStreamHandler, forbiddenRoute, getCurrentInvoke, getCurrentInvokeUuid, goneRoute, methodNotAllowedRoute, noContentRoute, notFoundRoute, notImplementedRoute };
868
1622
  //# sourceMappingURL=index.js.map