@apibara/plugin-sqlite 2.0.0-beta.35 → 2.0.0-beta.37
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 +54 -28
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +54 -28
- package/package.json +3 -3
- package/src/index.ts +44 -15
- package/src/persistence.ts +16 -16
- package/src/utils.ts +6 -2
package/dist/index.cjs
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const indexer = require('@apibara/indexer');
|
|
4
4
|
const plugins = require('@apibara/indexer/plugins');
|
|
5
5
|
const protocol = require('@apibara/protocol');
|
|
6
|
+
const internal = require('@apibara/indexer/internal');
|
|
7
|
+
const plugins$1 = require('@apibara/indexer/internal/plugins');
|
|
6
8
|
|
|
7
9
|
class SqliteStorageError extends Error {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, options);
|
|
10
12
|
this.name = "SqliteStorageError";
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -38,6 +40,9 @@ function serialize(obj) {
|
|
|
38
40
|
" "
|
|
39
41
|
);
|
|
40
42
|
}
|
|
43
|
+
function sleep(ms) {
|
|
44
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
function initializeKeyValueStore(db) {
|
|
43
48
|
assertInTransaction(db);
|
|
@@ -125,32 +130,32 @@ function initializePersistentState(db) {
|
|
|
125
130
|
db.exec(statements.createFiltersTable);
|
|
126
131
|
}
|
|
127
132
|
function persistState(props) {
|
|
128
|
-
const { db, endCursor, filter,
|
|
133
|
+
const { db, endCursor, filter, indexerId } = props;
|
|
129
134
|
assertInTransaction(db);
|
|
130
135
|
db.prepare(statements.putCheckpoint).run(
|
|
131
|
-
|
|
136
|
+
indexerId,
|
|
132
137
|
Number(endCursor.orderKey),
|
|
133
138
|
endCursor.uniqueKey
|
|
134
139
|
);
|
|
135
140
|
if (filter) {
|
|
136
141
|
db.prepare(statements.updateFilterToBlock).run(
|
|
137
142
|
Number(endCursor.orderKey),
|
|
138
|
-
|
|
143
|
+
indexerId
|
|
139
144
|
);
|
|
140
145
|
db.prepare(statements.insertFilter).run(
|
|
141
|
-
|
|
146
|
+
indexerId,
|
|
142
147
|
serialize(filter),
|
|
143
148
|
Number(endCursor.orderKey)
|
|
144
149
|
);
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
function getState(props) {
|
|
148
|
-
const { db,
|
|
153
|
+
const { db, indexerId } = props;
|
|
149
154
|
assertInTransaction(db);
|
|
150
155
|
const storedCursor = db.prepare(
|
|
151
156
|
statements.getCheckpoint
|
|
152
|
-
).get(
|
|
153
|
-
const storedFilter = db.prepare(statements.getFilter).get(
|
|
157
|
+
).get(indexerId);
|
|
158
|
+
const storedFilter = db.prepare(statements.getFilter).get(indexerId);
|
|
154
159
|
let cursor;
|
|
155
160
|
let filter;
|
|
156
161
|
if (storedCursor?.order_key) {
|
|
@@ -165,22 +170,22 @@ function getState(props) {
|
|
|
165
170
|
return { cursor, filter };
|
|
166
171
|
}
|
|
167
172
|
function finalizeState(props) {
|
|
168
|
-
const { cursor, db,
|
|
173
|
+
const { cursor, db, indexerId } = props;
|
|
169
174
|
assertInTransaction(db);
|
|
170
175
|
db.prepare(statements.finalizeFilter).run(
|
|
171
|
-
|
|
176
|
+
indexerId,
|
|
172
177
|
Number(cursor.orderKey)
|
|
173
178
|
);
|
|
174
179
|
}
|
|
175
180
|
function invalidateState(props) {
|
|
176
|
-
const { cursor, db,
|
|
181
|
+
const { cursor, db, indexerId } = props;
|
|
177
182
|
assertInTransaction(db);
|
|
178
183
|
db.prepare(statements.invalidateFilterDelete).run(
|
|
179
|
-
|
|
184
|
+
indexerId,
|
|
180
185
|
Number(cursor.orderKey)
|
|
181
186
|
);
|
|
182
187
|
db.prepare(statements.invalidateFilterUpdate).run(
|
|
183
|
-
|
|
188
|
+
indexerId,
|
|
184
189
|
Number(cursor.orderKey)
|
|
185
190
|
);
|
|
186
191
|
}
|
|
@@ -242,6 +247,7 @@ const statements = {
|
|
|
242
247
|
};
|
|
243
248
|
|
|
244
249
|
const KV_PROPERTY = "_kv_sqlite";
|
|
250
|
+
const MAX_RETRIES = 5;
|
|
245
251
|
function useSqliteKeyValueStore() {
|
|
246
252
|
const kv = indexer.useIndexerContext()[KV_PROPERTY];
|
|
247
253
|
if (!kv) {
|
|
@@ -257,25 +263,45 @@ function sqliteStorage({
|
|
|
257
263
|
keyValueStore: enableKeyValueStore = true,
|
|
258
264
|
serialize: serializeFn = serialize,
|
|
259
265
|
deserialize: deserializeFn = deserialize,
|
|
260
|
-
indexerName = "default"
|
|
266
|
+
indexerName: identifier = "default"
|
|
261
267
|
}) {
|
|
262
268
|
return plugins.defineIndexerPlugin((indexer) => {
|
|
269
|
+
let indexerId = "";
|
|
263
270
|
indexer.hooks.hook("run:before", async () => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
const { indexerName: indexerFileName, availableIndexers } = plugins$1.useInternalContext();
|
|
272
|
+
indexerId = internal.generateIndexerId(indexerFileName, identifier);
|
|
273
|
+
let retries = 0;
|
|
274
|
+
while (retries <= MAX_RETRIES) {
|
|
275
|
+
try {
|
|
276
|
+
await withTransaction(database, async (db) => {
|
|
277
|
+
if (enablePersistState) {
|
|
278
|
+
initializePersistentState(db);
|
|
279
|
+
}
|
|
280
|
+
if (enableKeyValueStore) {
|
|
281
|
+
initializeKeyValueStore(db);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
break;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (retries === MAX_RETRIES) {
|
|
287
|
+
throw new SqliteStorageError(
|
|
288
|
+
"Initialization failed after 5 retries",
|
|
289
|
+
{
|
|
290
|
+
cause: error
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
await sleep(retries * 1e3);
|
|
295
|
+
retries++;
|
|
270
296
|
}
|
|
271
|
-
}
|
|
297
|
+
}
|
|
272
298
|
});
|
|
273
299
|
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
274
300
|
if (!enablePersistState) {
|
|
275
301
|
return;
|
|
276
302
|
}
|
|
277
303
|
return await withTransaction(database, async (db) => {
|
|
278
|
-
const { cursor, filter } = getState({ db,
|
|
304
|
+
const { cursor, filter } = getState({ db, indexerId });
|
|
279
305
|
if (cursor) {
|
|
280
306
|
request.startingCursor = cursor;
|
|
281
307
|
}
|
|
@@ -291,7 +317,7 @@ function sqliteStorage({
|
|
|
291
317
|
}
|
|
292
318
|
await withTransaction(database, async (db) => {
|
|
293
319
|
if (enablePersistState) {
|
|
294
|
-
invalidateState({ db, cursor,
|
|
320
|
+
invalidateState({ db, cursor, indexerId });
|
|
295
321
|
}
|
|
296
322
|
if (enableKeyValueStore) {
|
|
297
323
|
invalidateKV(db, cursor);
|
|
@@ -307,7 +333,7 @@ function sqliteStorage({
|
|
|
307
333
|
persistState({
|
|
308
334
|
db: database,
|
|
309
335
|
endCursor,
|
|
310
|
-
|
|
336
|
+
indexerId,
|
|
311
337
|
filter: request.filter[1]
|
|
312
338
|
});
|
|
313
339
|
}
|
|
@@ -319,7 +345,7 @@ function sqliteStorage({
|
|
|
319
345
|
}
|
|
320
346
|
await withTransaction(database, async (db) => {
|
|
321
347
|
if (enablePersistState) {
|
|
322
|
-
finalizeState({ db, cursor,
|
|
348
|
+
finalizeState({ db, cursor, indexerId });
|
|
323
349
|
}
|
|
324
350
|
if (enableKeyValueStore) {
|
|
325
351
|
finalizeKV(db, cursor);
|
|
@@ -333,7 +359,7 @@ function sqliteStorage({
|
|
|
333
359
|
}
|
|
334
360
|
await withTransaction(database, async (db) => {
|
|
335
361
|
if (enablePersistState) {
|
|
336
|
-
invalidateState({ db, cursor,
|
|
362
|
+
invalidateState({ db, cursor, indexerId });
|
|
337
363
|
}
|
|
338
364
|
if (enableKeyValueStore) {
|
|
339
365
|
invalidateKV(db, cursor);
|
|
@@ -362,7 +388,7 @@ function sqliteStorage({
|
|
|
362
388
|
}
|
|
363
389
|
await next();
|
|
364
390
|
if (enablePersistState) {
|
|
365
|
-
persistState({ db, endCursor: ctx.endCursor,
|
|
391
|
+
persistState({ db, endCursor: ctx.endCursor, indexerId });
|
|
366
392
|
}
|
|
367
393
|
if (enableKeyValueStore) {
|
|
368
394
|
delete ctx[KV_PROPERTY];
|
package/dist/index.d.cts
CHANGED
|
@@ -37,6 +37,6 @@ type SqliteStorageOptions = {
|
|
|
37
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
38
|
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
39
39
|
*/
|
|
40
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName: identifier, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
41
41
|
|
|
42
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.d.mts
CHANGED
|
@@ -37,6 +37,6 @@ type SqliteStorageOptions = {
|
|
|
37
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
38
|
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
39
39
|
*/
|
|
40
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName: identifier, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
41
41
|
|
|
42
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,6 @@ type SqliteStorageOptions = {
|
|
|
37
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
38
|
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
39
39
|
*/
|
|
40
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName: identifier, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
41
41
|
|
|
42
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useIndexerContext } from '@apibara/indexer';
|
|
2
2
|
import { defineIndexerPlugin } from '@apibara/indexer/plugins';
|
|
3
3
|
import { isCursor } from '@apibara/protocol';
|
|
4
|
+
import { generateIndexerId } from '@apibara/indexer/internal';
|
|
5
|
+
import { useInternalContext } from '@apibara/indexer/internal/plugins';
|
|
4
6
|
|
|
5
7
|
class SqliteStorageError extends Error {
|
|
6
|
-
constructor(message) {
|
|
7
|
-
super(message);
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
8
10
|
this.name = "SqliteStorageError";
|
|
9
11
|
}
|
|
10
12
|
}
|
|
@@ -36,6 +38,9 @@ function serialize(obj) {
|
|
|
36
38
|
" "
|
|
37
39
|
);
|
|
38
40
|
}
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
39
44
|
|
|
40
45
|
function initializeKeyValueStore(db) {
|
|
41
46
|
assertInTransaction(db);
|
|
@@ -123,32 +128,32 @@ function initializePersistentState(db) {
|
|
|
123
128
|
db.exec(statements.createFiltersTable);
|
|
124
129
|
}
|
|
125
130
|
function persistState(props) {
|
|
126
|
-
const { db, endCursor, filter,
|
|
131
|
+
const { db, endCursor, filter, indexerId } = props;
|
|
127
132
|
assertInTransaction(db);
|
|
128
133
|
db.prepare(statements.putCheckpoint).run(
|
|
129
|
-
|
|
134
|
+
indexerId,
|
|
130
135
|
Number(endCursor.orderKey),
|
|
131
136
|
endCursor.uniqueKey
|
|
132
137
|
);
|
|
133
138
|
if (filter) {
|
|
134
139
|
db.prepare(statements.updateFilterToBlock).run(
|
|
135
140
|
Number(endCursor.orderKey),
|
|
136
|
-
|
|
141
|
+
indexerId
|
|
137
142
|
);
|
|
138
143
|
db.prepare(statements.insertFilter).run(
|
|
139
|
-
|
|
144
|
+
indexerId,
|
|
140
145
|
serialize(filter),
|
|
141
146
|
Number(endCursor.orderKey)
|
|
142
147
|
);
|
|
143
148
|
}
|
|
144
149
|
}
|
|
145
150
|
function getState(props) {
|
|
146
|
-
const { db,
|
|
151
|
+
const { db, indexerId } = props;
|
|
147
152
|
assertInTransaction(db);
|
|
148
153
|
const storedCursor = db.prepare(
|
|
149
154
|
statements.getCheckpoint
|
|
150
|
-
).get(
|
|
151
|
-
const storedFilter = db.prepare(statements.getFilter).get(
|
|
155
|
+
).get(indexerId);
|
|
156
|
+
const storedFilter = db.prepare(statements.getFilter).get(indexerId);
|
|
152
157
|
let cursor;
|
|
153
158
|
let filter;
|
|
154
159
|
if (storedCursor?.order_key) {
|
|
@@ -163,22 +168,22 @@ function getState(props) {
|
|
|
163
168
|
return { cursor, filter };
|
|
164
169
|
}
|
|
165
170
|
function finalizeState(props) {
|
|
166
|
-
const { cursor, db,
|
|
171
|
+
const { cursor, db, indexerId } = props;
|
|
167
172
|
assertInTransaction(db);
|
|
168
173
|
db.prepare(statements.finalizeFilter).run(
|
|
169
|
-
|
|
174
|
+
indexerId,
|
|
170
175
|
Number(cursor.orderKey)
|
|
171
176
|
);
|
|
172
177
|
}
|
|
173
178
|
function invalidateState(props) {
|
|
174
|
-
const { cursor, db,
|
|
179
|
+
const { cursor, db, indexerId } = props;
|
|
175
180
|
assertInTransaction(db);
|
|
176
181
|
db.prepare(statements.invalidateFilterDelete).run(
|
|
177
|
-
|
|
182
|
+
indexerId,
|
|
178
183
|
Number(cursor.orderKey)
|
|
179
184
|
);
|
|
180
185
|
db.prepare(statements.invalidateFilterUpdate).run(
|
|
181
|
-
|
|
186
|
+
indexerId,
|
|
182
187
|
Number(cursor.orderKey)
|
|
183
188
|
);
|
|
184
189
|
}
|
|
@@ -240,6 +245,7 @@ const statements = {
|
|
|
240
245
|
};
|
|
241
246
|
|
|
242
247
|
const KV_PROPERTY = "_kv_sqlite";
|
|
248
|
+
const MAX_RETRIES = 5;
|
|
243
249
|
function useSqliteKeyValueStore() {
|
|
244
250
|
const kv = useIndexerContext()[KV_PROPERTY];
|
|
245
251
|
if (!kv) {
|
|
@@ -255,25 +261,45 @@ function sqliteStorage({
|
|
|
255
261
|
keyValueStore: enableKeyValueStore = true,
|
|
256
262
|
serialize: serializeFn = serialize,
|
|
257
263
|
deserialize: deserializeFn = deserialize,
|
|
258
|
-
indexerName = "default"
|
|
264
|
+
indexerName: identifier = "default"
|
|
259
265
|
}) {
|
|
260
266
|
return defineIndexerPlugin((indexer) => {
|
|
267
|
+
let indexerId = "";
|
|
261
268
|
indexer.hooks.hook("run:before", async () => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
const { indexerName: indexerFileName, availableIndexers } = useInternalContext();
|
|
270
|
+
indexerId = generateIndexerId(indexerFileName, identifier);
|
|
271
|
+
let retries = 0;
|
|
272
|
+
while (retries <= MAX_RETRIES) {
|
|
273
|
+
try {
|
|
274
|
+
await withTransaction(database, async (db) => {
|
|
275
|
+
if (enablePersistState) {
|
|
276
|
+
initializePersistentState(db);
|
|
277
|
+
}
|
|
278
|
+
if (enableKeyValueStore) {
|
|
279
|
+
initializeKeyValueStore(db);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
break;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (retries === MAX_RETRIES) {
|
|
285
|
+
throw new SqliteStorageError(
|
|
286
|
+
"Initialization failed after 5 retries",
|
|
287
|
+
{
|
|
288
|
+
cause: error
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
await sleep(retries * 1e3);
|
|
293
|
+
retries++;
|
|
268
294
|
}
|
|
269
|
-
}
|
|
295
|
+
}
|
|
270
296
|
});
|
|
271
297
|
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
272
298
|
if (!enablePersistState) {
|
|
273
299
|
return;
|
|
274
300
|
}
|
|
275
301
|
return await withTransaction(database, async (db) => {
|
|
276
|
-
const { cursor, filter } = getState({ db,
|
|
302
|
+
const { cursor, filter } = getState({ db, indexerId });
|
|
277
303
|
if (cursor) {
|
|
278
304
|
request.startingCursor = cursor;
|
|
279
305
|
}
|
|
@@ -289,7 +315,7 @@ function sqliteStorage({
|
|
|
289
315
|
}
|
|
290
316
|
await withTransaction(database, async (db) => {
|
|
291
317
|
if (enablePersistState) {
|
|
292
|
-
invalidateState({ db, cursor,
|
|
318
|
+
invalidateState({ db, cursor, indexerId });
|
|
293
319
|
}
|
|
294
320
|
if (enableKeyValueStore) {
|
|
295
321
|
invalidateKV(db, cursor);
|
|
@@ -305,7 +331,7 @@ function sqliteStorage({
|
|
|
305
331
|
persistState({
|
|
306
332
|
db: database,
|
|
307
333
|
endCursor,
|
|
308
|
-
|
|
334
|
+
indexerId,
|
|
309
335
|
filter: request.filter[1]
|
|
310
336
|
});
|
|
311
337
|
}
|
|
@@ -317,7 +343,7 @@ function sqliteStorage({
|
|
|
317
343
|
}
|
|
318
344
|
await withTransaction(database, async (db) => {
|
|
319
345
|
if (enablePersistState) {
|
|
320
|
-
finalizeState({ db, cursor,
|
|
346
|
+
finalizeState({ db, cursor, indexerId });
|
|
321
347
|
}
|
|
322
348
|
if (enableKeyValueStore) {
|
|
323
349
|
finalizeKV(db, cursor);
|
|
@@ -331,7 +357,7 @@ function sqliteStorage({
|
|
|
331
357
|
}
|
|
332
358
|
await withTransaction(database, async (db) => {
|
|
333
359
|
if (enablePersistState) {
|
|
334
|
-
invalidateState({ db, cursor,
|
|
360
|
+
invalidateState({ db, cursor, indexerId });
|
|
335
361
|
}
|
|
336
362
|
if (enableKeyValueStore) {
|
|
337
363
|
invalidateKV(db, cursor);
|
|
@@ -360,7 +386,7 @@ function sqliteStorage({
|
|
|
360
386
|
}
|
|
361
387
|
await next();
|
|
362
388
|
if (enablePersistState) {
|
|
363
|
-
persistState({ db, endCursor: ctx.endCursor,
|
|
389
|
+
persistState({ db, endCursor: ctx.endCursor, indexerId });
|
|
364
390
|
}
|
|
365
391
|
if (enableKeyValueStore) {
|
|
366
392
|
delete ctx[KV_PROPERTY];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/plugin-sqlite",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.37",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"better-sqlite3": "^9.0.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@apibara/indexer": "2.0.0-beta.
|
|
39
|
-
"@apibara/protocol": "2.0.0-beta.
|
|
38
|
+
"@apibara/indexer": "2.0.0-beta.38",
|
|
39
|
+
"@apibara/protocol": "2.0.0-beta.38"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { defineIndexerPlugin } from "@apibara/indexer/plugins";
|
|
|
3
3
|
import { isCursor } from "@apibara/protocol";
|
|
4
4
|
import type { Database as SqliteDatabase } from "better-sqlite3";
|
|
5
5
|
|
|
6
|
+
import { generateIndexerId } from "@apibara/indexer/internal";
|
|
7
|
+
import { useInternalContext } from "@apibara/indexer/internal/plugins";
|
|
6
8
|
import {
|
|
7
9
|
KeyValueStore,
|
|
8
10
|
finalizeKV,
|
|
@@ -23,10 +25,12 @@ import {
|
|
|
23
25
|
assertInTransaction,
|
|
24
26
|
deserialize,
|
|
25
27
|
serialize,
|
|
28
|
+
sleep,
|
|
26
29
|
withTransaction,
|
|
27
30
|
} from "./utils";
|
|
28
31
|
|
|
29
32
|
const KV_PROPERTY = "_kv_sqlite" as const;
|
|
33
|
+
const MAX_RETRIES = 5;
|
|
30
34
|
|
|
31
35
|
export { KeyValueStore } from "./kv";
|
|
32
36
|
|
|
@@ -68,19 +72,44 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
68
72
|
keyValueStore: enableKeyValueStore = true,
|
|
69
73
|
serialize: serializeFn = serialize,
|
|
70
74
|
deserialize: deserializeFn = deserialize,
|
|
71
|
-
indexerName = "default",
|
|
75
|
+
indexerName: identifier = "default",
|
|
72
76
|
}: SqliteStorageOptions) {
|
|
73
77
|
return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
|
|
74
|
-
|
|
75
|
-
await withTransaction(database, async (db) => {
|
|
76
|
-
if (enablePersistState) {
|
|
77
|
-
initializePersistentState(db);
|
|
78
|
-
}
|
|
78
|
+
let indexerId = "";
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
indexer.hooks.hook("run:before", async () => {
|
|
81
|
+
const { indexerName: indexerFileName, availableIndexers } =
|
|
82
|
+
useInternalContext();
|
|
83
|
+
|
|
84
|
+
indexerId = generateIndexerId(indexerFileName, identifier);
|
|
85
|
+
|
|
86
|
+
let retries = 0;
|
|
87
|
+
|
|
88
|
+
while (retries <= MAX_RETRIES) {
|
|
89
|
+
try {
|
|
90
|
+
await withTransaction(database, async (db) => {
|
|
91
|
+
if (enablePersistState) {
|
|
92
|
+
initializePersistentState(db);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (enableKeyValueStore) {
|
|
96
|
+
initializeKeyValueStore(db);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (retries === MAX_RETRIES) {
|
|
102
|
+
throw new SqliteStorageError(
|
|
103
|
+
"Initialization failed after 5 retries",
|
|
104
|
+
{
|
|
105
|
+
cause: error,
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
await sleep(retries * 1000);
|
|
110
|
+
retries++;
|
|
82
111
|
}
|
|
83
|
-
}
|
|
112
|
+
}
|
|
84
113
|
});
|
|
85
114
|
|
|
86
115
|
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
@@ -89,7 +118,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
89
118
|
}
|
|
90
119
|
|
|
91
120
|
return await withTransaction(database, async (db) => {
|
|
92
|
-
const { cursor, filter } = getState<TFilter>({ db,
|
|
121
|
+
const { cursor, filter } = getState<TFilter>({ db, indexerId });
|
|
93
122
|
|
|
94
123
|
if (cursor) {
|
|
95
124
|
request.startingCursor = cursor;
|
|
@@ -111,7 +140,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
111
140
|
|
|
112
141
|
await withTransaction(database, async (db) => {
|
|
113
142
|
if (enablePersistState) {
|
|
114
|
-
invalidateState({ db, cursor,
|
|
143
|
+
invalidateState({ db, cursor, indexerId });
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
if (enableKeyValueStore) {
|
|
@@ -133,7 +162,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
133
162
|
persistState({
|
|
134
163
|
db: database,
|
|
135
164
|
endCursor,
|
|
136
|
-
|
|
165
|
+
indexerId,
|
|
137
166
|
filter: request.filter[1],
|
|
138
167
|
});
|
|
139
168
|
}
|
|
@@ -148,7 +177,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
148
177
|
|
|
149
178
|
await withTransaction(database, async (db) => {
|
|
150
179
|
if (enablePersistState) {
|
|
151
|
-
finalizeState({ db, cursor,
|
|
180
|
+
finalizeState({ db, cursor, indexerId });
|
|
152
181
|
}
|
|
153
182
|
|
|
154
183
|
if (enableKeyValueStore) {
|
|
@@ -166,7 +195,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
166
195
|
|
|
167
196
|
await withTransaction(database, async (db) => {
|
|
168
197
|
if (enablePersistState) {
|
|
169
|
-
invalidateState({ db, cursor,
|
|
198
|
+
invalidateState({ db, cursor, indexerId });
|
|
170
199
|
}
|
|
171
200
|
|
|
172
201
|
if (enableKeyValueStore) {
|
|
@@ -201,7 +230,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
201
230
|
await next();
|
|
202
231
|
|
|
203
232
|
if (enablePersistState) {
|
|
204
|
-
persistState({ db, endCursor: ctx.endCursor,
|
|
233
|
+
persistState({ db, endCursor: ctx.endCursor, indexerId });
|
|
205
234
|
}
|
|
206
235
|
|
|
207
236
|
if (enableKeyValueStore) {
|
package/src/persistence.ts
CHANGED
|
@@ -13,14 +13,14 @@ export function persistState<TFilter>(props: {
|
|
|
13
13
|
db: Database;
|
|
14
14
|
endCursor: Cursor;
|
|
15
15
|
filter?: TFilter;
|
|
16
|
-
|
|
16
|
+
indexerId: string;
|
|
17
17
|
}) {
|
|
18
|
-
const { db, endCursor, filter,
|
|
18
|
+
const { db, endCursor, filter, indexerId } = props;
|
|
19
19
|
|
|
20
20
|
assertInTransaction(db);
|
|
21
21
|
|
|
22
22
|
db.prepare(statements.putCheckpoint).run(
|
|
23
|
-
|
|
23
|
+
indexerId,
|
|
24
24
|
Number(endCursor.orderKey),
|
|
25
25
|
endCursor.uniqueKey,
|
|
26
26
|
);
|
|
@@ -28,10 +28,10 @@ export function persistState<TFilter>(props: {
|
|
|
28
28
|
if (filter) {
|
|
29
29
|
db.prepare(statements.updateFilterToBlock).run(
|
|
30
30
|
Number(endCursor.orderKey),
|
|
31
|
-
|
|
31
|
+
indexerId,
|
|
32
32
|
);
|
|
33
33
|
db.prepare(statements.insertFilter).run(
|
|
34
|
-
|
|
34
|
+
indexerId,
|
|
35
35
|
serialize(filter as Record<string, unknown>),
|
|
36
36
|
Number(endCursor.orderKey),
|
|
37
37
|
);
|
|
@@ -40,18 +40,18 @@ export function persistState<TFilter>(props: {
|
|
|
40
40
|
|
|
41
41
|
export function getState<TFilter>(props: {
|
|
42
42
|
db: Database;
|
|
43
|
-
|
|
43
|
+
indexerId: string;
|
|
44
44
|
}) {
|
|
45
|
-
const { db,
|
|
45
|
+
const { db, indexerId } = props;
|
|
46
46
|
assertInTransaction(db);
|
|
47
47
|
const storedCursor = db
|
|
48
48
|
.prepare<string, { order_key?: number; unique_key?: string }>(
|
|
49
49
|
statements.getCheckpoint,
|
|
50
50
|
)
|
|
51
|
-
.get(
|
|
51
|
+
.get(indexerId);
|
|
52
52
|
const storedFilter = db
|
|
53
53
|
.prepare<string, { filter: string }>(statements.getFilter)
|
|
54
|
-
.get(
|
|
54
|
+
.get(indexerId);
|
|
55
55
|
|
|
56
56
|
let cursor: Cursor | undefined;
|
|
57
57
|
let filter: TFilter | undefined;
|
|
@@ -73,12 +73,12 @@ export function getState<TFilter>(props: {
|
|
|
73
73
|
export function finalizeState(props: {
|
|
74
74
|
db: Database;
|
|
75
75
|
cursor: Cursor;
|
|
76
|
-
|
|
76
|
+
indexerId: string;
|
|
77
77
|
}) {
|
|
78
|
-
const { cursor, db,
|
|
78
|
+
const { cursor, db, indexerId } = props;
|
|
79
79
|
assertInTransaction(db);
|
|
80
80
|
db.prepare<[string, number]>(statements.finalizeFilter).run(
|
|
81
|
-
|
|
81
|
+
indexerId,
|
|
82
82
|
Number(cursor.orderKey),
|
|
83
83
|
);
|
|
84
84
|
}
|
|
@@ -86,16 +86,16 @@ export function finalizeState(props: {
|
|
|
86
86
|
export function invalidateState(props: {
|
|
87
87
|
db: Database;
|
|
88
88
|
cursor: Cursor;
|
|
89
|
-
|
|
89
|
+
indexerId: string;
|
|
90
90
|
}) {
|
|
91
|
-
const { cursor, db,
|
|
91
|
+
const { cursor, db, indexerId } = props;
|
|
92
92
|
assertInTransaction(db);
|
|
93
93
|
db.prepare<[string, number]>(statements.invalidateFilterDelete).run(
|
|
94
|
-
|
|
94
|
+
indexerId,
|
|
95
95
|
Number(cursor.orderKey),
|
|
96
96
|
);
|
|
97
97
|
db.prepare<[string, number]>(statements.invalidateFilterUpdate).run(
|
|
98
|
-
|
|
98
|
+
indexerId,
|
|
99
99
|
Number(cursor.orderKey),
|
|
100
100
|
);
|
|
101
101
|
}
|
package/src/utils.ts
CHANGED
|
@@ -4,8 +4,8 @@ export type SerializeFn = <T>(value: T) => string;
|
|
|
4
4
|
export type DeserializeFn = <T>(value: string) => T;
|
|
5
5
|
|
|
6
6
|
export class SqliteStorageError extends Error {
|
|
7
|
-
constructor(message: string) {
|
|
8
|
-
super(message);
|
|
7
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
8
|
+
super(message, options);
|
|
9
9
|
this.name = "SqliteStorageError";
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -45,3 +45,7 @@ export function serialize<T>(obj: T): string {
|
|
|
45
45
|
"\t",
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
export function sleep(ms: number) {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
|
+
}
|