@apibara/indexer 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 +134 -41
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +125 -34
- package/dist/index.mjs.map +1 -0
- package/dist/internal/index.cjs +1 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.mjs +1 -0
- package/dist/internal/index.mjs.map +1 -0
- package/dist/internal/plugins.cjs +5 -5
- package/dist/internal/plugins.cjs.map +1 -0
- package/dist/internal/plugins.d.cts +1 -1
- package/dist/internal/plugins.d.mts +1 -1
- package/dist/internal/plugins.d.ts +1 -1
- package/dist/internal/plugins.mjs +3 -3
- package/dist/internal/plugins.mjs.map +1 -0
- package/dist/internal/testing.cjs +35 -14
- package/dist/internal/testing.cjs.map +1 -0
- package/dist/internal/testing.d.cts +16 -12
- package/dist/internal/testing.d.mts +16 -12
- package/dist/internal/testing.d.ts +16 -12
- package/dist/internal/testing.mjs +33 -12
- package/dist/internal/testing.mjs.map +1 -0
- package/dist/plugins/index.cjs +4 -4
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +2 -2
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.mjs +4 -4
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/shared/{indexer.2416906c.cjs → indexer.03c9f151.cjs} +8 -5
- package/dist/shared/indexer.03c9f151.cjs.map +1 -0
- package/dist/shared/{indexer.ff25c953.mjs → indexer.2673dcb1.mjs} +7 -4
- package/dist/shared/indexer.2673dcb1.mjs.map +1 -0
- package/dist/shared/{indexer.077335f3.cjs → indexer.479ae593.cjs} +6 -0
- package/dist/shared/indexer.479ae593.cjs.map +1 -0
- package/dist/shared/{indexer.fedcd831.d.cts → indexer.4ef52548.d.cts} +40 -23
- package/dist/shared/{indexer.fedcd831.d.mts → indexer.4ef52548.d.mts} +40 -23
- package/dist/shared/{indexer.fedcd831.d.ts → indexer.4ef52548.d.ts} +40 -23
- package/dist/shared/{indexer.a55ad619.mjs → indexer.75773ef1.mjs} +6 -1
- package/dist/shared/indexer.75773ef1.mjs.map +1 -0
- package/dist/testing/index.cjs +19 -10
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +11 -7
- package/dist/testing/index.d.mts +11 -7
- package/dist/testing/index.d.ts +11 -7
- package/dist/testing/index.mjs +20 -11
- package/dist/testing/index.mjs.map +1 -0
- package/dist/vcr/index.cjs +3 -1
- package/dist/vcr/index.cjs.map +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 +3 -1
- package/dist/vcr/index.mjs.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/indexer.ts +151 -50
- package/src/internal/testing.ts +67 -23
- package/src/otel.ts +29 -2
- package/src/plugins/context.ts +1 -1
- package/src/plugins/logger.ts +11 -2
- package/src/testing/index.ts +30 -6
- package/src/utils/index.ts +21 -0
- package/dist/shared/indexer.601ceab0.cjs +0 -7
- package/dist/shared/indexer.9b21ddd2.mjs +0 -5
- package/src/compose.test.ts +0 -76
- package/src/indexer.test.ts +0 -430
package/src/indexer.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Client,
|
|
3
3
|
ClientError,
|
|
4
|
+
type CreateClientOptions,
|
|
4
5
|
type Cursor,
|
|
5
6
|
type DataFinality,
|
|
7
|
+
type DataProduction,
|
|
6
8
|
type Finalize,
|
|
7
|
-
type Heartbeat,
|
|
8
9
|
type Invalidate,
|
|
10
|
+
ServerError,
|
|
9
11
|
Status,
|
|
10
12
|
type StreamConfig,
|
|
11
13
|
type StreamDataOptions,
|
|
@@ -28,39 +30,66 @@ import {
|
|
|
28
30
|
indexerAsyncContext,
|
|
29
31
|
useIndexerContext,
|
|
30
32
|
} from "./context";
|
|
31
|
-
import {
|
|
33
|
+
import { createIndexerMetrics, createTracer } from "./otel";
|
|
32
34
|
import type { IndexerPlugin } from "./plugins";
|
|
35
|
+
import { useInternalContext } from "./plugins/context";
|
|
36
|
+
import { reloadIfNeeded } from "./utils";
|
|
33
37
|
|
|
34
38
|
export type UseMiddlewareFunction = (
|
|
35
39
|
fn: MiddlewareFunction<IndexerContext>,
|
|
36
40
|
) => void;
|
|
37
41
|
|
|
38
42
|
export interface IndexerHooks<TFilter, TBlock> {
|
|
39
|
-
"
|
|
40
|
-
"run:
|
|
43
|
+
"plugins:init": ({ abortSignal }: { abortSignal?: AbortSignal }) => void;
|
|
44
|
+
"run:before": ({ abortSignal }: { abortSignal?: AbortSignal }) => void;
|
|
45
|
+
"run:after": ({ abortSignal }: { abortSignal?: AbortSignal }) => void;
|
|
41
46
|
"connect:before": ({
|
|
42
47
|
request,
|
|
43
48
|
options,
|
|
44
49
|
}: {
|
|
45
50
|
request: StreamDataRequest<TFilter>;
|
|
46
51
|
options: StreamDataOptions;
|
|
52
|
+
abortSignal?: AbortSignal;
|
|
47
53
|
}) => void;
|
|
48
54
|
"connect:after": ({
|
|
49
55
|
request,
|
|
50
|
-
}: {
|
|
56
|
+
}: {
|
|
57
|
+
request: StreamDataRequest<TFilter>;
|
|
58
|
+
abortSignal?: AbortSignal;
|
|
59
|
+
}) => void;
|
|
51
60
|
"connect:factory": ({
|
|
52
61
|
request,
|
|
53
62
|
endCursor,
|
|
63
|
+
abortSignal,
|
|
54
64
|
}: {
|
|
55
65
|
request: StreamDataRequest<TFilter>;
|
|
56
66
|
endCursor?: Cursor;
|
|
67
|
+
abortSignal?: AbortSignal;
|
|
57
68
|
}) => void;
|
|
58
|
-
"handler:middleware": ({
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
"handler:middleware": ({
|
|
70
|
+
use,
|
|
71
|
+
abortSignal,
|
|
72
|
+
}: { use: UseMiddlewareFunction; abortSignal?: AbortSignal }) => void;
|
|
73
|
+
message: ({
|
|
74
|
+
message,
|
|
75
|
+
abortSignal,
|
|
76
|
+
}: {
|
|
77
|
+
message: StreamDataResponse<TBlock>;
|
|
78
|
+
abortSignal?: AbortSignal;
|
|
79
|
+
}) => void;
|
|
80
|
+
"message:invalidate": ({
|
|
81
|
+
message,
|
|
82
|
+
abortSignal,
|
|
83
|
+
}: { message: Invalidate; abortSignal?: AbortSignal }) => void;
|
|
84
|
+
"message:finalize": ({
|
|
85
|
+
message,
|
|
86
|
+
abortSignal,
|
|
87
|
+
}: { message: Finalize; abortSignal?: AbortSignal }) => void;
|
|
88
|
+
"message:heartbeat": ({ abortSignal }: { abortSignal?: AbortSignal }) => void;
|
|
89
|
+
"message:systemMessage": ({
|
|
90
|
+
message,
|
|
91
|
+
abortSignal,
|
|
92
|
+
}: { message: SystemMessage; abortSignal?: AbortSignal }) => void;
|
|
64
93
|
}
|
|
65
94
|
|
|
66
95
|
export type IndexerStartingCursor =
|
|
@@ -77,23 +106,25 @@ export type IndexerStartingCursor =
|
|
|
77
106
|
startingBlock?: never;
|
|
78
107
|
};
|
|
79
108
|
|
|
109
|
+
export type HandlerArgs<TBlock> = {
|
|
110
|
+
block: TBlock;
|
|
111
|
+
cursor?: Cursor | undefined;
|
|
112
|
+
endCursor?: Cursor | undefined;
|
|
113
|
+
finality: DataFinality;
|
|
114
|
+
production: DataProduction;
|
|
115
|
+
context: IndexerContext;
|
|
116
|
+
abortSignal?: AbortSignal;
|
|
117
|
+
};
|
|
118
|
+
|
|
80
119
|
export type IndexerConfig<TFilter, TBlock> = {
|
|
81
120
|
streamUrl: string;
|
|
82
121
|
filter: TFilter;
|
|
83
122
|
finality?: DataFinality;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
context,
|
|
87
|
-
}: { block: TBlock; context: IndexerContext }) => Promise<{
|
|
123
|
+
clientOptions?: CreateClientOptions;
|
|
124
|
+
factory?: (args: HandlerArgs<TBlock>) => Promise<{
|
|
88
125
|
filter?: TFilter;
|
|
89
126
|
}>;
|
|
90
|
-
transform: (args:
|
|
91
|
-
block: TBlock;
|
|
92
|
-
cursor?: Cursor | undefined;
|
|
93
|
-
endCursor?: Cursor | undefined;
|
|
94
|
-
finality: DataFinality;
|
|
95
|
-
context: IndexerContext;
|
|
96
|
-
}) => Promise<void>;
|
|
127
|
+
transform: (args: HandlerArgs<TBlock>) => Promise<void>;
|
|
97
128
|
hooks?: NestedHooks<IndexerHooks<TFilter, TBlock>>;
|
|
98
129
|
plugins?: ReadonlyArray<IndexerPlugin<TFilter, TBlock>>;
|
|
99
130
|
debug?: boolean;
|
|
@@ -163,15 +194,19 @@ export async function runWithReconnect<TFilter, TBlock>(
|
|
|
163
194
|
const retryDelay = options.retryDelay ?? 1_000;
|
|
164
195
|
const maxWait = options.maxWait ?? 30_000;
|
|
165
196
|
|
|
166
|
-
const runOptions: RunOptions = {
|
|
167
|
-
onConnect() {
|
|
168
|
-
retryCount = 0;
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
|
|
172
197
|
while (true) {
|
|
198
|
+
const abortController = new AbortController();
|
|
199
|
+
|
|
200
|
+
const runOptions: RunOptions = {
|
|
201
|
+
onConnect() {
|
|
202
|
+
retryCount = 0;
|
|
203
|
+
},
|
|
204
|
+
abortSignal: abortController.signal,
|
|
205
|
+
};
|
|
206
|
+
|
|
173
207
|
try {
|
|
174
208
|
await run(client, indexer, runOptions);
|
|
209
|
+
abortController.abort();
|
|
175
210
|
return;
|
|
176
211
|
} catch (error) {
|
|
177
212
|
// Only reconnect on internal/server errors.
|
|
@@ -179,13 +214,20 @@ export async function runWithReconnect<TFilter, TBlock>(
|
|
|
179
214
|
|
|
180
215
|
retryCount++;
|
|
181
216
|
|
|
182
|
-
|
|
217
|
+
abortController.abort();
|
|
218
|
+
|
|
219
|
+
if (error instanceof ClientError || error instanceof ServerError) {
|
|
220
|
+
const isServerError = error instanceof ServerError;
|
|
221
|
+
|
|
183
222
|
if (error.code === Status.INTERNAL) {
|
|
184
223
|
if (retryCount < maxRetries) {
|
|
185
224
|
consola.error(
|
|
186
|
-
|
|
187
|
-
|
|
225
|
+
`Internal ${isServerError ? "server" : "client"} error: ${
|
|
226
|
+
error.message
|
|
227
|
+
}`,
|
|
188
228
|
);
|
|
229
|
+
consola.start("Reconnecting...");
|
|
230
|
+
console.log();
|
|
189
231
|
|
|
190
232
|
// Add jitter to the retry delay to avoid all clients retrying at the same time.
|
|
191
233
|
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
@@ -197,7 +239,6 @@ export async function runWithReconnect<TFilter, TBlock>(
|
|
|
197
239
|
}
|
|
198
240
|
}
|
|
199
241
|
}
|
|
200
|
-
|
|
201
242
|
throw error;
|
|
202
243
|
}
|
|
203
244
|
}
|
|
@@ -205,6 +246,7 @@ export async function runWithReconnect<TFilter, TBlock>(
|
|
|
205
246
|
|
|
206
247
|
export interface RunOptions {
|
|
207
248
|
onConnect?: () => void | Promise<void>;
|
|
249
|
+
abortSignal?: AbortSignal;
|
|
208
250
|
}
|
|
209
251
|
|
|
210
252
|
export async function run<TFilter, TBlock>(
|
|
@@ -214,9 +256,23 @@ export async function run<TFilter, TBlock>(
|
|
|
214
256
|
) {
|
|
215
257
|
await indexerAsyncContext.callAsync({}, async () => {
|
|
216
258
|
const context = useIndexerContext();
|
|
217
|
-
const middleware = await registerMiddleware(indexer);
|
|
218
259
|
|
|
219
|
-
|
|
260
|
+
if (indexer.options.debug) {
|
|
261
|
+
context.debug = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { abortSignal } = runOptions;
|
|
265
|
+
|
|
266
|
+
await indexer.hooks.callHook("plugins:init", { abortSignal });
|
|
267
|
+
|
|
268
|
+
const middleware = await registerMiddleware(indexer, abortSignal);
|
|
269
|
+
|
|
270
|
+
const indexerMetrics = createIndexerMetrics();
|
|
271
|
+
const tracer = createTracer();
|
|
272
|
+
|
|
273
|
+
await indexer.hooks.callHook("run:before", { abortSignal });
|
|
274
|
+
|
|
275
|
+
const { indexerName: indexerId } = useInternalContext();
|
|
220
276
|
|
|
221
277
|
const isFactoryMode = indexer.options.factory !== undefined;
|
|
222
278
|
|
|
@@ -235,17 +291,21 @@ export async function run<TFilter, TBlock>(
|
|
|
235
291
|
}
|
|
236
292
|
|
|
237
293
|
// if factory mode we add a empty filter at the end of the filter array.
|
|
238
|
-
const request =
|
|
294
|
+
const request = {
|
|
239
295
|
filter: isFactoryMode
|
|
240
296
|
? [indexer.options.filter, {} as TFilter]
|
|
241
297
|
: [indexer.options.filter],
|
|
242
298
|
finality: indexer.options.finality,
|
|
243
299
|
startingCursor,
|
|
244
|
-
}
|
|
300
|
+
} as StreamDataRequest<TFilter>;
|
|
245
301
|
|
|
246
302
|
const options: StreamDataOptions = {};
|
|
247
303
|
|
|
248
|
-
await indexer.hooks.callHook("connect:before", {
|
|
304
|
+
await indexer.hooks.callHook("connect:before", {
|
|
305
|
+
request,
|
|
306
|
+
options,
|
|
307
|
+
abortSignal,
|
|
308
|
+
});
|
|
249
309
|
|
|
250
310
|
// store main filter, so later it can be merged
|
|
251
311
|
let mainFilter: TFilter;
|
|
@@ -258,7 +318,7 @@ export async function run<TFilter, TBlock>(
|
|
|
258
318
|
StreamDataResponse<TBlock>
|
|
259
319
|
> = client.streamData(request, options)[Symbol.asyncIterator]();
|
|
260
320
|
|
|
261
|
-
await indexer.hooks.callHook("connect:after", { request });
|
|
321
|
+
await indexer.hooks.callHook("connect:after", { request, abortSignal });
|
|
262
322
|
|
|
263
323
|
let onConnectCalled = false;
|
|
264
324
|
|
|
@@ -276,23 +336,33 @@ export async function run<TFilter, TBlock>(
|
|
|
276
336
|
}
|
|
277
337
|
}
|
|
278
338
|
|
|
279
|
-
await indexer.hooks.callHook("message", { message });
|
|
339
|
+
await indexer.hooks.callHook("message", { message, abortSignal });
|
|
280
340
|
|
|
281
341
|
switch (message._tag) {
|
|
282
342
|
case "data": {
|
|
283
343
|
await tracer.startActiveSpan("message data", async (span) => {
|
|
284
344
|
const blocks = message.data.data;
|
|
285
|
-
const { cursor, endCursor, finality } = message.data;
|
|
345
|
+
const { cursor, endCursor, finality, production } = message.data;
|
|
286
346
|
|
|
287
347
|
context.cursor = cursor;
|
|
288
348
|
context.endCursor = endCursor;
|
|
289
349
|
context.finality = finality;
|
|
290
350
|
|
|
351
|
+
// Record current block number being processed
|
|
352
|
+
indexerMetrics.currentBlockGauge.record(
|
|
353
|
+
Number(endCursor?.orderKey),
|
|
354
|
+
{
|
|
355
|
+
indexer_id: indexerId,
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
reloadIfNeeded();
|
|
360
|
+
|
|
291
361
|
await middleware(context, async () => {
|
|
292
362
|
let block: TBlock | null;
|
|
293
363
|
|
|
294
364
|
// when factory mode
|
|
295
|
-
if (isFactoryMode) {
|
|
365
|
+
if (isFactoryMode && finality !== "pending") {
|
|
296
366
|
assert(indexer.options.factory !== undefined);
|
|
297
367
|
|
|
298
368
|
const [factoryBlock, mainBlock] = blocks;
|
|
@@ -302,7 +372,12 @@ export async function run<TFilter, TBlock>(
|
|
|
302
372
|
if (factoryBlock !== null) {
|
|
303
373
|
const { filter } = await indexer.options.factory({
|
|
304
374
|
block: factoryBlock,
|
|
375
|
+
cursor,
|
|
376
|
+
endCursor,
|
|
377
|
+
finality,
|
|
378
|
+
production,
|
|
305
379
|
context,
|
|
380
|
+
abortSignal,
|
|
306
381
|
});
|
|
307
382
|
|
|
308
383
|
// write returned data from factory function if filter is not defined
|
|
@@ -315,15 +390,16 @@ export async function run<TFilter, TBlock>(
|
|
|
315
390
|
);
|
|
316
391
|
|
|
317
392
|
// create request with new filters
|
|
318
|
-
const request =
|
|
393
|
+
const request = {
|
|
319
394
|
filter: [indexer.options.filter, mainFilter],
|
|
320
395
|
finality: indexer.options.finality,
|
|
321
396
|
startingCursor: cursor,
|
|
322
|
-
}
|
|
397
|
+
} as StreamDataRequest<TFilter>;
|
|
323
398
|
|
|
324
399
|
await indexer.hooks.callHook("connect:factory", {
|
|
325
400
|
request,
|
|
326
401
|
endCursor,
|
|
402
|
+
abortSignal,
|
|
327
403
|
});
|
|
328
404
|
|
|
329
405
|
// create new stream with new request
|
|
@@ -353,7 +429,9 @@ export async function run<TFilter, TBlock>(
|
|
|
353
429
|
cursor,
|
|
354
430
|
endCursor,
|
|
355
431
|
finality,
|
|
432
|
+
production,
|
|
356
433
|
context,
|
|
434
|
+
abortSignal,
|
|
357
435
|
});
|
|
358
436
|
|
|
359
437
|
span.end();
|
|
@@ -361,9 +439,16 @@ export async function run<TFilter, TBlock>(
|
|
|
361
439
|
}
|
|
362
440
|
});
|
|
363
441
|
|
|
442
|
+
reloadIfNeeded();
|
|
443
|
+
|
|
364
444
|
span.end();
|
|
365
445
|
});
|
|
366
446
|
|
|
447
|
+
// Record processed block metric
|
|
448
|
+
indexerMetrics.processedBlockCounter.add(1, {
|
|
449
|
+
indexer_id: indexerId,
|
|
450
|
+
});
|
|
451
|
+
|
|
367
452
|
context.cursor = undefined;
|
|
368
453
|
context.endCursor = undefined;
|
|
369
454
|
context.finality = undefined;
|
|
@@ -372,21 +457,35 @@ export async function run<TFilter, TBlock>(
|
|
|
372
457
|
}
|
|
373
458
|
case "invalidate": {
|
|
374
459
|
await tracer.startActiveSpan("message invalidate", async (span) => {
|
|
375
|
-
|
|
460
|
+
// Record reorg metric
|
|
461
|
+
indexerMetrics.reorgCounter.add(1, {
|
|
462
|
+
indexer_id: indexerId,
|
|
463
|
+
});
|
|
464
|
+
await indexer.hooks.callHook("message:invalidate", {
|
|
465
|
+
message: message.invalidate,
|
|
466
|
+
abortSignal,
|
|
467
|
+
});
|
|
376
468
|
span.end();
|
|
377
469
|
});
|
|
378
470
|
break;
|
|
379
471
|
}
|
|
380
472
|
case "finalize": {
|
|
381
473
|
await tracer.startActiveSpan("message finalize", async (span) => {
|
|
382
|
-
await indexer.hooks.callHook("message:finalize", {
|
|
474
|
+
await indexer.hooks.callHook("message:finalize", {
|
|
475
|
+
message: message.finalize,
|
|
476
|
+
abortSignal,
|
|
477
|
+
});
|
|
383
478
|
span.end();
|
|
384
479
|
});
|
|
385
480
|
break;
|
|
386
481
|
}
|
|
387
482
|
case "heartbeat": {
|
|
388
483
|
await tracer.startActiveSpan("message heartbeat", async (span) => {
|
|
389
|
-
|
|
484
|
+
reloadIfNeeded();
|
|
485
|
+
|
|
486
|
+
await indexer.hooks.callHook("message:heartbeat", { abortSignal });
|
|
487
|
+
reloadIfNeeded();
|
|
488
|
+
|
|
390
489
|
span.end();
|
|
391
490
|
});
|
|
392
491
|
break;
|
|
@@ -409,7 +508,8 @@ export async function run<TFilter, TBlock>(
|
|
|
409
508
|
}
|
|
410
509
|
|
|
411
510
|
await indexer.hooks.callHook("message:systemMessage", {
|
|
412
|
-
message,
|
|
511
|
+
message: message.systemMessage,
|
|
512
|
+
abortSignal,
|
|
413
513
|
});
|
|
414
514
|
span.end();
|
|
415
515
|
},
|
|
@@ -421,21 +521,22 @@ export async function run<TFilter, TBlock>(
|
|
|
421
521
|
throw new Error("not implemented");
|
|
422
522
|
}
|
|
423
523
|
}
|
|
424
|
-
|
|
425
|
-
await indexer.hooks.callHook("run:after");
|
|
426
524
|
}
|
|
525
|
+
|
|
526
|
+
await indexer.hooks.callHook("run:after", { abortSignal });
|
|
427
527
|
});
|
|
428
528
|
}
|
|
429
529
|
|
|
430
530
|
async function registerMiddleware<TFilter, TBlock>(
|
|
431
531
|
indexer: Indexer<TFilter, TBlock>,
|
|
532
|
+
abortSignal?: AbortSignal,
|
|
432
533
|
): Promise<MiddlewareFunction<IndexerContext>> {
|
|
433
534
|
const middleware: MiddlewareFunction<IndexerContext>[] = [];
|
|
434
535
|
const use = (fn: MiddlewareFunction<IndexerContext>) => {
|
|
435
536
|
middleware.push(fn);
|
|
436
537
|
};
|
|
437
538
|
|
|
438
|
-
await indexer.hooks.callHook("handler:middleware", { use });
|
|
539
|
+
await indexer.hooks.callHook("handler:middleware", { use, abortSignal });
|
|
439
540
|
|
|
440
541
|
const composed = compose(middleware);
|
|
441
542
|
|
package/src/internal/testing.ts
CHANGED
|
@@ -5,58 +5,97 @@ import {
|
|
|
5
5
|
MockStream,
|
|
6
6
|
type MockStreamResponse,
|
|
7
7
|
} from "@apibara/protocol/testing";
|
|
8
|
-
|
|
9
8
|
import { useIndexerContext } from "../context";
|
|
10
9
|
import { type IndexerConfig, createIndexer, defineIndexer } from "../indexer";
|
|
11
|
-
import { defineIndexerPlugin } from "../plugins";
|
|
10
|
+
import { defineIndexerPlugin, logger } from "../plugins";
|
|
12
11
|
import { type InternalContext, internalContext } from "./plugins";
|
|
13
12
|
|
|
13
|
+
export type InvalidateConfig = {
|
|
14
|
+
invalidateFromIndex: number;
|
|
15
|
+
invalidateTriggerIndex: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type FinalizeConfig = {
|
|
19
|
+
finalizeToIndex: number;
|
|
20
|
+
finalizeTriggerIndex: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
14
23
|
export type MockMessagesOptions = {
|
|
15
|
-
invalidate?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
finalize?: {
|
|
20
|
-
finalizeToIndex: number;
|
|
21
|
-
finalizeTriggerIndex: number;
|
|
22
|
-
};
|
|
24
|
+
invalidate?: InvalidateConfig | InvalidateConfig[];
|
|
25
|
+
finalize?: FinalizeConfig | FinalizeConfig[];
|
|
26
|
+
uniqueKey?: boolean;
|
|
27
|
+
baseBlockNumber?: bigint;
|
|
23
28
|
};
|
|
24
29
|
|
|
25
30
|
export function generateMockMessages(
|
|
26
31
|
count = 10,
|
|
27
32
|
options?: MockMessagesOptions,
|
|
28
33
|
): MockStreamResponse[] {
|
|
29
|
-
const invalidateAt = options?.invalidate;
|
|
30
|
-
const finalizeAt = options?.finalize;
|
|
31
34
|
const messages: MockStreamResponse[] = [];
|
|
35
|
+
const baseBlockNumber = options?.baseBlockNumber ?? BigInt(5_000_000);
|
|
36
|
+
|
|
37
|
+
const invalidateConfigs = options?.invalidate
|
|
38
|
+
? Array.isArray(options.invalidate)
|
|
39
|
+
? options.invalidate
|
|
40
|
+
: [options.invalidate]
|
|
41
|
+
: [];
|
|
42
|
+
|
|
43
|
+
const finalizeConfigs = options?.finalize
|
|
44
|
+
? Array.isArray(options.finalize)
|
|
45
|
+
? options.finalize
|
|
46
|
+
: [options.finalize]
|
|
47
|
+
: [];
|
|
32
48
|
|
|
33
49
|
for (let i = 0; i < count; i++) {
|
|
34
|
-
|
|
50
|
+
const currentBlockNumber = baseBlockNumber + BigInt(i);
|
|
51
|
+
const uniqueKey = uniqueKeyFromOrderKey(currentBlockNumber);
|
|
52
|
+
|
|
53
|
+
const invalidateConfig = invalidateConfigs.find(
|
|
54
|
+
(cfg) => cfg.invalidateTriggerIndex === i,
|
|
55
|
+
);
|
|
56
|
+
const finalizeConfig = finalizeConfigs.find(
|
|
57
|
+
(cfg) => cfg.finalizeTriggerIndex === i,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (invalidateConfig) {
|
|
61
|
+
const invalidateToBlock =
|
|
62
|
+
baseBlockNumber + BigInt(invalidateConfig.invalidateFromIndex);
|
|
35
63
|
messages.push({
|
|
36
64
|
_tag: "invalidate",
|
|
37
65
|
invalidate: {
|
|
38
66
|
cursor: {
|
|
39
|
-
orderKey:
|
|
67
|
+
orderKey: invalidateToBlock,
|
|
68
|
+
uniqueKey: options?.uniqueKey
|
|
69
|
+
? uniqueKeyFromOrderKey(invalidateToBlock)
|
|
70
|
+
: undefined,
|
|
40
71
|
},
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
} else if (
|
|
72
|
+
} as Invalidate,
|
|
73
|
+
});
|
|
74
|
+
} else if (finalizeConfig) {
|
|
75
|
+
const finalizedToBlock =
|
|
76
|
+
baseBlockNumber + BigInt(finalizeConfig.finalizeToIndex);
|
|
44
77
|
messages.push({
|
|
45
78
|
_tag: "finalize",
|
|
46
79
|
finalize: {
|
|
47
80
|
cursor: {
|
|
48
|
-
orderKey:
|
|
81
|
+
orderKey: finalizedToBlock,
|
|
82
|
+
uniqueKey: options?.uniqueKey
|
|
83
|
+
? uniqueKeyFromOrderKey(finalizedToBlock)
|
|
84
|
+
: undefined,
|
|
49
85
|
},
|
|
50
|
-
},
|
|
51
|
-
}
|
|
86
|
+
} as Finalize,
|
|
87
|
+
});
|
|
52
88
|
} else {
|
|
53
89
|
messages.push({
|
|
54
90
|
_tag: "data",
|
|
55
91
|
data: {
|
|
56
|
-
cursor: { orderKey:
|
|
92
|
+
cursor: { orderKey: currentBlockNumber - 1n },
|
|
57
93
|
finality: "accepted",
|
|
58
|
-
data: [{ data: `${
|
|
59
|
-
endCursor: {
|
|
94
|
+
data: [{ data: `${baseBlockNumber + BigInt(i)}` }],
|
|
95
|
+
endCursor: {
|
|
96
|
+
orderKey: currentBlockNumber,
|
|
97
|
+
uniqueKey: options?.uniqueKey ? uniqueKey : undefined,
|
|
98
|
+
},
|
|
60
99
|
production: "backfill",
|
|
61
100
|
},
|
|
62
101
|
});
|
|
@@ -66,6 +105,10 @@ export function generateMockMessages(
|
|
|
66
105
|
return messages;
|
|
67
106
|
}
|
|
68
107
|
|
|
108
|
+
function uniqueKeyFromOrderKey(orderKey: bigint): `0x${string}` {
|
|
109
|
+
return `0xff00${orderKey.toString()}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
69
112
|
type MockIndexerParams = {
|
|
70
113
|
internalContext?: InternalContext;
|
|
71
114
|
override?: Partial<IndexerConfig<MockFilter, MockBlock>>;
|
|
@@ -82,6 +125,7 @@ export function getMockIndexer(params?: MockIndexerParams) {
|
|
|
82
125
|
filter: {},
|
|
83
126
|
async transform() {},
|
|
84
127
|
plugins: [
|
|
128
|
+
logger(),
|
|
85
129
|
internalContext(
|
|
86
130
|
contextParams ??
|
|
87
131
|
({
|
package/src/otel.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
import { trace } from "@opentelemetry/api";
|
|
1
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function createTracer() {
|
|
4
|
+
return trace.getTracer("@apibara/indexer");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createIndexerMetrics() {
|
|
8
|
+
const meter = metrics.getMeter("@apibara/indexer");
|
|
9
|
+
|
|
10
|
+
const currentBlockGauge = meter.createGauge("current_block", {
|
|
11
|
+
description: "Current block number being processed",
|
|
12
|
+
unit: "{block}",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const processedBlockCounter = meter.createCounter("processed_blocks", {
|
|
16
|
+
description: "Number of blocks processed",
|
|
17
|
+
unit: "{blocks}",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const reorgCounter = meter.createCounter("reorgs", {
|
|
21
|
+
description: "Number of reorgs (invalidate messages) received",
|
|
22
|
+
unit: "{reorgs}",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
currentBlockGauge,
|
|
27
|
+
processedBlockCounter,
|
|
28
|
+
reorgCounter,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/plugins/context.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function internalContext<TFilter, TBlock, TTxnParams>(
|
|
|
7
7
|
values: Record<string, unknown>,
|
|
8
8
|
) {
|
|
9
9
|
return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
|
|
10
|
-
indexer.hooks.hook("
|
|
10
|
+
indexer.hooks.hook("plugins:init", () => {
|
|
11
11
|
try {
|
|
12
12
|
const ctx = useIndexerContext();
|
|
13
13
|
ctx[INTERNAL_CONTEXT_PROPERTY] = {
|
package/src/plugins/logger.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ConsolaInstance,
|
|
3
|
+
type ConsolaReporter,
|
|
4
|
+
LogLevels,
|
|
5
|
+
consola,
|
|
6
|
+
} from "consola";
|
|
2
7
|
import { useIndexerContext } from "../context";
|
|
3
8
|
import { defineIndexerPlugin } from "./config";
|
|
4
9
|
|
|
@@ -8,7 +13,7 @@ export function logger<TFilter, TBlock, TTxnParams>({
|
|
|
8
13
|
logger,
|
|
9
14
|
}: { logger?: ConsolaReporter } = {}) {
|
|
10
15
|
return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
|
|
11
|
-
indexer.hooks.hook("
|
|
16
|
+
indexer.hooks.hook("plugins:init", () => {
|
|
12
17
|
const ctx = useIndexerContext();
|
|
13
18
|
|
|
14
19
|
if (logger) {
|
|
@@ -16,6 +21,10 @@ export function logger<TFilter, TBlock, TTxnParams>({
|
|
|
16
21
|
} else {
|
|
17
22
|
ctx.logger = consola.create({});
|
|
18
23
|
}
|
|
24
|
+
|
|
25
|
+
if (ctx.debug) {
|
|
26
|
+
ctx.logger.level = LogLevels.debug;
|
|
27
|
+
}
|
|
19
28
|
});
|
|
20
29
|
});
|
|
21
30
|
}
|