feat: valkey support and database request optimization modes - #118
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds optional valkey support (shared cache + write buffer) and a
DATABASE_REQUEST_OPTIMIZATIONmode (none/light/hard) so a serverless Postgres (e.g. neon.tech) can go to sleep between bursts of traffic.Implements
plans/2026-07-30-valkey-request-optimization.md.What's new
VALKEY_ENABLED=truebacks the existing cache-manager cache with valkey, so it is shared between instances and survives restarts. Independent of the optimization mode.DATABASE_REQUEST_OPTIMIZATION:none(default) — unchanged behavior.light— longer cache TTLs, cleanup schedulers throttled to every 15 minutes.hard(requires valkey) — rate limit tracking (requests/tokens/bans) moves fully into valkey, and new notes are buffered in valkey and written to the database in batches (interval or queue-size triggered).ValkeyConfigsection (VALKEY_*) with full JSDoc, so the typedoc config docs pick it up.docker-compose.valkey.ymldeployment example + README section.Default behavior is unchanged: with
VALKEY_ENABLED=falseandDATABASE_REQUEST_OPTIMIZATION=nonenothing about the current code paths changes. All mode branching lives insideDatabaseService— controllers and guards are untouched.Security & robustness fixes
Closes CodeQL alert #1 (
js/type-confusion-through-parameter-tampering, critical) and several crashes of the same class found while smoke testing. All are pre-existing and reproducible onmain, unrelated to valkey.fetch.controller.ts). A repeated query parameter (?key=<33 chars>&key=y) makes express deliver an array, sokey.lengthcounted elements (2) instead of characters and the 32-character guard passed. The key now has to be a string, sent exactly once, or the request is rejected with 400.GET /note/:id/decryptwithout a key andPOST /note/:id/decryptwithout a body returned 500 (dereferencingundefined). Both now return 400.DELETE /note/:idwithout a body returned 500, even though the body is documented as optional when deleting from the creator IP. Now 204 / 401 as documented.POST /note/textwithout a body returned 500. Now 400 "The note must not be empty".GET /file/:id?json=a&json=breturned 500 on.toLowerCase()of an array. Now falls back to the redirect.Checked and left alone: the
length,partand statspasswordparameters all fail closed on array input (Number([...])→NaN→ 400, and a strict!==against an array rejects), so they are not affected.Note one intentional behaviour change:
POST /note/textwith an empty body now returns 400 instead of creating an empty note. The endpoint already documented that 400, but thecost === 0check could never fire because theLIMITS_MIN_TOKENS_PER_CREATEfloor (default 1000) was applied first.Graceful shutdown
SIGTERM— whatdocker stopsends — previously calledprocess.exit(0)immediately without closing the app, so in hard mode the buffered notes were not flushed. Both signals now close the nest app (with a 5s force-exit fallback so a hanging shutdown cannot wedge the container).This also required moving the valkey disconnect from
onModuleDestroytoonApplicationShutdown: nest tore the valkey client down beforeDatabaseServicegot to flush, so the flush failed withConnection is closed. Verified against a real container — before the fix the buffer survived SIGTERM untouched, after it the note is flushed (Flushed 1 notes), the pending hash drops to 0, and the note is readable from the database after a restart.Verification
pnpm test— 14 suites, 105 tests passing;pnpm lintandpnpm buildclean.valkey/valkey:8container:not3:cache:statsappears after hitting/stats.Flushed 0 notes and 1 deletes); delete before flush removed straight from the buffer, never touching the DB.