diff --git a/change/@apibara-beaconchain-7b39a030-0724-44c0-8cec-7300e5efa49b.json b/change/@apibara-beaconchain-7b39a030-0724-44c0-8cec-7300e5efa49b.json new file mode 100644 index 00000000..56c9f2fc --- /dev/null +++ b/change/@apibara-beaconchain-7b39a030-0724-44c0-8cec-7300e5efa49b.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugins: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/beaconchain", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/change/@apibara-evm-c96dd7a5-d16a-44aa-ac3a-947a9b904517.json b/change/@apibara-evm-c96dd7a5-d16a-44aa-ac3a-947a9b904517.json new file mode 100644 index 00000000..f2130ec2 --- /dev/null +++ b/change/@apibara-evm-c96dd7a5-d16a-44aa-ac3a-947a9b904517.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugins: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/evm", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/change/@apibara-plugin-drizzle-c4aaf5bc-24c6-4883-9b29-cf614279c5db.json b/change/@apibara-plugin-drizzle-c4aaf5bc-24c6-4883-9b29-cf614279c5db.json new file mode 100644 index 00000000..e7a10899 --- /dev/null +++ b/change/@apibara-plugin-drizzle-c4aaf5bc-24c6-4883-9b29-cf614279c5db.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugin-drizzle: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/plugin-drizzle", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/change/@apibara-plugin-mongo-ddb9c304-419b-4291-bb27-6c198e0501cd.json b/change/@apibara-plugin-mongo-ddb9c304-419b-4291-bb27-6c198e0501cd.json new file mode 100644 index 00000000..b9a7c820 --- /dev/null +++ b/change/@apibara-plugin-mongo-ddb9c304-419b-4291-bb27-6c198e0501cd.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugins: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/plugin-mongo", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/change/@apibara-plugin-sqlite-ab552e08-0770-4528-b9a9-f1ffb9e0105f.json b/change/@apibara-plugin-sqlite-ab552e08-0770-4528-b9a9-f1ffb9e0105f.json new file mode 100644 index 00000000..99965cd8 --- /dev/null +++ b/change/@apibara-plugin-sqlite-ab552e08-0770-4528-b9a9-f1ffb9e0105f.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugins: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/plugin-sqlite", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/change/@apibara-protocol-c3b43226-48fe-4e9f-abe7-760ca8e09b82.json b/change/@apibara-protocol-c3b43226-48fe-4e9f-abe7-760ca8e09b82.json new file mode 100644 index 00000000..4056d7bd --- /dev/null +++ b/change/@apibara-protocol-c3b43226-48fe-4e9f-abe7-760ca8e09b82.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "plugins: fix issue caused by cursors with empty unique key", + "packageName": "@apibara/protocol", + "email": "francesco@ceccon.me", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index 32e7caa7..87f4dce5 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,10 @@ "packages/evm", "packages/indexer", "packages/protocol", - "packages/starknet" + "packages/starknet", + "packages/plugin-drizzle", + "packages/plugin-mongo", + "packages/plugin-sqlite" ] } ], diff --git a/packages/beaconchain/package.json b/packages/beaconchain/package.json index 78799b62..e22e6d6a 100644 --- a/packages/beaconchain/package.json +++ b/packages/beaconchain/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/beaconchain", - "version": "2.0.1-beta.31", + "version": "2.0.1-beta.39", "type": "module", "files": [ "dist", diff --git a/packages/evm/package.json b/packages/evm/package.json index 172c63ab..bd710518 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/evm", - "version": "2.0.1-beta.31", + "version": "2.0.1-beta.39", "type": "module", "files": [ "dist", diff --git a/packages/plugin-drizzle/package.json b/packages/plugin-drizzle/package.json index 73445745..eb4e5500 100644 --- a/packages/plugin-drizzle/package.json +++ b/packages/plugin-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/plugin-drizzle", - "version": "2.0.0-beta.38", + "version": "2.0.0-beta.39", "type": "module", "files": [ "dist", diff --git a/packages/plugin-drizzle/src/persistence.ts b/packages/plugin-drizzle/src/persistence.ts index 2a88c1a2..8e1fddb8 100644 --- a/packages/plugin-drizzle/src/persistence.ts +++ b/packages/plugin-drizzle/src/persistence.ts @@ -1,4 +1,4 @@ -import type { Cursor } from "@apibara/protocol"; +import { type Cursor, normalizeCursor } from "@apibara/protocol"; import { and, eq, gt, isNull, lt } from "drizzle-orm"; import type { ExtractTablesWithRelations, @@ -15,10 +15,7 @@ const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version"; export const checkpoints = pgTable(CHECKPOINTS_TABLE_NAME, { id: text("id").notNull().primaryKey(), orderKey: integer("order_key").notNull(), - uniqueKey: text("unique_key") - .$type<`0x${string}` | undefined>() - .notNull() - .default(undefined), + uniqueKey: text("unique_key"), }); export const filters = pgTable( @@ -87,7 +84,7 @@ export async function initializePersistentState< CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} ( id TEXT PRIMARY KEY, order_key INTEGER NOT NULL, - unique_key TEXT NOT NULL DEFAULT '' + unique_key TEXT ); `); @@ -212,10 +209,10 @@ export async function getState< .where(eq(checkpoints.id, indexerId)); const cursor = checkpointRows[0] - ? { + ? normalizeCursor({ orderKey: BigInt(checkpointRows[0].orderKey), uniqueKey: checkpointRows[0].uniqueKey, - } + }) : undefined; const filterRows = await tx diff --git a/packages/plugin-drizzle/tests/storage.test.ts b/packages/plugin-drizzle/tests/storage.test.ts index 956aadf9..2b8ab56f 100644 --- a/packages/plugin-drizzle/tests/storage.test.ts +++ b/packages/plugin-drizzle/tests/storage.test.ts @@ -680,7 +680,7 @@ describe("Drizzle test", () => { { "id": "indexer_testing_default", "orderKey": 5000005, - "uniqueKey": "", + "uniqueKey": null, }, ] `); @@ -846,7 +846,7 @@ describe("Drizzle test", () => { { "id": "indexer_testing_default", "orderKey": 108, - "uniqueKey": "", + "uniqueKey": null, }, ] `); @@ -1030,7 +1030,7 @@ describe("Drizzle test", () => { { "id": "indexer_testing_default", "orderKey": 107, - "uniqueKey": "", + "uniqueKey": null, }, ] `); @@ -1206,7 +1206,7 @@ describe("Drizzle test", () => { { "id": "indexer_testing_default", "orderKey": 107, - "uniqueKey": "", + "uniqueKey": null, }, ] `); diff --git a/packages/plugin-mongo/src/persistence.ts b/packages/plugin-mongo/src/persistence.ts index c632fc63..d0b25ac4 100644 --- a/packages/plugin-mongo/src/persistence.ts +++ b/packages/plugin-mongo/src/persistence.ts @@ -1,10 +1,10 @@ -import type { Cursor } from "@apibara/protocol"; +import { type Cursor, normalizeCursor } from "@apibara/protocol"; import type { ClientSession, Db } from "mongodb"; export type CheckpointSchema = { id: string; orderKey: number; - uniqueKey?: `0x${string}`; + uniqueKey: string | null; }; export type FilterSchema = { @@ -98,10 +98,10 @@ export async function getState(props: { .findOne({ id: indexerId }, { session }); if (checkpointRow) { - cursor = { + cursor = normalizeCursor({ orderKey: BigInt(checkpointRow.orderKey), uniqueKey: checkpointRow.uniqueKey, - }; + }); } const filterRow = await db diff --git a/packages/plugin-mongo/tests/storage.test.ts b/packages/plugin-mongo/tests/storage.test.ts index d05c5b3c..7ef79230 100644 --- a/packages/plugin-mongo/tests/storage.test.ts +++ b/packages/plugin-mongo/tests/storage.test.ts @@ -427,7 +427,6 @@ describe("MongoDB Test", () => { { "cursor": { "orderKey": 5000005n, - "uniqueKey": null, }, "filter": undefined, } diff --git a/packages/plugin-sqlite/package.json b/packages/plugin-sqlite/package.json index 0f15a45d..3826a12f 100644 --- a/packages/plugin-sqlite/package.json +++ b/packages/plugin-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/plugin-sqlite", - "version": "2.0.0-beta.38", + "version": "2.0.0-beta.39", "type": "module", "files": [ "dist", diff --git a/packages/plugin-sqlite/src/persistence.ts b/packages/plugin-sqlite/src/persistence.ts index fd191bb3..37e3736b 100644 --- a/packages/plugin-sqlite/src/persistence.ts +++ b/packages/plugin-sqlite/src/persistence.ts @@ -1,4 +1,4 @@ -import type { Cursor } from "@apibara/protocol"; +import { type Cursor, normalizeCursor } from "@apibara/protocol"; import type { Database } from "better-sqlite3"; import { assertInTransaction, deserialize, serialize } from "./utils"; @@ -57,10 +57,10 @@ export function getState(props: { let filter: TFilter | undefined; if (storedCursor?.order_key) { - cursor = { + cursor = normalizeCursor({ orderKey: BigInt(storedCursor.order_key), - uniqueKey: storedCursor.unique_key as `0x${string}`, - }; + uniqueKey: storedCursor.unique_key ? storedCursor.unique_key : null, + }); } if (storedFilter) { diff --git a/packages/protocol/src/common.ts b/packages/protocol/src/common.ts index fceb35b5..c76c2046 100644 --- a/packages/protocol/src/common.ts +++ b/packages/protocol/src/common.ts @@ -67,3 +67,27 @@ export const cursorFromBytes = Schema.decodeSync(CursorFromBytes); export function isCursor(value: unknown): value is Cursor { return Schema.is(Cursor)(value); } + +/** Normalize a cursor. + * + * The challenge is that the `Cursor` validator expects `uniqueKey` to be either a `0x${string}` + * or not present at all. Setting the field to `undefined` will result in a validation error. + * + * @param cursor The cursor to normalize + */ +export function normalizeCursor(cursor: { + orderKey: bigint; + uniqueKey: string | null; +}): Cursor { + if (cursor.uniqueKey !== null && cursor.uniqueKey.length > 0) { + const uniqueKey = cursor.uniqueKey as `0x${string}`; + return { + orderKey: BigInt(cursor.orderKey), + uniqueKey, + }; + } + + return { + orderKey: BigInt(cursor.orderKey), + }; +}