Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6acbea0
Create three database schemas for ENSDb
tk-o Mar 12, 2026
eed4800
Update import paths
tk-o Mar 12, 2026
16c5584
Enforce read-only connection with ENSDb for ENSApi runtime
tk-o Mar 12, 2026
92afddf
Update `EnsDbClient` for ENSIndexer to handle updated DB schema defin…
tk-o Mar 12, 2026
b5bd5ca
Replace Ponder Schema definitions with link to Ponder repository.
tk-o Mar 12, 2026
3f7f0e8
Introduce docs for the schema package
tk-o Mar 12, 2026
c1964a2
Apply AI PR feedback
tk-o Mar 12, 2026
b4fb135
docs(changeset): Split database schemas into Ponder Schema, ENSIndexe…
tk-o Mar 12, 2026
6b6f805
docs(changeset): Updated ENSDb connections to be always read-only.
tk-o Mar 12, 2026
9c05883
Update make drizzle helpers to only apply schema "monkeypatching" whe…
tk-o Mar 12, 2026
f13b6af
Remove db write workaround
tk-o Mar 12, 2026
1791217
Update ENSIndexer Schema references to be explicit
tk-o Mar 12, 2026
03fecc1
Merge remote-tracking branch 'origin/main' into refactor/ensdb-schema
tk-o Mar 12, 2026
e300888
Create a read-only `EnsDbClient` for ENSApi to handle updated DB sche…
tk-o Mar 12, 2026
a1656f6
Build ENSApi Config with ENSDb Cleint
tk-o Mar 12, 2026
d53db6d
Update example env file
tk-o Mar 12, 2026
7ec0fb8
Update config builder test
tk-o Mar 12, 2026
8a056f2
Apply terraform script updates
tk-o Mar 12, 2026
38f30d8
docs(changeset): Replaced ENSIndexer Public Config source, from ENSIn…
tk-o Mar 12, 2026
49c7deb
Setup drizzle-kit migrations
tk-o Mar 12, 2026
0c65914
Make ENSDb Writer Worker to execute database migrations
tk-o Mar 12, 2026
3a4ebe6
Update tests and mocks for EnsDbWriterWorker
tk-o Mar 12, 2026
b94f93d
Scope `drizzle-kit` as dev dependency to ENSIndexer
tk-o Mar 12, 2026
c5ef04d
docs(changeset): Introduced database migration toolkit based on `driz…
tk-o Mar 12, 2026
20af541
docs(changeset): Extended `EnsDbClient` with `EnsDbMigration` interfa…
tk-o Mar 12, 2026
b235706
docs(changeset): Introduced `EnsDbMigration` interface.
tk-o Mar 12, 2026
a6ea0b9
Update integration tests config
tk-o Mar 12, 2026
a9b1d39
Add unit tests for migration exec task
tk-o Mar 12, 2026
c07ac36
Streamline interface name as `EnsDbClientMigration`
tk-o Mar 12, 2026
1767c4e
Use ENSNode Schema for internal drizzle migration tables
tk-o Mar 12, 2026
1252713
Revert "Build ENSApi Config with ENSDb Cleint" in order to reduce sco…
tk-o Mar 13, 2026
069f133
Move ENSDb module contents from ENSNode SDK into ENSNode Schema packa…
tk-o Mar 13, 2026
867d643
Create Drizzle utils for future ENSDb SDK
tk-o Mar 13, 2026
0d2ee64
Create `EnsNodeDbReader` class to be shared among ENSNode apps.
tk-o Mar 13, 2026
6b8c42f
Create `EnsNodeDbWriter` to be shared with ENSIndexer app (which is c…
tk-o Mar 13, 2026
b803f50
Improve naming and code docs for ENSIndexer Schema defintions
tk-o Mar 13, 2026
d08743c
Use `EnsNodeDbWriter` in ENSIndexer app
tk-o Mar 13, 2026
2d760ee
Create `EnsIndexerDbReader` class to be used among ENSNode apps for u…
tk-o Mar 13, 2026
6c8b6d7
Revert "Update example env file"
tk-o Mar 13, 2026
1be0947
Revert "Update config builder test"
tk-o Mar 13, 2026
7705910
Update EnsNodeDb migrations to match the updated schema
tk-o Mar 13, 2026
16e5660
Integrate `EnsNodeDbWriter` into ENSIndexer app
tk-o Mar 13, 2026
d53f0bf
Update tests and mocks following `EnsDbWriterWorker` integration
tk-o Mar 13, 2026
5297c9c
Remove unused code
tk-o Mar 13, 2026
12359f8
Integrate `EnsIndexerDbReader` into ENSApi`
tk-o Mar 13, 2026
f35edb0
Fix exports for ENSDb Schema definitions
tk-o Mar 13, 2026
79a176d
Revert "Update integration tests config"
tk-o Mar 13, 2026
b4b5b0b
Revert "Apply terraform script updates"
tk-o Mar 13, 2026
24f66e7
Merge remote-tracking branch 'origin/main' into feat/ensdb-migrations
tk-o Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/big-impalas-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
---

Extended `EnsDbClient` with `EnsDbClientMigration` interface implementation.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Release note text appears outdated relative to the migration direction.
This sentence describes extending EnsDbClient, while this PR’s stated direction is moving to ENSNode reader/writer abstractions. Please update the note so release consumers get an accurate migration message.

✏️ Suggested wording
- Extended `EnsDbClient` with `EnsDbClientMigration` interface implementation.
+ Added ENSDb migration execution support in ENSIndexer via the new ENSNode DB migration tooling.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Extended `EnsDbClient` with `EnsDbClientMigration` interface implementation.
---
ensindexer: minor
---
Added ENSDb migration execution support in ENSIndexer via the new ENSNode DB migration tooling.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

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

In @.changeset/big-impalas-brush.md at line 5, Update the release note sentence
so it reflects the actual migration direction (moving to ENSNode reader/writer
abstractions) rather than saying "Extended `EnsDbClient` with
`EnsDbClientMigration` interface implementation"; reword to state that the
codebase is migrating away from `EnsDbClient` toward the new `ENSNode`
reader/writer abstractions (or that `EnsDbClientMigration` facilitates migration
to `ENSNode` reader/writer), ensuring `EnsDbClient`, `EnsDbClientMigration`, and
`ENSNode` are mentioned to make the migration intent clear.

5 changes: 5 additions & 0 deletions .changeset/humble-pets-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensapi": minor
---

Replaced ENSIndexer Public Config source, from ENSIndexer to ENSDb.
5 changes: 5 additions & 0 deletions .changeset/petite-peaches-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-schema": minor
---

Split database schemas into Ponder Schema, ENSIndexer Schema, and ENSNode Schema.
5 changes: 5 additions & 0 deletions .changeset/seven-hands-ask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": minor
---

Introduced `EnsDbClientMigration` interface.
5 changes: 5 additions & 0 deletions .changeset/silly-bats-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
---

Introduced database migration toolkit based on `drizzle-kit`.
5 changes: 5 additions & 0 deletions .changeset/vast-comics-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensapi": minor
---

Updated ENSDb connections to be always read-only.
25 changes: 18 additions & 7 deletions apps/ensapi/src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import config from "@/config";

import * as schema from "@ensnode/ensnode-schema";
import { EnsIndexerDbReader } from "@ensnode/ensnode-schema";

import { makeDrizzle } from "@/lib/handlers/drizzle";
// TODO: pending rename `config.databaseUrl` to `config.ensDbUrl`
// Will be executed once https://github.com/namehash/ensnode/issues/1763 is resolved.
const ensDbUrl = config.databaseUrl;
// TODO: pending rename `config.databaseSchemaName` to `config.ensIndexerSchemaName`
// Will be executed once https://github.com/namehash/ensnode/issues/1762 is resolved.
const ensIndexerSchemaName = config.databaseSchemaName;

export const db = makeDrizzle({
databaseUrl: config.databaseUrl,
databaseSchema: config.databaseSchemaName,
schema,
});
const ensIndexerDbReader = new EnsIndexerDbReader(ensDbUrl, ensIndexerSchemaName);

/**
* Read-only Drizzle instance for queries to ENSIndexer Schema in ENSDb.
*/
export const ensIndexerDbReadonly = ensIndexerDbReader.db;
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
export const ensIndexerDbReadonly = ensIndexerDbReader.db;
export const ensIndexerDb = ensIndexerDbReader.db;

I don't think it's necessary to include the detail that this object is readonly in the variable name. This attribute can simply be noted in the JSDoc for it (which you've already done).


/**
* Read-only Drizzle instance for queries to ENSIndexer Schema in ENSDb.
*/
export const db = ensIndexerDbReadonly;
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.

Suggest to remove this (db) and for all code that currently imports db to instead import ensIndexerDb (see related comment above).

Goal: remove ambiguity from code which schema each database operation is executing against.

24 changes: 18 additions & 6 deletions apps/ensapi/src/lib/handlers/drizzle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { setDatabaseSchema } from "@ponder/client";
import { drizzle } from "drizzle-orm/node-postgres";
import { parseIntoClientConfig } from "pg-connection-string";

import { makeLogger } from "@/lib/logger";

Expand All @@ -8,21 +9,32 @@ type Schema = { [name: string]: unknown };
const logger = makeLogger("drizzle");

/**
* Makes a Drizzle DB object.
* Makes a read-only Drizzle DB object.
*/
export const makeDrizzle = <SCHEMA extends Schema>({
export const makeReadOnlyDrizzle = <SCHEMA extends Schema>({
schema,
databaseUrl,
databaseSchema,
}: {
schema: SCHEMA;
databaseUrl: string;
databaseSchema: string;
databaseSchema?: string;
}) => {
// monkeypatch schema onto tables
setDatabaseSchema(schema, databaseSchema);
// monkeypatch schema onto tables if databaseSchema is provided
if (databaseSchema) {
setDatabaseSchema(schema, databaseSchema);
}

return drizzle(databaseUrl, {
const parsedConfig = parseIntoClientConfig(databaseUrl);
const existingOptions = parsedConfig.options || "";
const readOnlyOption = "-c default_transaction_read_only=on";

return drizzle({
connection: {
...parsedConfig,
// Combine existing options from URL with read-only requirement
options: existingOptions ? `${existingOptions} ${readOnlyOption}` : readOnlyOption,
},
schema,
casing: "snake_case",
logger: {
Expand Down
13 changes: 13 additions & 0 deletions apps/ensindexer/drizzle-kit/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { fileURLToPath } from "node:url";

import { defineConfig } from "drizzle-kit";

// Resolve the path to the database schema file for Drizzle migrations.
const dbSchemaPath = fileURLToPath(new URL("./schema.ts", import.meta.url));

export default defineConfig({
casing: "snake_case",
dialect: "postgresql",
out: `drizzle-kit/migrations`,
schema: dbSchemaPath,
});
Comment on lines +8 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using an absolute path for the out directory.

The out: "drizzle-kit/migrations" path is relative and may resolve differently depending on the working directory when drizzle-kit is invoked. For consistency, consider using fileURLToPath to construct an absolute path similar to how dbSchemaPath is resolved.

♻️ Suggested improvement for consistent path resolution
+const migrationsOutPath = fileURLToPath(new URL("./migrations", import.meta.url));
+
 export default defineConfig({
   casing: "snake_case",
   dialect: "postgresql",
-  out: `drizzle-kit/migrations`,
+  out: migrationsOutPath,
   schema: dbSchemaPath,
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/drizzle-kit/config.ts` around lines 8 - 13, The `out` option
in defineConfig is using a relative path which can vary by CWD; change the `out:
"drizzle-kit/migrations"` to an absolute path constructed with fileURLToPath +
new URL relative to import.meta.url (same approach used for dbSchemaPath) so the
migrations directory is resolved consistently; update the defineConfig call
(symbol: defineConfig) to pass that absolute path for the out property and
ensure fileURLToPath and URL usage are imported/available in the module.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE SCHEMA IF NOT EXISTS ensnode;

CREATE TABLE "ensnode"."ensnode_metadata" (
"ens_indexer_schema_name" text NOT NULL,
"key" text NOT NULL,
"value" jsonb NOT NULL,
CONSTRAINT "ensnode_metadata_pkey" PRIMARY KEY("ens_indexer_schema_name","key")
);
58 changes: 58 additions & 0 deletions apps/ensindexer/drizzle-kit/migrations/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"id": "033e8b27-4739-4da9-b9da-517b0c2700d7",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"ensnode.ensnode_metadata": {
"name": "ensnode_metadata",
"schema": "ensnode",
"columns": {
"ens_indexer_schema_name": {
"name": "ens_indexer_schema_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "jsonb",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"ensnode_metadata_pkey": {
"name": "ensnode_metadata_pkey",
"columns": [
"ens_indexer_schema_name",
"key"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
13 changes: 13 additions & 0 deletions apps/ensindexer/drizzle-kit/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1773421837301,
"tag": "0000_certain_slyde",
"breakpoints": true
}
]
}
2 changes: 2 additions & 0 deletions apps/ensindexer/drizzle-kit/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Re-export the ENSNode schema for Drizzle migrations.
export * from "@ensnode/ensnode-schema/ensnode";
4 changes: 3 additions & 1 deletion apps/ensindexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"test": "vitest",
"lint": "biome check --write .",
"lint:ci": "biome ci",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"drizzle-gen": "drizzle-kit generate --config ./drizzle-kit/config.ts"
},
"dependencies": {
"@ensdomains/ensjs": "^4.0.2",
Expand All @@ -47,6 +48,7 @@
"@types/dns-packet": "^5.6.5",
"@types/node": "catalog:",
"@types/pg": "8.16.0",
"drizzle-kit": "0.31.9",
"typescript": "catalog:",
"vitest": "catalog:"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/ensindexer/ponder/ponder.schema.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// export the shared ponder schema
export * from "@ensnode/ensnode-schema";
// export the ENSIndexer schema for Ponder to use when indexing data
export * from "@ensnode/ensnode-schema/ensindexer";
6 changes: 3 additions & 3 deletions apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
serializeIndexingStatusResponse,
} from "@ensnode/ensnode-sdk";

import { ensDbClient } from "@/lib/ensdb-client/singleton";
import { ensNodeDbWriter } from "@/lib/ensdb-client/singleton";

const app = new Hono();

// include ENSIndexer Public Config endpoint
app.get("/config", async (c) => {
const publicConfig = await ensDbClient.getEnsIndexerPublicConfig();
const publicConfig = await ensNodeDbWriter.getEnsIndexerPublicConfig();

// Invariant: the public config is guaranteed to be available in ENSDb after
// application startup.
Expand All @@ -30,7 +30,7 @@ app.get("/config", async (c) => {

app.get("/indexing-status", async (c) => {
try {
const crossChainSnapshot = await ensDbClient.getIndexingStatusSnapshot();
const crossChainSnapshot = await ensNodeDbWriter.getIndexingStatusSnapshot();

// Invariant: the Indexing Status Snapshot is expected to be available in
// ENSDb shortly after application startup. There is a possibility that
Expand Down
26 changes: 0 additions & 26 deletions apps/ensindexer/src/lib/ensdb-client/drizzle.ts

This file was deleted.

72 changes: 0 additions & 72 deletions apps/ensindexer/src/lib/ensdb-client/ensdb-client.mock.ts

This file was deleted.

Loading
Loading