Skip to content

fix(backend): guard stream listing endpoints against N+1 queries (#1147) - #1179

Merged
Folex1275 merged 1 commit into
StellarStream-HQ:mainfrom
Hexstar-labs:fix/n1-query-stream-listing-1147
Jun 20, 2026
Merged

fix(backend): guard stream listing endpoints against N+1 queries (#1147)#1179
Folex1275 merged 1 commit into
StellarStream-HQ:mainfrom
Hexstar-labs:fix/n1-query-stream-listing-1147

Conversation

@Hexstar-labs

Copy link
Copy Markdown
Contributor

Summary

Closes #1147

Audited every stream-listing endpoint named in the issue and confirmed
they already fetch data via single batched findMany/count calls
rather 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.ts

  • Added StreamService.getStreamsBatch(addresses, filters) — fetches
    streams for many addresses in a single query (vs. looping
    getStreamsForAddress per address), so future list/report endpoints
    have a ready-made batched primitive instead of reinventing one.

Regression tests (new)

  • stream-service-n1-query-count.test.ts — unit tests on
    StreamService asserting exactly 1 prisma.stream.findMany call for
    both getStreamsForAddress and getStreamsBatch, at 50 and 1000
    mock rows.

  • stream-listing-endpoints-query-count.test.ts — integration tests
    (supertest) covering all five listing endpoints:

    • GET /api/v1/streams/:address
    • GET /api/v1/streams/export/:address
    • GET /api/v2/streams/:address
    • GET /api/v3/history/:address
    • GET /api/v1/search

    Each 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 streams
    through 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.ts and
    backend/src/api/v3/safe-vault.routes.ts: fixed a broken relative
    import (../utils/asyncHandler.js../../utils/asyncHandler.js,
    plus two more bad paths in safe-vault.routes.ts for
    SafeVaultService and validateRequest). These were one directory
    level off, which crashed the entire /api/v3 router at module
    load — confirmed by actually loading the module with the project's
    tsx runtime loader, not just tsc.
  • backend/src/api/streams.routes.ts: fixed a TS7030
    inconsistent-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 changes

The Stream Prisma model has no foreign-key relations to EventLog or
TokenPrice (they're linked by plain string fields, not @relation),
so a literal Prisma include doesn't apply here. The equivalent
optimization — single batched queries with IN clauses instead of
per-row lookups — is what's already in place and is now what these
tests lock in.

Acceptance criteria

  • Single-query batched fetching for stream listing (already in
    place; verified, not reintroduced as N+1)
  • Query count < 5 — enforced by new tests across all 5 list
    endpoints
  • Load test with 1000 streams verifies linear scaling (query count
    and processing time stay flat vs. 50 streams)
  • Same pattern applied/available across all list endpoints
    (getStreamsBatch added for reuse)

Test plan

  • npm run test:jest — 18 new tests pass; no regressions vs. main
    (verified pre/post via git stash comparison: same 3
    pre-existing unrelated failures, no previously-passing test
    broke)
  • npm run lint — 0 new errors (pre-existing errors are all in
    files untouched by this PR)
  • npm run type-check — 0 new errors (same)
  • Verified v3/history.routes.ts and v3/safe-vault.routes.ts now
    load correctly under the project's actual tsx runtime loader

…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>
@Folex1275
Folex1275 merged commit 282f54b into StellarStream-HQ:main Jun 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N+1 Query Optimization - Stream Listing

2 participants