@jaypie/express 1.2.4 → 1.2.5

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 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 |
@@ -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
  }
@@ -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
- // Parse query string from URL
38
- const queryIndex = options.url.indexOf("?");
39
- if (queryIndex !== -1) {
40
- const queryString = options.url.slice(queryIndex + 1);
41
- const params = new URLSearchParams(queryString);
42
- for (const [key, value] of params) {
43
- this.query[key] = value;
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,66 @@ 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] = values.length === 1 && !rawKey.endsWith("[]") ? values[0] : values;
155
+ }
156
+ else if (Array.isArray(existingValues)) {
157
+ existingValues.push(...values);
158
+ }
159
+ else {
160
+ // Convert to array and merge
161
+ result[key] = [existingValues, ...values];
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ //
167
+ //
106
168
  // Type Guards
107
169
  //
108
170
  /**
@@ -128,6 +190,7 @@ function createLambdaRequest(event, context) {
128
190
  let url;
129
191
  let method;
130
192
  let protocol;
193
+ let query;
131
194
  let remoteAddress;
132
195
  const headers = { ...event.headers };
133
196
  if (isFunctionUrlEvent(event)) {
@@ -138,6 +201,10 @@ function createLambdaRequest(event, context) {
138
201
  method = event.requestContext.http.method;
139
202
  protocol = event.requestContext.http.protocol.split("/")[0].toLowerCase();
140
203
  remoteAddress = event.requestContext.http.sourceIp;
204
+ // Parse query string with proper multi-value and bracket notation support
205
+ if (event.rawQueryString) {
206
+ query = parseQueryString(event.rawQueryString);
207
+ }
141
208
  // Normalize cookies into Cookie header if not already present
142
209
  if (event.cookies && event.cookies.length > 0 && !headers.cookie) {
143
210
  headers.cookie = event.cookies.join("; ");
@@ -145,7 +212,13 @@ function createLambdaRequest(event, context) {
145
212
  }
146
213
  else if (isApiGatewayV1Event(event)) {
147
214
  // API Gateway REST API v1 format
215
+ // Use multiValueQueryStringParameters for proper array support
216
+ const multiValueParams = event.multiValueQueryStringParameters;
148
217
  const queryParams = event.queryStringParameters;
218
+ if (multiValueParams && Object.keys(multiValueParams).length > 0) {
219
+ query = buildQueryFromMultiValue(multiValueParams);
220
+ }
221
+ // Build URL with query string
149
222
  if (queryParams && Object.keys(queryParams).length > 0) {
150
223
  const queryString = new URLSearchParams(queryParams).toString();
151
224
  url = `${event.path}?${queryString}`;
@@ -179,6 +252,7 @@ function createLambdaRequest(event, context) {
179
252
  lambdaEvent: event,
180
253
  method,
181
254
  protocol,
255
+ query,
182
256
  remoteAddress,
183
257
  url,
184
258
  });