@everystack/server 0.4.35 → 0.4.36
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/CHANGELOG.md +6 -0
- package/package.json +3 -3
- package/src/index.ts +12 -1
- package/src/plugin.ts +12 -1
- package/src/strip-query-params.ts +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@everystack/server` are documented here.
|
|
|
4
4
|
|
|
5
5
|
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.4.36] - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- the Lambda entry strips edge query params (_v by default) before routing
|
|
12
|
+
|
|
7
13
|
## [0.4.35] - 2026-10-14
|
|
8
14
|
|
|
9
15
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.36",
|
|
4
4
|
"description": "Server runtime primitives for Lambda — event adapters, routing, SSR, image processing",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -192,8 +192,8 @@
|
|
|
192
192
|
"sst": "4.13.1",
|
|
193
193
|
"ts-jest": "29.4.9",
|
|
194
194
|
"typescript": "5.9.3",
|
|
195
|
-
"@everystack/
|
|
196
|
-
"@everystack/
|
|
195
|
+
"@everystack/cli": "0.4.100",
|
|
196
|
+
"@everystack/auth": "0.4.9"
|
|
197
197
|
},
|
|
198
198
|
"scripts": {
|
|
199
199
|
"test": "jest",
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
|
|
18
18
|
import { log } from './log';
|
|
19
19
|
import { buildTimingHeaders } from './timing';
|
|
20
|
+
import { DEFAULT_STRIP_QUERY_PARAMS, stripQueryParams } from './strip-query-params';
|
|
20
21
|
|
|
21
22
|
export { buildTimingHeaders } from './timing';
|
|
22
23
|
export type { TimingHeaderOptions } from './timing';
|
|
@@ -99,6 +100,13 @@ export interface LambdaHandlerOptions {
|
|
|
99
100
|
logSink?: LogSink;
|
|
100
101
|
/** Emit Server-Timing headers on responses. Default true. */
|
|
101
102
|
observability?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Query params removed from every HTTP request before routing, matched by decoded name whatever
|
|
105
|
+
* the value. The CDN edge appends `?_v=<epoch>` and CloudFront forwards the whole query string,
|
|
106
|
+
* so without this the REST handler would answer 400 to a param with no operator. List any params
|
|
107
|
+
* your own edge adds. Default `['_v']`; `[]` disables stripping.
|
|
108
|
+
*/
|
|
109
|
+
stripQueryParams?: string[];
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
// --- Event Adapter ---
|
|
@@ -256,7 +264,10 @@ export function createLambdaHandler(
|
|
|
256
264
|
|
|
257
265
|
try {
|
|
258
266
|
const { router } = await ensureInitialized();
|
|
259
|
-
const request =
|
|
267
|
+
const request = stripQueryParams(
|
|
268
|
+
eventToRequest(httpEvent),
|
|
269
|
+
options.stripQueryParams ?? DEFAULT_STRIP_QUERY_PARAMS,
|
|
270
|
+
);
|
|
260
271
|
const path = httpEvent.rawPath;
|
|
261
272
|
const method = httpEvent.requestContext.http.method;
|
|
262
273
|
const handlerFn = router(path, method);
|
package/src/plugin.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
log,
|
|
37
37
|
} from './index';
|
|
38
38
|
import { splitSqlStatements, stripSqlComments } from './sql-lex';
|
|
39
|
+
import { DEFAULT_STRIP_QUERY_PARAMS, stripQueryParams } from './strip-query-params';
|
|
39
40
|
|
|
40
41
|
// --- Plugin Types ---
|
|
41
42
|
|
|
@@ -138,6 +139,13 @@ export interface PluginLambdaHandlerOptions {
|
|
|
138
139
|
* here, not in the HTTP-facing API function.
|
|
139
140
|
*/
|
|
140
141
|
http?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Query params removed from every HTTP request before routing, matched by decoded name whatever
|
|
144
|
+
* the value. The CDN edge appends `?_v=<epoch>` and CloudFront forwards the whole query string,
|
|
145
|
+
* so without this the REST handler would answer 400 to a param with no operator. List any params
|
|
146
|
+
* your own edge adds. Default `['_v']`; `[]` disables stripping.
|
|
147
|
+
*/
|
|
148
|
+
stripQueryParams?: string[];
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
// --- Plugin Lambda Handler Implementation ---
|
|
@@ -272,7 +280,10 @@ export function createPluginLambdaHandler(
|
|
|
272
280
|
|
|
273
281
|
try {
|
|
274
282
|
const { router, logSink } = await ensureInitialized();
|
|
275
|
-
const request =
|
|
283
|
+
const request = stripQueryParams(
|
|
284
|
+
eventToRequest(httpEvent),
|
|
285
|
+
options.stripQueryParams ?? DEFAULT_STRIP_QUERY_PARAMS,
|
|
286
|
+
);
|
|
276
287
|
const path = httpEvent.rawPath;
|
|
277
288
|
const method = httpEvent.requestContext.http.method;
|
|
278
289
|
const handlerFn = router!(path, method);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal (not exported from the package): removes edge-added query params from a Request before
|
|
3
|
+
* routing. Both Lambda entry points (createLambdaHandler, createPluginLambdaHandler) call it right
|
|
4
|
+
* after eventToRequest.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The CDN edge's cache-version param (cacheVersionFeature, docs/caching.md). */
|
|
8
|
+
export const DEFAULT_STRIP_QUERY_PARAMS: readonly string[] = ['_v'];
|
|
9
|
+
|
|
10
|
+
/** Decodes a raw query-pair name the way a form-encoded parser does; null if it cannot decode. */
|
|
11
|
+
function decodeName(raw: string): string | null {
|
|
12
|
+
try {
|
|
13
|
+
return decodeURIComponent(raw.replace(/\+/g, ' '));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns `request` with every query pair whose decoded name is in `names` removed. The kept pairs
|
|
21
|
+
* keep their exact bytes and order (no URLSearchParams re-serialization). When nothing is removed
|
|
22
|
+
* the same Request object is returned; otherwise a new Request carries the original method,
|
|
23
|
+
* headers and body.
|
|
24
|
+
*/
|
|
25
|
+
export function stripQueryParams(request: Request, names: readonly string[]): Request {
|
|
26
|
+
if (names.length === 0) return request;
|
|
27
|
+
const url = request.url;
|
|
28
|
+
const q = url.indexOf('?');
|
|
29
|
+
if (q === -1) return request;
|
|
30
|
+
|
|
31
|
+
const pairs = url.slice(q + 1).split('&');
|
|
32
|
+
const kept = pairs.filter((pair) => {
|
|
33
|
+
const eq = pair.indexOf('=');
|
|
34
|
+
const name = decodeName(eq === -1 ? pair : pair.slice(0, eq));
|
|
35
|
+
return name === null || !names.includes(name);
|
|
36
|
+
});
|
|
37
|
+
if (kept.length === pairs.length) return request;
|
|
38
|
+
|
|
39
|
+
const query = kept.join('&');
|
|
40
|
+
return new Request(url.slice(0, q) + (query ? '?' + query : ''), request);
|
|
41
|
+
}
|