@jaypie/express 1.2.4 → 1.2.6

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.
@@ -8,6 +8,7 @@ interface LambdaRequestOptions {
8
8
  lambdaEvent: LambdaEvent;
9
9
  method: string;
10
10
  protocol: string;
11
+ query?: Record<string, string | string[]>;
11
12
  remoteAddress: string;
12
13
  url: string;
13
14
  }
package/dist/esm/index.js CHANGED
@@ -32,13 +32,15 @@ class LambdaRequest extends Readable {
32
32
  this.path = options.url.split("?")[0];
33
33
  this.headers = this.normalizeHeaders(options.headers);
34
34
  this.bodyBuffer = options.body ?? null;
35
- // Parse query string from URL
36
- const queryIndex = options.url.indexOf("?");
37
- if (queryIndex !== -1) {
38
- const queryString = options.url.slice(queryIndex + 1);
39
- const params = new URLSearchParams(queryString);
40
- for (const [key, value] of params) {
41
- this.query[key] = value;
35
+ // Use pre-parsed query if provided, otherwise parse from URL
36
+ if (options.query) {
37
+ this.query = options.query;
38
+ }
39
+ else {
40
+ const queryIndex = options.url.indexOf("?");
41
+ if (queryIndex !== -1) {
42
+ const queryString = options.url.slice(queryIndex + 1);
43
+ this.query = parseQueryString(queryString);
42
44
  }
43
45
  }
44
46
  // Store Lambda context
@@ -101,6 +103,67 @@ class LambdaRequest extends Readable {
101
103
  }
102
104
  //
103
105
  //
106
+ // Helper Functions
107
+ //
108
+ /**
109
+ * Normalize bracket notation in query parameter key.
110
+ * Removes trailing `[]` from keys (e.g., `filterByStatus[]` → `filterByStatus`).
111
+ */
112
+ function normalizeQueryKey(key) {
113
+ return key.endsWith("[]") ? key.slice(0, -2) : key;
114
+ }
115
+ /**
116
+ * Parse a query string into a record with proper array handling.
117
+ * Handles bracket notation (e.g., `param[]`) and multi-value parameters.
118
+ */
119
+ function parseQueryString(queryString) {
120
+ const result = {};
121
+ const params = new URLSearchParams(queryString);
122
+ for (const [rawKey, value] of params) {
123
+ const key = normalizeQueryKey(rawKey);
124
+ const existing = result[key];
125
+ if (existing === undefined) {
126
+ // First occurrence - check if it's bracket notation to determine if it should be an array
127
+ result[key] = rawKey.endsWith("[]") ? [value] : value;
128
+ }
129
+ else if (Array.isArray(existing)) {
130
+ existing.push(value);
131
+ }
132
+ else {
133
+ // Convert to array when we encounter a second value
134
+ result[key] = [existing, value];
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ /**
140
+ * Build query object from API Gateway v1 multiValueQueryStringParameters.
141
+ * Normalizes bracket notation and preserves array values.
142
+ */
143
+ function buildQueryFromMultiValue(multiValueParams) {
144
+ const result = {};
145
+ if (!multiValueParams)
146
+ return result;
147
+ for (const [rawKey, values] of Object.entries(multiValueParams)) {
148
+ const key = normalizeQueryKey(rawKey);
149
+ const existingValues = result[key];
150
+ if (existingValues === undefined) {
151
+ // First occurrence - use array if multiple values or bracket notation
152
+ result[key] =
153
+ values.length === 1 && !rawKey.endsWith("[]") ? values[0] : values;
154
+ }
155
+ else if (Array.isArray(existingValues)) {
156
+ existingValues.push(...values);
157
+ }
158
+ else {
159
+ // Convert to array and merge
160
+ result[key] = [existingValues, ...values];
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ //
166
+ //
104
167
  // Type Guards
105
168
  //
106
169
  /**
@@ -126,6 +189,7 @@ function createLambdaRequest(event, context) {
126
189
  let url;
127
190
  let method;
128
191
  let protocol;
192
+ let query;
129
193
  let remoteAddress;
130
194
  const headers = { ...event.headers };
131
195
  if (isFunctionUrlEvent(event)) {
@@ -136,6 +200,10 @@ function createLambdaRequest(event, context) {
136
200
  method = event.requestContext.http.method;
137
201
  protocol = event.requestContext.http.protocol.split("/")[0].toLowerCase();
138
202
  remoteAddress = event.requestContext.http.sourceIp;
203
+ // Parse query string with proper multi-value and bracket notation support
204
+ if (event.rawQueryString) {
205
+ query = parseQueryString(event.rawQueryString);
206
+ }
139
207
  // Normalize cookies into Cookie header if not already present
140
208
  if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
141
209
  headers.cookie = event.cookies.join("; ");
@@ -143,7 +211,13 @@ function createLambdaRequest(event, context) {
143
211
  }
144
212
  else if (isApiGatewayV1Event(event)) {
145
213
  // API Gateway REST API v1 format
214
+ // Use multiValueQueryStringParameters for proper array support
215
+ const multiValueParams = event.multiValueQueryStringParameters;
146
216
  const queryParams = event.queryStringParameters;
217
+ if (multiValueParams && Object.keys(multiValueParams).length > 0) {
218
+ query = buildQueryFromMultiValue(multiValueParams);
219
+ }
220
+ // Build URL with query string
147
221
  if (queryParams && Object.keys(queryParams).length > 0) {
148
222
  const queryString = new URLSearchParams(queryParams).toString();
149
223
  url = `${event.path}?${queryString}`;
@@ -177,6 +251,7 @@ function createLambdaRequest(event, context) {
177
251
  lambdaEvent: event,
178
252
  method,
179
253
  protocol,
254
+ query,
180
255
  remoteAddress,
181
256
  url,
182
257
  });
@@ -973,7 +1048,9 @@ function createLambdaHandler(app, _options) {
973
1048
  {
974
1049
  status: 500,
975
1050
  title: "Internal Server Error",
976
- detail: error instanceof Error ? error.message : "Unknown error occurred",
1051
+ detail: error instanceof Error
1052
+ ? error.message
1053
+ : "Unknown error occurred",
977
1054
  },
978
1055
  ],
979
1056
  }),
@@ -1060,6 +1137,33 @@ const ensureProtocol = (url) => {
1060
1137
  return url;
1061
1138
  return HTTPS_PROTOCOL + url;
1062
1139
  };
1140
+ const extractHostname = (origin) => {
1141
+ try {
1142
+ const url = new URL(origin);
1143
+ return url.hostname;
1144
+ }
1145
+ catch {
1146
+ return undefined;
1147
+ }
1148
+ };
1149
+ const isOriginAllowed = (requestOrigin, allowed) => {
1150
+ const normalizedAllowed = ensureProtocol(allowed);
1151
+ const normalizedRequest = ensureProtocol(requestOrigin);
1152
+ const allowedHostname = extractHostname(normalizedAllowed);
1153
+ const requestHostname = extractHostname(normalizedRequest);
1154
+ if (!allowedHostname || !requestHostname) {
1155
+ return false;
1156
+ }
1157
+ // Exact match
1158
+ if (requestHostname === allowedHostname) {
1159
+ return true;
1160
+ }
1161
+ // Subdomain match
1162
+ if (requestHostname.endsWith(`.${allowedHostname}`)) {
1163
+ return true;
1164
+ }
1165
+ return false;
1166
+ };
1063
1167
  const dynamicOriginCallbackHandler = (origin) => {
1064
1168
  return (requestOrigin, callback) => {
1065
1169
  // Handle wildcard origin
@@ -1093,7 +1197,7 @@ const dynamicOriginCallbackHandler = (origin) => {
1093
1197
  if (allowed instanceof RegExp) {
1094
1198
  return allowed.test(requestOrigin);
1095
1199
  }
1096
- return requestOrigin.includes(allowed);
1200
+ return isOriginAllowed(requestOrigin, allowed);
1097
1201
  });
1098
1202
  if (isAllowed) {
1099
1203
  callback(null, true);
@@ -1364,7 +1468,7 @@ const logger$1 = log;
1364
1468
  * Uses Symbol marker to survive prototype chain modifications from Express and dd-trace.
1365
1469
  */
1366
1470
  function isLambdaMockResponse(res) {
1367
- return res[JAYPIE_LAMBDA_MOCK] === true;
1471
+ return (res[JAYPIE_LAMBDA_MOCK] === true);
1368
1472
  }
1369
1473
  /**
1370
1474
  * Safely send a JSON response, avoiding dd-trace interception.