@apibara/plugin-mongo 2.1.0-beta.3 → 2.1.0-beta.30

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,14 +361,27 @@ 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();
345
370
  await withTransaction(client, async (session) => {
346
371
  const db = client.db(dbName, dbOptions);
347
372
  if (enablePersistence) {
348
373
  await initializePersistentState(db, session);
349
374
  }
375
+ if (alwaysReindex) {
376
+ logger.warn(
377
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`
378
+ );
379
+ await cleanupStorage(db, session, collections);
380
+ if (enablePersistence) {
381
+ await resetPersistence({ db, session, indexerId });
382
+ }
383
+ logger.success("All data has been cleaned up for reindexing");
384
+ }
350
385
  });
351
386
  });
352
387
  indexer.hooks.hook("connect:before", async ({ request }) => {
@@ -399,7 +434,7 @@ function mongoStorage({
399
434
  });
400
435
  });
401
436
  indexer.hooks.hook("message:finalize", async ({ message }) => {
402
- const { cursor } = message.finalize;
437
+ const { cursor } = message;
403
438
  if (!cursor) {
404
439
  throw new MongoStorageError("finalized cursor is undefined");
405
440
  }
@@ -412,7 +447,7 @@ function mongoStorage({
412
447
  });
413
448
  });
414
449
  indexer.hooks.hook("message:invalidate", async ({ message }) => {
415
- const { cursor } = message.invalidate;
450
+ const { cursor } = message;
416
451
  if (!cursor) {
417
452
  throw new MongoStorageError("invalidate cursor is undefined");
418
453
  }
@@ -426,16 +461,19 @@ function mongoStorage({
426
461
  });
427
462
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
428
463
  use(async (context, next) => {
429
- const { endCursor } = context;
464
+ const { endCursor, finality, cursor } = context;
430
465
  if (!endCursor) {
431
466
  throw new MongoStorageError("end cursor is undefined");
432
467
  }
433
468
  await withTransaction(client, async (session) => {
434
469
  const db = client.db(dbName, dbOptions);
435
470
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
471
+ if (prevFinality === "pending") {
472
+ await invalidate(db, session, cursor, collections);
473
+ }
436
474
  await next();
437
475
  delete context[MONGO_PROPERTY];
438
- if (enablePersistence) {
476
+ if (enablePersistence && finality !== "pending") {
439
477
  await persistState({
440
478
  db,
441
479
  endCursor,
@@ -443,6 +481,7 @@ function mongoStorage({
443
481
  indexerId
444
482
  });
445
483
  }
484
+ prevFinality = finality;
446
485
  });
447
486
  });
448
487
  });
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,14 +359,27 @@ 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();
343
368
  await withTransaction(client, async (session) => {
344
369
  const db = client.db(dbName, dbOptions);
345
370
  if (enablePersistence) {
346
371
  await initializePersistentState(db, session);
347
372
  }
373
+ if (alwaysReindex) {
374
+ logger.warn(
375
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`
376
+ );
377
+ await cleanupStorage(db, session, collections);
378
+ if (enablePersistence) {
379
+ await resetPersistence({ db, session, indexerId });
380
+ }
381
+ logger.success("All data has been cleaned up for reindexing");
382
+ }
348
383
  });
349
384
  });
350
385
  indexer.hooks.hook("connect:before", async ({ request }) => {
@@ -397,7 +432,7 @@ function mongoStorage({
397
432
  });
398
433
  });
399
434
  indexer.hooks.hook("message:finalize", async ({ message }) => {
400
- const { cursor } = message.finalize;
435
+ const { cursor } = message;
401
436
  if (!cursor) {
402
437
  throw new MongoStorageError("finalized cursor is undefined");
403
438
  }
@@ -410,7 +445,7 @@ function mongoStorage({
410
445
  });
411
446
  });
412
447
  indexer.hooks.hook("message:invalidate", async ({ message }) => {
413
- const { cursor } = message.invalidate;
448
+ const { cursor } = message;
414
449
  if (!cursor) {
415
450
  throw new MongoStorageError("invalidate cursor is undefined");
416
451
  }
@@ -424,16 +459,19 @@ function mongoStorage({
424
459
  });
425
460
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
426
461
  use(async (context, next) => {
427
- const { endCursor } = context;
462
+ const { endCursor, finality, cursor } = context;
428
463
  if (!endCursor) {
429
464
  throw new MongoStorageError("end cursor is undefined");
430
465
  }
431
466
  await withTransaction(client, async (session) => {
432
467
  const db = client.db(dbName, dbOptions);
433
468
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
469
+ if (prevFinality === "pending") {
470
+ await invalidate(db, session, cursor, collections);
471
+ }
434
472
  await next();
435
473
  delete context[MONGO_PROPERTY];
436
- if (enablePersistence) {
474
+ if (enablePersistence && finality !== "pending") {
437
475
  await persistState({
438
476
  db,
439
477
  endCursor,
@@ -441,6 +479,7 @@ function mongoStorage({
441
479
  indexerId
442
480
  });
443
481
  }
482
+ prevFinality = finality;
444
483
  });
445
484
  });
446
485
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apibara/plugin-mongo",
3
- "version": "2.1.0-beta.3",
3
+ "version": "2.1.0-beta.30",
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.3",
39
- "@apibara/protocol": "2.1.0-beta.3"
38
+ "@apibara/indexer": "2.1.0-beta.30",
39
+ "@apibara/protocol": "2.1.0-beta.30"
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,16 +62,33 @@ 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();
67
72
 
68
73
  await withTransaction(client, async (session) => {
69
74
  const db = client.db(dbName, dbOptions);
70
75
  if (enablePersistence) {
71
76
  await initializePersistentState(db, session);
72
77
  }
78
+
79
+ if (alwaysReindex) {
80
+ logger.warn(
81
+ `Reindexing: Deleting all data from collections - ${collections.join(", ")}`,
82
+ );
83
+
84
+ await cleanupStorage(db, session, collections);
85
+
86
+ if (enablePersistence) {
87
+ await resetPersistence({ db, session, indexerId });
88
+ }
89
+
90
+ logger.success("All data has been cleaned up for reindexing");
91
+ }
73
92
  });
74
93
  });
75
94
 
@@ -133,7 +152,7 @@ export function mongoStorage<TFilter, TBlock>({
133
152
  });
134
153
 
135
154
  indexer.hooks.hook("message:finalize", async ({ message }) => {
136
- const { cursor } = message.finalize;
155
+ const { cursor } = message;
137
156
 
138
157
  if (!cursor) {
139
158
  throw new MongoStorageError("finalized cursor is undefined");
@@ -150,7 +169,7 @@ export function mongoStorage<TFilter, TBlock>({
150
169
  });
151
170
 
152
171
  indexer.hooks.hook("message:invalidate", async ({ message }) => {
153
- const { cursor } = message.invalidate;
172
+ const { cursor } = message;
154
173
 
155
174
  if (!cursor) {
156
175
  throw new MongoStorageError("invalidate cursor is undefined");
@@ -168,7 +187,11 @@ export function mongoStorage<TFilter, TBlock>({
168
187
 
169
188
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
170
189
  use(async (context, next) => {
171
- const { endCursor } = context;
190
+ const { endCursor, finality, cursor } = context as {
191
+ cursor: Cursor;
192
+ endCursor: Cursor;
193
+ finality: DataFinality;
194
+ };
172
195
 
173
196
  if (!endCursor) {
174
197
  throw new MongoStorageError("end cursor is undefined");
@@ -178,10 +201,16 @@ export function mongoStorage<TFilter, TBlock>({
178
201
  const db = client.db(dbName, dbOptions);
179
202
  context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);
180
203
 
204
+ if (prevFinality === "pending") {
205
+ // invalidate if previous block's finality was "pending"
206
+ await invalidate(db, session, cursor, collections);
207
+ }
208
+
181
209
  await next();
210
+
182
211
  delete context[MONGO_PROPERTY];
183
212
 
184
- if (enablePersistence) {
213
+ if (enablePersistence && finality !== "pending") {
185
214
  await persistState({
186
215
  db,
187
216
  endCursor,
@@ -189,6 +218,8 @@ export function mongoStorage<TFilter, TBlock>({
189
218
  indexerId,
190
219
  });
191
220
  }
221
+
222
+ prevFinality = finality;
192
223
  });
193
224
  });
194
225
  });
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
  }