fix(backend): guard stream listing endpoints against N+1 queries (#1147) - #1179
Merged
Folex1275 merged 1 commit intoJun 20, 2026
Merged
Conversation
…llarStream-HQ#1147) Audited all stream listing endpoints (v1/v2 streams, v3 history, search, export) and confirmed they already batch-fetch via single findMany/count calls rather than per-row queries. Added regression tests that assert each endpoint issues <5 DB queries and that the query count stays constant between 50 and 1000 streams, so a future per-row query can't silently reintroduce an N+1. Added StreamService.getStreamsBatch() so callers needing streams for multiple addresses (e.g. a future admin/report list endpoint) can fetch them in one query instead of looping per address. Also fixed two pre-existing bugs uncovered while exercising these code paths: a broken relative import in v3/history.routes.ts and v3/safe-vault.routes.ts that crashed the entire v3 router at boot, and a TS7030 inconsistent-return in the stream verify route. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tasks
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.
Summary
Closes #1147
Audited every stream-listing endpoint named in the issue and confirmed
they already fetch data via single batched
findMany/countcallsrather than per-row queries — no live N+1 pattern exists in the current
code. The contribution here hardens that guarantee with regression
tests and a reusable batch-fetch primitive, and fixes two severe bugs
uncovered while exercising these code paths.
What changed
backend/src/services/stream.service.tsStreamService.getStreamsBatch(addresses, filters)— fetchesstreams for many addresses in a single query (vs. looping
getStreamsForAddressper address), so future list/report endpointshave a ready-made batched primitive instead of reinventing one.
Regression tests (new)
stream-service-n1-query-count.test.ts— unit tests onStreamServiceasserting exactly 1prisma.stream.findManycall forboth
getStreamsForAddressandgetStreamsBatch, at 50 and 1000mock rows.
stream-listing-endpoints-query-count.test.ts— integration tests(supertest) covering all five listing endpoints:
GET /api/v1/streams/:addressGET /api/v1/streams/export/:addressGET /api/v2/streams/:addressGET /api/v3/history/:addressGET /api/v1/searchEach asserts total DB query count stays under 5, and that the
count is identical at 50 vs. 1000 streams — directly encoding the
"query count < 5" and "linear scaling at 1000 streams" acceptance
criteria as a permanent regression guard.
stream-listing-load.test.ts— load test simulating 1000 streamsthrough the export endpoint, asserting a single bounded pass (no
per-row DB round-trips) and constant query count vs. the 50-stream
case.
Bug fixes found while exercising these endpoints
backend/src/api/v3/history.routes.tsandbackend/src/api/v3/safe-vault.routes.ts: fixed a broken relativeimport (
../utils/asyncHandler.js→../../utils/asyncHandler.js,plus two more bad paths in
safe-vault.routes.tsforSafeVaultServiceandvalidateRequest). These were one directorylevel off, which crashed the entire
/api/v3router at moduleload — confirmed by actually loading the module with the project's
tsxruntime loader, not justtsc.backend/src/api/streams.routes.ts: fixed a TS7030inconsistent-return in the stream verify route (
return res.status(404)...vs. an implicit-undefined success path), which blocked ts-jest from
compiling this file for testing.
Why no
include/relation changesThe
StreamPrisma model has no foreign-key relations toEventLogorTokenPrice(they're linked by plain string fields, not@relation),so a literal Prisma
includedoesn't apply here. The equivalentoptimization — single batched queries with
INclauses instead ofper-row lookups — is what's already in place and is now what these
tests lock in.
Acceptance criteria
place; verified, not reintroduced as N+1)
endpoints
and processing time stay flat vs. 50 streams)
(
getStreamsBatchadded for reuse)Test plan
npm run test:jest— 18 new tests pass; no regressions vs. main(verified pre/post via
git stashcomparison: same 3pre-existing unrelated failures, no previously-passing test
broke)
npm run lint— 0 new errors (pre-existing errors are all infiles untouched by this PR)
npm run type-check— 0 new errors (same)v3/history.routes.tsandv3/safe-vault.routes.tsnowload correctly under the project's actual
tsxruntime loader