@apibara/plugin-mongo 2.1.0-beta.2 → 2.1.0-beta.20

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/dist/index.cjs CHANGED
@@ -39,17 +39,42 @@ async function finalize(db, session, cursor, collections) {
39
39
  );
40
40
  }
41
41
  }
42
+ async function cleanupStorage(db, session, collections) {
43
+ for (const collection of collections) {
44
+ try {
45
+ await db.collection(collection).deleteMany({}, { session });
46
+ } catch (error) {
47
+ throw new Error(`Failed to clean up collection ${collection}`, {
48
+ cause: error
49
+ });
50
+ }
51
+ }
52
+ }
53
+
54
+ class MongoStorageError extends Error {
55
+ constructor(message, options) {
56
+ super(message, options);
57
+ this.name = "MongoStorageError";
58
+ }
59
+ }
60
+ async function withTransaction(client, cb) {
61
+ return await client.withSession(async (session) => {
62
+ return await session.withTransaction(
63
+ async (session2) => {
64
+ return await cb(session2);
65
+ },
66
+ {
67
+ retryWrites: false
68
+ }
69
+ );
70
+ });
71
+ }
42
72
 
43
73
  const checkpointCollectionName = "checkpoints";
44
74
  const filterCollectionName = "filters";
45
75
  async function initializePersistentState(db, session) {
46
- const checkpoint = await db.createCollection(
47
- checkpointCollectionName,
48
- { session }
49
- );
50
- const filter = await db.createCollection(filterCollectionName, {
51
- session
52
- });
76
+ const checkpoint = db.collection(checkpointCollectionName);
77
+ const filter = db.collection(filterCollectionName);
53
78
  await checkpoint.createIndex({ id: 1 }, { session });
54
79
  await filter.createIndex({ id: 1, fromBlock: 1 }, { session });
55
80
  }
@@ -61,7 +86,7 @@ async function persistState(props) {
61
86
  {
62
87
  $set: {
63
88
  orderKey: Number(endCursor.orderKey),
64
- uniqueKey: endCursor.uniqueKey
89
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
65
90
  }
66
91
  },
67
92
  { upsert: true, session }
@@ -134,6 +159,17 @@ async function finalizeState(props) {
134
159
  { session }
135
160
  );
136
161
  }
162
+ async function resetPersistence(props) {
163
+ const { db, session, indexerId } = props;
164
+ try {
165
+ await db.collection(checkpointCollectionName).deleteMany({ id: indexerId }, { session });
166
+ await db.collection(filterCollectionName).deleteMany({ id: indexerId }, { session });
167
+ } catch (error) {
168
+ throw new MongoStorageError("Failed to reset persistence state", {
169
+ cause: error
170
+ });
171
+ }
172
+ }
137
173
 
138
174
  class MongoStorage {
139
175
  constructor(db, session, endCursor) {
@@ -305,20 +341,6 @@ class MongoCollection {
305
341
  }
306
342
  }
307
343
 
308
- class MongoStorageError extends Error {
309
- constructor(message) {
310
- super(message);
311
- this.name = "MongoStorageError";
312
- }
313
- }
314
- async function withTransaction(client, cb) {
315
- return await client.withSession(async (session) => {
316
- return await session.withTransaction(async (session2) => {
317
- return await cb(session2);
318
- });
319
- });
320
- }
321
-
322
344
  const MONGO_PROPERTY = "_mongo";
323
345
  function useMongoStorage() {
324
346
  const context = indexer.useIndexerContext();
@@ -339,9 +361,25 @@ function mongoStorage({
339
361
  }) {
340
362
  return plugins.defineIndexerPlugin((indexer) => {
341
363
  let indexerId = "";
364
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
365
+ let prevFinality;
342
366
  indexer.hooks.hook("run:before", async () => {
343
367
  const { indexerName } = plugins$1.useInternalContext();
344
368
  indexerId = internal.generateIndexerId(indexerName, identifier);
369
+ const logger = plugins.useLogger();
370
+ if (alwaysReindex) {
371
+ logger.warn(
372
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`
373
+ );
374
+ await withTransaction(client, async (session) => {
375
+ const db = client.db(dbName, dbOptions);
376
+ await cleanupStorage(db, session, collections);
377
+ if (enablePersistence) {
378
+ await resetPersistence({ db, session, indexerId });
379
+ }
380
+ logger.success("All data has been cleaned up for reindexing");
381
+ });
382
+ }
345
383
  await withTransaction(client, async (session) => {
346
384
  const db = client.db(dbName, dbOptions);
347
385
  if (enablePersistence) {
@@ -426,16 +464,19 @@ function mongoStorage({
426
464
  });
427
465
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
428
466
  use(async (context, next) => {
429
- const { endCursor } = context;
467
+ const { endCursor, finality, cursor } = context;
430
468
  if (!endCursor) {
431
469
  throw new MongoStorageError("end cursor is undefined");
432
470
  }
433
471
  await withTransaction(client, async (session) => {
434
472
  const db = client.db(dbName, dbOptions);
435
473
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
474
+ if (prevFinality === "pending") {
475
+ await invalidate(db, session, cursor, collections);
476
+ }
436
477
  await next();
437
478
  delete context[MONGO_PROPERTY];
438
- if (enablePersistence) {
479
+ if (enablePersistence && finality !== "pending") {
439
480
  await persistState({
440
481
  db,
441
482
  endCursor,
@@ -443,6 +484,7 @@ function mongoStorage({
443
484
  indexerId
444
485
  });
445
486
  }
487
+ prevFinality = finality;
446
488
  });
447
489
  });
448
490
  });
package/dist/index.mjs 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
  import { generateIndexerId } from '@apibara/indexer/internal';
4
4
  import { useInternalContext } from '@apibara/indexer/internal/plugins';
5
5
  import { normalizeCursor } from '@apibara/protocol';
@@ -37,17 +37,42 @@ async function finalize(db, session, cursor, collections) {
37
37
  );
38
38
  }
39
39
  }
40
+ async function cleanupStorage(db, session, collections) {
41
+ for (const collection of collections) {
42
+ try {
43
+ await db.collection(collection).deleteMany({}, { session });
44
+ } catch (error) {
45
+ throw new Error(`Failed to clean up collection ${collection}`, {
46
+ cause: error
47
+ });
48
+ }
49
+ }
50
+ }
51
+
52
+ class MongoStorageError extends Error {
53
+ constructor(message, options) {
54
+ super(message, options);
55
+ this.name = "MongoStorageError";
56
+ }
57
+ }
58
+ async function withTransaction(client, cb) {
59
+ return await client.withSession(async (session) => {
60
+ return await session.withTransaction(
61
+ async (session2) => {
62
+ return await cb(session2);
63
+ },
64
+ {
65
+ retryWrites: false
66
+ }
67
+ );
68
+ });
69
+ }
40
70
 
41
71
  const checkpointCollectionName = "checkpoints";
42
72
  const filterCollectionName = "filters";
43
73
  async function initializePersistentState(db, session) {
44
- const checkpoint = await db.createCollection(
45
- checkpointCollectionName,
46
- { session }
47
- );
48
- const filter = await db.createCollection(filterCollectionName, {
49
- session
50
- });
74
+ const checkpoint = db.collection(checkpointCollectionName);
75
+ const filter = db.collection(filterCollectionName);
51
76
  await checkpoint.createIndex({ id: 1 }, { session });
52
77
  await filter.createIndex({ id: 1, fromBlock: 1 }, { session });
53
78
  }
@@ -59,7 +84,7 @@ async function persistState(props) {
59
84
  {
60
85
  $set: {
61
86
  orderKey: Number(endCursor.orderKey),
62
- uniqueKey: endCursor.uniqueKey
87
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
63
88
  }
64
89
  },
65
90
  { upsert: true, session }
@@ -132,6 +157,17 @@ async function finalizeState(props) {
132
157
  { session }
133
158
  );
134
159
  }
160
+ async function resetPersistence(props) {
161
+ const { db, session, indexerId } = props;
162
+ try {
163
+ await db.collection(checkpointCollectionName).deleteMany({ id: indexerId }, { session });
164
+ await db.collection(filterCollectionName).deleteMany({ id: indexerId }, { session });
165
+ } catch (error) {
166
+ throw new MongoStorageError("Failed to reset persistence state", {
167
+ cause: error
168
+ });
169
+ }
170
+ }
135
171
 
136
172
  class MongoStorage {
137
173
  constructor(db, session, endCursor) {
@@ -303,20 +339,6 @@ class MongoCollection {
303
339
  }
304
340
  }
305
341
 
306
- class MongoStorageError extends Error {
307
- constructor(message) {
308
- super(message);
309
- this.name = "MongoStorageError";
310
- }
311
- }
312
- async function withTransaction(client, cb) {
313
- return await client.withSession(async (session) => {
314
- return await session.withTransaction(async (session2) => {
315
- return await cb(session2);
316
- });
317
- });
318
- }
319
-
320
342
  const MONGO_PROPERTY = "_mongo";
321
343
  function useMongoStorage() {
322
344
  const context = useIndexerContext();
@@ -337,9 +359,25 @@ function mongoStorage({
337
359
  }) {
338
360
  return defineIndexerPlugin((indexer) => {
339
361
  let indexerId = "";
362
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
363
+ let prevFinality;
340
364
  indexer.hooks.hook("run:before", async () => {
341
365
  const { indexerName } = useInternalContext();
342
366
  indexerId = generateIndexerId(indexerName, identifier);
367
+ const logger = useLogger();
368
+ if (alwaysReindex) {
369
+ logger.warn(
370
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`
371
+ );
372
+ await withTransaction(client, async (session) => {
373
+ const db = client.db(dbName, dbOptions);
374
+ await cleanupStorage(db, session, collections);
375
+ if (enablePersistence) {
376
+ await resetPersistence({ db, session, indexerId });
377
+ }
378
+ logger.success("All data has been cleaned up for reindexing");
379
+ });
380
+ }
343
381
  await withTransaction(client, async (session) => {
344
382
  const db = client.db(dbName, dbOptions);
345
383
  if (enablePersistence) {
@@ -424,16 +462,19 @@ function mongoStorage({
424
462
  });
425
463
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
426
464
  use(async (context, next) => {
427
- const { endCursor } = context;
465
+ const { endCursor, finality, cursor } = context;
428
466
  if (!endCursor) {
429
467
  throw new MongoStorageError("end cursor is undefined");
430
468
  }
431
469
  await withTransaction(client, async (session) => {
432
470
  const db = client.db(dbName, dbOptions);
433
471
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
472
+ if (prevFinality === "pending") {
473
+ await invalidate(db, session, cursor, collections);
474
+ }
434
475
  await next();
435
476
  delete context[MONGO_PROPERTY];
436
- if (enablePersistence) {
477
+ if (enablePersistence && finality !== "pending") {
437
478
  await persistState({
438
479
  db,
439
480
  endCursor,
@@ -441,6 +482,7 @@ function mongoStorage({
441
482
  indexerId
442
483
  });
443
484
  }
485
+ prevFinality = finality;
444
486
  });
445
487
  });
446
488
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apibara/plugin-mongo",
3
- "version": "2.1.0-beta.2",
3
+ "version": "2.1.0-beta.20",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -35,7 +35,7 @@
35
35
  "mongodb": "^6.12.0"
36
36
  },
37
37
  "dependencies": {
38
- "@apibara/indexer": "2.1.0-beta.2",
39
- "@apibara/protocol": "2.1.0-beta.2"
38
+ "@apibara/indexer": "2.1.0-beta.20",
39
+ "@apibara/protocol": "2.1.0-beta.20"
40
40
  }
41
41
  }
package/src/index.ts CHANGED
@@ -1,16 +1,18 @@
1
1
  import { useIndexerContext } from "@apibara/indexer";
2
- import { defineIndexerPlugin } from "@apibara/indexer/plugins";
2
+ import { defineIndexerPlugin, useLogger } from "@apibara/indexer/plugins";
3
3
  import type { DbOptions, MongoClient } from "mongodb";
4
4
 
5
5
  import { generateIndexerId } from "@apibara/indexer/internal";
6
6
  import { useInternalContext } from "@apibara/indexer/internal/plugins";
7
- import { finalize, invalidate } from "./mongo";
7
+ import type { Cursor, DataFinality } from "@apibara/protocol";
8
+ import { cleanupStorage, finalize, invalidate } from "./mongo";
8
9
  import {
9
10
  finalizeState,
10
11
  getState,
11
12
  initializePersistentState,
12
13
  invalidateState,
13
14
  persistState,
15
+ resetPersistence,
14
16
  } from "./persistence";
15
17
  import { MongoStorage } from "./storage";
16
18
  import { MongoStorageError, withTransaction } from "./utils";
@@ -60,10 +62,31 @@ export function mongoStorage<TFilter, TBlock>({
60
62
  }: MongoStorageOptions) {
61
63
  return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
62
64
  let indexerId = "";
65
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
66
+ let prevFinality: DataFinality | undefined;
63
67
 
64
68
  indexer.hooks.hook("run:before", async () => {
65
69
  const { indexerName } = useInternalContext();
66
70
  indexerId = generateIndexerId(indexerName, identifier);
71
+ const logger = useLogger();
72
+
73
+ if (alwaysReindex) {
74
+ logger.warn(
75
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`,
76
+ );
77
+
78
+ await withTransaction(client, async (session) => {
79
+ const db = client.db(dbName, dbOptions);
80
+
81
+ await cleanupStorage(db, session, collections);
82
+
83
+ if (enablePersistence) {
84
+ await resetPersistence({ db, session, indexerId });
85
+ }
86
+
87
+ logger.success("All data has been cleaned up for reindexing");
88
+ });
89
+ }
67
90
 
68
91
  await withTransaction(client, async (session) => {
69
92
  const db = client.db(dbName, dbOptions);
@@ -168,7 +191,11 @@ export function mongoStorage<TFilter, TBlock>({
168
191
 
169
192
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
170
193
  use(async (context, next) => {
171
- const { endCursor } = context;
194
+ const { endCursor, finality, cursor } = context as {
195
+ cursor: Cursor;
196
+ endCursor: Cursor;
197
+ finality: DataFinality;
198
+ };
172
199
 
173
200
  if (!endCursor) {
174
201
  throw new MongoStorageError("end cursor is undefined");
@@ -178,10 +205,16 @@ export function mongoStorage<TFilter, TBlock>({
178
205
  const db = client.db(dbName, dbOptions);
179
206
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
180
207
 
208
+ if (prevFinality === "pending") {
209
+ // invalidate if previous block's finality was "pending"
210
+ await invalidate(db, session, cursor, collections);
211
+ }
212
+
181
213
  await next();
214
+
182
215
  delete context[MONGO_PROPERTY];
183
216
 
184
- if (enablePersistence) {
217
+ if (enablePersistence && finality !== "pending") {
185
218
  await persistState({
186
219
  db,
187
220
  endCursor,
@@ -189,6 +222,8 @@ export function mongoStorage<TFilter, TBlock>({
189
222
  indexerId,
190
223
  });
191
224
  }
225
+
226
+ prevFinality = finality;
192
227
  });
193
228
  });
194
229
  });
package/src/mongo.ts CHANGED
@@ -49,3 +49,20 @@ export async function finalize(
49
49
  );
50
50
  }
51
51
  }
52
+
53
+ export async function cleanupStorage(
54
+ db: Db,
55
+ session: ClientSession,
56
+ collections: string[],
57
+ ) {
58
+ for (const collection of collections) {
59
+ try {
60
+ // Delete all documents in the collection
61
+ await db.collection(collection).deleteMany({}, { session });
62
+ } catch (error) {
63
+ throw new Error(`Failed to clean up collection ${collection}`, {
64
+ cause: error,
65
+ });
66
+ }
67
+ }
68
+ }
@@ -1,5 +1,6 @@
1
1
  import { type Cursor, normalizeCursor } from "@apibara/protocol";
2
2
  import type { ClientSession, Db } from "mongodb";
3
+ import { MongoStorageError } from "./utils";
3
4
 
4
5
  export type CheckpointSchema = {
5
6
  id: string;
@@ -21,13 +22,8 @@ export async function initializePersistentState(
21
22
  db: Db,
22
23
  session: ClientSession,
23
24
  ) {
24
- const checkpoint = await db.createCollection<CheckpointSchema>(
25
- checkpointCollectionName,
26
- { session },
27
- );
28
- const filter = await db.createCollection<FilterSchema>(filterCollectionName, {
29
- session,
30
- });
25
+ const checkpoint = db.collection<CheckpointSchema>(checkpointCollectionName);
26
+ const filter = db.collection<FilterSchema>(filterCollectionName);
31
27
 
32
28
  await checkpoint.createIndex({ id: 1 }, { session });
33
29
  await filter.createIndex({ id: 1, fromBlock: 1 }, { session });
@@ -48,7 +44,7 @@ export async function persistState<TFilter>(props: {
48
44
  {
49
45
  $set: {
50
46
  orderKey: Number(endCursor.orderKey),
51
- uniqueKey: endCursor.uniqueKey,
47
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null,
52
48
  },
53
49
  },
54
50
  { upsert: true, session },
@@ -161,3 +157,27 @@ export async function finalizeState(props: {
161
157
  { session },
162
158
  );
163
159
  }
160
+
161
+ export async function resetPersistence(props: {
162
+ db: Db;
163
+ session: ClientSession;
164
+ indexerId: string;
165
+ }) {
166
+ const { db, session, indexerId } = props;
167
+
168
+ try {
169
+ // Delete all checkpoints for this indexer
170
+ await db
171
+ .collection<CheckpointSchema>(checkpointCollectionName)
172
+ .deleteMany({ id: indexerId }, { session });
173
+
174
+ // Delete all filters for this indexer
175
+ await db
176
+ .collection<FilterSchema>(filterCollectionName)
177
+ .deleteMany({ id: indexerId }, { session });
178
+ } catch (error) {
179
+ throw new MongoStorageError("Failed to reset persistence state", {
180
+ cause: error,
181
+ });
182
+ }
183
+ }
package/src/utils.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { ClientSession, MongoClient } from "mongodb";
2
2
 
3
3
  export class MongoStorageError extends Error {
4
- constructor(message: string) {
5
- super(message);
4
+ constructor(message: string, options?: ErrorOptions) {
5
+ super(message, options);
6
6
  this.name = "MongoStorageError";
7
7
  }
8
8
  }
@@ -12,8 +12,13 @@ export async function withTransaction<T>(
12
12
  cb: (session: ClientSession) => Promise<T>,
13
13
  ) {
14
14
  return await client.withSession(async (session) => {
15
- return await session.withTransaction(async (session) => {
16
- return await cb(session);
17
- });
15
+ return await session.withTransaction(
16
+ async (session) => {
17
+ return await cb(session);
18
+ },
19
+ {
20
+ retryWrites: false,
21
+ },
22
+ );
18
23
  });
19
24
  }