-
Notifications
You must be signed in to change notification settings - Fork 16
Fix ENSDb SDK builder for "concrete" ENSIndexer Schema #1832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1661f1c
7f4b83b
7b8c1a7
e9d496a
eac1297
13f969a
b45dea9
f18bd34
a9a08f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@ensnode/ensdb-sdk": minor | ||
| --- | ||
|
|
||
| Hotfixed the `buildConcreteEnsIndexerSchema` function by replacing the cloning approach with working mutation approach. |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,35 +21,16 @@ import * as ensNodeSchema from "../ensnode"; | |
| */ | ||
| export type AbstractEnsIndexerSchema = typeof abstractEnsIndexerSchema; | ||
|
|
||
| // TODO: remove the `appliedNameForConcreteEnsIndexerSchema` variable and | ||
| // related logic when the `buildConcreteEnsIndexerSchema` function is | ||
| // refactored to avoid mutating the "abstract" ENSIndexer Schema definition. | ||
| /** | ||
| * Clone a Drizzle Table object with a new schema name. | ||
| * Applied name for the "concrete" ENSIndexer Schema. | ||
| * | ||
| * Drizzle tables store their identity (name, columns, schema) on | ||
| * Symbol-keyed properties. Cloning a table requires creating | ||
| * a new object with the same prototype, copying all properties, | ||
| * and updating the schema name. | ||
| * This is needed to prevent multiple calls to `buildConcreteEnsIndexerSchema` with different schema names, | ||
| * which would mutate the same "abstract" ENSIndexer Schema and cause schema corruption. | ||
| */ | ||
| function cloneTableWithSchema<TableType extends Table>( | ||
| table: TableType, | ||
| schemaName: string, | ||
| ): TableType { | ||
| const clone = Object.create( | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Object.getPrototypeOf(table), | ||
| Object.getOwnPropertyDescriptors(table), | ||
| ) as TableType; | ||
|
|
||
| // @ts-expect-error - Drizzle's Table type for the schema symbol is | ||
| // not typed in a way that allows us to set it directly, | ||
| // but we know it exists and can be set. | ||
| clone[Table.Symbol.Schema] = schemaName; | ||
|
|
||
| // Fail-fast if the clone lost the Drizzle sentinel. | ||
| if (!isTable(clone)) { | ||
| throw new Error(`Cloned table is no longer a valid Drizzle Table (schema: ${schemaName}).`); | ||
| } | ||
|
|
||
| return clone; | ||
| } | ||
| let appliedNameForConcreteEnsIndexerSchema: string | undefined; | ||
|
|
||
| /** | ||
| * Build a "concrete" ENSIndexer Schema definition for ENSDb. | ||
|
|
@@ -66,34 +47,39 @@ function cloneTableWithSchema<TableType extends Table>( | |
| function buildConcreteEnsIndexerSchema<ConcreteEnsIndexerSchema extends AbstractEnsIndexerSchema>( | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ensIndexerSchemaName: string, | ||
| ): ConcreteEnsIndexerSchema { | ||
| const ensIndexerSchema = {} as ConcreteEnsIndexerSchema; | ||
|
|
||
| for (const [key, abstractSchemaObject] of Object.entries(abstractEnsIndexerSchema)) { | ||
| if (isTable(abstractSchemaObject)) { | ||
| (ensIndexerSchema as any)[key] = cloneTableWithSchema( | ||
| abstractSchemaObject, | ||
| ensIndexerSchemaName, | ||
| ); | ||
| } else if (isPgEnum(abstractSchemaObject)) { | ||
| // Enums are functions; clone by copying properties onto a new function. | ||
| // Unlike tables, enums don't rely on prototype identity, so | ||
| // Object.assign is sufficient here. | ||
| const concreteSchemaObject = Object.assign( | ||
| (...args: any[]) => abstractSchemaObject(...args), | ||
| abstractSchemaObject, | ||
| ); | ||
| // @ts-expect-error - Drizzle's PgEnum type for the schema symbol is | ||
| // typed as readonly, but we need to set it here so | ||
| // the output schema definition has the correct schema for | ||
| // all table and enum objects. | ||
| concreteSchemaObject.schema = ensIndexerSchemaName; | ||
| (ensIndexerSchema as any)[key] = concreteSchemaObject; | ||
| } else { | ||
| (ensIndexerSchema as any)[key] = abstractSchemaObject; | ||
| // TODO: Refactor this function to avoid mutating the "abstract" ENSIndexer Schema definition. | ||
| // https://github.com/namehash/ensnode/issues/1830 | ||
|
|
||
| if ( | ||
| appliedNameForConcreteEnsIndexerSchema !== undefined && | ||
| appliedNameForConcreteEnsIndexerSchema !== ensIndexerSchemaName | ||
| ) { | ||
| throw new Error( | ||
| `buildConcreteEnsIndexerSchema was already called with schema "${appliedNameForConcreteEnsIndexerSchema}". ` + | ||
| `Calling it again with "${ensIndexerSchemaName}" would corrupt the previously built schema.`, | ||
| ); | ||
|
Comment on lines
+53
to
+60
|
||
| } | ||
| appliedNameForConcreteEnsIndexerSchema = ensIndexerSchemaName; | ||
|
|
||
| const concreteEnsIndexerSchema = abstractEnsIndexerSchema as ConcreteEnsIndexerSchema; | ||
|
|
||
| for (const dbObject of Object.values(abstractEnsIndexerSchema)) { | ||
| if (isTable(dbObject)) { | ||
| // Update Drizzle table definition to reference | ||
| // the specific `ensIndexerSchemaName` name of the ENSIndexer Schema. | ||
| // @ts-expect-error - Drizzle types don't define `Table.Symbol.Schema` type, | ||
| // but it's present at runtime. | ||
| dbObject[Table.Symbol.Schema] = ensIndexerSchemaName; | ||
| } else if (isPgEnum(dbObject)) { | ||
| // Update Drizzle enum definition to reference | ||
| // the specific `ensIndexerSchemaName` name of the ENSIndexer Schema. | ||
| // @ts-expect-error - Drizzle types consider `schema` to be | ||
| // a readonly property. | ||
| dbObject.schema = ensIndexerSchemaName; | ||
lightwalker-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| return ensIndexerSchema; | ||
| return concreteEnsIndexerSchema; | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -189,35 +189,41 @@ async function pollIndexingStatus( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ensIndexerSchemaName: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeoutMs: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const client = new (await import("@ensnode/ensdb-sdk")).EnsDbReader( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ensDbUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ensIndexerSchemaName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { EnsDbReader } = await import("@ensnode/ensdb-sdk"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ensDbClient = new EnsDbReader(ensDbUrl, ensIndexerSchemaName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const start = Date.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log("Polling indexing status..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (Date.now() - start < timeoutMs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkAborted(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const snapshot = await client.getIndexingStatusSnapshot(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (snapshot !== undefined) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const omnichainStatus = snapshot.omnichainSnapshot.omnichainStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log(`Omnichain status: ${omnichainStatus}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| omnichainStatus === OmnichainIndexingStatusIds.Following || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| omnichainStatus === OmnichainIndexingStatusIds.Completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log("Indexing reached target status"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (Date.now() - start < timeoutMs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkAborted(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const snapshot = await ensDbClient.getIndexingStatusSnapshot(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (snapshot !== undefined) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const omnichainStatus = snapshot.omnichainSnapshot.omnichainStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log(`Omnichain status: ${omnichainStatus}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| omnichainStatus === OmnichainIndexingStatusIds.Following || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| omnichainStatus === OmnichainIndexingStatusIds.Completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log("Indexing reached target status"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // indexer may not be ready yet | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // indexer may not be ready yet | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await new Promise((r) => setTimeout(r, 3000)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await new Promise((r) => setTimeout(r, 3000)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`Indexing did not complete within ${timeoutMs / 1000}s`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Closing ENSDb client..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @ts-expect-error - DrizzleClient.$client is not typed to have an `end` method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // but in practice it does (e.g. pg's Client does). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await ensDbClient.ensDb.$client.end(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
tk-o marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("ENSDb client closed"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+222
to
+225
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @ts-expect-error - DrizzleClient.$client is not typed to have an `end` method, | |
| // but in practice it does (e.g. pg's Client does). | |
| await ensDbClient.ensDb.$client.end(); | |
| console.log("ENSDb client closed"); | |
| const anyClient = ensDbClient as any; | |
| try { | |
| const rawEnsDb = anyClient?.ensDb; | |
| const rawUnderlyingClient = rawEnsDb?.$client; | |
| const endFn = rawUnderlyingClient?.end; | |
| if (typeof endFn === "function") { | |
| await endFn.call(rawUnderlyingClient); | |
| console.log("ENSDb client closed"); | |
| } else { | |
| console.log("ENSDb client has no 'end' method; skipping close"); | |
| } | |
| } catch (closeErr) { | |
| console.log("Failed to close ENSDb client cleanly:", closeErr); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| console.log("Closing ENSDb client..."); | |
| // @ts-expect-error - DrizzleClient.$client is not typed to have an `end` method, | |
| // but in practice it does (e.g. pg's Client does). | |
| await ensDbClient.ensDb.$client.end(); | |
| console.log("ENSDb client closed"); | |
| log("Closing ENSDb client..."); | |
| // @ts-expect-error - DrizzleClient.$client is not typed to have an `end` method, | |
| // but in practice it does (e.g. pg's Client does). | |
| await ensDbClient.ensDb.$client.end(); | |
| log("ENSDb client closed"); |
Finally block uses console.log() instead of the standard log() function, causing inconsistent logging formatting
Uh oh!
There was an error while loading. Please reload this page.