@apibara/plugin-mongo 2.1.0-beta.5 → 2.1.0-beta.51
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 +67 -27
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +68 -28
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +38 -7
- 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 = "";
|
|
342
|
-
|
|
364
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
365
|
+
let prevFinality;
|
|
366
|
+
indexer.hooks.hook("plugins:init", 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
|
});
|
|
@@ -453,3 +492,4 @@ exports.MongoCollection = MongoCollection;
|
|
|
453
492
|
exports.MongoStorage = MongoStorage;
|
|
454
493
|
exports.mongoStorage = mongoStorage;
|
|
455
494
|
exports.useMongoStorage = useMongoStorage;
|
|
495
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/mongo.ts","../src/utils.ts","../src/persistence.ts","../src/storage.ts","../src/index.ts"],"sourcesContent":["import type { Cursor } from \"@apibara/protocol\";\nimport type { ClientSession, Db } from \"mongodb\";\n\nexport async function invalidate(\n db: Db,\n session: ClientSession,\n cursor: Cursor,\n collections: string[],\n) {\n const orderKeyValue = Number(cursor.orderKey);\n for (const collection of collections) {\n // Delete documents where the lower bound of _cursor is greater than the invalidate cursor\n await db.collection(collection).deleteMany(\n {\n \"_cursor.from\": {\n $gt: orderKeyValue,\n },\n },\n { session },\n );\n\n // Update documents where the upper bound of _cursor is greater than the invalidate cursor\n await db.collection(collection).updateMany(\n { \"_cursor.to\": { $gt: orderKeyValue } },\n {\n $set: {\n \"_cursor.to\": null,\n },\n },\n { session },\n );\n }\n}\n\nexport async function finalize(\n db: Db,\n session: ClientSession,\n cursor: Cursor,\n collections: string[],\n) {\n const orderKeyValue = Number(cursor.orderKey);\n for (const collection of collections) {\n // Delete documents where the upper bound of _cursor is less than the finalize cursor\n await db.collection(collection).deleteMany(\n {\n \"_cursor.to\": { $lte: orderKeyValue },\n },\n { session },\n );\n }\n}\n\nexport async function cleanupStorage(\n db: Db,\n session: ClientSession,\n collections: string[],\n) {\n for (const collection of collections) {\n try {\n // Delete all documents in the collection\n await db.collection(collection).deleteMany({}, { session });\n } catch (error) {\n throw new Error(`Failed to clean up collection ${collection}`, {\n cause: error,\n });\n }\n }\n}\n","import type { ClientSession, MongoClient } from \"mongodb\";\n\nexport class MongoStorageError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"MongoStorageError\";\n }\n}\n\nexport async function withTransaction<T>(\n client: MongoClient,\n cb: (session: ClientSession) => Promise<T>,\n) {\n return await client.withSession(async (session) => {\n return await session.withTransaction(\n async (session) => {\n return await cb(session);\n },\n {\n retryWrites: false,\n },\n );\n });\n}\n","import { type Cursor, normalizeCursor } from \"@apibara/protocol\";\nimport type { ClientSession, Db } from \"mongodb\";\nimport { MongoStorageError } from \"./utils\";\n\nexport type CheckpointSchema = {\n id: string;\n orderKey: number;\n uniqueKey: string | null;\n};\n\nexport type FilterSchema = {\n id: string;\n filter: Record<string, unknown>;\n fromBlock: number;\n toBlock: number | null;\n};\n\nexport const checkpointCollectionName = \"checkpoints\";\nexport const filterCollectionName = \"filters\";\n\nexport async function initializePersistentState(\n db: Db,\n session: ClientSession,\n) {\n const checkpoint = db.collection<CheckpointSchema>(checkpointCollectionName);\n const filter = db.collection<FilterSchema>(filterCollectionName);\n\n await checkpoint.createIndex({ id: 1 }, { session });\n await filter.createIndex({ id: 1, fromBlock: 1 }, { session });\n}\n\nexport async function persistState<TFilter>(props: {\n db: Db;\n session: ClientSession;\n endCursor: Cursor;\n filter?: TFilter;\n indexerId: string;\n}) {\n const { db, session, endCursor, filter, indexerId } = props;\n\n if (endCursor) {\n await db.collection<CheckpointSchema>(checkpointCollectionName).updateOne(\n { id: indexerId },\n {\n $set: {\n orderKey: Number(endCursor.orderKey),\n uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null,\n },\n },\n { upsert: true, session },\n );\n\n if (filter) {\n // Update existing filter's to_block\n await db\n .collection<FilterSchema>(filterCollectionName)\n .updateMany(\n { id: indexerId, toBlock: null },\n { $set: { toBlock: Number(endCursor.orderKey) } },\n { session },\n );\n\n // Insert new filter\n await db.collection<FilterSchema>(filterCollectionName).updateOne(\n {\n id: indexerId,\n fromBlock: Number(endCursor.orderKey),\n },\n {\n $set: {\n filter: filter as Record<string, unknown>,\n fromBlock: Number(endCursor.orderKey),\n toBlock: null,\n },\n },\n { upsert: true, session },\n );\n }\n }\n}\n\nexport async function getState<TFilter>(props: {\n db: Db;\n session: ClientSession;\n indexerId: string;\n}): Promise<{ cursor?: Cursor; filter?: TFilter }> {\n const { db, session, indexerId } = props;\n\n let cursor: Cursor | undefined;\n let filter: TFilter | undefined;\n\n const checkpointRow = await db\n .collection<CheckpointSchema>(checkpointCollectionName)\n .findOne({ id: indexerId }, { session });\n\n if (checkpointRow) {\n cursor = normalizeCursor({\n orderKey: BigInt(checkpointRow.orderKey),\n uniqueKey: checkpointRow.uniqueKey,\n });\n }\n\n const filterRow = await db\n .collection<FilterSchema>(filterCollectionName)\n .findOne(\n {\n id: indexerId,\n toBlock: null,\n },\n { session },\n );\n\n if (filterRow) {\n filter = filterRow.filter as TFilter;\n }\n\n return { cursor, filter };\n}\n\nexport async function invalidateState(props: {\n db: Db;\n session: ClientSession;\n cursor: Cursor;\n indexerId: string;\n}) {\n const { db, session, cursor, indexerId } = props;\n\n await db\n .collection<FilterSchema>(filterCollectionName)\n .deleteMany(\n { id: indexerId, fromBlock: { $gt: Number(cursor.orderKey) } },\n { session },\n );\n\n await db\n .collection<FilterSchema>(filterCollectionName)\n .updateMany(\n { id: indexerId, toBlock: { $gt: Number(cursor.orderKey) } },\n { $set: { toBlock: null } },\n { session },\n );\n}\n\nexport async function finalizeState(props: {\n db: Db;\n session: ClientSession;\n cursor: Cursor;\n indexerId: string;\n}) {\n const { db, session, cursor, indexerId } = props;\n\n await db.collection<FilterSchema>(filterCollectionName).deleteMany(\n {\n id: indexerId,\n toBlock: { $lte: Number(cursor.orderKey) },\n },\n { session },\n );\n}\n\nexport async function resetPersistence(props: {\n db: Db;\n session: ClientSession;\n indexerId: string;\n}) {\n const { db, session, indexerId } = props;\n\n try {\n // Delete all checkpoints for this indexer\n await db\n .collection<CheckpointSchema>(checkpointCollectionName)\n .deleteMany({ id: indexerId }, { session });\n\n // Delete all filters for this indexer\n await db\n .collection<FilterSchema>(filterCollectionName)\n .deleteMany({ id: indexerId }, { session });\n } catch (error) {\n throw new MongoStorageError(\"Failed to reset persistence state\", {\n cause: error,\n });\n }\n}\n","import type { Cursor } from \"@apibara/protocol\";\nimport type {\n BulkWriteOptions,\n ClientSession,\n Collection,\n CollectionOptions,\n Db,\n DeleteOptions,\n Document,\n Filter,\n FindCursor,\n FindOneAndUpdateOptions,\n FindOptions,\n InsertManyResult,\n InsertOneOptions,\n InsertOneResult,\n MatchKeysAndValues,\n OptionalUnlessRequiredId,\n UpdateFilter,\n UpdateOptions,\n UpdateResult,\n WithId,\n} from \"mongodb\";\n\nexport class MongoStorage {\n constructor(\n private db: Db,\n private session: ClientSession,\n private endCursor?: Cursor,\n ) {}\n\n collection<TSchema extends Document = Document>(\n name: string,\n options?: CollectionOptions,\n ) {\n const collection = this.db.collection<TSchema>(name, options);\n\n return new MongoCollection<TSchema>(\n this.session,\n collection,\n this.endCursor,\n );\n }\n}\n\nexport type MongoCursor = {\n from: number | null;\n to: number | null;\n};\n\nexport type CursoredSchema<TSchema extends Document> = TSchema & {\n _cursor: MongoCursor;\n};\n\nexport class MongoCollection<TSchema extends Document> {\n constructor(\n private session: ClientSession,\n private collection: Collection<TSchema>,\n private endCursor?: Cursor,\n ) {}\n\n async insertOne(\n doc: OptionalUnlessRequiredId<TSchema>,\n options?: InsertOneOptions,\n ): Promise<InsertOneResult<TSchema>> {\n return await this.collection.insertOne(\n {\n ...doc,\n _cursor: {\n from: Number(this.endCursor?.orderKey),\n to: null,\n } as MongoCursor,\n },\n { ...options, session: this.session },\n );\n }\n\n async insertMany(\n docs: ReadonlyArray<OptionalUnlessRequiredId<TSchema>>,\n options?: BulkWriteOptions,\n ): Promise<InsertManyResult<TSchema>> {\n return await this.collection.insertMany(\n docs.map((doc) => ({\n ...doc,\n _cursor: {\n from: Number(this.endCursor?.orderKey),\n to: null,\n } as MongoCursor,\n })),\n { ...options, session: this.session },\n );\n }\n\n async updateOne(\n filter: Filter<TSchema>,\n update: UpdateFilter<TSchema>,\n options?: UpdateOptions,\n ): Promise<UpdateResult<TSchema>> {\n // 1. Find and update the document, getting the old version\n const oldDoc = await this.collection.findOneAndUpdate(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n ...update,\n $set: {\n ...update.$set,\n \"_cursor.from\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n {\n ...options,\n session: this.session,\n returnDocument: \"before\",\n } as FindOneAndUpdateOptions,\n );\n\n // 2. If we found and updated a document, insert its old version\n if (oldDoc) {\n const { _id, ...doc } = oldDoc;\n await this.collection.insertOne(\n {\n ...doc,\n _cursor: {\n ...oldDoc._cursor,\n to: Number(this.endCursor?.orderKey),\n },\n } as unknown as OptionalUnlessRequiredId<TSchema>,\n { session: this.session },\n );\n }\n\n // 3. Return an UpdateResult-compatible object\n return {\n acknowledged: true,\n modifiedCount: oldDoc ? 1 : 0,\n upsertedId: null,\n upsertedCount: 0,\n matchedCount: oldDoc ? 1 : 0,\n };\n }\n\n async updateMany(\n filter: Filter<TSchema>,\n update: UpdateFilter<TSchema>,\n options?: UpdateOptions,\n ): Promise<UpdateResult<TSchema>> {\n // 1. Find all documents matching the filter that are latest (to: null)\n const oldDocs = await this.collection\n .find(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { session: this.session },\n )\n .toArray();\n\n // 2. Update to the new values with updateMany\n // (setting _cursor.from to endCursor, leaving _cursor.to unchanged)\n const updateResult = await this.collection.updateMany(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n ...update,\n $set: {\n ...update.$set,\n \"_cursor.from\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n\n // 3. Adjust the cursor.to of the old values\n const oldDocsWithUpdatedCursor = oldDocs.map(({ _id, ...doc }) => ({\n ...doc,\n _cursor: {\n ...doc._cursor,\n to: Number(this.endCursor?.orderKey),\n },\n }));\n\n // 4. Insert the old values back into the db\n if (oldDocsWithUpdatedCursor.length > 0) {\n await this.collection.insertMany(\n oldDocsWithUpdatedCursor as unknown as OptionalUnlessRequiredId<TSchema>[],\n { session: this.session },\n );\n }\n\n return updateResult;\n }\n\n async deleteOne(\n filter: Filter<TSchema>,\n options?: DeleteOptions,\n ): Promise<UpdateResult<TSchema>> {\n return await this.collection.updateOne(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n $set: {\n \"_cursor.to\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n }\n\n async deleteMany(\n filter?: Filter<TSchema>,\n options?: DeleteOptions,\n ): Promise<UpdateResult<TSchema>> {\n return await this.collection.updateMany(\n {\n ...((filter ?? {}) as Filter<TSchema>),\n \"_cursor.to\": null,\n },\n {\n $set: {\n \"_cursor.to\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n }\n\n async findOne(\n filter: Filter<TSchema>,\n options?: Omit<FindOptions, \"timeoutMode\">,\n ): Promise<WithId<TSchema> | null> {\n return await this.collection.findOne(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { ...options, session: this.session },\n );\n }\n\n find(\n filter: Filter<TSchema>,\n options?: FindOptions,\n ): FindCursor<WithId<TSchema>> {\n return this.collection.find(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { ...options, session: this.session },\n );\n }\n}\n","import { useIndexerContext } from \"@apibara/indexer\";\nimport { defineIndexerPlugin, useLogger } from \"@apibara/indexer/plugins\";\nimport type { DbOptions, MongoClient } from \"mongodb\";\n\nimport { generateIndexerId } from \"@apibara/indexer/internal\";\nimport { useInternalContext } from \"@apibara/indexer/internal/plugins\";\nimport type { Cursor, DataFinality } from \"@apibara/protocol\";\nimport { cleanupStorage, finalize, invalidate } from \"./mongo\";\nimport {\n finalizeState,\n getState,\n initializePersistentState,\n invalidateState,\n persistState,\n resetPersistence,\n} from \"./persistence\";\nimport { MongoStorage } from \"./storage\";\nimport { MongoStorageError, withTransaction } from \"./utils\";\n\nexport { MongoCollection, MongoStorage } from \"./storage\";\n\nconst MONGO_PROPERTY = \"_mongo\";\n\nexport function useMongoStorage(): MongoStorage {\n const context = useIndexerContext();\n\n if (!context[MONGO_PROPERTY]) {\n throw new MongoStorageError(\n \"mongo storage is not available. Did you register the plugin?\",\n );\n }\n\n return context[MONGO_PROPERTY] as MongoStorage;\n}\n\nexport interface MongoStorageOptions {\n client: MongoClient;\n dbName: string;\n dbOptions?: DbOptions;\n collections: string[];\n persistState?: boolean;\n indexerName?: string;\n}\n/**\n * Creates a plugin that uses MongoDB as the storage layer.\n *\n * Supports storing the indexer's state and provides a simple Key-Value store.\n * @param options.client - The MongoDB client instance.\n * @param options.dbName - The name of the database.\n * @param options.dbOptions - The database options.\n * @param options.collections - The collections to use.\n * @param options.persistState - Whether to persist the indexer's state. Defaults to true.\n * @param options.indexerName - The name of the indexer. Defaults value is 'default'.\n */\nexport function mongoStorage<TFilter, TBlock>({\n client,\n dbName,\n dbOptions,\n collections,\n persistState: enablePersistence = true,\n indexerName: identifier = \"default\",\n}: MongoStorageOptions) {\n return defineIndexerPlugin<TFilter, TBlock>((indexer) => {\n let indexerId = \"\";\n const alwaysReindex = process.env[\"APIBARA_ALWAYS_REINDEX\"] === \"true\";\n let prevFinality: DataFinality | undefined;\n\n indexer.hooks.hook(\"plugins:init\", async () => {\n const { indexerName } = useInternalContext();\n indexerId = generateIndexerId(indexerName, identifier);\n const logger = useLogger();\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n if (enablePersistence) {\n await initializePersistentState(db, session);\n }\n\n if (alwaysReindex) {\n logger.warn(\n `Reindexing: Deleting all data from collections - ${collections.join(\", \")}`,\n );\n\n await cleanupStorage(db, session, collections);\n\n if (enablePersistence) {\n await resetPersistence({ db, session, indexerId });\n }\n\n logger.success(\"All data has been cleaned up for reindexing\");\n }\n });\n });\n\n indexer.hooks.hook(\"connect:before\", async ({ request }) => {\n if (!enablePersistence) {\n return;\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n const { cursor, filter } = await getState<TFilter>({\n db,\n session,\n indexerId,\n });\n\n if (cursor) {\n request.startingCursor = cursor;\n }\n\n if (filter) {\n request.filter[1] = filter;\n }\n });\n });\n\n indexer.hooks.hook(\"connect:after\", async ({ request }) => {\n // On restart, we need to invalidate data for blocks that were processed but not persisted.\n const cursor = request.startingCursor;\n\n if (!cursor) {\n return;\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await invalidate(db, session, cursor, collections);\n\n if (enablePersistence) {\n await invalidateState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"connect:factory\", async ({ request, endCursor }) => {\n if (!enablePersistence) {\n return;\n }\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n if (endCursor && request.filter[1]) {\n await persistState({\n db,\n endCursor,\n session,\n filter: request.filter[1],\n indexerId,\n });\n }\n });\n });\n\n indexer.hooks.hook(\"message:finalize\", async ({ message }) => {\n const { cursor } = message;\n\n if (!cursor) {\n throw new MongoStorageError(\"finalized cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await finalize(db, session, cursor, collections);\n\n if (enablePersistence) {\n await finalizeState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"message:invalidate\", async ({ message }) => {\n const { cursor } = message;\n\n if (!cursor) {\n throw new MongoStorageError(\"invalidate cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await invalidate(db, session, cursor, collections);\n\n if (enablePersistence) {\n await invalidateState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"handler:middleware\", async ({ use }) => {\n use(async (context, next) => {\n const { endCursor, finality, cursor } = context as {\n cursor: Cursor;\n endCursor: Cursor;\n finality: DataFinality;\n };\n\n if (!endCursor) {\n throw new MongoStorageError(\"end cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);\n\n if (prevFinality === \"pending\") {\n // invalidate if previous block's finality was \"pending\"\n await invalidate(db, session, cursor, collections);\n }\n\n await next();\n\n delete context[MONGO_PROPERTY];\n\n if (enablePersistence && finality !== \"pending\") {\n await persistState({\n db,\n endCursor,\n session,\n indexerId,\n });\n }\n\n prevFinality = finality;\n });\n });\n });\n });\n}\n"],"names":["session","normalizeCursor","useIndexerContext","defineIndexerPlugin","useInternalContext","generateIndexerId","useLogger"],"mappings":"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"}
|
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 = "";
|
|
340
|
-
|
|
362
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
363
|
+
let prevFinality;
|
|
364
|
+
indexer.hooks.hook("plugins:init", 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
|
});
|
|
@@ -448,3 +487,4 @@ function mongoStorage({
|
|
|
448
487
|
}
|
|
449
488
|
|
|
450
489
|
export { MongoCollection, MongoStorage, mongoStorage, useMongoStorage };
|
|
490
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/mongo.ts","../src/utils.ts","../src/persistence.ts","../src/storage.ts","../src/index.ts"],"sourcesContent":["import type { Cursor } from \"@apibara/protocol\";\nimport type { ClientSession, Db } from \"mongodb\";\n\nexport async function invalidate(\n db: Db,\n session: ClientSession,\n cursor: Cursor,\n collections: string[],\n) {\n const orderKeyValue = Number(cursor.orderKey);\n for (const collection of collections) {\n // Delete documents where the lower bound of _cursor is greater than the invalidate cursor\n await db.collection(collection).deleteMany(\n {\n \"_cursor.from\": {\n $gt: orderKeyValue,\n },\n },\n { session },\n );\n\n // Update documents where the upper bound of _cursor is greater than the invalidate cursor\n await db.collection(collection).updateMany(\n { \"_cursor.to\": { $gt: orderKeyValue } },\n {\n $set: {\n \"_cursor.to\": null,\n },\n },\n { session },\n );\n }\n}\n\nexport async function finalize(\n db: Db,\n session: ClientSession,\n cursor: Cursor,\n collections: string[],\n) {\n const orderKeyValue = Number(cursor.orderKey);\n for (const collection of collections) {\n // Delete documents where the upper bound of _cursor is less than the finalize cursor\n await db.collection(collection).deleteMany(\n {\n \"_cursor.to\": { $lte: orderKeyValue },\n },\n { session },\n );\n }\n}\n\nexport async function cleanupStorage(\n db: Db,\n session: ClientSession,\n collections: string[],\n) {\n for (const collection of collections) {\n try {\n // Delete all documents in the collection\n await db.collection(collection).deleteMany({}, { session });\n } catch (error) {\n throw new Error(`Failed to clean up collection ${collection}`, {\n cause: error,\n });\n }\n }\n}\n","import type { ClientSession, MongoClient } from \"mongodb\";\n\nexport class MongoStorageError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"MongoStorageError\";\n }\n}\n\nexport async function withTransaction<T>(\n client: MongoClient,\n cb: (session: ClientSession) => Promise<T>,\n) {\n return await client.withSession(async (session) => {\n return await session.withTransaction(\n async (session) => {\n return await cb(session);\n },\n {\n retryWrites: false,\n },\n );\n });\n}\n","import { type Cursor, normalizeCursor } from \"@apibara/protocol\";\nimport type { ClientSession, Db } from \"mongodb\";\nimport { MongoStorageError } from \"./utils\";\n\nexport type CheckpointSchema = {\n id: string;\n orderKey: number;\n uniqueKey: string | null;\n};\n\nexport type FilterSchema = {\n id: string;\n filter: Record<string, unknown>;\n fromBlock: number;\n toBlock: number | null;\n};\n\nexport const checkpointCollectionName = \"checkpoints\";\nexport const filterCollectionName = \"filters\";\n\nexport async function initializePersistentState(\n db: Db,\n session: ClientSession,\n) {\n const checkpoint = db.collection<CheckpointSchema>(checkpointCollectionName);\n const filter = db.collection<FilterSchema>(filterCollectionName);\n\n await checkpoint.createIndex({ id: 1 }, { session });\n await filter.createIndex({ id: 1, fromBlock: 1 }, { session });\n}\n\nexport async function persistState<TFilter>(props: {\n db: Db;\n session: ClientSession;\n endCursor: Cursor;\n filter?: TFilter;\n indexerId: string;\n}) {\n const { db, session, endCursor, filter, indexerId } = props;\n\n if (endCursor) {\n await db.collection<CheckpointSchema>(checkpointCollectionName).updateOne(\n { id: indexerId },\n {\n $set: {\n orderKey: Number(endCursor.orderKey),\n uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null,\n },\n },\n { upsert: true, session },\n );\n\n if (filter) {\n // Update existing filter's to_block\n await db\n .collection<FilterSchema>(filterCollectionName)\n .updateMany(\n { id: indexerId, toBlock: null },\n { $set: { toBlock: Number(endCursor.orderKey) } },\n { session },\n );\n\n // Insert new filter\n await db.collection<FilterSchema>(filterCollectionName).updateOne(\n {\n id: indexerId,\n fromBlock: Number(endCursor.orderKey),\n },\n {\n $set: {\n filter: filter as Record<string, unknown>,\n fromBlock: Number(endCursor.orderKey),\n toBlock: null,\n },\n },\n { upsert: true, session },\n );\n }\n }\n}\n\nexport async function getState<TFilter>(props: {\n db: Db;\n session: ClientSession;\n indexerId: string;\n}): Promise<{ cursor?: Cursor; filter?: TFilter }> {\n const { db, session, indexerId } = props;\n\n let cursor: Cursor | undefined;\n let filter: TFilter | undefined;\n\n const checkpointRow = await db\n .collection<CheckpointSchema>(checkpointCollectionName)\n .findOne({ id: indexerId }, { session });\n\n if (checkpointRow) {\n cursor = normalizeCursor({\n orderKey: BigInt(checkpointRow.orderKey),\n uniqueKey: checkpointRow.uniqueKey,\n });\n }\n\n const filterRow = await db\n .collection<FilterSchema>(filterCollectionName)\n .findOne(\n {\n id: indexerId,\n toBlock: null,\n },\n { session },\n );\n\n if (filterRow) {\n filter = filterRow.filter as TFilter;\n }\n\n return { cursor, filter };\n}\n\nexport async function invalidateState(props: {\n db: Db;\n session: ClientSession;\n cursor: Cursor;\n indexerId: string;\n}) {\n const { db, session, cursor, indexerId } = props;\n\n await db\n .collection<FilterSchema>(filterCollectionName)\n .deleteMany(\n { id: indexerId, fromBlock: { $gt: Number(cursor.orderKey) } },\n { session },\n );\n\n await db\n .collection<FilterSchema>(filterCollectionName)\n .updateMany(\n { id: indexerId, toBlock: { $gt: Number(cursor.orderKey) } },\n { $set: { toBlock: null } },\n { session },\n );\n}\n\nexport async function finalizeState(props: {\n db: Db;\n session: ClientSession;\n cursor: Cursor;\n indexerId: string;\n}) {\n const { db, session, cursor, indexerId } = props;\n\n await db.collection<FilterSchema>(filterCollectionName).deleteMany(\n {\n id: indexerId,\n toBlock: { $lte: Number(cursor.orderKey) },\n },\n { session },\n );\n}\n\nexport async function resetPersistence(props: {\n db: Db;\n session: ClientSession;\n indexerId: string;\n}) {\n const { db, session, indexerId } = props;\n\n try {\n // Delete all checkpoints for this indexer\n await db\n .collection<CheckpointSchema>(checkpointCollectionName)\n .deleteMany({ id: indexerId }, { session });\n\n // Delete all filters for this indexer\n await db\n .collection<FilterSchema>(filterCollectionName)\n .deleteMany({ id: indexerId }, { session });\n } catch (error) {\n throw new MongoStorageError(\"Failed to reset persistence state\", {\n cause: error,\n });\n }\n}\n","import type { Cursor } from \"@apibara/protocol\";\nimport type {\n BulkWriteOptions,\n ClientSession,\n Collection,\n CollectionOptions,\n Db,\n DeleteOptions,\n Document,\n Filter,\n FindCursor,\n FindOneAndUpdateOptions,\n FindOptions,\n InsertManyResult,\n InsertOneOptions,\n InsertOneResult,\n MatchKeysAndValues,\n OptionalUnlessRequiredId,\n UpdateFilter,\n UpdateOptions,\n UpdateResult,\n WithId,\n} from \"mongodb\";\n\nexport class MongoStorage {\n constructor(\n private db: Db,\n private session: ClientSession,\n private endCursor?: Cursor,\n ) {}\n\n collection<TSchema extends Document = Document>(\n name: string,\n options?: CollectionOptions,\n ) {\n const collection = this.db.collection<TSchema>(name, options);\n\n return new MongoCollection<TSchema>(\n this.session,\n collection,\n this.endCursor,\n );\n }\n}\n\nexport type MongoCursor = {\n from: number | null;\n to: number | null;\n};\n\nexport type CursoredSchema<TSchema extends Document> = TSchema & {\n _cursor: MongoCursor;\n};\n\nexport class MongoCollection<TSchema extends Document> {\n constructor(\n private session: ClientSession,\n private collection: Collection<TSchema>,\n private endCursor?: Cursor,\n ) {}\n\n async insertOne(\n doc: OptionalUnlessRequiredId<TSchema>,\n options?: InsertOneOptions,\n ): Promise<InsertOneResult<TSchema>> {\n return await this.collection.insertOne(\n {\n ...doc,\n _cursor: {\n from: Number(this.endCursor?.orderKey),\n to: null,\n } as MongoCursor,\n },\n { ...options, session: this.session },\n );\n }\n\n async insertMany(\n docs: ReadonlyArray<OptionalUnlessRequiredId<TSchema>>,\n options?: BulkWriteOptions,\n ): Promise<InsertManyResult<TSchema>> {\n return await this.collection.insertMany(\n docs.map((doc) => ({\n ...doc,\n _cursor: {\n from: Number(this.endCursor?.orderKey),\n to: null,\n } as MongoCursor,\n })),\n { ...options, session: this.session },\n );\n }\n\n async updateOne(\n filter: Filter<TSchema>,\n update: UpdateFilter<TSchema>,\n options?: UpdateOptions,\n ): Promise<UpdateResult<TSchema>> {\n // 1. Find and update the document, getting the old version\n const oldDoc = await this.collection.findOneAndUpdate(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n ...update,\n $set: {\n ...update.$set,\n \"_cursor.from\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n {\n ...options,\n session: this.session,\n returnDocument: \"before\",\n } as FindOneAndUpdateOptions,\n );\n\n // 2. If we found and updated a document, insert its old version\n if (oldDoc) {\n const { _id, ...doc } = oldDoc;\n await this.collection.insertOne(\n {\n ...doc,\n _cursor: {\n ...oldDoc._cursor,\n to: Number(this.endCursor?.orderKey),\n },\n } as unknown as OptionalUnlessRequiredId<TSchema>,\n { session: this.session },\n );\n }\n\n // 3. Return an UpdateResult-compatible object\n return {\n acknowledged: true,\n modifiedCount: oldDoc ? 1 : 0,\n upsertedId: null,\n upsertedCount: 0,\n matchedCount: oldDoc ? 1 : 0,\n };\n }\n\n async updateMany(\n filter: Filter<TSchema>,\n update: UpdateFilter<TSchema>,\n options?: UpdateOptions,\n ): Promise<UpdateResult<TSchema>> {\n // 1. Find all documents matching the filter that are latest (to: null)\n const oldDocs = await this.collection\n .find(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { session: this.session },\n )\n .toArray();\n\n // 2. Update to the new values with updateMany\n // (setting _cursor.from to endCursor, leaving _cursor.to unchanged)\n const updateResult = await this.collection.updateMany(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n ...update,\n $set: {\n ...update.$set,\n \"_cursor.from\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n\n // 3. Adjust the cursor.to of the old values\n const oldDocsWithUpdatedCursor = oldDocs.map(({ _id, ...doc }) => ({\n ...doc,\n _cursor: {\n ...doc._cursor,\n to: Number(this.endCursor?.orderKey),\n },\n }));\n\n // 4. Insert the old values back into the db\n if (oldDocsWithUpdatedCursor.length > 0) {\n await this.collection.insertMany(\n oldDocsWithUpdatedCursor as unknown as OptionalUnlessRequiredId<TSchema>[],\n { session: this.session },\n );\n }\n\n return updateResult;\n }\n\n async deleteOne(\n filter: Filter<TSchema>,\n options?: DeleteOptions,\n ): Promise<UpdateResult<TSchema>> {\n return await this.collection.updateOne(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n {\n $set: {\n \"_cursor.to\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n }\n\n async deleteMany(\n filter?: Filter<TSchema>,\n options?: DeleteOptions,\n ): Promise<UpdateResult<TSchema>> {\n return await this.collection.updateMany(\n {\n ...((filter ?? {}) as Filter<TSchema>),\n \"_cursor.to\": null,\n },\n {\n $set: {\n \"_cursor.to\": Number(this.endCursor?.orderKey),\n } as unknown as MatchKeysAndValues<TSchema>,\n },\n { ...options, session: this.session },\n );\n }\n\n async findOne(\n filter: Filter<TSchema>,\n options?: Omit<FindOptions, \"timeoutMode\">,\n ): Promise<WithId<TSchema> | null> {\n return await this.collection.findOne(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { ...options, session: this.session },\n );\n }\n\n find(\n filter: Filter<TSchema>,\n options?: FindOptions,\n ): FindCursor<WithId<TSchema>> {\n return this.collection.find(\n {\n ...filter,\n \"_cursor.to\": null,\n },\n { ...options, session: this.session },\n );\n }\n}\n","import { useIndexerContext } from \"@apibara/indexer\";\nimport { defineIndexerPlugin, useLogger } from \"@apibara/indexer/plugins\";\nimport type { DbOptions, MongoClient } from \"mongodb\";\n\nimport { generateIndexerId } from \"@apibara/indexer/internal\";\nimport { useInternalContext } from \"@apibara/indexer/internal/plugins\";\nimport type { Cursor, DataFinality } from \"@apibara/protocol\";\nimport { cleanupStorage, finalize, invalidate } from \"./mongo\";\nimport {\n finalizeState,\n getState,\n initializePersistentState,\n invalidateState,\n persistState,\n resetPersistence,\n} from \"./persistence\";\nimport { MongoStorage } from \"./storage\";\nimport { MongoStorageError, withTransaction } from \"./utils\";\n\nexport { MongoCollection, MongoStorage } from \"./storage\";\n\nconst MONGO_PROPERTY = \"_mongo\";\n\nexport function useMongoStorage(): MongoStorage {\n const context = useIndexerContext();\n\n if (!context[MONGO_PROPERTY]) {\n throw new MongoStorageError(\n \"mongo storage is not available. Did you register the plugin?\",\n );\n }\n\n return context[MONGO_PROPERTY] as MongoStorage;\n}\n\nexport interface MongoStorageOptions {\n client: MongoClient;\n dbName: string;\n dbOptions?: DbOptions;\n collections: string[];\n persistState?: boolean;\n indexerName?: string;\n}\n/**\n * Creates a plugin that uses MongoDB as the storage layer.\n *\n * Supports storing the indexer's state and provides a simple Key-Value store.\n * @param options.client - The MongoDB client instance.\n * @param options.dbName - The name of the database.\n * @param options.dbOptions - The database options.\n * @param options.collections - The collections to use.\n * @param options.persistState - Whether to persist the indexer's state. Defaults to true.\n * @param options.indexerName - The name of the indexer. Defaults value is 'default'.\n */\nexport function mongoStorage<TFilter, TBlock>({\n client,\n dbName,\n dbOptions,\n collections,\n persistState: enablePersistence = true,\n indexerName: identifier = \"default\",\n}: MongoStorageOptions) {\n return defineIndexerPlugin<TFilter, TBlock>((indexer) => {\n let indexerId = \"\";\n const alwaysReindex = process.env[\"APIBARA_ALWAYS_REINDEX\"] === \"true\";\n let prevFinality: DataFinality | undefined;\n\n indexer.hooks.hook(\"plugins:init\", async () => {\n const { indexerName } = useInternalContext();\n indexerId = generateIndexerId(indexerName, identifier);\n const logger = useLogger();\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n if (enablePersistence) {\n await initializePersistentState(db, session);\n }\n\n if (alwaysReindex) {\n logger.warn(\n `Reindexing: Deleting all data from collections - ${collections.join(\", \")}`,\n );\n\n await cleanupStorage(db, session, collections);\n\n if (enablePersistence) {\n await resetPersistence({ db, session, indexerId });\n }\n\n logger.success(\"All data has been cleaned up for reindexing\");\n }\n });\n });\n\n indexer.hooks.hook(\"connect:before\", async ({ request }) => {\n if (!enablePersistence) {\n return;\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n const { cursor, filter } = await getState<TFilter>({\n db,\n session,\n indexerId,\n });\n\n if (cursor) {\n request.startingCursor = cursor;\n }\n\n if (filter) {\n request.filter[1] = filter;\n }\n });\n });\n\n indexer.hooks.hook(\"connect:after\", async ({ request }) => {\n // On restart, we need to invalidate data for blocks that were processed but not persisted.\n const cursor = request.startingCursor;\n\n if (!cursor) {\n return;\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await invalidate(db, session, cursor, collections);\n\n if (enablePersistence) {\n await invalidateState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"connect:factory\", async ({ request, endCursor }) => {\n if (!enablePersistence) {\n return;\n }\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n if (endCursor && request.filter[1]) {\n await persistState({\n db,\n endCursor,\n session,\n filter: request.filter[1],\n indexerId,\n });\n }\n });\n });\n\n indexer.hooks.hook(\"message:finalize\", async ({ message }) => {\n const { cursor } = message;\n\n if (!cursor) {\n throw new MongoStorageError(\"finalized cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await finalize(db, session, cursor, collections);\n\n if (enablePersistence) {\n await finalizeState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"message:invalidate\", async ({ message }) => {\n const { cursor } = message;\n\n if (!cursor) {\n throw new MongoStorageError(\"invalidate cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n await invalidate(db, session, cursor, collections);\n\n if (enablePersistence) {\n await invalidateState({ db, session, cursor, indexerId });\n }\n });\n });\n\n indexer.hooks.hook(\"handler:middleware\", async ({ use }) => {\n use(async (context, next) => {\n const { endCursor, finality, cursor } = context as {\n cursor: Cursor;\n endCursor: Cursor;\n finality: DataFinality;\n };\n\n if (!endCursor) {\n throw new MongoStorageError(\"end cursor is undefined\");\n }\n\n await withTransaction(client, async (session) => {\n const db = client.db(dbName, dbOptions);\n context[MONGO_PROPERTY] = new MongoStorage(db, session, endCursor);\n\n if (prevFinality === \"pending\") {\n // invalidate if previous block's finality was \"pending\"\n await invalidate(db, session, cursor, collections);\n }\n\n await next();\n\n delete context[MONGO_PROPERTY];\n\n if (enablePersistence && finality !== \"pending\") {\n await persistState({\n db,\n endCursor,\n session,\n indexerId,\n });\n }\n\n prevFinality = finality;\n });\n });\n });\n });\n}\n"],"names":["session"],"mappings":"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"}
|
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.51",
|
|
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.52",
|
|
39
|
+
"@apibara/protocol": "2.1.0-beta.52"
|
|
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
|
-
indexer.hooks.hook("
|
|
68
|
+
indexer.hooks.hook("plugins:init", 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
|
}
|