@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.
Files changed (70) hide show
  1. package/dist/index.cjs +134 -41
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +8 -1
  4. package/dist/index.d.mts +8 -1
  5. package/dist/index.d.ts +8 -1
  6. package/dist/index.mjs +125 -34
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/internal/index.cjs +1 -0
  9. package/dist/internal/index.cjs.map +1 -0
  10. package/dist/internal/index.mjs +1 -0
  11. package/dist/internal/index.mjs.map +1 -0
  12. package/dist/internal/plugins.cjs +5 -5
  13. package/dist/internal/plugins.cjs.map +1 -0
  14. package/dist/internal/plugins.d.cts +1 -1
  15. package/dist/internal/plugins.d.mts +1 -1
  16. package/dist/internal/plugins.d.ts +1 -1
  17. package/dist/internal/plugins.mjs +3 -3
  18. package/dist/internal/plugins.mjs.map +1 -0
  19. package/dist/internal/testing.cjs +35 -14
  20. package/dist/internal/testing.cjs.map +1 -0
  21. package/dist/internal/testing.d.cts +16 -12
  22. package/dist/internal/testing.d.mts +16 -12
  23. package/dist/internal/testing.d.ts +16 -12
  24. package/dist/internal/testing.mjs +33 -12
  25. package/dist/internal/testing.mjs.map +1 -0
  26. package/dist/plugins/index.cjs +4 -4
  27. package/dist/plugins/index.cjs.map +1 -0
  28. package/dist/plugins/index.d.cts +2 -2
  29. package/dist/plugins/index.d.mts +2 -2
  30. package/dist/plugins/index.d.ts +2 -2
  31. package/dist/plugins/index.mjs +4 -4
  32. package/dist/plugins/index.mjs.map +1 -0
  33. package/dist/shared/{indexer.2416906c.cjs → indexer.03c9f151.cjs} +8 -5
  34. package/dist/shared/indexer.03c9f151.cjs.map +1 -0
  35. package/dist/shared/{indexer.ff25c953.mjs → indexer.2673dcb1.mjs} +7 -4
  36. package/dist/shared/indexer.2673dcb1.mjs.map +1 -0
  37. package/dist/shared/{indexer.077335f3.cjs → indexer.479ae593.cjs} +6 -0
  38. package/dist/shared/indexer.479ae593.cjs.map +1 -0
  39. package/dist/shared/{indexer.fedcd831.d.cts → indexer.4ef52548.d.cts} +40 -23
  40. package/dist/shared/{indexer.fedcd831.d.mts → indexer.4ef52548.d.mts} +40 -23
  41. package/dist/shared/{indexer.fedcd831.d.ts → indexer.4ef52548.d.ts} +40 -23
  42. package/dist/shared/{indexer.a55ad619.mjs → indexer.75773ef1.mjs} +6 -1
  43. package/dist/shared/indexer.75773ef1.mjs.map +1 -0
  44. package/dist/testing/index.cjs +19 -10
  45. package/dist/testing/index.cjs.map +1 -0
  46. package/dist/testing/index.d.cts +11 -7
  47. package/dist/testing/index.d.mts +11 -7
  48. package/dist/testing/index.d.ts +11 -7
  49. package/dist/testing/index.mjs +20 -11
  50. package/dist/testing/index.mjs.map +1 -0
  51. package/dist/vcr/index.cjs +3 -1
  52. package/dist/vcr/index.cjs.map +1 -0
  53. package/dist/vcr/index.d.cts +1 -1
  54. package/dist/vcr/index.d.mts +1 -1
  55. package/dist/vcr/index.d.ts +1 -1
  56. package/dist/vcr/index.mjs +3 -1
  57. package/dist/vcr/index.mjs.map +1 -0
  58. package/package.json +3 -3
  59. package/src/index.ts +1 -0
  60. package/src/indexer.ts +151 -50
  61. package/src/internal/testing.ts +67 -23
  62. package/src/otel.ts +29 -2
  63. package/src/plugins/context.ts +1 -1
  64. package/src/plugins/logger.ts +11 -2
  65. package/src/testing/index.ts +30 -6
  66. package/src/utils/index.ts +21 -0
  67. package/dist/shared/indexer.601ceab0.cjs +0 -7
  68. package/dist/shared/indexer.9b21ddd2.mjs +0 -5
  69. package/src/compose.test.ts +0 -76
  70. 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 { tracer } from "./otel";
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
- "run:before": () => void;
40
- "run:after": () => void;
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
- }: { request: StreamDataRequest<TFilter> }) => void;
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": ({ use }: { use: UseMiddlewareFunction }) => void;
59
- message: ({ message }: { message: StreamDataResponse<TBlock> }) => void;
60
- "message:invalidate": ({ message }: { message: Invalidate }) => void;
61
- "message:finalize": ({ message }: { message: Finalize }) => void;
62
- "message:heartbeat": ({ message }: { message: Heartbeat }) => void;
63
- "message:systemMessage": ({ message }: { message: SystemMessage }) => void;
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
- factory?: ({
85
- block,
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
- if (error instanceof ClientError) {
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
- "Internal server error, reconnecting...",
187
- error.message,
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
- await indexer.hooks.callHook("run:before");
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 = indexer.streamConfig.Request.make({
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", { request, options });
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 = indexer.streamConfig.Request.make({
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
- await indexer.hooks.callHook("message:invalidate", { message });
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", { message });
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
- await indexer.hooks.callHook("message:heartbeat", { message });
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
 
@@ -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
- invalidateFromIndex: number;
17
- invalidateTriggerIndex: number;
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
- if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
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: BigInt(5_000_000 + invalidateAt.invalidateFromIndex),
67
+ orderKey: invalidateToBlock,
68
+ uniqueKey: options?.uniqueKey
69
+ ? uniqueKeyFromOrderKey(invalidateToBlock)
70
+ : undefined,
40
71
  },
41
- },
42
- } as Invalidate);
43
- } else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
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: BigInt(5_000_000 + finalizeAt.finalizeToIndex),
81
+ orderKey: finalizedToBlock,
82
+ uniqueKey: options?.uniqueKey
83
+ ? uniqueKeyFromOrderKey(finalizedToBlock)
84
+ : undefined,
49
85
  },
50
- },
51
- } as Finalize);
86
+ } as Finalize,
87
+ });
52
88
  } else {
53
89
  messages.push({
54
90
  _tag: "data",
55
91
  data: {
56
- cursor: { orderKey: BigInt(5_000_000 + i - 1) },
92
+ cursor: { orderKey: currentBlockNumber - 1n },
57
93
  finality: "accepted",
58
- data: [{ data: `${5_000_000 + i}` }],
59
- endCursor: { orderKey: BigInt(5_000_000 + i) },
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 const tracer = trace.getTracer("@apibara/indexer");
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
+ }
@@ -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("run:before", () => {
10
+ indexer.hooks.hook("plugins:init", () => {
11
11
  try {
12
12
  const ctx = useIndexerContext();
13
13
  ctx[INTERNAL_CONTEXT_PROPERTY] = {
@@ -1,4 +1,9 @@
1
- import { type ConsolaInstance, type ConsolaReporter, consola } from "consola";
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("run:before", () => {
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
  }