@apibara/plugin-drizzle 2.1.0-beta.2 → 2.1.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/helper.ts ADDED
@@ -0,0 +1,202 @@
1
+ import { PGlite, type PGliteOptions } from "@electric-sql/pglite";
2
+ import type { DrizzleConfig } from "drizzle-orm";
3
+ import type { MigrationConfig } from "drizzle-orm/migrator";
4
+ import {
5
+ type NodePgDatabase as OriginalNodePgDatabase,
6
+ drizzle as drizzleNode,
7
+ } from "drizzle-orm/node-postgres";
8
+ import { migrate as migrateNode } from "drizzle-orm/node-postgres/migrator";
9
+ import {} from "drizzle-orm/pg-core";
10
+ import {
11
+ type PgliteDatabase as OriginalPgliteDatabase,
12
+ drizzle as drizzlePGLite,
13
+ } from "drizzle-orm/pglite";
14
+ import { migrate as migratePGLite } from "drizzle-orm/pglite/migrator";
15
+ import pg from "pg";
16
+ import { DrizzleStorageError } from "./utils";
17
+
18
+ /**
19
+ * Union type of all possible drizzle database options
20
+ */
21
+ export type DrizzleOptions = PgliteDrizzleOptions | NodePgDrizzleOptions;
22
+
23
+ /**
24
+ * Configuration options for Node-Postgres database connection
25
+ */
26
+ export type NodePgDrizzleOptions = {
27
+ /**
28
+ * Type of database to use -
29
+ * - "pglite" - PGLite database
30
+ * - "node-postgres" - Node-Postgres database
31
+ * @default "pglite"
32
+ */
33
+ type: "node-postgres";
34
+ /**
35
+ * Connection string to use for the database
36
+ * @default ""
37
+ */
38
+ connectionString?: string;
39
+ /**
40
+ * Pool configuration options for Node-Postgres
41
+ */
42
+ poolConfig?: pg.PoolConfig;
43
+ /**
44
+ * Additional drizzle configuration options
45
+ */
46
+ config?: Omit<DrizzleConfig, "schema">;
47
+ };
48
+
49
+ /**
50
+ * Configuration options for PGLite database connection
51
+ */
52
+ export type PgliteDrizzleOptions = {
53
+ /**
54
+ * Type of database to use -
55
+ * - "pglite" - PGLite database
56
+ * - "node-postgres" - Node-Postgres database
57
+ */
58
+ type?: "pglite";
59
+ /**
60
+ * Connection string to use for the database
61
+ * @default process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://pglite"
62
+ */
63
+ connectionString?: string;
64
+ /**
65
+ * Pool configuration is not supported for PGLite
66
+ */
67
+ poolConfig?: never;
68
+ /**
69
+ * Additional drizzle configuration options with PGLite specific connection options
70
+ */
71
+ config?: Omit<DrizzleConfig, "schema"> & {
72
+ connection?:
73
+ | (PGliteOptions & {
74
+ dataDir?: string;
75
+ })
76
+ | string;
77
+ };
78
+ };
79
+
80
+ /**
81
+ * Extended PGLite database type with client information
82
+ */
83
+ export type PgliteDatabase<TSchema extends Record<string, unknown>> =
84
+ OriginalPgliteDatabase<TSchema> & {
85
+ $client: PGlite;
86
+ };
87
+
88
+ /**
89
+ * Extended Node-Postgres database type with client information
90
+ */
91
+ export type NodePgDatabase<TSchema extends Record<string, unknown>> =
92
+ OriginalNodePgDatabase<TSchema> & {
93
+ $client: pg.Pool;
94
+ };
95
+
96
+ export type Database<
97
+ TOptions extends DrizzleOptions,
98
+ TSchema extends Record<string, unknown>,
99
+ > = TOptions extends PgliteDrizzleOptions
100
+ ? PgliteDatabase<TSchema>
101
+ : NodePgDatabase<TSchema>;
102
+
103
+ /**
104
+ * Creates a new Drizzle database instance based on the provided options
105
+ *
106
+ * @important connectionString defaults to process.env["POSTGRES_CONNECTION_STRING"], if not set, it defaults to "memory://" (in-memory pglite)
107
+ *
108
+ * @param options - Configuration options for the database connection
109
+ * @returns A configured Drizzle database instance
110
+ * @throws {Error} If an invalid database type is specified
111
+ */
112
+ export function drizzle<
113
+ TSchema extends Record<string, unknown>,
114
+ TOptions extends DrizzleOptions,
115
+ >(
116
+ options?: TOptions & {
117
+ /**
118
+ * Schema to use for the database
119
+ * @default {}
120
+ */
121
+ schema?: TSchema;
122
+ },
123
+ ): Database<TOptions, TSchema> {
124
+ const {
125
+ connectionString = process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://",
126
+ schema,
127
+ type = "pglite",
128
+ config,
129
+ poolConfig,
130
+ } = options ?? {};
131
+
132
+ if (
133
+ isPostgresConnectionString(connectionString) ||
134
+ type === "node-postgres"
135
+ ) {
136
+ const pool = new pg.Pool({
137
+ connectionString,
138
+ ...(poolConfig || {}),
139
+ });
140
+ return drizzleNode(pool, { schema, ...(config || {}) }) as Database<
141
+ TOptions,
142
+ TSchema
143
+ >;
144
+ }
145
+
146
+ if (type === "pglite") {
147
+ return drizzlePGLite({
148
+ schema: schema as TSchema,
149
+ connection: {
150
+ dataDir: connectionString || "memory://pglite",
151
+ },
152
+ ...(config || {}),
153
+ }) as Database<TOptions, TSchema>;
154
+ }
155
+
156
+ throw new Error("Invalid database type");
157
+ }
158
+
159
+ /**
160
+ * Options for database migration
161
+ */
162
+ export type MigrateOptions = MigrationConfig;
163
+
164
+ /**
165
+ * Performs database migration based on the provided configuration
166
+ * @param db - The database instance to migrate
167
+ * @param options - Migration configuration options
168
+ *
169
+ * @important This function runs migrations on the database instance provided to the `drizzleStorage` plugin.
170
+ * It automatically detects the type of database and runs the appropriate migrate function
171
+ * (PGLite or Node-Postgres).
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * await migrate(db, { migrationsFolder: "./drizzle" });
176
+ * ```
177
+ */
178
+ export async function migrate<TSchema extends Record<string, unknown>>(
179
+ db: PgliteDatabase<TSchema> | NodePgDatabase<TSchema>,
180
+ options: MigrateOptions,
181
+ ) {
182
+ const isPglite = !!("$client" in db && db.$client instanceof PGlite);
183
+
184
+ try {
185
+ if (isPglite) {
186
+ await migratePGLite(db as PgliteDatabase<TSchema>, options);
187
+ } else {
188
+ await migrateNode(db as NodePgDatabase<TSchema>, options);
189
+ }
190
+ } catch (error) {
191
+ throw new DrizzleStorageError(
192
+ "Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
193
+ {
194
+ cause: error,
195
+ },
196
+ );
197
+ }
198
+ }
199
+
200
+ function isPostgresConnectionString(conn: string) {
201
+ return conn.startsWith("postgres://") || conn.startsWith("postgresql://");
202
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useIndexerContext } from "@apibara/indexer";
2
- import { defineIndexerPlugin } from "@apibara/indexer/plugins";
2
+ import { defineIndexerPlugin, useLogger } from "@apibara/indexer/plugins";
3
3
 
4
4
  import type {
5
5
  ExtractTablesWithRelations,
@@ -14,23 +14,36 @@ import type {
14
14
  PgQueryResultHKT,
15
15
  PgTransaction,
16
16
  } from "drizzle-orm/pg-core";
17
+ import { DRIZZLE_PROPERTY, DRIZZLE_STORAGE_DB_PROPERTY } from "./constants";
18
+ import { type MigrateOptions, migrate } from "./helper";
17
19
  import {
18
20
  finalizeState,
19
21
  getState,
20
22
  initializePersistentState,
21
23
  invalidateState,
22
24
  persistState,
25
+ resetPersistence,
23
26
  } from "./persistence";
24
27
  import {
28
+ cleanupStorage,
25
29
  finalize,
26
30
  initializeReorgRollbackTable,
27
31
  invalidate,
28
32
  registerTriggers,
29
33
  removeTriggers,
30
34
  } from "./storage";
31
- import { DrizzleStorageError, sleep, withTransaction } from "./utils";
35
+ import {
36
+ DrizzleStorageError,
37
+ type IdColumnMap,
38
+ getIdColumnForTable,
39
+ sleep,
40
+ withTransaction,
41
+ } from "./utils";
42
+
43
+ export * from "./helper";
44
+
45
+ export type { IdColumnMap };
32
46
 
33
- const DRIZZLE_PROPERTY = "_drizzle";
34
47
  const MAX_RETRIES = 5;
35
48
 
36
49
  export type DrizzleStorage<
@@ -67,11 +80,53 @@ export interface DrizzleStorageOptions<
67
80
  TSchema extends
68
81
  TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
69
82
  > {
83
+ /**
84
+ * The Drizzle database instance.
85
+ */
70
86
  db: PgDatabase<TQueryResult, TFullSchema, TSchema>;
87
+ /**
88
+ * Whether to persist the indexer's state. Defaults to true.
89
+ */
71
90
  persistState?: boolean;
91
+ /**
92
+ * The name of the indexer. Default value is 'default'.
93
+ */
72
94
  indexerName?: string;
95
+ /**
96
+ * The schema of the database.
97
+ */
73
98
  schema?: Record<string, unknown>;
74
- idColumn?: string;
99
+ /**
100
+ * The column to use as the primary identifier for each table.
101
+ *
102
+ * This identifier is used for tracking changes during reorgs and rollbacks.
103
+ *
104
+ * Can be specified in two ways:
105
+ *
106
+ * 1. As a single string that applies to all tables:
107
+ * ```ts
108
+ * idColumn: "_id" // Uses "_id" column for all tables
109
+ * ```
110
+ *
111
+ * 2. As an object mapping table names to their ID columns:
112
+ * ```ts
113
+ * idColumn: {
114
+ * transfers: "transaction_hash", // Use "transaction_hash" for transfers table
115
+ * blocks: "block_number", // Use "block_number" for blocks table
116
+ * "*": "_id" // Use "_id" for all other tables | defaults to "id"
117
+ * }
118
+ * ```
119
+ *
120
+ * The special "*" key acts as a fallback for any tables not explicitly mapped.
121
+ *
122
+ * @default "id"
123
+ * @type {string | Partial<IdColumnMap>}
124
+ */
125
+ idColumn?: string | Partial<IdColumnMap>;
126
+ /**
127
+ * The options for the database migration. When provided, the database will automatically run migrations before the indexer runs.
128
+ */
129
+ migrate?: MigrateOptions;
75
130
  }
76
131
 
77
132
  /**
@@ -83,6 +138,7 @@ export interface DrizzleStorageOptions<
83
138
  * @param options.indexerName - The name of the indexer. Defaults value is 'default'.
84
139
  * @param options.schema - The schema of the database.
85
140
  * @param options.idColumn - The column to use as the id. Defaults to 'id'.
141
+ * @param options.migrate - The options for the database migration. when provided, the database will automatically run migrations before the indexer runs.
86
142
  */
87
143
  export function drizzleStorage<
88
144
  TFilter,
@@ -95,42 +151,101 @@ export function drizzleStorage<
95
151
  db,
96
152
  persistState: enablePersistence = true,
97
153
  indexerName: identifier = "default",
98
- schema,
99
- idColumn = "id",
154
+ schema: _schema,
155
+ idColumn,
156
+ migrate: migrateOptions,
100
157
  }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>) {
101
158
  return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
102
159
  let tableNames: string[] = [];
103
160
  let indexerId = "";
161
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
162
+ let prevFinality: DataFinality | undefined;
163
+ const schema: TSchema = (_schema as TSchema) ?? db._.schema ?? {};
164
+ const idColumnMap: IdColumnMap = {
165
+ "*": typeof idColumn === "string" ? idColumn : "id",
166
+ ...(typeof idColumn === "object" ? idColumn : {}),
167
+ };
104
168
 
105
169
  try {
106
- tableNames = Object.values((schema as TSchema) ?? db._.schema ?? {}).map(
107
- (table) => table.dbName,
108
- );
170
+ tableNames = Object.values(schema).map((table) => table.dbName);
109
171
  } catch (error) {
110
172
  throw new DrizzleStorageError("Failed to get table names from schema", {
111
173
  cause: error,
112
174
  });
113
175
  }
114
176
 
177
+ // Check if specified idColumn exists in all the tables in schema
178
+ for (const table of Object.values(schema)) {
179
+ const columns = table.columns;
180
+ const tableIdColumn = getIdColumnForTable(table.dbName, idColumnMap);
181
+
182
+ const columnExists = Object.values(columns).some(
183
+ (column) => column.name === tableIdColumn,
184
+ );
185
+
186
+ if (!columnExists) {
187
+ throw new DrizzleStorageError(
188
+ `Column \`"${tableIdColumn}"\` does not exist in table \`"${table.dbName}"\`. ` +
189
+ "Make sure the table has the specified column or provide a valid `idColumn` mapping to `drizzleStorage`.",
190
+ );
191
+ }
192
+ }
193
+
115
194
  indexer.hooks.hook("run:before", async () => {
195
+ const internalContext = useInternalContext();
196
+ const context = useIndexerContext();
197
+ const logger = useLogger();
198
+
199
+ // For testing purposes using vcr.
200
+ context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
201
+
116
202
  const { indexerName: indexerFileName, availableIndexers } =
117
- useInternalContext();
203
+ internalContext;
118
204
 
119
205
  indexerId = generateIndexerId(indexerFileName, identifier);
120
206
 
121
207
  let retries = 0;
122
208
 
209
+ // incase the migrations are already applied, we don't want to run them again
210
+ let migrationsApplied = false;
211
+ let cleanupApplied = false;
212
+
123
213
  while (retries <= MAX_RETRIES) {
124
214
  try {
215
+ if (migrateOptions && !migrationsApplied) {
216
+ // @ts-ignore type mismatch for db
217
+ await migrate(db, migrateOptions);
218
+ migrationsApplied = true;
219
+ logger.success("Migrations applied");
220
+ }
125
221
  await withTransaction(db, async (tx) => {
126
222
  await initializeReorgRollbackTable(tx, indexerId);
127
223
  if (enablePersistence) {
128
224
  await initializePersistentState(tx);
129
225
  }
226
+
227
+ if (alwaysReindex && !cleanupApplied) {
228
+ logger.warn(
229
+ `Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`,
230
+ );
231
+
232
+ await cleanupStorage(tx, tableNames, indexerId);
233
+
234
+ if (enablePersistence) {
235
+ await resetPersistence({ tx, indexerId });
236
+ }
237
+
238
+ cleanupApplied = true;
239
+
240
+ logger.success("Tables have been cleaned up for reindexing");
241
+ }
130
242
  });
131
243
  break;
132
244
  } catch (error) {
133
245
  if (retries === MAX_RETRIES) {
246
+ if (error instanceof DrizzleStorageError) {
247
+ throw error;
248
+ }
134
249
  throw new DrizzleStorageError(
135
250
  "Initialization failed after 5 retries",
136
251
  {
@@ -177,7 +292,8 @@ export function drizzleStorage<
177
292
  }
178
293
 
179
294
  await withTransaction(db, async (tx) => {
180
- await invalidate(tx, cursor, idColumn, indexerId);
295
+ // Use the appropriate idColumn for each table when calling invalidate
296
+ await invalidate(tx, cursor, idColumnMap, indexerId);
181
297
 
182
298
  if (enablePersistence) {
183
299
  await invalidateState({ tx, cursor, indexerId });
@@ -227,7 +343,8 @@ export function drizzleStorage<
227
343
  }
228
344
 
229
345
  await withTransaction(db, async (tx) => {
230
- await invalidate(tx, cursor, idColumn, indexerId);
346
+ // Use the appropriate idColumn for each table when calling invalidate
347
+ await invalidate(tx, cursor, idColumnMap, indexerId);
231
348
 
232
349
  if (enablePersistence) {
233
350
  await invalidateState({ tx, cursor, indexerId });
@@ -238,7 +355,8 @@ export function drizzleStorage<
238
355
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
239
356
  use(async (context, next) => {
240
357
  try {
241
- const { endCursor, finality } = context as {
358
+ const { endCursor, finality, cursor } = context as {
359
+ cursor: Cursor;
242
360
  endCursor: Cursor;
243
361
  finality: DataFinality;
244
362
  };
@@ -254,12 +372,17 @@ export function drizzleStorage<
254
372
  TSchema
255
373
  >;
256
374
 
375
+ if (prevFinality === "pending") {
376
+ // invalidate if previous block's finality was "pending"
377
+ await invalidate(tx, cursor, idColumnMap, indexerId);
378
+ }
379
+
257
380
  if (finality !== "finalized") {
258
381
  await registerTriggers(
259
382
  tx,
260
383
  tableNames,
261
384
  endCursor,
262
- idColumn,
385
+ idColumnMap,
263
386
  indexerId,
264
387
  );
265
388
  }
@@ -267,13 +390,15 @@ export function drizzleStorage<
267
390
  await next();
268
391
  delete context[DRIZZLE_PROPERTY];
269
392
 
270
- if (enablePersistence) {
393
+ if (enablePersistence && finality !== "pending") {
271
394
  await persistState({
272
395
  tx,
273
396
  endCursor,
274
397
  indexerId,
275
398
  });
276
399
  }
400
+
401
+ prevFinality = finality;
277
402
  });
278
403
 
279
404
  if (finality !== "finalized") {
@@ -1,24 +1,29 @@
1
1
  import { type Cursor, normalizeCursor } from "@apibara/protocol";
2
- import { and, eq, gt, isNull, lt } from "drizzle-orm";
2
+ import { and, eq, gt, isNull, lt, sql } from "drizzle-orm";
3
3
  import type {
4
4
  ExtractTablesWithRelations,
5
5
  TablesRelationalConfig,
6
6
  } from "drizzle-orm";
7
7
  import type { PgQueryResultHKT, PgTransaction } from "drizzle-orm/pg-core";
8
- import { integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core";
8
+ import { integer, pgSchema, primaryKey, text } from "drizzle-orm/pg-core";
9
+ import { SCHEMA_NAME } from "./constants";
9
10
  import { DrizzleStorageError, deserialize, serialize } from "./utils";
10
11
 
11
- const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
12
- const FILTERS_TABLE_NAME = "__indexer_filters";
13
- const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
12
+ const CHECKPOINTS_TABLE_NAME = "checkpoints";
13
+ const FILTERS_TABLE_NAME = "filters";
14
+ const SCHEMA_VERSION_TABLE_NAME = "schema_version";
14
15
 
15
- export const checkpoints = pgTable(CHECKPOINTS_TABLE_NAME, {
16
+ const schema = pgSchema(SCHEMA_NAME);
17
+
18
+ /** This table is not used for migrations, its only used for ease of internal operations with drizzle. */
19
+ export const checkpoints = schema.table(CHECKPOINTS_TABLE_NAME, {
16
20
  id: text("id").notNull().primaryKey(),
17
21
  orderKey: integer("order_key").notNull(),
18
22
  uniqueKey: text("unique_key"),
19
23
  });
20
24
 
21
- export const filters = pgTable(
25
+ /** This table is not used for migrations, its only used for ease of internal operations with drizzle. */
26
+ export const filters = schema.table(
22
27
  FILTERS_TABLE_NAME,
23
28
  {
24
29
  id: text("id").notNull(),
@@ -33,7 +38,8 @@ export const filters = pgTable(
33
38
  ],
34
39
  );
35
40
 
36
- export const schemaVersion = pgTable(SCHEMA_VERSION_TABLE_NAME, {
41
+ /** This table is not used for migrations, its only used for ease of internal operations with drizzle. */
42
+ export const schemaVersion = schema.table(SCHEMA_VERSION_TABLE_NAME, {
37
43
  k: integer("k").notNull().primaryKey(),
38
44
  version: integer("version").notNull(),
39
45
  });
@@ -53,13 +59,22 @@ export async function initializePersistentState<
53
59
  TSchema extends
54
60
  TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
55
61
  >(tx: PgTransaction<TQueryResult, TFullSchema, TSchema>) {
62
+ // Create schema if it doesn't exist
63
+ await tx.execute(
64
+ sql.raw(`
65
+ CREATE SCHEMA IF NOT EXISTS ${SCHEMA_NAME};
66
+ `),
67
+ );
68
+
56
69
  // Create schema version table
57
- await tx.execute(`
58
- CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE_NAME} (
70
+ await tx.execute(
71
+ sql.raw(`
72
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${SCHEMA_VERSION_TABLE_NAME} (
59
73
  k INTEGER PRIMARY KEY,
60
74
  version INTEGER NOT NULL
61
75
  );
62
- `);
76
+ `),
77
+ );
63
78
 
64
79
  // Get current schema version
65
80
  const versionRows = await tx
@@ -80,23 +95,27 @@ export async function initializePersistentState<
80
95
  try {
81
96
  if (storedVersion === -1) {
82
97
  // First time initialization
83
- await tx.execute(`
84
- CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} (
98
+ await tx.execute(
99
+ sql.raw(`
100
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${CHECKPOINTS_TABLE_NAME} (
85
101
  id TEXT PRIMARY KEY,
86
102
  order_key INTEGER NOT NULL,
87
103
  unique_key TEXT
88
104
  );
89
- `);
105
+ `),
106
+ );
90
107
 
91
- await tx.execute(`
92
- CREATE TABLE IF NOT EXISTS ${FILTERS_TABLE_NAME} (
108
+ await tx.execute(
109
+ sql.raw(`
110
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${FILTERS_TABLE_NAME} (
93
111
  id TEXT NOT NULL,
94
112
  filter TEXT NOT NULL,
95
113
  from_block INTEGER NOT NULL,
96
114
  to_block INTEGER DEFAULT NULL,
97
115
  PRIMARY KEY (id, from_block)
98
116
  );
99
- `);
117
+ `),
118
+ );
100
119
 
101
120
  // Set initial schema version
102
121
  await tx.insert(schemaVersion).values({
@@ -155,7 +174,9 @@ export async function persistState<
155
174
  target: checkpoints.id,
156
175
  set: {
157
176
  orderKey: Number(endCursor.orderKey),
158
- uniqueKey: endCursor.uniqueKey,
177
+ // Explicitly set the unique key to `null` to indicate that it has been deleted
178
+ // Otherwise drizzle will not update its value.
179
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null,
159
180
  },
160
181
  });
161
182
 
@@ -297,3 +318,24 @@ export async function finalizeState<
297
318
  });
298
319
  }
299
320
  }
321
+
322
+ export async function resetPersistence<
323
+ TQueryResult extends PgQueryResultHKT,
324
+ TFullSchema extends Record<string, unknown> = Record<string, never>,
325
+ TSchema extends
326
+ TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
327
+ >(props: {
328
+ tx: PgTransaction<TQueryResult, TFullSchema, TSchema>;
329
+ indexerId: string;
330
+ }) {
331
+ const { tx, indexerId } = props;
332
+
333
+ try {
334
+ await tx.delete(checkpoints).where(eq(checkpoints.id, indexerId));
335
+ await tx.delete(filters).where(eq(filters.id, indexerId));
336
+ } catch (error) {
337
+ throw new DrizzleStorageError("Failed to reset persistence state", {
338
+ cause: error,
339
+ });
340
+ }
341
+ }