@apibara/indexer 2.0.0-beta.20 → 2.0.0-beta.22
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 +45 -1
- 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 +45 -2
- package/dist/plugins/index.d.cts +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/kv.cjs +1 -0
- package/dist/plugins/kv.d.cts +1 -1
- package/dist/plugins/kv.d.mts +1 -1
- package/dist/plugins/kv.d.ts +1 -1
- package/dist/plugins/kv.mjs +1 -0
- package/dist/plugins/logger.d.cts +1 -1
- package/dist/plugins/logger.d.mts +1 -1
- package/dist/plugins/logger.d.ts +1 -1
- package/dist/plugins/persistence.cjs +27 -0
- package/dist/plugins/persistence.d.cts +3 -2
- package/dist/plugins/persistence.d.mts +3 -2
- package/dist/plugins/persistence.d.ts +3 -2
- package/dist/plugins/persistence.mjs +27 -1
- package/dist/shared/{indexer.f97f8e70.d.cts → indexer.3bc6abe3.d.cts} +11 -2
- package/dist/shared/{indexer.5a040ab3.d.ts → indexer.a05207df.d.ts} +11 -2
- package/dist/shared/{indexer.6f70ff10.d.mts → indexer.b4be75f1.d.mts} +11 -2
- package/dist/testing/index.d.cts +1 -1
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/vcr/index.cjs +1 -0
- package/dist/vcr/index.d.cts +1 -1
- package/dist/vcr/index.d.mts +1 -1
- package/dist/vcr/index.d.ts +1 -1
- package/dist/vcr/index.mjs +1 -0
- package/package.json +2 -2
- package/src/indexer.ts +85 -12
- package/src/plugins/persistence.ts +30 -0
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const protocol = require('@apibara/protocol');
|
|
3
4
|
const consola = require('consola');
|
|
4
5
|
const hookable = require('hookable');
|
|
5
6
|
const assert = require('node:assert');
|
|
@@ -39,7 +40,42 @@ function createIndexer({
|
|
|
39
40
|
}
|
|
40
41
|
return indexer;
|
|
41
42
|
}
|
|
42
|
-
async function
|
|
43
|
+
async function runWithReconnect(client, indexer, options = {}) {
|
|
44
|
+
let retryCount = 0;
|
|
45
|
+
const maxRetries = options.maxRetries ?? 10;
|
|
46
|
+
const retryDelay = options.retryDelay ?? 1e3;
|
|
47
|
+
const maxWait = options.maxWait ?? 3e4;
|
|
48
|
+
const runOptions = {
|
|
49
|
+
onConnect() {
|
|
50
|
+
retryCount = 0;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
while (true) {
|
|
54
|
+
try {
|
|
55
|
+
await run(client, indexer, runOptions);
|
|
56
|
+
return;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
retryCount++;
|
|
59
|
+
if (error instanceof protocol.ClientError) {
|
|
60
|
+
if (error.code === protocol.Status.INTERNAL) {
|
|
61
|
+
if (retryCount < maxRetries) {
|
|
62
|
+
consola__default.error(
|
|
63
|
+
"Internal server error, reconnecting...",
|
|
64
|
+
error.message
|
|
65
|
+
);
|
|
66
|
+
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
67
|
+
await new Promise(
|
|
68
|
+
(resolve) => setTimeout(resolve, Math.min(retryCount * delay, maxWait))
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function run(client, indexer, runOptions = {}) {
|
|
43
79
|
await context.indexerAsyncContext.callAsync({}, async () => {
|
|
44
80
|
const context$1 = context.useIndexerContext();
|
|
45
81
|
const sink$1 = indexer.options.sink ?? sink.defaultSink();
|
|
@@ -60,11 +96,18 @@ async function run(client, indexer) {
|
|
|
60
96
|
}
|
|
61
97
|
let stream = client.streamData(request, options)[Symbol.asyncIterator]();
|
|
62
98
|
await indexer.hooks.callHook("connect:after");
|
|
99
|
+
let onConnectCalled = false;
|
|
63
100
|
while (true) {
|
|
64
101
|
const { value: message, done } = await stream.next();
|
|
65
102
|
if (done) {
|
|
66
103
|
break;
|
|
67
104
|
}
|
|
105
|
+
if (!onConnectCalled) {
|
|
106
|
+
onConnectCalled = true;
|
|
107
|
+
if (runOptions.onConnect) {
|
|
108
|
+
await runOptions.onConnect();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
68
111
|
await indexer.hooks.callHook("message", { message });
|
|
69
112
|
switch (message._tag) {
|
|
70
113
|
case "data": {
|
|
@@ -212,3 +255,4 @@ exports.useSink = sink.useSink;
|
|
|
212
255
|
exports.createIndexer = createIndexer;
|
|
213
256
|
exports.defineIndexer = defineIndexer;
|
|
214
257
|
exports.run = run;
|
|
258
|
+
exports.runWithReconnect = runWithReconnect;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, g as createIndexer, f as defineIndexer,
|
|
1
|
+
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect } from './shared/indexer.3bc6abe3.cjs';
|
|
2
2
|
export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink, u as useIndexerContext, c as useSink } from './shared/indexer.47b4546b.cjs';
|
|
3
3
|
import '@apibara/protocol';
|
|
4
4
|
import 'hookable';
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, g as createIndexer, f as defineIndexer,
|
|
1
|
+
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect } from './shared/indexer.b4be75f1.mjs';
|
|
2
2
|
export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink, u as useIndexerContext, c as useSink } from './shared/indexer.47b4546b.mjs';
|
|
3
3
|
import '@apibara/protocol';
|
|
4
4
|
import 'hookable';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, g as createIndexer, f as defineIndexer,
|
|
1
|
+
export { b as Indexer, e as IndexerConfig, c as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect } from './shared/indexer.a05207df.js';
|
|
2
2
|
export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink, u as useIndexerContext, c as useSink } from './shared/indexer.47b4546b.js';
|
|
3
3
|
import '@apibara/protocol';
|
|
4
4
|
import 'hookable';
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientError, Status } from '@apibara/protocol';
|
|
1
2
|
import consola from 'consola';
|
|
2
3
|
import { createHooks, createDebugger } from 'hookable';
|
|
3
4
|
import assert from 'node:assert';
|
|
@@ -33,7 +34,42 @@ function createIndexer({
|
|
|
33
34
|
}
|
|
34
35
|
return indexer;
|
|
35
36
|
}
|
|
36
|
-
async function
|
|
37
|
+
async function runWithReconnect(client, indexer, options = {}) {
|
|
38
|
+
let retryCount = 0;
|
|
39
|
+
const maxRetries = options.maxRetries ?? 10;
|
|
40
|
+
const retryDelay = options.retryDelay ?? 1e3;
|
|
41
|
+
const maxWait = options.maxWait ?? 3e4;
|
|
42
|
+
const runOptions = {
|
|
43
|
+
onConnect() {
|
|
44
|
+
retryCount = 0;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
while (true) {
|
|
48
|
+
try {
|
|
49
|
+
await run(client, indexer, runOptions);
|
|
50
|
+
return;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
retryCount++;
|
|
53
|
+
if (error instanceof ClientError) {
|
|
54
|
+
if (error.code === Status.INTERNAL) {
|
|
55
|
+
if (retryCount < maxRetries) {
|
|
56
|
+
consola.error(
|
|
57
|
+
"Internal server error, reconnecting...",
|
|
58
|
+
error.message
|
|
59
|
+
);
|
|
60
|
+
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
61
|
+
await new Promise(
|
|
62
|
+
(resolve) => setTimeout(resolve, Math.min(retryCount * delay, maxWait))
|
|
63
|
+
);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function run(client, indexer, runOptions = {}) {
|
|
37
73
|
await indexerAsyncContext.callAsync({}, async () => {
|
|
38
74
|
const context = useIndexerContext();
|
|
39
75
|
const sink = indexer.options.sink ?? defaultSink();
|
|
@@ -54,11 +90,18 @@ async function run(client, indexer) {
|
|
|
54
90
|
}
|
|
55
91
|
let stream = client.streamData(request, options)[Symbol.asyncIterator]();
|
|
56
92
|
await indexer.hooks.callHook("connect:after");
|
|
93
|
+
let onConnectCalled = false;
|
|
57
94
|
while (true) {
|
|
58
95
|
const { value: message, done } = await stream.next();
|
|
59
96
|
if (done) {
|
|
60
97
|
break;
|
|
61
98
|
}
|
|
99
|
+
if (!onConnectCalled) {
|
|
100
|
+
onConnectCalled = true;
|
|
101
|
+
if (runOptions.onConnect) {
|
|
102
|
+
await runOptions.onConnect();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
62
105
|
await indexer.hooks.callHook("message", { message });
|
|
63
106
|
switch (message._tag) {
|
|
64
107
|
case "data": {
|
|
@@ -198,4 +241,4 @@ async function run(client, indexer) {
|
|
|
198
241
|
});
|
|
199
242
|
}
|
|
200
243
|
|
|
201
|
-
export { createIndexer, defaultSink, defineIndexer, run, useIndexerContext };
|
|
244
|
+
export { createIndexer, defaultSink, defineIndexer, run, runWithReconnect, useIndexerContext };
|
package/dist/plugins/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.
|
|
1
|
+
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.3bc6abe3.cjs';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
import '../shared/indexer.47b4546b.cjs';
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.
|
|
1
|
+
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.b4be75f1.mjs';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
import '../shared/indexer.47b4546b.mjs';
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.
|
|
1
|
+
export { a as IndexerPlugin, d as defineIndexerPlugin } from '../shared/indexer.a05207df.js';
|
|
2
2
|
import '@apibara/protocol';
|
|
3
3
|
import 'hookable';
|
|
4
4
|
import '../shared/indexer.47b4546b.js';
|
package/dist/plugins/kv.cjs
CHANGED
package/dist/plugins/kv.d.cts
CHANGED
package/dist/plugins/kv.d.mts
CHANGED
package/dist/plugins/kv.d.ts
CHANGED
package/dist/plugins/kv.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.3bc6abe3.cjs';
|
|
2
2
|
import { ConsolaReporter, ConsolaInstance } from 'consola';
|
|
3
3
|
export { ConsolaInstance, ConsolaReporter } from 'consola';
|
|
4
4
|
import '@apibara/protocol';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.b4be75f1.mjs';
|
|
2
2
|
import { ConsolaReporter, ConsolaInstance } from 'consola';
|
|
3
3
|
export { ConsolaInstance, ConsolaReporter } from 'consola';
|
|
4
4
|
import '@apibara/protocol';
|
package/dist/plugins/logger.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.a05207df.js';
|
|
2
2
|
import { ConsolaReporter, ConsolaInstance } from 'consola';
|
|
3
3
|
export { ConsolaInstance, ConsolaReporter } from 'consola';
|
|
4
4
|
import '@apibara/protocol';
|
|
@@ -4,6 +4,7 @@ const helper = require('../shared/indexer.219f58e0.cjs');
|
|
|
4
4
|
require('node:fs/promises');
|
|
5
5
|
require('node:path');
|
|
6
6
|
require('klona/full');
|
|
7
|
+
require('@apibara/protocol');
|
|
7
8
|
require('consola');
|
|
8
9
|
require('hookable');
|
|
9
10
|
require('node:assert');
|
|
@@ -22,6 +23,31 @@ var __publicField = (obj, key, value) => {
|
|
|
22
23
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
23
24
|
return value;
|
|
24
25
|
};
|
|
26
|
+
function inMemoryPersistence() {
|
|
27
|
+
return plugins_index.defineIndexerPlugin((indexer) => {
|
|
28
|
+
let lastCursor;
|
|
29
|
+
let lastFilter;
|
|
30
|
+
indexer.hooks.hook("connect:before", ({ request }) => {
|
|
31
|
+
if (lastCursor) {
|
|
32
|
+
request.startingCursor = lastCursor;
|
|
33
|
+
}
|
|
34
|
+
if (lastFilter) {
|
|
35
|
+
request.filter[1] = lastFilter;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
indexer.hooks.hook("transaction:commit", ({ endCursor }) => {
|
|
39
|
+
if (endCursor) {
|
|
40
|
+
lastCursor = endCursor;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
44
|
+
if (request.filter[1]) {
|
|
45
|
+
lastCursor = endCursor;
|
|
46
|
+
lastFilter = request.filter[1];
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
25
51
|
function sqlitePersistence({
|
|
26
52
|
database
|
|
27
53
|
}) {
|
|
@@ -181,4 +207,5 @@ const statements = {
|
|
|
181
207
|
};
|
|
182
208
|
|
|
183
209
|
exports.SqlitePersistence = SqlitePersistence;
|
|
210
|
+
exports.inMemoryPersistence = inMemoryPersistence;
|
|
184
211
|
exports.sqlitePersistence = sqlitePersistence;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.3bc6abe3.cjs';
|
|
2
2
|
import { Cursor } from '@apibara/protocol';
|
|
3
3
|
import { Database } from 'better-sqlite3';
|
|
4
4
|
import 'hookable';
|
|
5
5
|
import '../shared/indexer.47b4546b.cjs';
|
|
6
6
|
|
|
7
|
+
declare function inMemoryPersistence<TFilter, TBlock, TTxnParams>(): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
7
8
|
declare function sqlitePersistence<TFilter, TBlock, TTxnParams>({ database, }: {
|
|
8
9
|
database: Database;
|
|
9
10
|
}): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
@@ -47,4 +48,4 @@ type FilterRow = {
|
|
|
47
48
|
to_block?: number;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export { type CheckpointRow, type FilterRow, SqlitePersistence, sqlitePersistence };
|
|
51
|
+
export { type CheckpointRow, type FilterRow, SqlitePersistence, inMemoryPersistence, sqlitePersistence };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.b4be75f1.mjs';
|
|
2
2
|
import { Cursor } from '@apibara/protocol';
|
|
3
3
|
import { Database } from 'better-sqlite3';
|
|
4
4
|
import 'hookable';
|
|
5
5
|
import '../shared/indexer.47b4546b.mjs';
|
|
6
6
|
|
|
7
|
+
declare function inMemoryPersistence<TFilter, TBlock, TTxnParams>(): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
7
8
|
declare function sqlitePersistence<TFilter, TBlock, TTxnParams>({ database, }: {
|
|
8
9
|
database: Database;
|
|
9
10
|
}): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
@@ -47,4 +48,4 @@ type FilterRow = {
|
|
|
47
48
|
to_block?: number;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export { type CheckpointRow, type FilterRow, SqlitePersistence, sqlitePersistence };
|
|
51
|
+
export { type CheckpointRow, type FilterRow, SqlitePersistence, inMemoryPersistence, sqlitePersistence };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { a as IndexerPlugin } from '../shared/indexer.
|
|
1
|
+
import { a as IndexerPlugin } from '../shared/indexer.a05207df.js';
|
|
2
2
|
import { Cursor } from '@apibara/protocol';
|
|
3
3
|
import { Database } from 'better-sqlite3';
|
|
4
4
|
import 'hookable';
|
|
5
5
|
import '../shared/indexer.47b4546b.js';
|
|
6
6
|
|
|
7
|
+
declare function inMemoryPersistence<TFilter, TBlock, TTxnParams>(): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
7
8
|
declare function sqlitePersistence<TFilter, TBlock, TTxnParams>({ database, }: {
|
|
8
9
|
database: Database;
|
|
9
10
|
}): IndexerPlugin<TFilter, TBlock, TTxnParams>;
|
|
@@ -47,4 +48,4 @@ type FilterRow = {
|
|
|
47
48
|
to_block?: number;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export { type CheckpointRow, type FilterRow, SqlitePersistence, sqlitePersistence };
|
|
51
|
+
export { type CheckpointRow, type FilterRow, SqlitePersistence, inMemoryPersistence, sqlitePersistence };
|
|
@@ -2,6 +2,7 @@ import { d as deserialize, s as serialize } from '../shared/indexer.0c0510df.mjs
|
|
|
2
2
|
import 'node:fs/promises';
|
|
3
3
|
import 'node:path';
|
|
4
4
|
import 'klona/full';
|
|
5
|
+
import '@apibara/protocol';
|
|
5
6
|
import 'consola';
|
|
6
7
|
import 'hookable';
|
|
7
8
|
import 'node:assert';
|
|
@@ -20,6 +21,31 @@ var __publicField = (obj, key, value) => {
|
|
|
20
21
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
22
|
return value;
|
|
22
23
|
};
|
|
24
|
+
function inMemoryPersistence() {
|
|
25
|
+
return defineIndexerPlugin((indexer) => {
|
|
26
|
+
let lastCursor;
|
|
27
|
+
let lastFilter;
|
|
28
|
+
indexer.hooks.hook("connect:before", ({ request }) => {
|
|
29
|
+
if (lastCursor) {
|
|
30
|
+
request.startingCursor = lastCursor;
|
|
31
|
+
}
|
|
32
|
+
if (lastFilter) {
|
|
33
|
+
request.filter[1] = lastFilter;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
indexer.hooks.hook("transaction:commit", ({ endCursor }) => {
|
|
37
|
+
if (endCursor) {
|
|
38
|
+
lastCursor = endCursor;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
42
|
+
if (request.filter[1]) {
|
|
43
|
+
lastCursor = endCursor;
|
|
44
|
+
lastFilter = request.filter[1];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
23
49
|
function sqlitePersistence({
|
|
24
50
|
database
|
|
25
51
|
}) {
|
|
@@ -178,4 +204,4 @@ const statements = {
|
|
|
178
204
|
WHERE id = ?`
|
|
179
205
|
};
|
|
180
206
|
|
|
181
|
-
export { SqlitePersistence, sqlitePersistence };
|
|
207
|
+
export { SqlitePersistence, inMemoryPersistence, sqlitePersistence };
|
|
@@ -83,6 +83,15 @@ interface Indexer<TFilter, TBlock, TTxnParams> {
|
|
|
83
83
|
hooks: Hookable<IndexerHooks<TFilter, TBlock>>;
|
|
84
84
|
}
|
|
85
85
|
declare function createIndexer<TFilter, TBlock, TTxnParams>({ streamConfig, ...options }: IndexerWithStreamConfig<TFilter, TBlock, TTxnParams>): Indexer<TFilter, TBlock, TTxnParams>;
|
|
86
|
-
|
|
86
|
+
interface ReconnectOptions {
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
retryDelay?: number;
|
|
89
|
+
maxWait?: number;
|
|
90
|
+
}
|
|
91
|
+
declare function runWithReconnect<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, options?: ReconnectOptions): Promise<void>;
|
|
92
|
+
interface RunOptions {
|
|
93
|
+
onConnect?: () => void | Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
declare function run<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, runOptions?: RunOptions): Promise<void>;
|
|
87
96
|
|
|
88
|
-
export { type IndexerWithStreamConfig as I, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, run as r };
|
|
97
|
+
export { type IndexerWithStreamConfig as I, type ReconnectOptions as R, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, type RunOptions as h, run as i, runWithReconnect as r };
|
|
@@ -83,6 +83,15 @@ interface Indexer<TFilter, TBlock, TTxnParams> {
|
|
|
83
83
|
hooks: Hookable<IndexerHooks<TFilter, TBlock>>;
|
|
84
84
|
}
|
|
85
85
|
declare function createIndexer<TFilter, TBlock, TTxnParams>({ streamConfig, ...options }: IndexerWithStreamConfig<TFilter, TBlock, TTxnParams>): Indexer<TFilter, TBlock, TTxnParams>;
|
|
86
|
-
|
|
86
|
+
interface ReconnectOptions {
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
retryDelay?: number;
|
|
89
|
+
maxWait?: number;
|
|
90
|
+
}
|
|
91
|
+
declare function runWithReconnect<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, options?: ReconnectOptions): Promise<void>;
|
|
92
|
+
interface RunOptions {
|
|
93
|
+
onConnect?: () => void | Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
declare function run<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, runOptions?: RunOptions): Promise<void>;
|
|
87
96
|
|
|
88
|
-
export { type IndexerWithStreamConfig as I, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, run as r };
|
|
97
|
+
export { type IndexerWithStreamConfig as I, type ReconnectOptions as R, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, type RunOptions as h, run as i, runWithReconnect as r };
|
|
@@ -83,6 +83,15 @@ interface Indexer<TFilter, TBlock, TTxnParams> {
|
|
|
83
83
|
hooks: Hookable<IndexerHooks<TFilter, TBlock>>;
|
|
84
84
|
}
|
|
85
85
|
declare function createIndexer<TFilter, TBlock, TTxnParams>({ streamConfig, ...options }: IndexerWithStreamConfig<TFilter, TBlock, TTxnParams>): Indexer<TFilter, TBlock, TTxnParams>;
|
|
86
|
-
|
|
86
|
+
interface ReconnectOptions {
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
retryDelay?: number;
|
|
89
|
+
maxWait?: number;
|
|
90
|
+
}
|
|
91
|
+
declare function runWithReconnect<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, options?: ReconnectOptions): Promise<void>;
|
|
92
|
+
interface RunOptions {
|
|
93
|
+
onConnect?: () => void | Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
declare function run<TFilter, TBlock, TTxnParams>(client: Client<TFilter, TBlock>, indexer: Indexer<TFilter, TBlock, TTxnParams>, runOptions?: RunOptions): Promise<void>;
|
|
87
96
|
|
|
88
|
-
export { type IndexerWithStreamConfig as I, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, run as r };
|
|
97
|
+
export { type IndexerWithStreamConfig as I, type ReconnectOptions as R, type IndexerPlugin as a, type Indexer as b, type IndexerHooks as c, defineIndexerPlugin as d, type IndexerConfig as e, defineIndexer as f, createIndexer as g, type RunOptions as h, run as i, runWithReconnect as r };
|
package/dist/testing/index.d.cts
CHANGED
package/dist/testing/index.d.mts
CHANGED
package/dist/testing/index.d.ts
CHANGED
package/dist/vcr/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ const index = require('../index.cjs');
|
|
|
8
8
|
const assert = require('node:assert');
|
|
9
9
|
const fs$1 = require('node:fs');
|
|
10
10
|
const testing = require('@apibara/protocol/testing');
|
|
11
|
+
require('@apibara/protocol');
|
|
11
12
|
require('consola');
|
|
12
13
|
require('hookable');
|
|
13
14
|
require('../shared/indexer.077335f3.cjs');
|
package/dist/vcr/index.d.cts
CHANGED
package/dist/vcr/index.d.mts
CHANGED
package/dist/vcr/index.d.ts
CHANGED
package/dist/vcr/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { run } from '../index.mjs';
|
|
|
7
7
|
import assert from 'node:assert';
|
|
8
8
|
import fs$1 from 'node:fs';
|
|
9
9
|
import { MockClient } from '@apibara/protocol/testing';
|
|
10
|
+
import '@apibara/protocol';
|
|
10
11
|
import 'consola';
|
|
11
12
|
import 'hookable';
|
|
12
13
|
import '../shared/indexer.a55ad619.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/indexer",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"vitest": "^1.6.0"
|
|
93
93
|
},
|
|
94
94
|
"dependencies": {
|
|
95
|
-
"@apibara/protocol": "2.0.0-beta.
|
|
95
|
+
"@apibara/protocol": "2.0.0-beta.22",
|
|
96
96
|
"@opentelemetry/api": "^1.9.0",
|
|
97
97
|
"ci-info": "^4.1.0",
|
|
98
98
|
"consola": "^3.2.3",
|
package/src/indexer.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
Client,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Client,
|
|
3
|
+
ClientError,
|
|
4
|
+
type Cursor,
|
|
5
|
+
type DataFinality,
|
|
6
|
+
type Finalize,
|
|
7
|
+
type Heartbeat,
|
|
8
|
+
type Invalidate,
|
|
9
|
+
Status,
|
|
10
|
+
type StreamConfig,
|
|
11
|
+
type StreamDataOptions,
|
|
12
|
+
type StreamDataRequest,
|
|
13
|
+
type StreamDataResponse,
|
|
14
|
+
type SystemMessage,
|
|
13
15
|
} from "@apibara/protocol";
|
|
14
16
|
import consola from "consola";
|
|
15
17
|
import {
|
|
@@ -149,9 +151,71 @@ export function createIndexer<TFilter, TBlock, TTxnParams>({
|
|
|
149
151
|
return indexer;
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
export interface ReconnectOptions {
|
|
155
|
+
maxRetries?: number;
|
|
156
|
+
retryDelay?: number;
|
|
157
|
+
maxWait?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function runWithReconnect<TFilter, TBlock, TTxnParams>(
|
|
161
|
+
client: Client<TFilter, TBlock>,
|
|
162
|
+
indexer: Indexer<TFilter, TBlock, TTxnParams>,
|
|
163
|
+
options: ReconnectOptions = {},
|
|
164
|
+
) {
|
|
165
|
+
let retryCount = 0;
|
|
166
|
+
|
|
167
|
+
const maxRetries = options.maxRetries ?? 10;
|
|
168
|
+
const retryDelay = options.retryDelay ?? 1_000;
|
|
169
|
+
const maxWait = options.maxWait ?? 30_000;
|
|
170
|
+
|
|
171
|
+
const runOptions: RunOptions = {
|
|
172
|
+
onConnect() {
|
|
173
|
+
retryCount = 0;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
try {
|
|
179
|
+
await run(client, indexer, runOptions);
|
|
180
|
+
return;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
// Only reconnect on internal/server errors.
|
|
183
|
+
// All other errors should be rethrown.
|
|
184
|
+
|
|
185
|
+
retryCount++;
|
|
186
|
+
|
|
187
|
+
if (error instanceof ClientError) {
|
|
188
|
+
if (error.code === Status.INTERNAL) {
|
|
189
|
+
if (retryCount < maxRetries) {
|
|
190
|
+
consola.error(
|
|
191
|
+
"Internal server error, reconnecting...",
|
|
192
|
+
error.message,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Add jitter to the retry delay to avoid all clients retrying at the same time.
|
|
196
|
+
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
197
|
+
await new Promise((resolve) =>
|
|
198
|
+
setTimeout(resolve, Math.min(retryCount * delay, maxWait)),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface RunOptions {
|
|
212
|
+
onConnect?: () => void | Promise<void>;
|
|
213
|
+
}
|
|
214
|
+
|
|
152
215
|
export async function run<TFilter, TBlock, TTxnParams>(
|
|
153
216
|
client: Client<TFilter, TBlock>,
|
|
154
217
|
indexer: Indexer<TFilter, TBlock, TTxnParams>,
|
|
218
|
+
runOptions: RunOptions = {},
|
|
155
219
|
) {
|
|
156
220
|
await indexerAsyncContext.callAsync({}, async () => {
|
|
157
221
|
const context = useIndexerContext<TTxnParams>();
|
|
@@ -195,6 +259,8 @@ export async function run<TFilter, TBlock, TTxnParams>(
|
|
|
195
259
|
|
|
196
260
|
await indexer.hooks.callHook("connect:after");
|
|
197
261
|
|
|
262
|
+
let onConnectCalled = false;
|
|
263
|
+
|
|
198
264
|
while (true) {
|
|
199
265
|
const { value: message, done } = await stream.next();
|
|
200
266
|
|
|
@@ -202,6 +268,13 @@ export async function run<TFilter, TBlock, TTxnParams>(
|
|
|
202
268
|
break;
|
|
203
269
|
}
|
|
204
270
|
|
|
271
|
+
if (!onConnectCalled) {
|
|
272
|
+
onConnectCalled = true;
|
|
273
|
+
if (runOptions.onConnect) {
|
|
274
|
+
await runOptions.onConnect();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
205
278
|
await indexer.hooks.callHook("message", { message });
|
|
206
279
|
|
|
207
280
|
switch (message._tag) {
|
|
@@ -3,6 +3,36 @@ import type { Database as SqliteDatabase, Statement } from "better-sqlite3";
|
|
|
3
3
|
import { deserialize, serialize } from "../vcr";
|
|
4
4
|
import { defineIndexerPlugin } from "./config";
|
|
5
5
|
|
|
6
|
+
export function inMemoryPersistence<TFilter, TBlock, TTxnParams>() {
|
|
7
|
+
return defineIndexerPlugin<TFilter, TBlock, TTxnParams>((indexer) => {
|
|
8
|
+
let lastCursor: Cursor | undefined;
|
|
9
|
+
let lastFilter: TFilter | undefined;
|
|
10
|
+
|
|
11
|
+
indexer.hooks.hook("connect:before", ({ request }) => {
|
|
12
|
+
if (lastCursor) {
|
|
13
|
+
request.startingCursor = lastCursor;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (lastFilter) {
|
|
17
|
+
request.filter[1] = lastFilter;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
indexer.hooks.hook("transaction:commit", ({ endCursor }) => {
|
|
22
|
+
if (endCursor) {
|
|
23
|
+
lastCursor = endCursor;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
28
|
+
if (request.filter[1]) {
|
|
29
|
+
lastCursor = endCursor;
|
|
30
|
+
lastFilter = request.filter[1];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
6
36
|
export function sqlitePersistence<TFilter, TBlock, TTxnParams>({
|
|
7
37
|
database,
|
|
8
38
|
}: { database: SqliteDatabase }) {
|