Skip to content

feat(ensindexer): leverage SWR cache to achieve goals related to LocalPonderClient#1652

Merged
tk-o merged 25 commits intofeat/indexing-status-builder-3from
feat/indexing-status-builder-3-with-cache
Feb 23, 2026
Merged

feat(ensindexer): leverage SWR cache to achieve goals related to LocalPonderClient#1652
tk-o merged 25 commits intofeat/indexing-status-builder-3from
feat/indexing-status-builder-3-with-cache

Conversation

@tk-o
Copy link
Copy Markdown
Member

@tk-o tk-o commented Feb 19, 2026

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • Creates SWR cache for fetching PonderAppMetadata value
  • Updates LocalPonderClient to read data from aforementioned SWR cache to simplify how possible data loading failures are handled.
  • Exports localPonderClient singleton

Why

  • Fetching data may fail, and we need to manage the complexity that stems from these possible failures in a simple way. SWRCache is a great abstraction for that goal, and LocalPonderClient should leverage it.

Testing

  • I ran static code analysis (lint, typecheck) and testing suite
  • I ran ENSIndexer service locally and tested it /api/indexing-status endpoint, including test logs

Notes for Reviewer (Optional)

  • Reviewing commit-by-commit is highly encouraged.
  • This is a follow-up PR to PR Integration of Indexing Status Builder into ENSIndexer API #1614 and simplifies LocalPonderClient idea in a meaningful way. LocalPonderClient "knows" about Ponder APIs and configs, and allows caching data that can be later used to handle Indexing Status API requests.

Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

Copilot AI review requested due to automatic review settings February 19, 2026 16:23
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
admin.ensnode.io Skipped Skipped Feb 23, 2026 4:52pm
ensnode.io Skipped Skipped Feb 23, 2026 4:52pm
ensrainbow.io Skipped Skipped Feb 23, 2026 4:52pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: 11c2c23

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
ensindexer Major
ensadmin Major
ensrainbow Major
ensapi Major
fallback-ensapi Major
@ensnode/datasources Major
@ensnode/ensrainbow-sdk Major
@ensnode/ponder-metadata Major
@ensnode/ensnode-schema Major
@ensnode/ensnode-react Major
@ensnode/ensnode-sdk Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@docs/ensnode Major
@docs/ensrainbow Major
@docs/mintlify Major
@namehash/ens-referrals Major
@namehash/namehash-ui Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors ENSIndexer’s LocalPonderClient initialization to rely on SWR caches (with proactive revalidation) and extends the shared SWRCache utility to support a context object for cache loaders.

Changes:

  • Extend SWRCache to accept an optional context parameter in fn() and add a setContext() API.
  • Refactor LocalPonderClient to use new SWR-backed caches for Ponder indexing metrics/status (dynamic) and derived immutable chain metadata.
  • Update the Ponder API handler to use a singleton LocalPonderClient instance (no per-request initialization).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/ensnode-sdk/src/shared/cache/swr-cache.ts Adds context support and proactive revalidation stop API to the shared SWR cache implementation.
packages/ensnode-sdk/src/shared/cache/swr-cache.test.ts Updates tests for the new (cachedResult, context) callback signature and adds context coverage.
apps/ensindexer/src/lib/ponder-api-client.ts Switches singleton getter to synchronous construction and injects SWR caches into LocalPonderClient.
apps/ensindexer/ponder/src/api/lib/local-ponder-client.ts Reworks LocalPonderClient to extend PonderClient and source metadata via SWR caches.
apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-immutable.ts Introduces builder for immutable chain indexing metadata.
apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-dynamic.ts Introduces builder for dynamic chain indexing metadata from metrics/status.
apps/ensindexer/ponder/src/api/lib/cache/ponder-client.cache.ts Adds SWR cache for Ponder metrics/status with proactive revalidation.
apps/ensindexer/ponder/src/api/lib/cache/chains-indexing-metadata-immutable.cache.ts Adds SWR cache for immutable chain metadata and stops revalidation after success.
apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts Uses a module-level singleton LocalPonderClient for request handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-dynamic.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/local-ponder-client.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-immutable.ts Outdated
Comment thread packages/ensnode-sdk/src/shared/cache/swr-cache.ts Outdated
Comment thread apps/ensindexer/src/lib/ponder-api-client.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/cache/ponder-client.cache.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/local-ponder-client.ts Outdated
Copy link
Copy Markdown
Member Author

@tk-o tk-o left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review completed.

Comment thread packages/ensnode-sdk/src/shared/cache/swr-cache.ts Outdated
Comment thread packages/ensnode-sdk/src/shared/cache/swr-cache.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
Comment thread apps/ensindexer/ponder/src/api/lib/cache/ponder-indexing-metrics.cache.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/cache/ponder-indexing-status.cache.ts Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/indexing-status-builder-3-with-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tk-o tk-o force-pushed the feat/indexing-status-builder-3 branch from 34db972 to b8b2f19 Compare February 20, 2026 10:39
Copilot AI review requested due to automatic review settings February 20, 2026 12:08
@tk-o tk-o force-pushed the feat/indexing-status-builder-3-with-cache branch from 0999394 to d314b5d Compare February 20, 2026 12:08
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 20, 2026 12:08 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 20, 2026 12:08 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io February 20, 2026 12:12 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 20, 2026 12:12 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 20, 2026 12:12 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/ensnode-sdk/src/shared/cache/swr-cache.ts Outdated
Comment thread apps/ensindexer/src/lib/ponder-api-client.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/lib/chains-indexing-metadata-immutable.ts Outdated
Comment thread packages/ensnode-sdk/src/shared/cache/swr-cache.ts Outdated
tk-o added 2 commits February 20, 2026 13:22
Move logic from `LocalPonderClient` into a separate file.
Move logic from `LocalPonderClient` into a separate file.
@tk-o tk-o force-pushed the feat/indexing-status-builder-3-with-cache branch from 9f9ca10 to dc578eb Compare February 20, 2026 12:42
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 20, 2026 12:42 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 20, 2026 12:42 Inactive
Copilot AI review requested due to automatic review settings February 20, 2026 12:54
@tk-o tk-o marked this pull request as ready for review February 20, 2026 12:54
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io February 23, 2026 06:57 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 23, 2026 06:57 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 23, 2026 06:57 Inactive
Copy link
Copy Markdown
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Thanks for these updates 👍 Reviewed and shared some feedback with suggestions. Please feel welcome to merge when ready.

// Initialize the singleton LocalPonderClient instance if it hasn't been
// initialized yet.
if (localPonderClient === undefined) {
localPonderClient = new LocalPonderClient(config.ensIndexerUrl, config.indexedChainIds);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why create it here? Why not define localPonderClient as a const and move this logic next to the definition of localPonderClient?

And likewise, why have a getLocalPonderClient function at all? Why not just have people import localPonderClient and make direct use of it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of eager evaluation, as it makes it harder to test code, and also harder to run code in other contexts than server rutime. Referring here to our latest issue with creating OpenAPI spec where config object is eagerly evaluated, forcing us to find workarounds.

That's why I prefer lazy evaluation, and one way to achieve it is through factory functions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering your question directly, we can define localPonderClient as a const and move it next to definition of LocalPonderClient in the following file:

apps/ensindexer/ponder/src/api/lib/local-ponder-client.ts

* connecting to the local Ponder app and fetching the necessary data to
* build the client's state. The initialized client is cached and returned
* on subsequent calls.
* This function relies on SWR caches with proactive revalidation to load
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there any references to SWR caches here? It doesn't seem relevant at this layer of responsibility.

indexedChainIds,
chainIndexingMetadataImmutable,
);
const ponderClientCacheResult = await ponderClientCache.read();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear what we're trying to do here?

The variable names are confusing. A few lines down it says "indexing metrics" and then later "Ponder client data ..."

What is it exactly? So many names that aren't aligned.

get indexedChainIds() {
return this.#indexedChainIds;
}
// Build and cache immutable metadata for indexed chains if not already cached.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's difficult for me to wrap my head around what's going on here.

Is this a possible asynchronous fetch operation? Or is it just in memory data manipulation?

Maybe this operation better belongs wherever we process receiving a new ponderIndexingMetrics?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the SWR cache to store, both types of chain indexing metadata. Then, LocalPonderClient will simply read these values from cache.

publicClient(chainId: ChainId): PublicClient {
const publicClient = this.#publicClients.get(chainId);
try {
chainsIndexingMetadataImmutable = await this.#chainsIndexingMetadataImmutable;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why try? Why await? I'm confused. I thought this should just be reading a simple in-memory value?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the SWR cache to store, both types of chain indexing metadata. Then, LocalPonderClient will simply read these values from cache.

*/
export async function buildChainsIndexingMetadataImmutable(
indexedChainIds: Set<ChainId>,
chainsConfigBlockrange: Map<ChainId, BlockrangeWithStartBlock>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this function need to be passed the indexedChainIds param?

It seems redundant with chainsConfigBlockrange and publicClients -- are these not going to have exactly the same set of chainIds?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't always trust that Ponder metrics will come complete for every indexed chain. There were cases in the past where Ponder metrics generation had a bug and made the metrics incomplete. That's why we need to use indexedChainIds value coming from ENSIndexer config. Invariants included in the inside the for loop are ultimate test checking ponderIndexingMetrics are complete in the context of indexedChainIds.

Comment on lines +10 to +26
/**
* Result of the Ponder Client cache.
*/
export interface PonderClientCacheResult {
ponderIndexingMetrics: PonderIndexingMetrics;
ponderIndexingStatus: PonderIndexingStatus;
}

/**
* SWR Cache for Ponder Client data
*/
export type PonderClientCache = SWRCache<PonderClientCacheResult>;

const ponderClient = new PonderClient(config.ensIndexerUrl);

/**
* Cache for Ponder Client data
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Result of the Ponder Client cache.
*/
export interface PonderClientCacheResult {
ponderIndexingMetrics: PonderIndexingMetrics;
ponderIndexingStatus: PonderIndexingStatus;
}
/**
* SWR Cache for Ponder Client data
*/
export type PonderClientCache = SWRCache<PonderClientCacheResult>;
const ponderClient = new PonderClient(config.ensIndexerUrl);
/**
* Cache for Ponder Client data
/**
* Metadata from a Ponder app.
*/
export interface PonderAppMetadata {
ponderIndexingMetrics: PonderIndexingMetrics;
ponderIndexingStatus: PonderIndexingStatus;
}
/**
* SWR Cache for Ponder App Metadata
*/
export type PonderAppMetadataCache = SWRCache<PonderAppMetadata>;
const ponderClient = new PonderClient(config.ensIndexerUrl);
/**
* Cache for Ponder App Metadata

Goal: Improve terminology so it's more clear whats going on here.

We aren't caching things from the ponder client. The client isn't the origin of this data. We are caching things from the ponder app.

* consistency to call all endpoints at once. This cache loads both
* Ponder Indexing Metrics and Ponder Indexing Status together, and provides
* them as a single cached result. This way, we ensure that the metrics and
* status data are always from the same point in time, and avoid potential
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* status data are always from the same point in time, and avoid potential
* status data are always from (approximately) the same point in time, and minimize potential

}
},
ttl: Number.POSITIVE_INFINITY,
proactiveRevalidationInterval: 10 satisfies Duration,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this should be more frequent: Ex: Every 1 second? We want the indexing metrics to be more frequently updating.

// This ensures that the client is ready to use when handling requests,
// and allows us to catch initialization errors early.
getLocalPonderClient();
// Get the local Ponder Client instance
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These ideas don't belong at this layer of responsibility.

tk-o added 7 commits February 23, 2026 15:33
Keep the singleton instance next to the `LocalPonderClient` definition.
This will support direct imports inside the Ponder App Metadata cache.
…a for each indexed chain

This will allow the cache consumers to simply await the cache read and use the ChainIndexingMetadata object with no need for further refinements.
The client reads data from Ponder App Metadata cache.
Copilot AI review requested due to automatic review settings February 23, 2026 16:33
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io February 23, 2026 16:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 23, 2026 16:33 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 23, 2026 16:33 Inactive
@tk-o tk-o changed the title feat(ensindexer): leverage SWR caches to achieve goals related to LocalPonderClient feat(ensindexer): leverage SWR cache to achieve goals related to LocalPonderClient Feb 23, 2026
@tk-o
Copy link
Copy Markdown
Member Author

tk-o commented Feb 23, 2026

@greptile review

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread apps/ensindexer/ponder/src/api/lib/cache/ponder-app-metadata.cache.ts Outdated
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io February 23, 2026 16:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 23, 2026 16:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 23, 2026 16:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io February 23, 2026 16:52 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io February 23, 2026 16:52 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io February 23, 2026 16:52 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@tk-o tk-o merged commit ead602f into feat/indexing-status-builder-3 Feb 23, 2026
11 checks passed
@tk-o tk-o deleted the feat/indexing-status-builder-3-with-cache branch February 23, 2026 18:26
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.

3 participants