@jaypie/express 1.2.1 → 1.2.3

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.
@@ -0,0 +1,60 @@
1
+ import type { Application, RequestHandler } from "express";
2
+ import type { Server } from "http";
3
+ import type { CorsConfig } from "./cors.helper.js";
4
+ export interface CreateServerOptions {
5
+ /**
6
+ * CORS configuration. Pass false to disable CORS middleware.
7
+ */
8
+ cors?: CorsConfig | false;
9
+ /**
10
+ * JSON body parser limit. Defaults to "1mb".
11
+ */
12
+ jsonLimit?: string;
13
+ /**
14
+ * Additional middleware to apply before routes.
15
+ */
16
+ middleware?: RequestHandler[];
17
+ /**
18
+ * Port to listen on. Defaults to PORT env var or 8080.
19
+ */
20
+ port?: number | string;
21
+ }
22
+ export interface ServerResult {
23
+ /**
24
+ * The HTTP server instance.
25
+ */
26
+ server: Server;
27
+ /**
28
+ * The port the server is listening on.
29
+ */
30
+ port: number;
31
+ }
32
+ /**
33
+ * Creates and starts an Express server with standard Jaypie middleware.
34
+ *
35
+ * Features:
36
+ * - CORS handling (configurable)
37
+ * - JSON body parsing
38
+ * - Listens on PORT env var (default 8080)
39
+ *
40
+ * Usage:
41
+ * ```ts
42
+ * import express from "express";
43
+ * import { createServer, expressHandler } from "@jaypie/express";
44
+ *
45
+ * const app = express();
46
+ *
47
+ * app.get("/", expressHandler(async (req, res) => {
48
+ * return { message: "Hello World" };
49
+ * }));
50
+ *
51
+ * const { server, port } = await createServer(app);
52
+ * console.log(`Server running on port ${port}`);
53
+ * ```
54
+ *
55
+ * @param app - Express application instance
56
+ * @param options - Server configuration options
57
+ * @returns Promise resolving to server instance and port
58
+ */
59
+ declare function createServer(app: Application, options?: CreateServerOptions): Promise<ServerResult>;
60
+ export default createServer;
@@ -7,6 +7,7 @@ export interface ExpressHandlerOptions {
7
7
  chaos?: string;
8
8
  locals?: Record<string, unknown | ExpressHandlerLocals>;
9
9
  name?: string;
10
+ secrets?: string[];
10
11
  setup?: JaypieHandlerSetup[] | JaypieHandlerSetup;
11
12
  teardown?: JaypieHandlerTeardown[] | JaypieHandlerTeardown;
12
13
  unavailable?: boolean;
@@ -8,6 +8,7 @@ export interface ExpressStreamHandlerOptions {
8
8
  contentType?: string;
9
9
  locals?: Record<string, unknown | ExpressStreamHandlerLocals>;
10
10
  name?: string;
11
+ secrets?: string[];
11
12
  setup?: JaypieStreamHandlerSetup[] | JaypieStreamHandlerSetup;
12
13
  teardown?: JaypieStreamHandlerTeardown[] | JaypieStreamHandlerTeardown;
13
14
  unavailable?: boolean;
@@ -1,2 +1,11 @@
1
- declare const getCurrentInvokeUuid: () => string | undefined;
1
+ import type { Request } from "express";
2
+ /**
3
+ * Get the current invoke UUID from Lambda context.
4
+ * Works in both serverless-express mode and Lambda Web Adapter mode.
5
+ *
6
+ * @param req - Optional Express request object. Required for Web Adapter mode
7
+ * to extract the x-amzn-request-id header.
8
+ * @returns The AWS request ID or undefined if not in Lambda context
9
+ */
10
+ declare function getCurrentInvokeUuid(req?: Request): string | undefined;
2
11
  export default getCurrentInvokeUuid;
@@ -0,0 +1,12 @@
1
+ import type { Request } from "express";
2
+ /**
3
+ * Get the current invoke UUID from Lambda Web Adapter context.
4
+ * This function extracts the request ID from either:
5
+ * 1. The x-amzn-request-id header (set by Lambda Web Adapter)
6
+ * 2. The _X_AMZN_TRACE_ID environment variable (set by Lambda runtime)
7
+ *
8
+ * @param req - Optional Express request object to extract headers from
9
+ * @returns The AWS request ID or undefined if not in Lambda context
10
+ */
11
+ declare function getWebAdapterUuid(req?: Request): string | undefined;
12
+ export default getWebAdapterUuid;
@@ -4,6 +4,7 @@ var errors = require('@jaypie/errors');
4
4
  var kit = require('@jaypie/kit');
5
5
  var expressCors = require('cors');
6
6
  var logger$2 = require('@jaypie/logger');
7
+ var aws = require('@jaypie/aws');
7
8
  var datadog = require('@jaypie/datadog');
8
9
  var serverlessExpress = require('@codegenie/serverless-express');
9
10
 
@@ -93,7 +94,7 @@ const corsHelper = (config = {}) => {
93
94
  };
94
95
  return expressCors(options);
95
96
  };
96
- var cors_helper = (config) => {
97
+ var cors = (config) => {
97
98
  const cors = corsHelper(config);
98
99
  return (req, res, next) => {
99
100
  cors(req, res, (error) => {
@@ -108,11 +109,157 @@ var cors_helper = (config) => {
108
109
  };
109
110
  };
110
111
 
112
+ //
113
+ //
114
+ // Constants
115
+ //
116
+ const DEFAULT_PORT = 8080;
117
+ //
118
+ //
119
+ // Main
120
+ //
121
+ /**
122
+ * Creates and starts an Express server with standard Jaypie middleware.
123
+ *
124
+ * Features:
125
+ * - CORS handling (configurable)
126
+ * - JSON body parsing
127
+ * - Listens on PORT env var (default 8080)
128
+ *
129
+ * Usage:
130
+ * ```ts
131
+ * import express from "express";
132
+ * import { createServer, expressHandler } from "@jaypie/express";
133
+ *
134
+ * const app = express();
135
+ *
136
+ * app.get("/", expressHandler(async (req, res) => {
137
+ * return { message: "Hello World" };
138
+ * }));
139
+ *
140
+ * const { server, port } = await createServer(app);
141
+ * console.log(`Server running on port ${port}`);
142
+ * ```
143
+ *
144
+ * @param app - Express application instance
145
+ * @param options - Server configuration options
146
+ * @returns Promise resolving to server instance and port
147
+ */
148
+ async function createServer(app, options = {}) {
149
+ const { cors: corsConfig, jsonLimit = "1mb", middleware = [], port: portOption, } = options;
150
+ // Determine port
151
+ const port = typeof portOption === "string"
152
+ ? parseInt(portOption, 10)
153
+ : (portOption ?? parseInt(process.env.PORT || String(DEFAULT_PORT), 10));
154
+ // Apply CORS middleware (unless explicitly disabled)
155
+ if (corsConfig !== false) {
156
+ app.use(cors(corsConfig));
157
+ }
158
+ // Apply JSON body parser
159
+ // Note: We use dynamic import to avoid requiring express as a direct dependency
160
+ const express = await import('express');
161
+ app.use(express.json({ limit: jsonLimit }));
162
+ // Apply additional middleware
163
+ for (const mw of middleware) {
164
+ app.use(mw);
165
+ }
166
+ // Start server
167
+ return new Promise((resolve, reject) => {
168
+ try {
169
+ const server = app.listen(port, () => {
170
+ // Get the actual port (important when port 0 is passed to get an ephemeral port)
171
+ const address = server.address();
172
+ const actualPort = address?.port ?? port;
173
+ logger$2.log.info(`Server listening on port ${actualPort}`);
174
+ resolve({ port: actualPort, server });
175
+ });
176
+ server.on("error", (error) => {
177
+ logger$2.log.error("Server error", { error });
178
+ reject(error);
179
+ });
180
+ }
181
+ catch (error) {
182
+ reject(error);
183
+ }
184
+ });
185
+ }
186
+
187
+ //
188
+ //
189
+ // Constants
190
+ //
191
+ const HEADER_AMZN_REQUEST_ID$1 = "x-amzn-request-id";
192
+ const ENV_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID";
111
193
  //
112
194
  //
113
195
  // Helper Functions
114
196
  //
115
- // Adapter for the "@codegenie/serverless-express" uuid
197
+ /**
198
+ * Extract request ID from X-Ray trace ID environment variable
199
+ * Format: Root=1-5e6b4a90-example;Parent=example;Sampled=1
200
+ * We extract the trace ID from the Root segment
201
+ */
202
+ function parseTraceId(traceId) {
203
+ if (!traceId)
204
+ return undefined;
205
+ // Extract the Root segment (format: Root=1-{timestamp}-{uuid})
206
+ const rootMatch = traceId.match(/Root=([^;]+)/);
207
+ if (rootMatch && rootMatch[1]) {
208
+ return rootMatch[1];
209
+ }
210
+ return undefined;
211
+ }
212
+ //
213
+ //
214
+ // Main
215
+ //
216
+ /**
217
+ * Get the current invoke UUID from Lambda Web Adapter context.
218
+ * This function extracts the request ID from either:
219
+ * 1. The x-amzn-request-id header (set by Lambda Web Adapter)
220
+ * 2. The _X_AMZN_TRACE_ID environment variable (set by Lambda runtime)
221
+ *
222
+ * @param req - Optional Express request object to extract headers from
223
+ * @returns The AWS request ID or undefined if not in Lambda context
224
+ */
225
+ function getWebAdapterUuid(req) {
226
+ // First, try to get from request headers
227
+ if (req && req.headers) {
228
+ const headerValue = req.headers[HEADER_AMZN_REQUEST_ID$1];
229
+ if (headerValue) {
230
+ return Array.isArray(headerValue) ? headerValue[0] : headerValue;
231
+ }
232
+ }
233
+ // Fall back to environment variable (X-Ray trace ID)
234
+ const traceId = process.env[ENV_AMZN_TRACE_ID];
235
+ if (traceId) {
236
+ return parseTraceId(traceId);
237
+ }
238
+ return undefined;
239
+ }
240
+
241
+ //
242
+ //
243
+ // Constants
244
+ //
245
+ const HEADER_AMZN_REQUEST_ID = "x-amzn-request-id";
246
+ //
247
+ //
248
+ // Helper Functions
249
+ //
250
+ /**
251
+ * Detect if we're running in Lambda Web Adapter mode.
252
+ * Web Adapter sets the x-amzn-request-id header on requests.
253
+ */
254
+ function isWebAdapterMode(req) {
255
+ if (req && req.headers && req.headers[HEADER_AMZN_REQUEST_ID]) {
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ /**
261
+ * Adapter for the "@codegenie/serverless-express" uuid
262
+ */
116
263
  function getServerlessExpressUuid() {
117
264
  const currentInvoke = serverlessExpress.getCurrentInvoke();
118
265
  if (currentInvoke &&
@@ -126,7 +273,27 @@ function getServerlessExpressUuid() {
126
273
  //
127
274
  // Main
128
275
  //
129
- const getCurrentInvokeUuid = () => getServerlessExpressUuid();
276
+ /**
277
+ * Get the current invoke UUID from Lambda context.
278
+ * Works in both serverless-express mode and Lambda Web Adapter mode.
279
+ *
280
+ * @param req - Optional Express request object. Required for Web Adapter mode
281
+ * to extract the x-amzn-request-id header.
282
+ * @returns The AWS request ID or undefined if not in Lambda context
283
+ */
284
+ function getCurrentInvokeUuid(req) {
285
+ // If request is provided and has Web Adapter header, use Web Adapter mode
286
+ if (isWebAdapterMode(req)) {
287
+ return getWebAdapterUuid(req);
288
+ }
289
+ // Try serverless-express mode first
290
+ const serverlessExpressUuid = getServerlessExpressUuid();
291
+ if (serverlessExpressUuid) {
292
+ return serverlessExpressUuid;
293
+ }
294
+ // If no request but we might be in Web Adapter mode, try env var fallback
295
+ return getWebAdapterUuid();
296
+ }
130
297
 
131
298
  //
132
299
  //
@@ -251,7 +418,7 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
251
418
  //
252
419
  // Validate
253
420
  //
254
- let { chaos, locals, name, setup = [], teardown = [], unavailable, validate, } = options;
421
+ let { chaos, locals, name, secrets, setup = [], teardown = [], unavailable, validate, } = options;
255
422
  if (typeof handler !== "function") {
256
423
  throw new errors.BadRequestError(`Argument "${handler}" doesn't match type "function"`);
257
424
  }
@@ -288,7 +455,7 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
288
455
  lib: kit.JAYPIE.LIB.EXPRESS,
289
456
  });
290
457
  // Update the public logger with the request ID
291
- const invokeUuid = getCurrentInvokeUuid();
458
+ const invokeUuid = getCurrentInvokeUuid(req);
292
459
  if (invokeUuid) {
293
460
  logger$1.tag({ invoke: invokeUuid });
294
461
  logger$1.tag({ shortInvoke: invokeUuid.slice(0, 8) });
@@ -357,6 +524,14 @@ function expressHandler(handlerOrOptions, optionsOrHandler) {
357
524
  //
358
525
  // Preprocess
359
526
  //
527
+ // Load secrets into process.env if configured
528
+ if (secrets && secrets.length > 0) {
529
+ const secretsToLoad = secrets;
530
+ const secretsSetup = async () => {
531
+ await aws.loadEnvSecrets(...secretsToLoad);
532
+ };
533
+ setup.unshift(secretsSetup);
534
+ }
360
535
  if (locals) {
361
536
  // Locals
362
537
  const keys = Object.keys(locals);
@@ -546,7 +721,7 @@ const logger = logger$2.log;
546
721
  function formatErrorSSE(error) {
547
722
  const isJaypieError = error.isProjectError;
548
723
  const body = isJaypieError
549
- ? error.body?.() ?? { error: error.message }
724
+ ? (error.body?.() ?? { error: error.message })
550
725
  : new errors.UnhandledError().body();
551
726
  return `event: error\ndata: ${JSON.stringify(body)}\n\n`;
552
727
  }
@@ -568,7 +743,7 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
568
743
  //
569
744
  // Validate
570
745
  //
571
- let { chaos, contentType = "text/event-stream", locals, name, setup = [], teardown = [], unavailable, validate, } = options;
746
+ let { chaos, contentType = "text/event-stream", locals, name, secrets, setup = [], teardown = [], unavailable, validate, } = options;
572
747
  if (typeof handler !== "function") {
573
748
  throw new errors.BadRequestError(`Argument "${handler}" doesn't match type "function"`);
574
749
  }
@@ -602,7 +777,7 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
602
777
  lib: kit.JAYPIE.LIB.EXPRESS,
603
778
  });
604
779
  // Update the public logger with the request ID
605
- const invokeUuid = getCurrentInvokeUuid();
780
+ const invokeUuid = getCurrentInvokeUuid(req);
606
781
  if (invokeUuid) {
607
782
  logger.tag({ invoke: invokeUuid });
608
783
  logger.tag({ shortInvoke: invokeUuid.slice(0, 8) });
@@ -646,6 +821,14 @@ function expressStreamHandler(handlerOrOptions, optionsOrHandler) {
646
821
  //
647
822
  // Preprocess
648
823
  //
824
+ // Load secrets into process.env if configured
825
+ if (secrets && secrets.length > 0) {
826
+ const secretsToLoad = secrets;
827
+ const secretsSetup = async () => {
828
+ await aws.loadEnvSecrets(...secretsToLoad);
829
+ };
830
+ setup.unshift(secretsSetup);
831
+ }
649
832
  if (locals) {
650
833
  const keys = Object.keys(locals);
651
834
  if (keys.length > 0) {
@@ -851,12 +1034,14 @@ const notImplementedRoute = routes.notImplementedRoute;
851
1034
 
852
1035
  exports.EXPRESS = EXPRESS;
853
1036
  exports.badRequestRoute = badRequestRoute;
854
- exports.cors = cors_helper;
1037
+ exports.cors = cors;
1038
+ exports.createServer = createServer;
855
1039
  exports.echoRoute = echoRoute;
856
1040
  exports.expressHandler = expressHandler;
857
1041
  exports.expressHttpCodeHandler = httpHandler;
858
1042
  exports.expressStreamHandler = expressStreamHandler;
859
1043
  exports.forbiddenRoute = forbiddenRoute;
1044
+ exports.getCurrentInvokeUuid = getCurrentInvokeUuid;
860
1045
  exports.goneRoute = goneRoute;
861
1046
  exports.methodNotAllowedRoute = methodNotAllowedRoute;
862
1047
  exports.noContentRoute = noContentRoute;