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