Skip to content

Split ENSApi and ENSIndexer#1716

Open
tk-o wants to merge 14 commits intomainfrom
feat/detach-ensapi-from-ensindexer
Open

Split ENSApi and ENSIndexer#1716
tk-o wants to merge 14 commits intomainfrom
feat/detach-ensapi-from-ensindexer

Conversation

@tk-o
Copy link
Contributor

@tk-o tk-o commented Mar 4, 2026

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • Created ENSDb implementation for ENSApi.
  • Created PublicConfigBuilder class to cover all abstractions required for building EnsApiPublicConfig object within ENSIndexer runtime.
  • Removed async dependency that buildConfigFromEnvironment function had on ENSIndexer Client. This dependency has been replaced with ENSDb Client call from PublicConfigBuilder.getPublicConfig() method leve.
  • UpdatedEnsDbWriterWorker to build ENSIndexer Public Config object with PublicConfigBuilder, instead of reading the config from EnsIndexerClient.
  • Updated ENSAPI HTTP handlers to (inderictly) read ENSIndexer Public Config and Indexing Status from ENSDb Client, instead of building those objects at the time of a HTTP request.
    • Indexing Status Cache reads from ENSDb Client now.
  • All ENSApi code that needed to get EnsIndexerPublicConfig object with config.ensIndexerPublicConfig reference has been updated. Now, the ENSApi modules can get EnsIndexerPublicConfig object with const { ensIndexerPublicConfig } = await publicConfigBuilder.getPublicConfig(); call.

Why

  • We need to detach ENSIndexer from ENSApi. Indexing Status API for both of these services must be powered solely by ENSDb Client when it comes to reading ENSIndexer Public Config or the current Indexing Status.
  • We also needed ENSApi Config to stop having dependency on ENSIndexer Public Config.

Testing

  • Ran lint, and typecheck.
  • Extended testing suite, all tests are in green.
  • Tested HTTP APIs of a local ENSApi instance, including local ENSAdmin website.

Notes for Reviewer (Optional)

  • Please review commit-by-commit

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)

Resolves #1328

tk-o added 9 commits March 4, 2026 14:49
This builder reads ENSIndexer Public Config object from ENSDb and build `EnsApiPublicConfig` object that is later available for all modules of ENSApi.
`EnsIndexerPublicConfig` is managed by `PublicConfigBuilder` now. The `EnsApiConfig` has been updated to a) not include `ensIndexerPublicConfig` field anymore; b) include `databaseSchemaName` and `namespace` fields. The newly added fields are required to kick-off ENSApi app, while the `ensIndxerPublicConfig` can be fetched async.
Copilot AI review requested due to automatic review settings March 4, 2026 15:05
@vercel
Copy link
Contributor

vercel bot commented Mar 4, 2026

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

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Mar 4, 2026 3:58pm
ensnode.io Ready Ready Preview, Comment Mar 4, 2026 3:58pm
ensrainbow.io Ready Ready Preview, Comment Mar 4, 2026 3:58pm

@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: a8d11e0

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

This PR includes changesets to release 18 packages
Name Type
ensapi Major
@ensnode/ensnode-sdk Major
ensadmin Major
ensindexer Major
ensrainbow Major
fallback-ensapi Major
@namehash/ens-referrals Major
@ensnode/ensnode-react Major
@ensnode/ensrainbow-sdk Major
@namehash/namehash-ui Major
@ensnode/datasources Major
@ensnode/ensnode-schema Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@docs/ensnode Major
@docs/ensrainbow Major
@docs/mintlify 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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Warning

Rate limit exceeded

@tk-o has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e814480-76f0-4391-9a54-2c5386593598

📥 Commits

Reviewing files that changed from the base of the PR and between ac27630 and a8d11e0.

📒 Files selected for processing (16)
  • .changeset/chilly-plums-lose.md
  • .changeset/fancy-cloths-fly.md
  • apps/ensapi/.env.local.example
  • apps/ensapi/src/config/config.schema.ts
  • apps/ensapi/src/handlers/ensnode-api.ts
  • apps/ensapi/src/index.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.test.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.ts
  • apps/ensapi/src/lib/ensdb-client/singleton.ts
  • apps/ensapi/src/lib/public-config-builder/public-config-builder.test.ts
  • apps/ensapi/src/lib/public-config-builder/public-config-builder.ts
  • apps/ensapi/src/middleware/can-accelerate.middleware.ts
  • apps/ensapi/src/middleware/name-tokens.middleware.ts
  • apps/ensapi/src/middleware/registrar-actions.middleware.ts
  • apps/ensindexer/src/lib/version-info.ts
  • packages/ensnode-sdk/src/ensapi/config/conversions.test.ts
📝 Walkthrough

Walkthrough

Reads public config and indexing status from ENSDb: adds EnsDbClient, a Drizzle helper and singleton, introduces PublicConfigBuilder (cached, validated public config), adds DATABASE_SCHEMA and NAMESPACE env vars, converts handlers/middleware to fetch runtime public config, and switches indexing-status retrieval to ENSDb snapshots.

Changes

Cohort / File(s) Summary
Environment & Config
apps/ensapi/.env.local.example, apps/ensapi/src/config/environment.ts, apps/ensapi/src/config/config.schema.ts, apps/ensapi/src/config/config.schema.test.ts
Added DATABASE_SCHEMA and NAMESPACE env vars; config builder made synchronous; added databaseSchemaName and version fields; removed prior ENSIndexer HTTP public-config construction in favor of ENSDb-driven approach.
ENSDb Client & Drizzle
apps/ensapi/src/lib/ensdb-client/drizzle.ts, .../ensdb-client/ensdb-client.ts, .../ensdb-client/ensdb-client.mock.ts, .../ensdb-client/ensdb-client.test.ts, .../ensdb-client/singleton.ts
New Drizzle factory, EnsDbClient class with getEnsDbVersion/getEnsIndexerPublicConfig/getIndexingStatusSnapshot, mocks/tests, and exported singleton ensDbClient initialized from config.
PublicConfigBuilder
apps/ensapi/src/lib/public-config-builder/public-config-builder.ts, .../public-config-builder.test.ts, .../singleton.ts, .../index.ts
New PublicConfigBuilder that fetches ENSIndexer public config from ENSDb, computes theGraphFallback, validates via SDK schema, caches result; exported singleton and tests verifying caching, errors, and retry.
Handlers & Cache
apps/ensapi/src/handlers/ensnode-api.ts, apps/ensapi/src/handlers/name-tokens-api.ts, apps/ensapi/src/cache/indexing-status.cache.ts
Handlers now retrieve public config via publicConfigBuilder.getPublicConfig(); indexing-status now reads snapshot via ensDbClient.getIndexingStatusSnapshot() with updated error handling and logging.
Middleware & Runtime Config Reads
apps/ensapi/src/middleware/... (can-accelerate.middleware.ts, name-tokens.middleware.ts, registrar-actions.middleware.ts, require-core-plugin.middleware.ts, subgraph-meta.middleware.ts, thegraph-fallback.middleware.ts)
Replaced static config references with runtime publicConfigBuilder.getPublicConfig() and made affected middleware async where needed.
Resolution & Subgraph Meta
apps/ensapi/src/lib/resolution/forward-resolution.ts, apps/ensapi/src/lib/subgraph/indexing-status-to-subgraph-meta.ts
Now fetches ensIndexerPublicConfig from publicConfigBuilder; indexingContextToSubgraphMeta converted to async and uses public-config versionInfo for deployment metadata.
SDK: Public Config Validation & Schemas
packages/ensnode-sdk/src/ensapi/config/index.ts, .../validate/ensapi-public-config.ts, .../zod-schemas.ts, .../conversions.test.ts
Added validateEnsApiPublicConfig validator, introduced shared makeEnsApiVersionSchema, re-exported validation module, and updated tests/schema usage.
Infra / Misc
terraform/modules/ensindexer/main.tf, apps/ensindexer/src/lib/version-info.ts, .changeset/*
Added DATABASE_SCHEMA and NAMESPACE to infra common vars; removed unused imports and added changesets documenting ENSDb-backed refactor.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Handler as ENSApi Handler
  participant PCB as PublicConfigBuilder
  participant ENSDb as ENSDbClient
  participant DB as Database

  Client->>Handler: Request needing public config
  Handler->>PCB: getPublicConfig()
  alt cache miss
    PCB->>ENSDb: getEnsIndexerPublicConfig()
    ENSDb->>DB: query ens_node_metadata (key: "ens-indexer-public-config")
    DB-->>ENSDb: serialized config
    ENSDb-->>PCB: deserialized ENSIndexerPublicConfig
    PCB->>PCB: compute theGraphFallback, validate (validateEnsApiPublicConfig)
    PCB-->>Handler: EnsApiPublicConfig (cached)
  else cache hit
    PCB-->>Handler: EnsApiPublicConfig (cached)
  end
  Handler-->>Client: Response using public config
Loading
sequenceDiagram
  participant Client as Client
  participant Cache as IndexingStatus Cache
  participant ENSDb as ENSDbClient
  participant DB as Database

  Client->>Cache: getIndexingStatus()
  Cache->>ENSDb: getIndexingStatusSnapshot()
  ENSDb->>DB: query cross_chain_indexing_status_snapshot
  DB-->>ENSDb: serialized snapshot
  ENSDb-->>Cache: deserialized snapshot
  Cache-->>Client: snapshot (or error if undefined)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #1328: Add DATABASE_SCHEMA environment variable to all ENSApi instances — this PR adds DATABASE_SCHEMA and moves config sourcing to ENSDb, addressing the objective.
  • Epic: Decouple ENSApi and ENSIndexer #1468: ENSDbClient and ENSDb-backed reads — PR implements the ENSDb-backed public-config/indexing-status flow described by the issue.

Possibly related PRs

Poem

"I nibble bytes beneath the log,
I hop through rows to fetch a cog,
Cached and tidy, schema in tow,
ENSDb hums — the configs grow,
A rabbit's cheer for cleaner flow! 🐇"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Split ENSApi and ENSIndexer' clearly summarizes the main objective of detaching these two services from each other, which is the primary focus of the changeset.
Description check ✅ Passed The PR description follows the template with clear sections including Summary, Why, Testing, Notes for Reviewer, and Pre-Review Checklist. All required sections are populated with relevant details.
Linked Issues check ✅ Passed The PR successfully implements the objective from issue #1328: adding DATABASE_SCHEMA environment variable to ENSApi and decoupling it from ENSIndexer by reading configuration from ENSDb instead of ENSIndexer Client.
Out of Scope Changes check ✅ Passed All changes are scoped to decoupling ENSApi from ENSIndexer through ENSDb integration. Minor updates to SDK schemas and test files support the core objective without introducing unrelated functionality.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/detach-ensapi-from-ensindexer

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.

Copy link
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 advances the “Split ENSApi and ENSIndexer” effort by decoupling ENSApi from ENSIndexer’s HTTP config/status endpoints and instead sourcing ENSIndexer public config + indexing status via ENSDb (plus adding SDK validation helpers to support the new config flow).

Changes:

  • Add an ENSApi-side ENSDb client + public config builder and migrate middlewares/handlers to use it.
  • Introduce ENSApi public-config validation in @ensnode/ensnode-sdk and reuse a shared version schema.
  • Update env/terraform wiring to pass DATABASE_SCHEMA and NAMESPACE explicitly for the split.

Reviewed changes

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

Show a summary per file
File Description
terraform/modules/ensindexer/main.tf Moves DATABASE_SCHEMA/NAMESPACE into common env vars for the service.
packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts Extracts makeEnsApiVersionSchema() and reuses it in public-config schemas.
packages/ensnode-sdk/src/ensapi/config/validate/ensapi-public-config.ts Adds validateEnsApiPublicConfig() with Zod-based validation + prettified errors.
packages/ensnode-sdk/src/ensapi/config/index.ts Re-exports the new validateEnsApiPublicConfig() API.
apps/ensapi/src/middleware/thegraph-fallback.middleware.ts Reads isSubgraphCompatible via ENSDb-backed public config instead of local config copy.
apps/ensapi/src/middleware/subgraph-meta.middleware.ts Awaits async subgraph meta conversion.
apps/ensapi/src/middleware/require-core-plugin.middleware.ts Uses ENSDb-backed public config to check plugin support.
apps/ensapi/src/middleware/registrar-actions.middleware.ts Uses ENSDb-backed public config for registrar-actions prerequisites.
apps/ensapi/src/middleware/name-tokens.middleware.ts Uses ENSDb-backed public config for name-tokens prerequisites.
apps/ensapi/src/middleware/can-accelerate.middleware.ts Uses ENSDb-backed public config for plugin checks related to acceleration.
apps/ensapi/src/lib/subgraph/indexing-status-to-subgraph-meta.ts Makes conversion async and pulls deployment/version from ENSDb-backed config.
apps/ensapi/src/lib/resolution/forward-resolution.ts Uses ENSDb-backed public config for ENSv2 bailout check.
apps/ensapi/src/lib/public-config-builder/singleton.ts Adds a singleton PublicConfigBuilder wired to the ENSDb client.
apps/ensapi/src/lib/public-config-builder/public-config-builder.ts Implements immutable, validated ENSApi public-config construction from ENSDb + env.
apps/ensapi/src/lib/public-config-builder/public-config-builder.test.ts Adds unit tests for caching, masking API key, and error propagation.
apps/ensapi/src/lib/public-config-builder/index.ts Barrel export for the public-config-builder module.
apps/ensapi/src/lib/ensdb-client/singleton.ts Adds ENSApi-local singleton ENSDb client instance.
apps/ensapi/src/lib/ensdb-client/ensdb-client.ts Adds ENSApi-local ENSDb query client using Drizzle + ensnode_metadata.
apps/ensapi/src/lib/ensdb-client/ensdb-client.test.ts Adds unit tests for ENSDb client query behavior.
apps/ensapi/src/lib/ensdb-client/ensdb-client.mock.ts Provides fixtures for ENSDb client tests.
apps/ensapi/src/lib/ensdb-client/drizzle.ts Duplicates makeDrizzle helper (currently) for ENSApi DB access.
apps/ensapi/src/handlers/name-tokens-api.ts Defers subregistry computation until request-time using ENSDb-backed config.
apps/ensapi/src/handlers/ensnode-api.ts Serves ENSApi public config from PublicConfigBuilder instead of constructing locally.
apps/ensapi/src/config/environment.ts Updates env typing to include DATABASE_SCHEMA and NAMESPACE in ENSApi env surface.
apps/ensapi/src/config/config.schema.ts Removes ENSIndexer fetch at startup; parses env for namespace/schema and adds config version.
apps/ensapi/src/config/config.schema.test.ts Removes tests for deleted buildEnsApiPublicConfig().
apps/ensapi/src/cache/indexing-status.cache.ts Switches indexing status source from ENSNodeClient HTTP call to ENSDb snapshot reads.
apps/ensapi/.env.local.example Documents required DATABASE_SCHEMA and NAMESPACE env vars for ENSApi.
Comments suppressed due to low confidence (1)

apps/ensapi/src/lib/subgraph/indexing-status-to-subgraph-meta.ts:26

  • indexingContextToSubgraphMeta returns null in several branches, but the signature is Promise<SubgraphMeta>. This is a type mismatch that should fail typechecking; update the return type to Promise<SubgraphMeta | null> (and ideally align the JSDoc accordingly).
export async function indexingContextToSubgraphMeta(
  indexingStatus: IndexingStatusMiddlewareVariables["indexingStatus"],
): Promise<SubgraphMeta> {
  // indexing status middleware has never successfully fetched (and cached) an indexing status snapshot
  // for the lifetime of this service instance.
  if (indexingStatus instanceof Error) return null;


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

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Test file still expects async function and ensIndexerPublicConfig, but buildConfigFromEnvironment is now synchronous and no longer returns that config

Fix on Vercel

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ensapi/src/config/config.schema.test.ts (1)

26-177: ⚠️ Potential issue | 🔴 Critical

Remove .resolves and .awaitbuildConfigFromEnvironment is now synchronous, and add missing required environment variables.

The test suite still uses pre-refactor patterns: (1) buildConfigFromEnvironment is now a synchronous function that parses environment variables directly, not an async function that fetches public config; (2) BASE_ENV is missing required NAMESPACE and DATABASE_SCHEMA keys that the implementation expects; (3) the returned config shape no longer includes ensIndexerPublicConfig, namespace and databaseSchemaName now come from the environment, not from a fetched response.

Update the test as follows:

  • Add NAMESPACE: "mainnet" and DATABASE_SCHEMA: "ensapi" to BASE_ENV
  • Remove the global fetch mock and mockFetch setup—the function does not make HTTP calls
  • Remove await and .resolves from the test assertion (line 67)
  • Update expected namespace and databaseSchemaName to reference BASE_ENV values instead of ENSINDEXER_PUBLIC_CONFIG
  • Remove the ensIndexerPublicConfig field from the expected config object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/config/config.schema.test.ts` around lines 26 - 177, The
tests still treat buildConfigFromEnvironment as async and rely on fetched ENS
indexer data; update tests to reflect the new synchronous behavior by: add
NAMESPACE ("mainnet") and DATABASE_SCHEMA ("ensapi") to BASE_ENV, remove
mockFetch and the global fetch stub and any fetch-related setup/teardown, remove
await and .resolves usage when calling buildConfigFromEnvironment (call it
synchronously), remove the ensIndexerPublicConfig expectation and change
expected namespace and databaseSchemaName to BASE_ENV.NAMESPACE and
BASE_ENV.DATABASE_SCHEMA, and update/clean related tests (e.g., those
referencing ENSINDEXER_PUBLIC_CONFIG) so they no longer depend on serialized
remote config.
apps/ensapi/src/config/config.schema.ts (1)

77-82: 🛠️ Refactor suggestion | 🟠 Major

JSDoc is stale and has redundant @returns

The summary still says this function fetches ENSIndexer public config, which is no longer true, and the @returns tag is redundant.

🧹 Proposed JSDoc fix
 /**
- * Builds the EnsApiConfig from an EnsApiEnvironment object, fetching the EnsIndexerPublicConfig.
- *
- * `@returns` A validated EnsApiConfig object
+ * Builds the EnsApiConfig from an EnsApiEnvironment object.
  * `@throws` Error with formatted validation messages if environment parsing fails
  */
As per coding guidelines, "Do not add JSDoc `@returns` tags that merely restate the method summary; remove such redundancy during PR review".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/config/config.schema.ts` around lines 77 - 82, Update the
JSDoc for the function that builds the EnsApiConfig from an EnsApiEnvironment
(the block referencing EnsApiConfig and EnsApiEnvironment) to remove the stale
claim about fetching the EnsIndexerPublicConfig and delete the redundant
`@returns` tag; keep a concise summary that accurately reflects the function's
behavior and preserve the `@throws` note for validation errors if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/.env.local.example`:
- Around line 15-21: The README/env example contains contradictory guidance
about DATABASE_SCHEMA: remove or reconcile the conflicting lines so it's clear
whether ENSApi requires DATABASE_SCHEMA; update the environment example and
comments to state one consistent rule (e.g., "ENSApi does not need to set
DATABASE_SCHEMA; it is inferred from the connected ENSIndexer's Config" OR "If
reading directly from ENSDb, set DATABASE_SCHEMA to an existing namespace in
DATABASE_URL"), and update references to DATABASE_SCHEMA, ENSApi, ENSDbClient
and ENSIndexer's Config so the example and comments align and cannot cause
startup misconfiguration.

In `@apps/ensapi/src/config/config.schema.ts`:
- Around line 85-96: Replace the raw env.NAMESPACE with the already-validated
namespace variable when constructing the config: use the parsed namespace (from
ENSNamespaceSchema.parse assigned to namespace) in the object passed to
EnsApiConfigSchema.parse and anywhere else the code currently uses env.NAMESPACE
(e.g., when calling buildRpcConfigsFromEnv and building the returned config) to
ensure the validated value is the single source of truth.
- Line 83: You changed buildConfigFromEnvironment to return EnsApiConfig
synchronously, so update all call sites and tests that expect a Promise (e.g.,
usages that use .resolves or await on a promise) to the new sync contract:
replace assertions using .resolves with direct assertions (e.g., await
expect(...).resolves -> expect(buildConfigFromEnvironment(...)).toEqual(...)),
remove unnecessary await/then chains or wrap calls appropriately, and update any
helper functions that treated buildConfigFromEnvironment as async; search for
buildConfigFromEnvironment and update tests and code to call it synchronously
and assert its returned EnsApiConfig directly.

In `@apps/ensapi/src/lib/ensdb-client/ensdb-client.ts`:
- Around line 118-124: Remove the redundant JSDoc `@returns` line from the
documentation block that begins with "Get ENSNode metadata record" in
ensdb-client.ts; leave the summary and the `@throws` tag intact, deleting only the
`@returns` tag that repeats the method summary so the JSDoc is concise and follows
the guideline.

In `@apps/ensapi/src/lib/ensdb-client/singleton.ts`:
- Around line 5-7: The docstring for the EnsDbClient singleton incorrectly
references "ENSIndexer"; update the comment to reference the correct service
(ENSApi / ENS API) so it accurately documents the singleton instance of
EnsDbClient used by the ENSApi service; locate the comment above the EnsDbClient
singleton in singleton.ts and replace "ENSIndexer" with "ENSApi" (or "ENS API")
to remove the confusion.

In `@apps/ensapi/src/lib/public-config-builder/public-config-builder.test.ts`:
- Around line 135-138: The test creates two PublicConfigBuilder instances
unnecessarily; replace the two constructions with a single instance and call
getPublicConfig() on that instance (e.g., instantiate PublicConfigBuilder once
into a variable and then await variable.getPublicConfig()), keeping the rest of
the test assertions intact and referencing PublicConfigBuilder and
getPublicConfig to locate the change.

In `@apps/ensapi/src/lib/public-config-builder/public-config-builder.ts`:
- Around line 12-16: Update the incorrect doc comment that reads "ENSRainbow
Client" to accurately say "ENSDb Client" in the top-of-file
header/documentation; search for the exact string "ENSRainbow Client" in
public-config-builder.ts and replace it with "ENSDb Client" (and update any
adjacent descriptive lines mentioning ENSRainbow to refer to ENSDb Client or
ENSDb Public Config) so the comment matches the actual dependency.

---

Outside diff comments:
In `@apps/ensapi/src/config/config.schema.test.ts`:
- Around line 26-177: The tests still treat buildConfigFromEnvironment as async
and rely on fetched ENS indexer data; update tests to reflect the new
synchronous behavior by: add NAMESPACE ("mainnet") and DATABASE_SCHEMA
("ensapi") to BASE_ENV, remove mockFetch and the global fetch stub and any
fetch-related setup/teardown, remove await and .resolves usage when calling
buildConfigFromEnvironment (call it synchronously), remove the
ensIndexerPublicConfig expectation and change expected namespace and
databaseSchemaName to BASE_ENV.NAMESPACE and BASE_ENV.DATABASE_SCHEMA, and
update/clean related tests (e.g., those referencing ENSINDEXER_PUBLIC_CONFIG) so
they no longer depend on serialized remote config.

In `@apps/ensapi/src/config/config.schema.ts`:
- Around line 77-82: Update the JSDoc for the function that builds the
EnsApiConfig from an EnsApiEnvironment (the block referencing EnsApiConfig and
EnsApiEnvironment) to remove the stale claim about fetching the
EnsIndexerPublicConfig and delete the redundant `@returns` tag; keep a concise
summary that accurately reflects the function's behavior and preserve the
`@throws` note for validation errors if present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1312259c-4986-4138-b39c-514d847555cb

📥 Commits

Reviewing files that changed from the base of the PR and between 9657006 and 6b6f23c.

📒 Files selected for processing (28)
  • apps/ensapi/.env.local.example
  • apps/ensapi/src/cache/indexing-status.cache.ts
  • apps/ensapi/src/config/config.schema.test.ts
  • apps/ensapi/src/config/config.schema.ts
  • apps/ensapi/src/config/environment.ts
  • apps/ensapi/src/handlers/ensnode-api.ts
  • apps/ensapi/src/handlers/name-tokens-api.ts
  • apps/ensapi/src/lib/ensdb-client/drizzle.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.mock.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.test.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.ts
  • apps/ensapi/src/lib/ensdb-client/singleton.ts
  • apps/ensapi/src/lib/public-config-builder/index.ts
  • apps/ensapi/src/lib/public-config-builder/public-config-builder.test.ts
  • apps/ensapi/src/lib/public-config-builder/public-config-builder.ts
  • apps/ensapi/src/lib/public-config-builder/singleton.ts
  • apps/ensapi/src/lib/resolution/forward-resolution.ts
  • apps/ensapi/src/lib/subgraph/indexing-status-to-subgraph-meta.ts
  • apps/ensapi/src/middleware/can-accelerate.middleware.ts
  • apps/ensapi/src/middleware/name-tokens.middleware.ts
  • apps/ensapi/src/middleware/registrar-actions.middleware.ts
  • apps/ensapi/src/middleware/require-core-plugin.middleware.ts
  • apps/ensapi/src/middleware/subgraph-meta.middleware.ts
  • apps/ensapi/src/middleware/thegraph-fallback.middleware.ts
  • packages/ensnode-sdk/src/ensapi/config/index.ts
  • packages/ensnode-sdk/src/ensapi/config/validate/ensapi-public-config.ts
  • packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts
  • terraform/modules/ensindexer/main.tf

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ensapi/src/config/config.schema.ts (1)

76-81: 🛠️ Refactor suggestion | 🟠 Major

Clean up stale/redundant JSDoc on buildConfigFromEnvironment

The docblock still mentions “fetching the EnsIndexerPublicConfig” (no longer true) and the @returns line is redundant. Please update/remove this block to match current behavior.

As per coding guidelines, "Do not add JSDoc @returns tags that merely restate the method summary; remove such redundancy during PR review".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/config/config.schema.ts` around lines 76 - 81, Update the
JSDoc for buildConfigFromEnvironment to reflect current behavior: remove the
stale phrase “fetching the EnsIndexerPublicConfig” and delete the redundant
`@returns` tag that merely restates the summary; instead provide a concise
one-line summary such as “Builds an EnsApiConfig from an EnsApiEnvironment” and
keep or adjust any `@throws` description if it adds useful detail. Ensure the
docblock above buildConfigFromEnvironment references only accurate
responsibilities and validation/throw behavior, and remove any mention of
fetching external configs.
♻️ Duplicate comments (2)
apps/ensapi/src/config/config.schema.ts (1)

84-95: ⚠️ Potential issue | 🟡 Minor

Use the parsed namespace value when constructing config

Line 84 parses env.NAMESPACE, but Line 94 uses raw env.NAMESPACE again. Use the parsed namespace variable as the single source of truth.

♻️ Proposed fix
     return EnsApiConfigSchema.parse({
       version: packageJson.version,
       port: env.PORT,
       databaseUrl: env.DATABASE_URL,
       databaseSchemaName: env.DATABASE_SCHEMA,
       ensIndexerUrl: env.ENSINDEXER_URL,
       theGraphApiKey: env.THEGRAPH_API_KEY,
-      namespace: env.NAMESPACE,
+      namespace,
       rpcConfigs,
       customReferralProgramEditionConfigSetUrl: env.CUSTOM_REFERRAL_PROGRAM_EDITIONS,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/config/config.schema.ts` around lines 84 - 95, The code
parses env.NAMESPACE into the variable namespace using ENSNamespaceSchema.parse
but then passes the raw env.NAMESPACE into EnsApiConfigSchema.parse; update the
config construction to use the parsed namespace variable instead of
env.NAMESPACE. Locate the block where namespace is set (namespace =
ENSNamespaceSchema.parse(env.NAMESPACE)) and where EnsApiConfigSchema.parse is
called (the object with properties like version, port, rpcConfigs), and replace
the namespace property value to the parsed namespace variable so
buildRpcConfigsFromEnv and EnsApiConfigSchema.parse use the same validated
value.
apps/ensapi/src/lib/ensdb-client/ensdb-client.ts (1)

115-121: ⚠️ Potential issue | 🟡 Minor

Remove redundant @returns JSDoc line

The @returns line repeats the summary and should be removed for consistency with the TS doc guideline.

Proposed cleanup
   /**
    * Get ENSNode metadata record
    *
-   * `@returns` selected record in ENSDb.
    * `@throws` when more than one matching metadata record is found
    *         (should be impossible given the PK constraint on 'key')
    */
As per coding guidelines, "Do not add JSDoc `@returns` tags that merely restate the method summary; remove such redundancy during PR review".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/lib/ensdb-client/ensdb-client.ts` around lines 115 - 121,
Remove the redundant JSDoc `@returns` tag in the "Get ENSNode metadata record"
comment block (the JSDoc immediately above the function that retrieves the ENS
node metadata record) since it only restates the summary; leave the summary and
the `@throws` line intact and ensure no other JSDoc tags are duplicated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/lib/ensdb-client/ensdb-client.ts`:
- Around line 57-62: The constructor in ensdb-client.ts passes the wrong option
key to makeDrizzle; change the argument from databaseSchema to
databaseSchemaName so the call in the ENSDB client constructor (the makeDrizzle
invocation inside constructor(databaseUrl, databaseSchemaName)) uses the
expected key name (databaseSchemaName) to restore correct typing and schema
binding.

In `@apps/ensapi/src/middleware/registrar-actions.middleware.ts`:
- Around line 37-39: Wrap the await publicConfigBuilder.getPublicConfig() call
in a try/catch inside the registrar-actions middleware so failures don't bubble
out; if getPublicConfig throws, log the error and treat it as "registrar actions
unsupported" by setting the result used by hasRegistrarActionsConfigSupport to a
safe default (or set configSupport = false), then return the existing
backward-compatible serialized response using serializeRegistrarActionsResponse
with the 503 responseCode path so the middleware preserves the "Registrar
Actions unavailable" contract; reference publicConfigBuilder.getPublicConfig,
hasRegistrarActionsConfigSupport, serializeRegistrarActionsResponse and
responseCode when making the change.

---

Outside diff comments:
In `@apps/ensapi/src/config/config.schema.ts`:
- Around line 76-81: Update the JSDoc for buildConfigFromEnvironment to reflect
current behavior: remove the stale phrase “fetching the EnsIndexerPublicConfig”
and delete the redundant `@returns` tag that merely restates the summary; instead
provide a concise one-line summary such as “Builds an EnsApiConfig from an
EnsApiEnvironment” and keep or adjust any `@throws` description if it adds useful
detail. Ensure the docblock above buildConfigFromEnvironment references only
accurate responsibilities and validation/throw behavior, and remove any mention
of fetching external configs.

---

Duplicate comments:
In `@apps/ensapi/src/config/config.schema.ts`:
- Around line 84-95: The code parses env.NAMESPACE into the variable namespace
using ENSNamespaceSchema.parse but then passes the raw env.NAMESPACE into
EnsApiConfigSchema.parse; update the config construction to use the parsed
namespace variable instead of env.NAMESPACE. Locate the block where namespace is
set (namespace = ENSNamespaceSchema.parse(env.NAMESPACE)) and where
EnsApiConfigSchema.parse is called (the object with properties like version,
port, rpcConfigs), and replace the namespace property value to the parsed
namespace variable so buildRpcConfigsFromEnv and EnsApiConfigSchema.parse use
the same validated value.

In `@apps/ensapi/src/lib/ensdb-client/ensdb-client.ts`:
- Around line 115-121: Remove the redundant JSDoc `@returns` tag in the "Get
ENSNode metadata record" comment block (the JSDoc immediately above the function
that retrieves the ENS node metadata record) since it only restates the summary;
leave the summary and the `@throws` line intact and ensure no other JSDoc tags are
duplicated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb5c75ec-02c1-4ab0-b8e8-912a934dc20c

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6f23c and 39df7dd.

📒 Files selected for processing (10)
  • apps/ensapi/src/config/config.schema.test.ts
  • apps/ensapi/src/config/config.schema.ts
  • apps/ensapi/src/handlers/ensnode-api.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.test.ts
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.ts
  • apps/ensapi/src/middleware/can-accelerate.middleware.ts
  • apps/ensapi/src/middleware/name-tokens.middleware.ts
  • apps/ensapi/src/middleware/registrar-actions.middleware.ts
  • apps/ensindexer/src/lib/version-info.ts
  • packages/ensnode-sdk/src/ensapi/config/conversions.test.ts
💤 Files with no reviewable changes (1)
  • apps/ensindexer/src/lib/version-info.ts

Copy link
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 32 out of 32 changed files in this pull request and generated 5 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

@tk-o
Copy link
Contributor Author

tk-o commented Mar 4, 2026

@greptile review

Copy link
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 33 out of 33 changed files in this pull request and generated 6 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +58 to 73
const EnsApiVersionSchema = makeEnsApiVersionSchema();

const EnsApiConfigSchema = z
.object({
version: EnsApiVersionSchema,
port: OptionalPortNumberSchema.default(ENSApi_DEFAULT_PORT),
databaseUrl: DatabaseUrlSchema,
databaseSchemaName: DatabaseSchemaNameSchema,
ensIndexerUrl: EnsIndexerUrlSchema,
theGraphApiKey: TheGraphApiKeySchema,
namespace: ENSNamespaceSchema,
rpcConfigs: RpcConfigsSchema,
ensIndexerPublicConfig: makeENSIndexerPublicConfigSchema("ensIndexerPublicConfig"),
customReferralProgramEditionConfigSetUrl: CustomReferralProgramEditionConfigSetUrlSchema,
})
.check(invariant_rpcConfigsSpecifiedForRootChain)
.check(invariant_ensIndexerPublicConfigVersionInfo);
.check(invariant_rpcConfigsSpecifiedForRootChain);

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The previous startup-time version compatibility check between ENSApi and the ENSIndexer/ENSDb versions (via invariant_ensIndexerPublicConfigVersionInfo) has been removed, but there doesn’t appear to be an equivalent validation when reading config from ENSDb. This means ENSApi may now run against incompatible ENSDb/ENSIndexer/ENSRainbow versions without failing fast. Consider reintroducing an explicit version-mismatch guard in PublicConfigBuilder.getPublicConfig() (or as a Zod .check in validateEnsApiPublicConfig) to preserve the earlier safety behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to +41
@@ -35,8 +36,9 @@ export function indexingContextToSubgraphMeta(
case ChainIndexingStatusIds.Completed:
case ChainIndexingStatusIds.Backfill:
case ChainIndexingStatusIds.Following: {
const { ensIndexerPublicConfig } = await publicConfigBuilder.getPublicConfig();
return {
deployment: config.ensIndexerPublicConfig.versionInfo.ensIndexer,
deployment: ensIndexerPublicConfig.versionInfo.ensIndexer,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

indexingContextToSubgraphMeta determines the ENS root chain ID using config.namespace, while other data in this function (indexing status snapshot + deployment version) is now sourced from ENSDb / ENSIndexer public config. If config.namespace and ensIndexerPublicConfig.namespace ever diverge, _meta will be computed against the wrong root chain (likely returning null). Consider deriving the namespace from publicConfigBuilder.getPublicConfig().ensIndexerPublicConfig.namespace (or asserting the two match) to keep the computation consistent with ENSDb-sourced state.

Copilot uses AI. Check for mistakes.
Comment on lines 86 to +90
// will automatically 503 if config is not available due to ensIndexerPublicConfigMiddleware
app.get("/health", async (c) => {
return c.json({ message: "fallback ok" });
try {
// ENSApi is healthy when its Public Config can be successfully built.
await publicConfigBuilder.getPublicConfig();
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The comment above /health still references ensIndexerPublicConfigMiddleware, but the endpoint now checks health by calling publicConfigBuilder.getPublicConfig(). Updating/removing this comment will avoid confusion during ops/debugging.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
const ensApiPublicConfig = await publicConfigBuilder.getPublicConfig();
return c.json(serializeEnsApiPublicConfig(ensApiPublicConfig));
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

/api/config now awaits publicConfigBuilder.getPublicConfig(), which can throw (e.g., ENSDb not reachable / missing ENSIndexer public config). Right now that exception will bubble to the global onError handler and return a 500. Consider catching expected “not ready/unavailable” failures here and returning a 503 (similar to the /health endpoint) so clients get a more accurate status code.

Suggested change
const ensApiPublicConfig = await publicConfigBuilder.getPublicConfig();
return c.json(serializeEnsApiPublicConfig(ensApiPublicConfig));
try {
const ensApiPublicConfig = await publicConfigBuilder.getPublicConfig();
return c.json(serializeEnsApiPublicConfig(ensApiPublicConfig));
} catch (error) {
return c.json(
{
error: "ENS API public config is not available",
},
503,
);
}

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 27
@@ -20,7 +23,7 @@ export function makeEnsApiPublicConfigSchema(valueLabel?: string) {
const label = valueLabel ?? "ENSApiPublicConfig";

return z.object({
version: z.string().min(1, `${label}.version must be a non-empty string`),
version: makeEnsApiVersionSchema(),
theGraphFallback: TheGraphFallbackSchema,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

makeEnsApiVersionSchema() uses a fixed error message, so makeEnsApiPublicConfigSchema(valueLabel) no longer includes the caller-provided valueLabel context for version validation errors (it used to include ${label}.version). Consider accepting a valueLabel/path argument in makeEnsApiVersionSchema (or restoring the ${label}.version message at the call site) so custom labels remain useful and consistent with other schema builders.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +60
this.immutablePublicConfig = validateEnsApiPublicConfig({
version: config.version,
theGraphFallback: canFallbackToTheGraph({
namespace: config.namespace,
// NOTE: very important here that we replace the actual server-side api key with a placeholder
// so that it's not sent to clients as part of the `theGraphFallback.url`. The placeholder must
// pass validation, of course, but the only validation necessary is that it is a string.
theGraphApiKey: config.theGraphApiKey ? "<API_KEY>" : undefined,
isSubgraphCompatible: ensIndexerPublicConfig.isSubgraphCompatible,
}),
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

getPublicConfig() uses config.namespace when building theGraphFallback, even though the authoritative namespace for this ENSApi instance is now coming from ENSDb (ensIndexerPublicConfig.namespace). If these ever diverge due to misconfiguration, the public config could become internally inconsistent. Consider using ensIndexerPublicConfig.namespace here (and/or asserting it matches config.namespace and throwing a clear error if it doesn’t).

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR architecturally decouples ENSApi from ENSIndexer by replacing the direct ENSIndexer HTTP client dependency with a shared ENSDb (PostgreSQL) client. ENSApi now reads NAMESPACE and DATABASE_SCHEMA directly from its own environment, buildConfigFromEnvironment becomes synchronous, and all runtime reads of EnsIndexerPublicConfig / indexing status flow through the new EnsDbClient + PublicConfigBuilder abstractions.

Key changes:

  • EnsDbClient — new Drizzle-backed client that reads ensnode_metadata rows for ENSIndexer public config, indexing status snapshots, and ENSDb version.
  • PublicConfigBuilder — lazily fetches EnsIndexerPublicConfig from ENSDb on first call, caches the result as immutablePublicConfig, and used everywhere config.ensIndexerPublicConfig was previously accessed.
  • validateEnsApiPublicConfig — new SDK function that validates and returns a typed EnsApiPublicConfig.
  • Config startupbuildConfigFromEnvironment is now fully synchronous; pRetry-wrapped ENSIndexer HTTP fetch at startup is removed.
  • Health check/health now actively verifies ENSApi can build its public config and returns 503 if not.
  • TerraformDATABASE_SCHEMA and NAMESPACE added to shared ENSApi/ENSIndexer variables.

Notable findings:

  • makeEnsApiVersionSchema() uses a hard-coded error message ("ENSApi version must be a non-empty string") that no longer incorporates the valueLabel path prefix. The related test was changed to avoid testing the version field with a custom label, masking the regression in error message quality.
  • Middleware functions that call publicConfigBuilder.getPublicConfig() (e.g., can-accelerate, name-tokens, require-core-plugin, registrar-actions, thegraph-fallback) do not wrap the call in try/catch. If the config is unavailable (e.g., ENSDb not yet populated at startup), the error propagates unhandled and Hono's onError returns 500 Internal Server Error rather than the more appropriate 503 Service Unavailable that the health check correctly uses.

Confidence Score: 3/5

  • Safe to merge with awareness of the 500 vs 503 middleware error handling gap and the version error message regression.
  • The architectural direction is sound and well-tested. The two remaining issues — middleware not mapping getPublicConfig() failures to 503, and the version validation error message losing its valueLabel path context (with the test updated to avoid the regression) — are non-blocking but worth addressing to improve observability and debuggability.
  • packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts (version error message regression) and all updated middleware files in apps/ensapi/src/middleware/ (unhandled 500 vs 503 on config unavailability).

Important Files Changed

Filename Overview
apps/ensapi/src/lib/public-config-builder/public-config-builder.ts New PublicConfigBuilder class that lazily fetches and caches EnsApiPublicConfig from ENSDb; caching logic is correct and tested, but concurrent callers can issue redundant DB round-trips before the cache is populated (acknowledged "Won't fix" by the team).
apps/ensapi/src/lib/ensdb-client/ensdb-client.ts New EnsDbClient implementing EnsDbClientQuery to read ENSIndexer public config, indexing status, and ENSDb version from Postgres via Drizzle ORM; logic is sound and well-tested.
apps/ensapi/src/config/config.schema.ts buildConfigFromEnvironment is now synchronous and no longer depends on fetching ENSIndexer config at startup; NAMESPACE and DATABASE_SCHEMA are read directly from the environment.
packages/ensnode-sdk/src/ensapi/config/zod-schemas.ts Extracts makeEnsApiVersionSchema() as a reusable factory, but the hard-coded error message loses the valueLabel path context that existing callers of makeEnsApiPublicConfigSchema relied upon; a corresponding test was changed to avoid exposing the regression.
apps/ensapi/src/index.ts Health check updated to call publicConfigBuilder.getPublicConfig() and return 503 if config is unavailable; stale comment about ensIndexerPublicConfigMiddleware remains (team acknowledged as out of scope).
apps/ensapi/src/middleware/can-accelerate.middleware.ts Now fetches ensIndexerPublicConfig from publicConfigBuilder on each request; if getPublicConfig() throws, the unhandled error yields a 500 rather than a more appropriate 503. Same pattern across all updated middlewares.
apps/ensapi/src/cache/indexing-status.cache.ts Indexing status cache now reads from ensDbClient.getIndexingStatusSnapshot() instead of the ENSIndexer HTTP client; logic mirrors the previous implementation cleanly.
terraform/modules/ensindexer/main.tf DATABASE_SCHEMA and NAMESPACE added to common_variables (shared by ENSIndexer and ENSApi); both variables are properly declared in variables.tf.
packages/ensnode-sdk/src/ensapi/config/validate/ensapi-public-config.ts New validateEnsApiPublicConfig function that validates and returns an EnsApiPublicConfig, used by PublicConfigBuilder; straightforward and well-structured.
apps/ensapi/src/lib/ensdb-client/singleton.ts Simple singleton exporting a module-level EnsDbClient instance constructed from config; clean pattern consistent with existing singleton files in the codebase.

Sequence Diagram

sequenceDiagram
    participant Client as HTTP Client
    participant ENSApi as ENSApi
    participant PCB as PublicConfigBuilder
    participant DbClient as EnsDbClient
    participant DB as PostgreSQL ENSDb
    participant Indexer as ENSIndexer

    Note over Indexer,DB: ENSIndexer writes metadata to ENSDb on startup
    Indexer->>DB: Store EnsIndexerPublicConfig
    Indexer->>DB: Store IndexingStatus

    Note over ENSApi,DB: ENSApi startup self health-check
    ENSApi->>PCB: getPublicConfig()
    PCB->>DbClient: getEnsIndexerPublicConfig()
    DbClient->>DB: Query ensnode_metadata by public_config
    DB-->>DbClient: EnsIndexerPublicConfig data
    DbClient-->>PCB: Deserialized EnsIndexerPublicConfig
    PCB->>PCB: validateEnsApiPublicConfig
    PCB->>PCB: Assign immutablePublicConfig
    PCB-->>ENSApi: EnsApiPublicConfig cached

    Note over Client,ENSApi: Subsequent requests use cached config
    Client->>ENSApi: GET /api/config
    ENSApi->>PCB: getPublicConfig()
    PCB-->>ENSApi: EnsApiPublicConfig from cache
    ENSApi-->>Client: 200 OK

    Note over Client,ENSApi: Indexing status always read from ENSDb
    Client->>ENSApi: GET /api/indexing-status
    ENSApi->>DbClient: getIndexingStatusSnapshot()
    DbClient->>DB: Query ensnode_metadata by indexing_status
    DB-->>DbClient: IndexingStatus data
    DbClient-->>ENSApi: CrossChainIndexingStatusSnapshot
    ENSApi-->>Client: 200 OK
Loading

Last reviewed commit: a8d11e0

@@ -84,7 +85,17 @@ app.get("/openapi.json", (c) => {

// will automatically 503 if config is not available due to ensIndexerPublicConfigMiddleware
Copy link
Contributor

Choose a reason for hiding this comment

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

Stale comment references a non-existent middleware

The comment // will automatically 503 if config is not available due to ensIndexerPublicConfigMiddleware refers to a middleware that no longer exists in the codebase after this refactor. The 503 is now returned by the try/catch block inside the handler itself, not by a middleware.

Suggested change
// will automatically 503 if config is not available due to ensIndexerPublicConfigMiddleware
// will automatically 503 if config is not available (see try/catch below)

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 4, 2026

Additional Comments (1)

apps/ensapi/src/lib/ensdb-client/drizzle.ts
setDatabaseSchema mutates the shared schema objects — test isolation risk

setDatabaseSchema monkeypatches the Drizzle table objects in place (e.g. ensNodeMetadata). If makeDrizzle is ever called a second time with a different databaseSchema value (e.g. in a future integration test that creates two EnsDbClient instances, or if a second package imports the same schema), the last call silently overwrites the schema name for all previous instances sharing those table references.

The existing test suite sidesteps this entirely by mocking makeDrizzle at the module boundary (vi.mock("./drizzle", ...)), which is the right mitigation for unit tests. It is worth adding an explicit note here (or in the shared drizzle.ts this was copied from) warning that:

  1. Only one EnsDbClient / Drizzle instance should be active per process.
  2. Integration tests must mock makeDrizzle or otherwise ensure no second instantiation occurs.

This is a pre-existing pattern, but as this file is newly introduced in ENSApi it is worth documenting clearly.

@tk-o
Copy link
Contributor Author

tk-o commented Mar 4, 2026

@greptile review


return z.object({
version: z.string().min(1, `${label}.version must be a non-empty string`),
version: makeEnsApiVersionSchema(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Version error message no longer contextualizes the valueLabel

makeEnsApiVersionSchema() emits a hard-coded error message ("ENSApi version must be a non-empty string") regardless of which valueLabel is passed to the parent schema builders. Before this PR, makeEnsApiPublicConfigSchema("testConfig") would produce "testConfig.version must be a non-empty string", allowing callers to trace a validation failure back to the exact path.

Now, when deserializeEnsApiPublicConfig(config, "testConfig") fails on the version field, the error message will say "ENSApi version must be a non-empty string" with no mention of the testConfig. prefix, making nested validation errors harder to diagnose.

The regression is masked by the corresponding test change in conversions.test.ts that switches from testing version: "" (which would expose this) to testing ensIndexerPublicConfig: {} instead:

// Before:
version: z.string().min(1, `${label}.version must be a non-empty string`)

// After (no longer label-aware):
version: makeEnsApiVersionSchema()

Consider passing label into makeEnsApiVersionSchema:

export const makeEnsApiVersionSchema = (label?: string) =>
  z.string().min(1, `${label ? label + "." : ""}version must be a non-empty string`);

And update the callers:

return z.object({
  version: makeEnsApiVersionSchema(label),
  ...

////////////////////////////
/// Temporary ENSv2 Bailout
////////////////////////////
// TODO: re-enable acceleration for ensv2 once implemented
Copy link
Contributor

Choose a reason for hiding this comment

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

Unhandled getPublicConfig() throws yield 500 instead of 503

When publicConfigBuilder.getPublicConfig() throws (e.g., ENSIndexer hasn't written its config to ENSDb yet during startup, or the DB connection is temporarily unavailable), the error propagates uncaught through the middleware chain and reaches Hono's onError handler, which returns 500 Internal Server Error.

Contrast this with the /health endpoint which wraps the same call in try/catch and returns a proper 503 Service Unavailable. API consumers would benefit from the same informative status code in regular request paths.

The same pattern applies to all other middlewares that were updated in this PR:

  • apps/ensapi/src/middleware/name-tokens.middleware.ts
  • apps/ensapi/src/middleware/registrar-actions.middleware.ts
  • apps/ensapi/src/middleware/require-core-plugin.middleware.ts
  • apps/ensapi/src/middleware/thegraph-fallback.middleware.ts
  • apps/ensapi/src/lib/subgraph/indexing-status-to-subgraph-meta.ts
  • apps/ensapi/src/lib/resolution/forward-resolution.ts

Consider adding a centralized guard, or at minimum wrapping getPublicConfig() in each middleware with a try/catch that maps the error to a 503.

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.

Add DATABASE_SCHEMA environment variable to all ENSApi instances

2 participants