@luckystack/server 0.10.0 → 0.10.2
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 +5 -1
- package/CLAUDE.md +1 -0
- package/dist/index.js +32 -7
- package/dist/index.js.map +1 -1
- package/docs/http-routes.md +2 -2
- package/docs/security-defaults.md +4 -2
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,11 @@ All notable changes to `@luckystack/server` are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.10.2] - 2026-10-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `/_test/reset` accepts `?only=<target>[,<target>]` to clear exactly those targets (the shared list in core `TEST_RESET_TARGETS`). An unknown or empty target is `400 test.invalidResetTarget` — a typo must not silently reset nothing, or everything. Without `only` the default reset is unchanged. This lets an authenticated test sweep drain limiter buckets without deleting its own session.
|
|
9
13
|
|
|
10
14
|
## [0.8.7] - 2026-08-18
|
|
11
15
|
|
package/CLAUDE.md
CHANGED
|
@@ -69,6 +69,7 @@ One-call server bootstrap for a LuckyStack project. Wires together a raw Node.js
|
|
|
69
69
|
- `SERVER_IP` (env, optional, default `127.0.0.1`) — bind address fallback when `options.ip` is omitted.
|
|
70
70
|
- `NODE_ENV` (env, required for security-sensitive branches) — `development` / `test` toggle devkit hot reload + REPL and gate `/_test/reset`.
|
|
71
71
|
- `TEST_RESET_TOKEN` (env, required for `/_test/reset` to be reachable at all) — must match the `x-test-reset-token` request header. No fallback "no auth" mode.
|
|
72
|
+
- `/_test/reset?only=<target>[,<target>]` (query) — clear only the listed targets (core `TEST_RESET_TARGETS`); unknown/empty → `400 test.invalidResetTarget`. Without `only` the default reset (rate limits + sessions + activeUsers, hooks via `?include=hooks`) is unchanged.
|
|
72
73
|
- `projectConfig.http.healthEndpoint` (config) — path served by `handleHealthRoute`. Default `/_health`.
|
|
73
74
|
- `projectConfig.http.liveEndpoint` (config) — path served by `handleLivezRoute`. Default `/livez`.
|
|
74
75
|
- `projectConfig.http.readyEndpoint` (config) — path served by `handleReadyzRoute`. Default `/readyz`.
|
package/dist/index.js
CHANGED
|
@@ -391,9 +391,12 @@ import {
|
|
|
391
391
|
clearAllRateLimits,
|
|
392
392
|
getProjectConfig as getProjectConfig4,
|
|
393
393
|
formatKey,
|
|
394
|
+
isTestResetTarget,
|
|
394
395
|
redis as redis2,
|
|
396
|
+
TEST_RESET_ONLY_PARAM,
|
|
395
397
|
tryCatch as tryCatch2
|
|
396
398
|
} from "@luckystack/core";
|
|
399
|
+
var splitList = (value) => (value ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
397
400
|
var handleTestResetRoute = async ({ req, res, routePath }) => {
|
|
398
401
|
if (routePath !== getProjectConfig4().http.testResetEndpoint) return false;
|
|
399
402
|
const nodeEnv = process.env.NODE_ENV;
|
|
@@ -419,9 +422,33 @@ var handleTestResetRoute = async ({ req, res, routePath }) => {
|
|
|
419
422
|
res.end(JSON.stringify({ status: "error", errorCode: "auth.forbidden" }));
|
|
420
423
|
return true;
|
|
421
424
|
}
|
|
425
|
+
const rawUrl = req.url ?? "/";
|
|
426
|
+
const searchParams = URL.canParse(rawUrl, "http://localhost") ? new URL(rawUrl, "http://localhost").searchParams : new URLSearchParams();
|
|
427
|
+
const onlyParam = searchParams.get(TEST_RESET_ONLY_PARAM);
|
|
428
|
+
let targets;
|
|
429
|
+
if (onlyParam === null) {
|
|
430
|
+
targets = /* @__PURE__ */ new Set(["rateLimits", "sessions", "activeUsers"]);
|
|
431
|
+
if (splitList(searchParams.get("include")).includes("hooks")) targets.add("hooks");
|
|
432
|
+
} else {
|
|
433
|
+
const requested = splitList(onlyParam);
|
|
434
|
+
const unknown = requested.filter((target) => !isTestResetTarget(target));
|
|
435
|
+
if (requested.length === 0 || unknown.length > 0) {
|
|
436
|
+
res.statusCode = 400;
|
|
437
|
+
res.setHeader("Content-Type", "application/json");
|
|
438
|
+
res.end(JSON.stringify({
|
|
439
|
+
status: "error",
|
|
440
|
+
errorCode: "test.invalidResetTarget",
|
|
441
|
+
errorParams: [{ key: "target", value: unknown.join(",") || "(empty)" }]
|
|
442
|
+
}));
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
targets = new Set(requested.filter(isTestResetTarget));
|
|
446
|
+
}
|
|
422
447
|
const cleared = [];
|
|
423
|
-
|
|
424
|
-
|
|
448
|
+
if (targets.has("rateLimits")) {
|
|
449
|
+
await clearAllRateLimits();
|
|
450
|
+
cleared.push("rateLimits");
|
|
451
|
+
}
|
|
425
452
|
const sessionPattern = `${formatKey("-session", "")}:*`;
|
|
426
453
|
const activeUsersPattern = `${formatKey("-activeUsers", "")}:*`;
|
|
427
454
|
const scanAndDelete = async (pattern, label) => {
|
|
@@ -442,11 +469,9 @@ var handleTestResetRoute = async ({ req, res, routePath }) => {
|
|
|
442
469
|
if (deleted > 0) cleared.push(label);
|
|
443
470
|
return deleted;
|
|
444
471
|
};
|
|
445
|
-
await scanAndDelete(sessionPattern, "sessions");
|
|
446
|
-
await scanAndDelete(activeUsersPattern, "activeUsers");
|
|
447
|
-
|
|
448
|
-
const includeFlag = URL.canParse(rawUrl, "http://localhost") ? new URL(rawUrl, "http://localhost").searchParams.get("include") ?? "" : "";
|
|
449
|
-
if (includeFlag.split(",").map((s) => s.trim()).includes("hooks")) {
|
|
472
|
+
if (targets.has("sessions")) await scanAndDelete(sessionPattern, "sessions");
|
|
473
|
+
if (targets.has("activeUsers")) await scanAndDelete(activeUsersPattern, "activeUsers");
|
|
474
|
+
if (targets.has("hooks")) {
|
|
450
475
|
clearAllHooks();
|
|
451
476
|
cleared.push("hooks");
|
|
452
477
|
}
|