@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 +65 -26
- package/dist/index.mjs +66 -27
- package/package.json +3 -3
- package/src/index.ts +37 -6
- package/src/mongo.ts +17 -0
- package/src/persistence.ts +28 -8
- package/src/utils.ts +10 -5
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 =
|
|
47
|
-
|
|
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
|
|
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
|
|
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 =
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
+
"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.
|
|
39
|
-
"@apibara/protocol": "2.1.0-beta.
|
|
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 {
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/persistence.ts
CHANGED
|
@@ -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 =
|
|
25
|
-
|
|
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(
|
|
16
|
-
|
|
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
|
}
|