@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.
- package/README.md +1 -0
- package/dist/cjs/adapter/LambdaRequest.d.ts +1 -0
- package/dist/cjs/index.cjs +114 -10
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/adapter/LambdaRequest.d.ts +1 -0
- package/dist/esm/index.js +114 -10
- package/dist/esm/index.js.map +1 -1
- package/package.json +6 -6
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
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
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.
|