Skip to content

Integrate ENSDb into ENSApi#1753

Draft
tk-o wants to merge 6 commits intorefactor/ensdb-schemafrom
feat/integrate-ensdb-into-ensapi
Draft

Integrate ENSDb into ENSApi#1753
tk-o wants to merge 6 commits intorefactor/ensdb-schemafrom
feat/integrate-ensdb-into-ensapi

Conversation

@tk-o
Copy link
Contributor

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

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • Created EnsDbClient for ENSApi
    • This is a read-only client.
  • Updated buildConfigFromEnvironment to replace method for getting ENSIndexer Public Config
    • From: ENSIndexer endpoint fetch.
    • To: ENSDb read.

Why

  • ENSApi must not reference ENSIndexer instance directly. Instead, ENSApi should use ENSDb to fetch any information produced by ENSIndexer.

Testing

  • I ran local instances of ENSIndexer and ENSApi working together.
    • Confirmed that Indexing Status API for ENSApi worked as expected.
  • I manually created ensnode schema in local ENSDb.

Notes for Reviewer (Optional)


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 March 12, 2026 12:11
@vercel
Copy link
Contributor

vercel bot commented Mar 12, 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 12, 2026 2:20pm
ensnode.io Ready Ready Preview, Comment Mar 12, 2026 2:20pm
ensrainbow.io Ready Ready Preview, Comment Mar 12, 2026 2:20pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 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.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a5d8618d-b1c5-455b-836a-3447a91e3d63

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/integrate-ensdb-into-ensapi
📝 Coding Plan for PR comments
  • Generate coding plan

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.

@changeset-bot
Copy link

changeset-bot bot commented Mar 12, 2026

🦋 Changeset detected

Latest commit: 38f30d8

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

This PR includes changesets to release 19 packages
Name Type
ensapi Major
ensindexer Major
ensadmin Major
ensrainbow Major
fallback-ensapi Major
@ensnode/datasources Major
@ensnode/ensrainbow-sdk 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
@ensnode/integration-test-env Patch

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR decouples ENSApi from ENSIndexer by replacing the HTTP-based ENSIndexer config fetch (using pRetry + fetchENSIndexerConfig) with a new EnsDbClient that reads config directly from ENSDb (Postgres). DATABASE_SCHEMA is now a required environment variable for ENSApi, and the Terraform module is updated to share it across both services via common_variables.

Key changes:

  • New EnsDbClient class provides a typed, read-only Drizzle ORM interface over ENSDb, querying the ensNodeMetadata table with row-level multi-tenancy via ensIndexerRef
  • buildConfigFromEnvironment now instantiates EnsDbClient at startup instead of fetching over HTTP
  • EnsApiEnvironment now includes DATABASE_SCHEMA (previously it was explicitly Omit-ted)
  • fetch-ensindexer-config.ts is deleted
  • The connection pool created in EnsDbClient's constructor is never explicitly closed after the one-time config load, which is the main concern in this PR
  • The previously existing pRetry retry logic for transient unavailability is not carried over to the new DB-based path

Confidence Score: 3/5

  • Safe to merge with caveats — the connection pool leak and missing retry logic should be addressed before or shortly after landing.
  • The architectural direction is sound and the implementation is straightforward, but the EnsDbClient constructor opens a node-postgres connection pool that is never closed when the client is used as a one-shot config loader at startup. Additionally, the removal of pRetry means a momentary DB blip at startup will cause an immediate process.exit(1) with no recovery attempt.
  • apps/ensapi/src/lib/ensdb-client/ensdb-client.ts (connection pool lifetime) and apps/ensapi/src/config/config.schema.ts (missing retry logic). Test cleanup in config.schema.test.ts is lower priority.

Important Files Changed

Filename Overview
apps/ensapi/src/lib/ensdb-client/ensdb-client.ts New EnsDbClient class wrapping Drizzle ORM for read-only ENSDb access; has a connection pool leak since the underlying pg.Pool is never closed after single-use config loading.
apps/ensapi/src/config/config.schema.ts Replaces pRetry+fetchENSIndexerConfig with a single EnsDbClient.getEnsIndexerPublicConfig() call; loses retry logic that protected against transient DB unavailability at startup.
apps/ensapi/src/config/config.schema.test.ts Adds EnsDbClient mock and DATABASE_SCHEMA to test env; leaves dead mockFetch.mockResolvedValueOnce calls and a misnamed mock method (getVersion instead of getEnsDbVersion).
apps/ensapi/src/config/environment.ts Includes DATABASE_SCHEMA in EnsApiEnvironment by removing the Omit<DatabaseEnvironment, "DATABASE_SCHEMA"> exclusion; change is correct and minimal.
apps/ensapi/src/lib/fetch-ensindexer-config.ts File deleted; the HTTP-based ENSIndexer config fetcher is fully superseded by EnsDbClient.getEnsIndexerPublicConfig().
terraform/modules/ensindexer/main.tf Moves DATABASE_SCHEMA from the ensindexer-only env_vars block to common_variables so it is correctly shared with the ensapi service as well.
apps/ensapi/.env.local.example Documents the new required DATABASE_SCHEMA env var and removes the stale note that ENSApi did not need to define it.
.changeset/humble-pets-trade.md Adds a minor changeset entry describing the replacement of ENSIndexer HTTP config source with ENSDb.

Sequence Diagram

sequenceDiagram
    participant Env as Process Env
    participant BSC as buildConfigFromEnvironment
    participant EnsDbClient
    participant PgPool as node-postgres Pool
    participant ENSDb as ENSDb (Postgres)

    BSC->>Env: read DATABASE_URL + DATABASE_SCHEMA
    BSC->>EnsDbClient: new EnsDbClient(databaseUrl, ensIndexerRef)
    EnsDbClient->>PgPool: create read-only pool (never closed ⚠️)
    BSC->>EnsDbClient: getEnsIndexerPublicConfig()
    EnsDbClient->>ENSDb: SELECT * FROM ensnode.ens_node_metadata WHERE ens_indexer_ref = ? AND key = ?
    ENSDb-->>EnsDbClient: row or empty
    EnsDbClient-->>BSC: EnsIndexerPublicConfig | undefined
    Note over BSC: if undefined → throw Error → process.exit(1)
    BSC->>BSC: EnsApiConfigSchema.parse(...)
    BSC-->>BSC: EnsApiConfig (EnsDbClient discarded, pool leaks)
Loading

Comments Outside Diff (1)

  1. apps/ensapi/src/config/config.schema.test.ts, line 75-87 (link)

    Dead mockFetch setup calls throughout the test file

    EnsDbClient is now fully mocked and buildConfigFromEnvironment no longer calls fetch. Yet mockFetch.mockResolvedValueOnce(...) is still called at lines 84–87, 114–117, 146–149, 162–165, 182–185, and the mockFetch reset in afterEach (line 80–82) remains. None of these interact with the real code path anymore.

    These dead mock setups should be removed to avoid misleading future readers into thinking fetch is still in play. The vi.stubGlobal("fetch", mockFetch) declaration and the afterEach reset can be removed entirely.

Last reviewed commit: 0a54b8a

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

Integrates ENSDb into ENSApi by introducing a read-only ENSDb client and switching ENSApi’s startup config loading to read ENSIndexer public config from ENSDb instead of fetching it from an ENSIndexer HTTP endpoint.

Changes:

  • Added EnsDbClient (read-only) in ENSApi for querying ENSNode metadata stored in ENSDb.
  • Updated ENSApi config bootstrapping to load EnsIndexerPublicConfig from ENSDb and made DATABASE_SCHEMA required for ENSApi.
  • Removed the old fetchENSIndexerConfig() HTTP-based implementation and updated examples/tests accordingly.

Reviewed changes

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

Show a summary per file
File Description
terraform/modules/ensindexer/main.tf Moves DATABASE_SCHEMA into shared env var locals for the Render service config.
apps/ensapi/src/lib/fetch-ensindexer-config.ts Removes the old HTTP fetch helper for ENSIndexer config.
apps/ensapi/src/lib/ensdb-client/ensdb-client.ts Adds a read-only ENSDb client to query ENSNode metadata (public config, version, indexing status).
apps/ensapi/src/config/environment.ts Makes DATABASE_SCHEMA part of EnsApiEnvironment.
apps/ensapi/src/config/config.schema.ts Switches config bootstrapping from HTTP fetch (with retry) to ENSDb reads via EnsDbClient.
apps/ensapi/src/config/config.schema.test.ts Updates tests/mocks for the new ENSDb-based config loading.
apps/ensapi/.env.local.example Documents new required DATABASE_SCHEMA for ENSApi.

💡 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 +87 to +89
const ensIndexerSchemaName = DatabaseSchemaNameSchema.parse(env.DATABASE_SCHEMA);

return new EnsDbClient(databaseUrl, ensIndexerSchemaName);
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

DATABASE_SCHEMA is parsed into ensIndexerSchemaName and passed as the ensIndexerRef argument to EnsDbClient. Since ensIndexerRef represents the ENSIndexer instance identifier (often the schema name), consider renaming ensIndexerSchemaName to ensIndexerRef (and/or clarifying via comments) to avoid confusing it with ensIndexerPublicConfig.databaseSchemaName used later for queries.

Suggested change
const ensIndexerSchemaName = DatabaseSchemaNameSchema.parse(env.DATABASE_SCHEMA);
return new EnsDbClient(databaseUrl, ensIndexerSchemaName);
// NOTE: This is the ENSIndexer instance identifier (often the schema name), used as `ensIndexerRef` in EnsDbClient.
const ensIndexerRef = DatabaseSchemaNameSchema.parse(env.DATABASE_SCHEMA);
return new EnsDbClient(databaseUrl, ensIndexerRef);

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +106
const ensDbClient = buildEnsDbClientFromEnvironment(env);

const ensIndexerPublicConfig = await ensDbClient.getEnsIndexerPublicConfig();

if (!ensIndexerPublicConfig) {
throw new Error("Failed to load EnsIndexerPublicConfig from ENSDb.");
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Previously the ENSIndexer public config load had retry/backoff; the ENSDb read now happens once and any transient DB startup error will immediately exit the process. Consider reintroducing a small retry (or a clearer classification of “missing row” vs “DB connectivity/permission error”) around getEnsIndexerPublicConfig() to avoid flakiness during deploy/startup ordering.

Copilot uses AI. Check for mistakes.
Comment on lines 98 to +106
export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promise<EnsApiConfig> {
try {
const ensIndexerUrl = EnsIndexerUrlSchema.parse(env.ENSINDEXER_URL);

const ensIndexerPublicConfig = await pRetry(() => fetchENSIndexerConfig(ensIndexerUrl), {
retries: 3,
onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
logger.info(
`ENSIndexer Config fetch attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
);
},
});
const ensDbClient = buildEnsDbClientFromEnvironment(env);

const ensIndexerPublicConfig = await ensDbClient.getEnsIndexerPublicConfig();

if (!ensIndexerPublicConfig) {
throw new Error("Failed to load EnsIndexerPublicConfig from ENSDb.");
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

PR description implies ENSApi won’t reference an ENSIndexer instance directly, but this function still builds a config that includes ensIndexerUrl (and ENSApi still has codepaths that use it). If full decoupling isn’t part of this PR, consider tightening the PR description/scope so it matches what’s actually changed here (public config source only).

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
const mockGetVersion = vi.fn().mockResolvedValue("1.0.0");
const mockGetEnsIndexerPublicConfig = vi.fn().mockResolvedValue(ENSINDEXER_PUBLIC_CONFIG);
const mockGetIndexingStatusSnapshot = vi.fn().mockResolvedValue(null);

vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getVersion = mockGetVersion;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The EnsDbClient mock doesn’t match the real EnsDbClientQuery interface: it defines getVersion instead of getEnsDbVersion, and getIndexingStatusSnapshot resolves to null instead of undefined. Aligning the mock with the interface will prevent future tests from silently diverging when those methods start being used.

Suggested change
const mockGetVersion = vi.fn().mockResolvedValue("1.0.0");
const mockGetEnsIndexerPublicConfig = vi.fn().mockResolvedValue(ENSINDEXER_PUBLIC_CONFIG);
const mockGetIndexingStatusSnapshot = vi.fn().mockResolvedValue(null);
vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getVersion = mockGetVersion;
const mockGetEnsDbVersion = vi.fn().mockResolvedValue("1.0.0");
const mockGetEnsIndexerPublicConfig = vi.fn().mockResolvedValue(ENSINDEXER_PUBLIC_CONFIG);
const mockGetIndexingStatusSnapshot = vi.fn().mockResolvedValue(undefined);
vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getEnsDbVersion = mockGetEnsDbVersion;

Copilot uses AI. Check for mistakes.
Comment on lines 75 to 77
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

fetch is still stubbed and configured in these tests, but buildConfigFromEnvironment no longer calls fetch after the switch to EnsDbClient. This makes the test setup misleading and reduces confidence that the new ENSDb path is being exercised; consider removing the fetch mocking and instead asserting EnsDbClient construction + getEnsIndexerPublicConfig() calls.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +64
constructor(databaseUrl: string, ensIndexerRef: string) {
this.db = makeReadOnlyDrizzle({
databaseUrl,
schema: ensNodeSchema,
});

this.ensIndexerRef = ensIndexerRef;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Database connection pool is never closed

makeReadOnlyDrizzle creates a node-postgres connection pool under the hood. When buildConfigFromEnvironment creates an EnsDbClient to fetch the startup config, it makes a single query and then discards the client — but the underlying pg.Pool is never explicitly ended. In Node.js, a pg.Pool holds open TCP connections to Postgres until it is told to end(), so the pool will stay alive for the rest of the process lifetime after the config load.

Since buildConfigFromEnvironment runs once at startup (and the EnsDbClient is not reused), the simplest fix is to expose a close() method and call it after the query:

// in EnsDbClient
async close(): Promise<void> {
  await (this.db as any).$client?.end();
}
// in buildConfigFromEnvironment
const ensDbClient = buildEnsDbClientFromEnvironment(env);
try {
  const ensIndexerPublicConfig = await ensDbClient.getEnsIndexerPublicConfig();
  ...
} finally {
  await ensDbClient.close();
}

Alternatively, consider sharing the EnsDbClient instance across the application lifetime (instead of creating a disposable one just for config loading) so the pool is reused rather than leaked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The connection must remain open for the process lifetime. It will be needed for loading current indexing status object while handling HTTP traffic.

Comment on lines 99 to +106
try {
const ensIndexerUrl = EnsIndexerUrlSchema.parse(env.ENSINDEXER_URL);

const ensIndexerPublicConfig = await pRetry(() => fetchENSIndexerConfig(ensIndexerUrl), {
retries: 3,
onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
logger.info(
`ENSIndexer Config fetch attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
);
},
});
const ensDbClient = buildEnsDbClientFromEnvironment(env);

const ensIndexerPublicConfig = await ensDbClient.getEnsIndexerPublicConfig();

if (!ensIndexerPublicConfig) {
throw new Error("Failed to load EnsIndexerPublicConfig from ENSDb.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

No retry logic for DB query at startup

The previous implementation used pRetry with 3 retries (and informative log messages) when fetching the ENSIndexer config from the ENSIndexer HTTP endpoint. The new code performs a single database query with no retry: if the database is temporarily unavailable at startup (e.g., during a rolling deploy), ENSApi will immediately log an error and call process.exit(1), requiring an operator to manually restart the service.

Consider wrapping the ensDbClient.getEnsIndexerPublicConfig() call in similar retry logic, for example:

import pRetry from "p-retry";

const ensIndexerPublicConfig = await pRetry(
  () => ensDbClient.getEnsIndexerPublicConfig(),
  {
    retries: 3,
    onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
      logger.info(
        `ENSIndexer Public Config DB read attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
      );
    },
  },
);

Comment on lines +63 to +73
const mockGetVersion = vi.fn().mockResolvedValue("1.0.0");
const mockGetEnsIndexerPublicConfig = vi.fn().mockResolvedValue(ENSINDEXER_PUBLIC_CONFIG);
const mockGetIndexingStatusSnapshot = vi.fn().mockResolvedValue(null);

vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getVersion = mockGetVersion;
getEnsIndexerPublicConfig = mockGetEnsIndexerPublicConfig;
getIndexingStatusSnapshot = mockGetIndexingStatusSnapshot;
},
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

Mock method name getVersion doesn't match actual implementation

The mock defines getVersion (line 69) but the real EnsDbClient exposes getEnsDbVersion(). The EnsDbClientQuery interface is the source of truth here, but the discrepancy can be misleading and won't surface as a type error because the mock class is untyped.

Suggested change
const mockGetVersion = vi.fn().mockResolvedValue("1.0.0");
const mockGetEnsIndexerPublicConfig = vi.fn().mockResolvedValue(ENSINDEXER_PUBLIC_CONFIG);
const mockGetIndexingStatusSnapshot = vi.fn().mockResolvedValue(null);
vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getVersion = mockGetVersion;
getEnsIndexerPublicConfig = mockGetEnsIndexerPublicConfig;
getIndexingStatusSnapshot = mockGetIndexingStatusSnapshot;
},
}));
vi.mock("@/lib/ensdb-client/ensdb-client", () => ({
EnsDbClient: class MockEnsDbClient {
getEnsDbVersion = mockGetVersion;
getEnsIndexerPublicConfig = mockGetEnsIndexerPublicConfig;
getIndexingStatusSnapshot = mockGetIndexingStatusSnapshot;
},
}));

@tk-o tk-o mentioned this pull request Mar 12, 2026
2 tasks
* @param ensIndexerRef reference string for ENSIndexer instance (used for multi-tenancy in ENSDb)
*/
constructor(databaseUrl: string, ensIndexerRef: string) {
this.db = makeReadOnlyDrizzle({
Copy link
Contributor

@vercel vercel bot Mar 12, 2026

Choose a reason for hiding this comment

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

EnsDbClient constructor doesn't pass databaseSchema to makeReadOnlyDrizzle(), causing queries to execute against the wrong schema

Fix on Vercel

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing the name of target database schema is only required while working with ENSIndexer schema, as the schema name is dynamic in this case. Otherwise, we don't have to pass the schema name, as it's bound to the database object definition.

const ensDbClient = buildEnsDbClientFromEnvironment(env);

const ensIndexerPublicConfig = await ensDbClient.getEnsIndexerPublicConfig();

Copy link
Contributor

@vercel vercel bot Mar 12, 2026

Choose a reason for hiding this comment

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

Database connection pool created in buildConfigFromEnvironment() is never closed, causing resource leak

Fix on Vercel

Copy link
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 Lots of issues to fix here.

"ensapi": minor
---

Replaced ENSIndexer Public Config source, from ENSIndexer to ENSDb.
Copy link
Member

Choose a reason for hiding this comment

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

Is it fair to also say that this change fully decouples ENSApi and ENSIndexer?

Comment on lines +17 to +19
# ENSDb: Database Schema name for ENSIndexer Schema
# Required. Should match the DATABASE_SCHEMA used by the connected ENSIndexer.
DATABASE_SCHEMA=public
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# ENSDb: Database Schema name for ENSIndexer Schema
# Required. Should match the DATABASE_SCHEMA used by the connected ENSIndexer.
DATABASE_SCHEMA=public
# Schema name for the "ENSIndexer Schema" in ENSDb that ENSApi should read indexed data from.
# Required. Should match the ENSINDEXER_SCHEMA environment variable in ENSIndexer that ENSApi should read indexed data from.
ENSINDEXER_SCHEMA=public

Comment on lines +17 to +19
# ENSDb: Database Schema name for ENSIndexer Schema
# Required. Should match the DATABASE_SCHEMA used by the connected ENSIndexer.
DATABASE_SCHEMA=public
Copy link
Member

Choose a reason for hiding this comment

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

Why are we promoting the use of public as the default value for the ENSINDEXER_SCHEMA? This will create more pain for devs.

Better to name the default something like ensindexer_0. That both makes it more intuitive how it's from ENSIndexer and it also plants a seed in people's minds how they can just increment the number if they want to build a new ENSIndexer schema.

Please think more about the mental model of devs who are new to ENSNode: they have a big learning curve and we should be doing everything we can to help them.

Please apply this idea everywhere across our monorepo.


const BASE_ENV = {
DATABASE_URL: "postgresql://user:password@localhost:5432/mydb",
DATABASE_SCHEMA: "public",
Copy link
Member

Choose a reason for hiding this comment

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

Please see related comment, even for test files.

@@ -116,6 +138,7 @@ describe("buildConfigFromEnvironment", () => {

const TEST_ENV: EnsApiEnvironment = {
DATABASE_URL: BASE_ENV.DATABASE_URL,
Copy link
Member

Choose a reason for hiding this comment

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

Please create a follow-up issue to rename DATABASE_URL to ENSDB_URL everywhere.

We should be maximally aligning our terminology everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

*/
export type EnsApiEnvironment = Omit<DatabaseEnvironment, "DATABASE_SCHEMA"> &
export type EnsApiEnvironment = DatabaseEnvironment &
EnsIndexerUrlEnvironment &
Copy link
Member

Choose a reason for hiding this comment

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

Remove this now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not yet, but will be removed in PR #1716.

@@ -85,16 +97,13 @@ export type EnsApiConfig = z.infer<typeof EnsApiConfigSchema>;
*/
export async function buildConfigFromEnvironment(env: EnsApiEnvironment): Promise<EnsApiConfig> {
Copy link
Member

Choose a reason for hiding this comment

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

This needs refactoring / renaming.

It's not appropriate to call this function buildConfigFromEnvironment.

Copy link
Contributor Author

@tk-o tk-o Mar 13, 2026

Choose a reason for hiding this comment

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

@lightwalker-eth this week, we discussed how managing configuration objects in ENSApi has to be changed.

I suggest we address the refactoring buildConfigFromEnvironment function in PR #1716. The PR 1716 needs PRs 1752, 1753, and 1754 to be merged first.

);
},
});
const ensDbClient = buildEnsDbClientFromEnvironment(env);
Copy link
Member

Choose a reason for hiding this comment

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

In my mind, there should only ever be a single ensDbClient ever created in ENSApi.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PR #1716 wil address required refactoring of how ENSApi configuration is applied. For now, we need to create ensDbClient to fetch EnsIndexerPublicConfig. The target state that PR 1716 will introduce is that there will be no need to call ensDbClient when building configuration from environment.

* @returns instance of {@link EnsDbClient}
* @throws Error with formatted validation messages if environment parsing fails
*/
function buildEnsDbClientFromEnvironment(env: EnsApiEnvironment): EnsDbClient {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this function in this file? This seems like bad code organization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can move it to a separate file, but it's also a temporary measure that will be resolved with PR #1716, when there will be no need to call ensDbClient when building configuration from environment.

Copy link
Contributor Author

@tk-o tk-o Mar 13, 2026

Choose a reason for hiding this comment

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

Here, I'm just updating this bit, where ENSIndexer client configuration was parsed from env, and then ENSIndexer client object would be created from it.

If you like, I can replace buildEnsDbClientFromEnvironment function with inline code, just as the original.

const ensIndexerUrl = EnsIndexerUrlSchema.parse(env.ENSINDEXER_URL);
const ensIndexerPublicConfig = await pRetry(() => fetchENSIndexerConfig(ensIndexerUrl), {
retries: 3,
onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
logger.info(
`ENSIndexer Config fetch attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
);
},
});

@lightwalker-eth advice appreciated.

* - ENSIndexer Public Config,
* - Indexing Status Snapshot.
*/
export class EnsDbClient implements EnsDbClientQuery {
Copy link
Member

Choose a reason for hiding this comment

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

We're getting so many ideas badly mixed up. This is taking way too much of my time to identify all the problems and suggest solutions. It's very inappropriate to push all of these problems on me.

Issues here include how you're mixing up the idea of ENSDb with the ENSNode Schema in ENSDb.

@tk-o tk-o marked this pull request as draft March 13, 2026 17:31
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