Skip to content

feat(indexer): add ETag caching for GET /streams and GET /splits responses - #713

Open
olatechy0 wants to merge 5 commits into
vestflow-labs:mainfrom
olatechy0:feat/etag-caching
Open

feat(indexer): add ETag caching for GET /streams and GET /splits responses#713
olatechy0 wants to merge 5 commits into
vestflow-labs:mainfrom
olatechy0:feat/etag-caching

Conversation

@olatechy0

Copy link
Copy Markdown

Summary

Closes #674Return an ETag header on GET /streams and GET /splits responses based on the last-updated timestamp of the data, and respond with 304 Not Modified when the client sends a matching If-None-Match header.

Changes

indexer/src/server.ts

  • Added computeEtag — a strong ETag derived from a SHA-256 hash of the canonical JSON body joined with the data's last-updated timestamp. It is stable across identical payloads and changes the instant the underlying data changes.
  • Added jsonWithEtag — writes the ETag header on every 200 response and answers 304 Not Modified (no body) when If-None-Match matches the current ETag.
  • Wired If-None-Match into handleStreams.
  • Added the missing GET /splits indexer endpoint (the SDK already calls {indexerUrl}/splits but the route did not exist), serving the account's current splits config with the same ETag/304 behavior.

indexer/src/db.ts

  • getDripsStreamsLastUpdated(account) — most recent created_at/ended_at across an account's streams, for the /streams ETag.
  • getDripsSplitsLastUpdated(account) — most recent updated_at across an account's current_streams rows, for the /splits ETag.
  • queryDripsSplits(account) — aggregates the account's splits receivers from current_streams (verbatim stream_set receivers) with a stable SHA-256 hash and a last_updated value.

Behavior

  • GET /streams?account=…&network=…200 with ETag; 304 (no body) when If-None-Match matches; a new ETag as soon as stream data changes.
  • GET /splits?account=…&network=…200 with ETag; 304 when If-None-Match matches; a new ETag when the account's splits config changes.
  • Error responses (400/404/405/500) are unchanged.

Tests (indexer/test/drips-api.test.ts)

Added focused regression tests for both endpoints covering the acceptance criteria:

  • First request returns 200 with an ETag header.
  • Unchanged data + matching If-None-Match returns 304 with no body.
  • Changed data returns a different ETag (stream added; splits config updated), and the new ETag then validates to 304.

Verification

  • npx ts-node --transpile-only test/drips-api.test.ts → passes (covers the ETag scenarios).
  • npx ts-node --transpile-only test/drips-events-projection.test.ts → passes.
  • npx tsc --noEmit in indexer/ shows only two pre-existing errors in the gives handler (GiveQueryParams/queryGives not defined at HEAD); db.ts has no errors. The root tsconfig.json excludes indexer, so CI's backend-build type-check does not cover the indexer.
  • The root vitest run suite has pre-existing failures at HEAD (tough-cookie/jsdom module resolution, and gives-endpoint tests that depend on the undefined queryGives) — identical failure set on main; this change adds no new failures.

Return an ETag on GET /streams and GET /splits responses derived from a
hash of the response body joined with the data's last-updated timestamp,
and answer 304 Not Modified (no body) when the client sends a matching
If-None-Match header.

- server.ts: add computeEtag/jsonWithEtag helpers and wire the
  If-None-Match header into both handlers. Adds the missing /splits
  indexer endpoint serving the account's current splits config.
- db.ts: add getDripsStreamsLastUpdated, getDripsSplitsLastUpdated, and
  queryDripsSplits (aggregates receivers from current_streams with a
  stable hash).
- test: cover first request (200 + ETag), unchanged (304 with matching
  If-None-Match), and changed data (new ETag) for both endpoints.

Closes vestflow-labs#674
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@olatechy0 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@libby-coder

Copy link
Copy Markdown
Contributor

RESOLVE CONFLIUCTS

Resolve conflicts with upstream main (multi-line signature reformatting of
numParam, handleStreams, getDripsStreamingTvl, queryGivesForAccount; newer
indexer work):

- db.ts: keep the PR's getDripsStreamsLastUpdated / getDripsSplitsLastUpdated /
  queryDripsSplits additions while adopting main's formatting for the shared
  functions they landed adjacent to. No duplicate exports (main has none of
  these symbols).
- server.ts: retain computeEtag/jsonWithEtag from the PR; keep main's
  single-line numParam formatting; merge main's multi-line handleStreams
  signature with the PR's new optional ifNoneMatch parameter.

Verified: 'npx tsc --noEmit' in indexer/ passes; 'npm run test:drips-api'
passes (ETag/304 scenarios). The drips-events type-check failure (TS2783 in
the untouched test helper) is identical on main/HEAD and pre-existing.
@olatechy0

Copy link
Copy Markdown
Author

@libby-coder @Ceejaytech25 - The branch conflicts are resolved and the branch is up to date with main.

I merged the latest main into feat/etag-caching and resolved the conflicts in indexer/src/db.ts and indexer/src/server.ts, keeping the ETag/304 additions intact alongside main's surrounding reformatting.

Verification:

  • npx tsc --noEmit in indexer/ passes
  • npm run test:drips-api passes (covers the ETag / 304 / changed-data scenarios)

The PR is now conflict-free and mergeable - ready for review. CI is just waiting on approval to run for this fork PR.

Thanks!

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.

feat(backend): add ETag caching for GET /streams and GET /splits responses

3 participants