@apibara/indexer 2.1.0-beta.2 → 2.1.0-beta.21

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 CHANGED
@@ -4,8 +4,9 @@ const protocol = require('@apibara/protocol');
4
4
  const consola = require('consola');
5
5
  const hookable = require('hookable');
6
6
  const assert = require('node:assert');
7
- const context = require('./shared/indexer.077335f3.cjs');
7
+ const config = require('./shared/indexer.479ae593.cjs');
8
8
  const api = require('@opentelemetry/api');
9
+ const internal_plugins = require('./internal/plugins.cjs');
9
10
  require('node:async_hooks');
10
11
  require('unctx');
11
12
 
@@ -43,7 +44,29 @@ function compose(middleware) {
43
44
  };
44
45
  }
45
46
 
46
- const tracer = api.trace.getTracer("@apibara/indexer");
47
+ function createTracer() {
48
+ return api.trace.getTracer("@apibara/indexer");
49
+ }
50
+ function createIndexerMetrics() {
51
+ const meter = api.metrics.getMeter("@apibara/indexer");
52
+ const currentBlockGauge = meter.createGauge("current_block", {
53
+ description: "Current block number being processed",
54
+ unit: "{block}"
55
+ });
56
+ const processedBlockCounter = meter.createCounter("processed_blocks", {
57
+ description: "Number of blocks processed",
58
+ unit: "{blocks}"
59
+ });
60
+ const reorgCounter = meter.createCounter("reorgs", {
61
+ description: "Number of reorgs (invalidate messages) received",
62
+ unit: "{reorgs}"
63
+ });
64
+ return {
65
+ currentBlockGauge,
66
+ processedBlockCounter,
67
+ reorgCounter
68
+ };
69
+ }
47
70
 
48
71
  function defineIndexer(streamConfig) {
49
72
  return (config) => ({
@@ -105,10 +128,13 @@ async function runWithReconnect(client, indexer, options = {}) {
105
128
  }
106
129
  }
107
130
  async function run(client, indexer, runOptions = {}) {
108
- await context.indexerAsyncContext.callAsync({}, async () => {
109
- const context$1 = context.useIndexerContext();
131
+ await config.indexerAsyncContext.callAsync({}, async () => {
132
+ const context = config.useIndexerContext();
110
133
  const middleware = await registerMiddleware(indexer);
134
+ const indexerMetrics = createIndexerMetrics();
135
+ const tracer = createTracer();
111
136
  await indexer.hooks.callHook("run:before");
137
+ const { indexerName: indexerId } = internal_plugins.useInternalContext();
112
138
  const isFactoryMode = indexer.options.factory !== void 0;
113
139
  let startingCursor;
114
140
  if (indexer.options.startingCursor) {
@@ -153,19 +179,25 @@ async function run(client, indexer, runOptions = {}) {
153
179
  await tracer.startActiveSpan("message data", async (span) => {
154
180
  const blocks = message.data.data;
155
181
  const { cursor, endCursor, finality } = message.data;
156
- context$1.cursor = cursor;
157
- context$1.endCursor = endCursor;
158
- context$1.finality = finality;
159
- await middleware(context$1, async () => {
182
+ context.cursor = cursor;
183
+ context.endCursor = endCursor;
184
+ context.finality = finality;
185
+ indexerMetrics.currentBlockGauge.record(
186
+ Number(endCursor?.orderKey),
187
+ {
188
+ indexer_id: indexerId
189
+ }
190
+ );
191
+ await middleware(context, async () => {
160
192
  let block;
161
- if (isFactoryMode) {
193
+ if (isFactoryMode && finality !== "pending") {
162
194
  assert__default(indexer.options.factory !== void 0);
163
195
  const [factoryBlock, mainBlock] = blocks;
164
196
  block = mainBlock;
165
197
  if (factoryBlock !== null) {
166
198
  const { filter } = await indexer.options.factory({
167
199
  block: factoryBlock,
168
- context: context$1
200
+ context
169
201
  });
170
202
  if (filter) {
171
203
  mainFilter = indexer.streamConfig.mergeFilter(
@@ -198,7 +230,7 @@ async function run(client, indexer, runOptions = {}) {
198
230
  cursor,
199
231
  endCursor,
200
232
  finality,
201
- context: context$1
233
+ context
202
234
  });
203
235
  span2.end();
204
236
  });
@@ -206,13 +238,19 @@ async function run(client, indexer, runOptions = {}) {
206
238
  });
207
239
  span.end();
208
240
  });
209
- context$1.cursor = void 0;
210
- context$1.endCursor = void 0;
211
- context$1.finality = void 0;
241
+ indexerMetrics.processedBlockCounter.add(1, {
242
+ indexer_id: indexerId
243
+ });
244
+ context.cursor = void 0;
245
+ context.endCursor = void 0;
246
+ context.finality = void 0;
212
247
  break;
213
248
  }
214
249
  case "invalidate": {
215
250
  await tracer.startActiveSpan("message invalidate", async (span) => {
251
+ indexerMetrics.reorgCounter.add(1, {
252
+ indexer_id: indexerId
253
+ });
216
254
  await indexer.hooks.callHook("message:invalidate", { message });
217
255
  span.end();
218
256
  });
@@ -275,7 +313,7 @@ async function registerMiddleware(indexer) {
275
313
  };
276
314
  }
277
315
 
278
- exports.useIndexerContext = context.useIndexerContext;
316
+ exports.useIndexerContext = config.useIndexerContext;
279
317
  exports.createIndexer = createIndexer;
280
318
  exports.defineIndexer = defineIndexer;
281
319
  exports.run = run;
package/dist/index.mjs CHANGED
@@ -2,8 +2,9 @@ import { ClientError, Status } from '@apibara/protocol';
2
2
  import consola from 'consola';
3
3
  import { createHooks, createDebugger } from 'hookable';
4
4
  import assert from 'node:assert';
5
- import { i as indexerAsyncContext, u as useIndexerContext } from './shared/indexer.a55ad619.mjs';
6
- import { trace } from '@opentelemetry/api';
5
+ import { i as indexerAsyncContext, u as useIndexerContext } from './shared/indexer.75773ef1.mjs';
6
+ import { trace, metrics } from '@opentelemetry/api';
7
+ import { useInternalContext } from './internal/plugins.mjs';
7
8
  import 'node:async_hooks';
8
9
  import 'unctx';
9
10
 
@@ -36,7 +37,29 @@ function compose(middleware) {
36
37
  };
37
38
  }
38
39
 
39
- const tracer = trace.getTracer("@apibara/indexer");
40
+ function createTracer() {
41
+ return trace.getTracer("@apibara/indexer");
42
+ }
43
+ function createIndexerMetrics() {
44
+ const meter = metrics.getMeter("@apibara/indexer");
45
+ const currentBlockGauge = meter.createGauge("current_block", {
46
+ description: "Current block number being processed",
47
+ unit: "{block}"
48
+ });
49
+ const processedBlockCounter = meter.createCounter("processed_blocks", {
50
+ description: "Number of blocks processed",
51
+ unit: "{blocks}"
52
+ });
53
+ const reorgCounter = meter.createCounter("reorgs", {
54
+ description: "Number of reorgs (invalidate messages) received",
55
+ unit: "{reorgs}"
56
+ });
57
+ return {
58
+ currentBlockGauge,
59
+ processedBlockCounter,
60
+ reorgCounter
61
+ };
62
+ }
40
63
 
41
64
  function defineIndexer(streamConfig) {
42
65
  return (config) => ({
@@ -101,7 +124,10 @@ async function run(client, indexer, runOptions = {}) {
101
124
  await indexerAsyncContext.callAsync({}, async () => {
102
125
  const context = useIndexerContext();
103
126
  const middleware = await registerMiddleware(indexer);
127
+ const indexerMetrics = createIndexerMetrics();
128
+ const tracer = createTracer();
104
129
  await indexer.hooks.callHook("run:before");
130
+ const { indexerName: indexerId } = useInternalContext();
105
131
  const isFactoryMode = indexer.options.factory !== void 0;
106
132
  let startingCursor;
107
133
  if (indexer.options.startingCursor) {
@@ -149,9 +175,15 @@ async function run(client, indexer, runOptions = {}) {
149
175
  context.cursor = cursor;
150
176
  context.endCursor = endCursor;
151
177
  context.finality = finality;
178
+ indexerMetrics.currentBlockGauge.record(
179
+ Number(endCursor?.orderKey),
180
+ {
181
+ indexer_id: indexerId
182
+ }
183
+ );
152
184
  await middleware(context, async () => {
153
185
  let block;
154
- if (isFactoryMode) {
186
+ if (isFactoryMode && finality !== "pending") {
155
187
  assert(indexer.options.factory !== void 0);
156
188
  const [factoryBlock, mainBlock] = blocks;
157
189
  block = mainBlock;
@@ -199,6 +231,9 @@ async function run(client, indexer, runOptions = {}) {
199
231
  });
200
232
  span.end();
201
233
  });
234
+ indexerMetrics.processedBlockCounter.add(1, {
235
+ indexer_id: indexerId
236
+ });
202
237
  context.cursor = void 0;
203
238
  context.endCursor = void 0;
204
239
  context.finality = void 0;
@@ -206,6 +241,9 @@ async function run(client, indexer, runOptions = {}) {
206
241
  }
207
242
  case "invalidate": {
208
243
  await tracer.startActiveSpan("message invalidate", async (span) => {
244
+ indexerMetrics.reorgCounter.add(1, {
245
+ indexer_id: indexerId
246
+ });
209
247
  await indexer.hooks.callHook("message:invalidate", { message });
210
248
  span.end();
211
249
  });
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const context = require('../shared/indexer.077335f3.cjs');
4
- const config = require('../shared/indexer.601ceab0.cjs');
3
+ const config = require('../shared/indexer.479ae593.cjs');
5
4
  require('node:async_hooks');
6
5
  require('unctx');
7
6
 
@@ -10,7 +9,7 @@ function internalContext(values) {
10
9
  return config.defineIndexerPlugin((indexer) => {
11
10
  indexer.hooks.hook("run:before", () => {
12
11
  try {
13
- const ctx = context.useIndexerContext();
12
+ const ctx = config.useIndexerContext();
14
13
  ctx[INTERNAL_CONTEXT_PROPERTY] = {
15
14
  ...ctx[INTERNAL_CONTEXT_PROPERTY] || {},
16
15
  ...values
@@ -24,7 +23,7 @@ function internalContext(values) {
24
23
  });
25
24
  }
26
25
  function useInternalContext() {
27
- const ctx = context.useIndexerContext();
26
+ const ctx = config.useIndexerContext();
28
27
  if (ctx[INTERNAL_CONTEXT_PROPERTY] === void 0) {
29
28
  throw new Error(
30
29
  "Internal context is not available, possibly 'internalContext' plugin is missing!"
@@ -1,5 +1,4 @@
1
- import { u as useIndexerContext } from '../shared/indexer.a55ad619.mjs';
2
- import { d as defineIndexerPlugin } from '../shared/indexer.9b21ddd2.mjs';
1
+ import { d as defineIndexerPlugin, u as useIndexerContext } from '../shared/indexer.75773ef1.mjs';
3
2
  import 'node:async_hooks';
4
3
  import 'unctx';
5
4
 
@@ -2,13 +2,13 @@
2
2
 
3
3
  const protocol = require('@apibara/protocol');
4
4
  const testing = require('@apibara/protocol/testing');
5
- const context = require('../shared/indexer.077335f3.cjs');
5
+ const config = require('../shared/indexer.479ae593.cjs');
6
6
  const index = require('../index.cjs');
7
- const config = require('../shared/indexer.601ceab0.cjs');
8
- require('consola');
7
+ const logger = require('../shared/indexer.a09fa402.cjs');
9
8
  const internal_plugins = require('./plugins.cjs');
10
9
  require('node:async_hooks');
11
10
  require('unctx');
11
+ require('consola');
12
12
  require('hookable');
13
13
  require('node:assert');
14
14
  require('@opentelemetry/api');
@@ -17,22 +17,29 @@ function generateMockMessages(count = 10, options) {
17
17
  const invalidateAt = options?.invalidate;
18
18
  const finalizeAt = options?.finalize;
19
19
  const messages = [];
20
+ const baseBlockNumber = options?.baseBlockNumber ?? BigInt(5e6);
20
21
  for (let i = 0; i < count; i++) {
22
+ const currentBlockNumber = baseBlockNumber + BigInt(i);
23
+ const uniqueKey = uniqueKeyFromOrderKey(currentBlockNumber);
21
24
  if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
25
+ const invalidateToBlock = baseBlockNumber + BigInt(invalidateAt.invalidateFromIndex);
22
26
  messages.push({
23
27
  _tag: "invalidate",
24
28
  invalidate: {
25
29
  cursor: {
26
- orderKey: BigInt(5e6 + invalidateAt.invalidateFromIndex)
30
+ orderKey: invalidateToBlock,
31
+ uniqueKey: options?.uniqueKey ? uniqueKeyFromOrderKey(invalidateToBlock) : void 0
27
32
  }
28
33
  }
29
34
  });
30
35
  } else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
36
+ const fianlizedToBlock = baseBlockNumber + BigInt(finalizeAt.finalizeToIndex);
31
37
  messages.push({
32
38
  _tag: "finalize",
33
39
  finalize: {
34
40
  cursor: {
35
- orderKey: BigInt(5e6 + finalizeAt.finalizeToIndex)
41
+ orderKey: fianlizedToBlock,
42
+ uniqueKey: options?.uniqueKey ? uniqueKeyFromOrderKey(fianlizedToBlock) : void 0
36
43
  }
37
44
  }
38
45
  });
@@ -40,10 +47,13 @@ function generateMockMessages(count = 10, options) {
40
47
  messages.push({
41
48
  _tag: "data",
42
49
  data: {
43
- cursor: { orderKey: BigInt(5e6 + i - 1) },
50
+ cursor: { orderKey: currentBlockNumber - 1n },
44
51
  finality: "accepted",
45
- data: [{ data: `${5e6 + i}` }],
46
- endCursor: { orderKey: BigInt(5e6 + i) },
52
+ data: [{ data: `${baseBlockNumber + BigInt(i)}` }],
53
+ endCursor: {
54
+ orderKey: currentBlockNumber,
55
+ uniqueKey: options?.uniqueKey ? uniqueKey : void 0
56
+ },
47
57
  production: "backfill"
48
58
  }
49
59
  });
@@ -51,6 +61,9 @@ function generateMockMessages(count = 10, options) {
51
61
  }
52
62
  return messages;
53
63
  }
64
+ function uniqueKeyFromOrderKey(orderKey) {
65
+ return `0xff00${orderKey.toString()}`;
66
+ }
54
67
  function getMockIndexer(params) {
55
68
  const { internalContext: contextParams, override } = params ?? {};
56
69
  const { plugins, ...rest } = override ?? {};
@@ -62,6 +75,7 @@ function getMockIndexer(params) {
62
75
  async transform() {
63
76
  },
64
77
  plugins: [
78
+ logger.logger(),
65
79
  internal_plugins.internalContext(
66
80
  contextParams ?? {
67
81
  availableIndexers: ["testing"],
@@ -108,8 +122,8 @@ function mockSink({
108
122
  });
109
123
  }
110
124
  function useMockSink() {
111
- const context$1 = context.useIndexerContext();
112
- return { output: context$1.output };
125
+ const context = config.useIndexerContext();
126
+ return { output: context.output };
113
127
  }
114
128
 
115
129
  exports.generateMockMessages = generateMockMessages;
@@ -13,6 +13,8 @@ type MockMessagesOptions = {
13
13
  finalizeToIndex: number;
14
14
  finalizeTriggerIndex: number;
15
15
  };
16
+ uniqueKey?: boolean;
17
+ baseBlockNumber?: bigint;
16
18
  };
17
19
  declare function generateMockMessages(count?: number, options?: MockMessagesOptions): MockStreamResponse[];
18
20
  type MockIndexerParams = {
@@ -13,6 +13,8 @@ type MockMessagesOptions = {
13
13
  finalizeToIndex: number;
14
14
  finalizeTriggerIndex: number;
15
15
  };
16
+ uniqueKey?: boolean;
17
+ baseBlockNumber?: bigint;
16
18
  };
17
19
  declare function generateMockMessages(count?: number, options?: MockMessagesOptions): MockStreamResponse[];
18
20
  type MockIndexerParams = {
@@ -13,6 +13,8 @@ type MockMessagesOptions = {
13
13
  finalizeToIndex: number;
14
14
  finalizeTriggerIndex: number;
15
15
  };
16
+ uniqueKey?: boolean;
17
+ baseBlockNumber?: bigint;
16
18
  };
17
19
  declare function generateMockMessages(count?: number, options?: MockMessagesOptions): MockStreamResponse[];
18
20
  type MockIndexerParams = {
@@ -1,12 +1,12 @@
1
1
  import { isCursor } from '@apibara/protocol';
2
2
  import { MockStream } from '@apibara/protocol/testing';
3
- import { u as useIndexerContext } from '../shared/indexer.a55ad619.mjs';
3
+ import { d as defineIndexerPlugin, u as useIndexerContext } from '../shared/indexer.75773ef1.mjs';
4
4
  import { createIndexer, defineIndexer } from '../index.mjs';
5
- import { d as defineIndexerPlugin } from '../shared/indexer.9b21ddd2.mjs';
6
- import 'consola';
5
+ import { l as logger } from '../shared/indexer.98a921a7.mjs';
7
6
  import { internalContext } from './plugins.mjs';
8
7
  import 'node:async_hooks';
9
8
  import 'unctx';
9
+ import 'consola';
10
10
  import 'hookable';
11
11
  import 'node:assert';
12
12
  import '@opentelemetry/api';
@@ -15,22 +15,29 @@ function generateMockMessages(count = 10, options) {
15
15
  const invalidateAt = options?.invalidate;
16
16
  const finalizeAt = options?.finalize;
17
17
  const messages = [];
18
+ const baseBlockNumber = options?.baseBlockNumber ?? BigInt(5e6);
18
19
  for (let i = 0; i < count; i++) {
20
+ const currentBlockNumber = baseBlockNumber + BigInt(i);
21
+ const uniqueKey = uniqueKeyFromOrderKey(currentBlockNumber);
19
22
  if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
23
+ const invalidateToBlock = baseBlockNumber + BigInt(invalidateAt.invalidateFromIndex);
20
24
  messages.push({
21
25
  _tag: "invalidate",
22
26
  invalidate: {
23
27
  cursor: {
24
- orderKey: BigInt(5e6 + invalidateAt.invalidateFromIndex)
28
+ orderKey: invalidateToBlock,
29
+ uniqueKey: options?.uniqueKey ? uniqueKeyFromOrderKey(invalidateToBlock) : void 0
25
30
  }
26
31
  }
27
32
  });
28
33
  } else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
34
+ const fianlizedToBlock = baseBlockNumber + BigInt(finalizeAt.finalizeToIndex);
29
35
  messages.push({
30
36
  _tag: "finalize",
31
37
  finalize: {
32
38
  cursor: {
33
- orderKey: BigInt(5e6 + finalizeAt.finalizeToIndex)
39
+ orderKey: fianlizedToBlock,
40
+ uniqueKey: options?.uniqueKey ? uniqueKeyFromOrderKey(fianlizedToBlock) : void 0
34
41
  }
35
42
  }
36
43
  });
@@ -38,10 +45,13 @@ function generateMockMessages(count = 10, options) {
38
45
  messages.push({
39
46
  _tag: "data",
40
47
  data: {
41
- cursor: { orderKey: BigInt(5e6 + i - 1) },
48
+ cursor: { orderKey: currentBlockNumber - 1n },
42
49
  finality: "accepted",
43
- data: [{ data: `${5e6 + i}` }],
44
- endCursor: { orderKey: BigInt(5e6 + i) },
50
+ data: [{ data: `${baseBlockNumber + BigInt(i)}` }],
51
+ endCursor: {
52
+ orderKey: currentBlockNumber,
53
+ uniqueKey: options?.uniqueKey ? uniqueKey : void 0
54
+ },
45
55
  production: "backfill"
46
56
  }
47
57
  });
@@ -49,6 +59,9 @@ function generateMockMessages(count = 10, options) {
49
59
  }
50
60
  return messages;
51
61
  }
62
+ function uniqueKeyFromOrderKey(orderKey) {
63
+ return `0xff00${orderKey.toString()}`;
64
+ }
52
65
  function getMockIndexer(params) {
53
66
  const { internalContext: contextParams, override } = params ?? {};
54
67
  const { plugins, ...rest } = override ?? {};
@@ -60,6 +73,7 @@ function getMockIndexer(params) {
60
73
  async transform() {
61
74
  },
62
75
  plugins: [
76
+ logger(),
63
77
  internalContext(
64
78
  contextParams ?? {
65
79
  availableIndexers: ["testing"],
@@ -1,12 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const config = require('../shared/indexer.601ceab0.cjs');
4
- const logger = require('../shared/indexer.2416906c.cjs');
3
+ const config = require('../shared/indexer.479ae593.cjs');
4
+ const logger = require('../shared/indexer.a09fa402.cjs');
5
5
  const protocol = require('@apibara/protocol');
6
- require('consola');
7
- require('../shared/indexer.077335f3.cjs');
8
6
  require('node:async_hooks');
9
7
  require('unctx');
8
+ require('consola');
10
9
 
11
10
  function inMemoryPersistence() {
12
11
  return config.defineIndexerPlugin((indexer) => {
@@ -1,10 +1,9 @@
1
- import { d as defineIndexerPlugin } from '../shared/indexer.9b21ddd2.mjs';
2
- export { l as logger, u as useLogger } from '../shared/indexer.ff25c953.mjs';
1
+ import { d as defineIndexerPlugin } from '../shared/indexer.75773ef1.mjs';
2
+ export { l as logger, u as useLogger } from '../shared/indexer.98a921a7.mjs';
3
3
  import { isCursor } from '@apibara/protocol';
4
- import 'consola';
5
- import '../shared/indexer.a55ad619.mjs';
6
4
  import 'node:async_hooks';
7
5
  import 'unctx';
6
+ import 'consola';
8
7
 
9
8
  function inMemoryPersistence() {
10
9
  return defineIndexerPlugin((indexer) => {
@@ -11,5 +11,10 @@ function useIndexerContext() {
11
11
  return indexerAsyncContext.use();
12
12
  }
13
13
 
14
+ function defineIndexerPlugin(def) {
15
+ return def;
16
+ }
17
+
18
+ exports.defineIndexerPlugin = defineIndexerPlugin;
14
19
  exports.indexerAsyncContext = indexerAsyncContext;
15
20
  exports.useIndexerContext = useIndexerContext;
@@ -9,4 +9,8 @@ function useIndexerContext() {
9
9
  return indexerAsyncContext.use();
10
10
  }
11
11
 
12
- export { indexerAsyncContext as i, useIndexerContext as u };
12
+ function defineIndexerPlugin(def) {
13
+ return def;
14
+ }
15
+
16
+ export { defineIndexerPlugin as d, indexerAsyncContext as i, useIndexerContext as u };
@@ -1,6 +1,5 @@
1
1
  import { consola } from 'consola';
2
- import { u as useIndexerContext } from './indexer.a55ad619.mjs';
3
- import { d as defineIndexerPlugin } from './indexer.9b21ddd2.mjs';
2
+ import { d as defineIndexerPlugin, u as useIndexerContext } from './indexer.75773ef1.mjs';
4
3
 
5
4
  function logger({
6
5
  logger: logger2
@@ -1,15 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  const consola = require('consola');
4
- const context = require('./indexer.077335f3.cjs');
5
- const config = require('./indexer.601ceab0.cjs');
4
+ const config = require('./indexer.479ae593.cjs');
6
5
 
7
6
  function logger({
8
7
  logger: logger2
9
8
  } = {}) {
10
9
  return config.defineIndexerPlugin((indexer) => {
11
10
  indexer.hooks.hook("run:before", () => {
12
- const ctx = context.useIndexerContext();
11
+ const ctx = config.useIndexerContext();
13
12
  if (logger2) {
14
13
  ctx.logger = consola.consola.create({ reporters: [logger2] });
15
14
  } else {
@@ -19,7 +18,7 @@ function logger({
19
18
  });
20
19
  }
21
20
  function useLogger() {
22
- const ctx = context.useIndexerContext();
21
+ const ctx = config.useIndexerContext();
23
22
  if (!ctx?.logger)
24
23
  throw new Error("Logger plugin is not available in context");
25
24
  return ctx.logger;
@@ -2,18 +2,17 @@
2
2
 
3
3
  const protocol = require('@apibara/protocol');
4
4
  const ci = require('ci-info');
5
+ const config = require('../shared/indexer.479ae593.cjs');
5
6
  const index = require('../index.cjs');
6
7
  const internal_plugins = require('../internal/plugins.cjs');
7
- const logger = require('../shared/indexer.2416906c.cjs');
8
+ const logger = require('../shared/indexer.a09fa402.cjs');
8
9
  const vcr_index = require('../vcr/index.cjs');
10
+ require('node:async_hooks');
11
+ require('unctx');
9
12
  require('consola');
10
13
  require('hookable');
11
14
  require('node:assert');
12
- require('../shared/indexer.077335f3.cjs');
13
- require('node:async_hooks');
14
- require('unctx');
15
15
  require('@opentelemetry/api');
16
- require('../shared/indexer.601ceab0.cjs');
17
16
  require('node:fs/promises');
18
17
  require('node:path');
19
18
  require('node:fs');
@@ -24,6 +23,7 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
24
23
  const ci__default = /*#__PURE__*/_interopDefaultCompat(ci);
25
24
 
26
25
  function createVcr() {
26
+ let result;
27
27
  return {
28
28
  async run(cassetteName, indexerConfig, range) {
29
29
  const vcrConfig = {
@@ -47,6 +47,9 @@ function createVcr() {
47
47
  ...indexerConfig.plugins ?? []
48
48
  ];
49
49
  const indexer = index.createIndexer(indexerConfig);
50
+ indexer.hooks.hook("run:after", () => {
51
+ result = config.useIndexerContext();
52
+ });
50
53
  if (!vcr_index.isCassetteAvailable(vcrConfig, cassetteName)) {
51
54
  if (ci__default.isCI) {
52
55
  throw new Error("Cannot record cassette in CI");
@@ -59,6 +62,7 @@ function createVcr() {
59
62
  } else {
60
63
  await vcr_index.replay(vcrConfig, indexer, cassetteName);
61
64
  }
65
+ return result;
62
66
  }
63
67
  };
64
68
  }
@@ -2,11 +2,12 @@ import { I as IndexerWithStreamConfig } from '../shared/indexer.fedcd831.cjs';
2
2
  import '@apibara/protocol';
3
3
  import 'hookable';
4
4
 
5
+ type VcrResult = Record<string, unknown>;
5
6
  declare function createVcr(): {
6
7
  run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
7
8
  fromBlock: bigint;
8
9
  toBlock: bigint;
9
- }): Promise<void>;
10
+ }): Promise<VcrResult>;
10
11
  };
11
12
 
12
- export { createVcr };
13
+ export { type VcrResult, createVcr };
@@ -2,11 +2,12 @@ import { I as IndexerWithStreamConfig } from '../shared/indexer.fedcd831.mjs';
2
2
  import '@apibara/protocol';
3
3
  import 'hookable';
4
4
 
5
+ type VcrResult = Record<string, unknown>;
5
6
  declare function createVcr(): {
6
7
  run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
7
8
  fromBlock: bigint;
8
9
  toBlock: bigint;
9
- }): Promise<void>;
10
+ }): Promise<VcrResult>;
10
11
  };
11
12
 
12
- export { createVcr };
13
+ export { type VcrResult, createVcr };
@@ -2,11 +2,12 @@ import { I as IndexerWithStreamConfig } from '../shared/indexer.fedcd831.js';
2
2
  import '@apibara/protocol';
3
3
  import 'hookable';
4
4
 
5
+ type VcrResult = Record<string, unknown>;
5
6
  declare function createVcr(): {
6
7
  run<TFilter, TBlock>(cassetteName: string, indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>, range: {
7
8
  fromBlock: bigint;
8
9
  toBlock: bigint;
9
- }): Promise<void>;
10
+ }): Promise<VcrResult>;
10
11
  };
11
12
 
12
- export { createVcr };
13
+ export { type VcrResult, createVcr };
@@ -1,23 +1,23 @@
1
1
  import { createClient } from '@apibara/protocol';
2
2
  import ci from 'ci-info';
3
+ import { u as useIndexerContext } from '../shared/indexer.75773ef1.mjs';
3
4
  import { createIndexer } from '../index.mjs';
4
5
  import { internalContext } from '../internal/plugins.mjs';
5
- import { l as logger } from '../shared/indexer.ff25c953.mjs';
6
+ import { l as logger } from '../shared/indexer.98a921a7.mjs';
6
7
  import { isCassetteAvailable, record, replay } from '../vcr/index.mjs';
8
+ import 'node:async_hooks';
9
+ import 'unctx';
7
10
  import 'consola';
8
11
  import 'hookable';
9
12
  import 'node:assert';
10
- import '../shared/indexer.a55ad619.mjs';
11
- import 'node:async_hooks';
12
- import 'unctx';
13
13
  import '@opentelemetry/api';
14
- import '../shared/indexer.9b21ddd2.mjs';
15
14
  import 'node:fs/promises';
16
15
  import 'node:path';
17
16
  import 'node:fs';
18
17
  import '@apibara/protocol/testing';
19
18
 
20
19
  function createVcr() {
20
+ let result;
21
21
  return {
22
22
  async run(cassetteName, indexerConfig, range) {
23
23
  const vcrConfig = {
@@ -41,6 +41,9 @@ function createVcr() {
41
41
  ...indexerConfig.plugins ?? []
42
42
  ];
43
43
  const indexer = createIndexer(indexerConfig);
44
+ indexer.hooks.hook("run:after", () => {
45
+ result = useIndexerContext();
46
+ });
44
47
  if (!isCassetteAvailable(vcrConfig, cassetteName)) {
45
48
  if (ci.isCI) {
46
49
  throw new Error("Cannot record cassette in CI");
@@ -53,6 +56,7 @@ function createVcr() {
53
56
  } else {
54
57
  await replay(vcrConfig, indexer, cassetteName);
55
58
  }
59
+ return result;
56
60
  }
57
61
  };
58
62
  }
@@ -9,10 +9,11 @@ const testing = require('@apibara/protocol/testing');
9
9
  require('@apibara/protocol');
10
10
  require('consola');
11
11
  require('hookable');
12
- require('../shared/indexer.077335f3.cjs');
12
+ require('../shared/indexer.479ae593.cjs');
13
13
  require('node:async_hooks');
14
14
  require('unctx');
15
15
  require('@opentelemetry/api');
16
+ require('../internal/plugins.cjs');
16
17
 
17
18
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
18
19
 
@@ -7,10 +7,11 @@ import { MockClient } from '@apibara/protocol/testing';
7
7
  import '@apibara/protocol';
8
8
  import 'consola';
9
9
  import 'hookable';
10
- import '../shared/indexer.a55ad619.mjs';
10
+ import '../shared/indexer.75773ef1.mjs';
11
11
  import 'node:async_hooks';
12
12
  import 'unctx';
13
13
  import '@opentelemetry/api';
14
+ import '../internal/plugins.mjs';
14
15
 
15
16
  function deserialize(str) {
16
17
  return JSON.parse(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apibara/indexer",
3
- "version": "2.1.0-beta.2",
3
+ "version": "2.1.0-beta.21",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -70,10 +70,10 @@
70
70
  "vitest": "^1.6.0"
71
71
  },
72
72
  "dependencies": {
73
- "@apibara/protocol": "2.1.0-beta.2",
73
+ "@apibara/protocol": "2.1.0-beta.21",
74
74
  "@opentelemetry/api": "^1.9.0",
75
75
  "ci-info": "^4.1.0",
76
- "consola": "^3.2.3",
76
+ "consola": "^3.4.2",
77
77
  "hookable": "^5.5.3",
78
78
  "klona": "^2.0.6",
79
79
  "nice-grpc": "^2.1.8",
package/src/indexer.ts CHANGED
@@ -28,8 +28,9 @@ import {
28
28
  indexerAsyncContext,
29
29
  useIndexerContext,
30
30
  } from "./context";
31
- import { tracer } from "./otel";
31
+ import { createIndexerMetrics, createTracer } from "./otel";
32
32
  import type { IndexerPlugin } from "./plugins";
33
+ import { useInternalContext } from "./plugins/context";
33
34
 
34
35
  export type UseMiddlewareFunction = (
35
36
  fn: MiddlewareFunction<IndexerContext>,
@@ -216,8 +217,13 @@ export async function run<TFilter, TBlock>(
216
217
  const context = useIndexerContext();
217
218
  const middleware = await registerMiddleware(indexer);
218
219
 
220
+ const indexerMetrics = createIndexerMetrics();
221
+ const tracer = createTracer();
222
+
219
223
  await indexer.hooks.callHook("run:before");
220
224
 
225
+ const { indexerName: indexerId } = useInternalContext();
226
+
221
227
  const isFactoryMode = indexer.options.factory !== undefined;
222
228
 
223
229
  // Give priority to startingCursor over startingBlock.
@@ -288,11 +294,19 @@ export async function run<TFilter, TBlock>(
288
294
  context.endCursor = endCursor;
289
295
  context.finality = finality;
290
296
 
297
+ // Record current block number being processed
298
+ indexerMetrics.currentBlockGauge.record(
299
+ Number(endCursor?.orderKey),
300
+ {
301
+ indexer_id: indexerId,
302
+ },
303
+ );
304
+
291
305
  await middleware(context, async () => {
292
306
  let block: TBlock | null;
293
307
 
294
308
  // when factory mode
295
- if (isFactoryMode) {
309
+ if (isFactoryMode && finality !== "pending") {
296
310
  assert(indexer.options.factory !== undefined);
297
311
 
298
312
  const [factoryBlock, mainBlock] = blocks;
@@ -364,6 +378,11 @@ export async function run<TFilter, TBlock>(
364
378
  span.end();
365
379
  });
366
380
 
381
+ // Record processed block metric
382
+ indexerMetrics.processedBlockCounter.add(1, {
383
+ indexer_id: indexerId,
384
+ });
385
+
367
386
  context.cursor = undefined;
368
387
  context.endCursor = undefined;
369
388
  context.finality = undefined;
@@ -372,6 +391,10 @@ export async function run<TFilter, TBlock>(
372
391
  }
373
392
  case "invalidate": {
374
393
  await tracer.startActiveSpan("message invalidate", async (span) => {
394
+ // Record reorg metric
395
+ indexerMetrics.reorgCounter.add(1, {
396
+ indexer_id: indexerId,
397
+ });
375
398
  await indexer.hooks.callHook("message:invalidate", { message });
376
399
  span.end();
377
400
  });
@@ -5,10 +5,9 @@ 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
 
14
13
  export type MockMessagesOptions = {
@@ -20,6 +19,8 @@ export type MockMessagesOptions = {
20
19
  finalizeToIndex: number;
21
20
  finalizeTriggerIndex: number;
22
21
  };
22
+ uniqueKey?: boolean;
23
+ baseBlockNumber?: bigint;
23
24
  };
24
25
 
25
26
  export function generateMockMessages(
@@ -30,22 +31,36 @@ export function generateMockMessages(
30
31
  const finalizeAt = options?.finalize;
31
32
  const messages: MockStreamResponse[] = [];
32
33
 
34
+ const baseBlockNumber = options?.baseBlockNumber ?? BigInt(5_000_000);
35
+
33
36
  for (let i = 0; i < count; i++) {
37
+ const currentBlockNumber = baseBlockNumber + BigInt(i);
38
+ const uniqueKey = uniqueKeyFromOrderKey(currentBlockNumber);
34
39
  if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
40
+ const invalidateToBlock =
41
+ baseBlockNumber + BigInt(invalidateAt.invalidateFromIndex);
35
42
  messages.push({
36
43
  _tag: "invalidate",
37
44
  invalidate: {
38
45
  cursor: {
39
- orderKey: BigInt(5_000_000 + invalidateAt.invalidateFromIndex),
46
+ orderKey: invalidateToBlock,
47
+ uniqueKey: options?.uniqueKey
48
+ ? uniqueKeyFromOrderKey(invalidateToBlock)
49
+ : undefined,
40
50
  },
41
51
  },
42
52
  } as Invalidate);
43
53
  } else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
54
+ const fianlizedToBlock =
55
+ baseBlockNumber + BigInt(finalizeAt.finalizeToIndex);
44
56
  messages.push({
45
57
  _tag: "finalize",
46
58
  finalize: {
47
59
  cursor: {
48
- orderKey: BigInt(5_000_000 + finalizeAt.finalizeToIndex),
60
+ orderKey: fianlizedToBlock,
61
+ uniqueKey: options?.uniqueKey
62
+ ? uniqueKeyFromOrderKey(fianlizedToBlock)
63
+ : undefined,
49
64
  },
50
65
  },
51
66
  } as Finalize);
@@ -53,10 +68,13 @@ export function generateMockMessages(
53
68
  messages.push({
54
69
  _tag: "data",
55
70
  data: {
56
- cursor: { orderKey: BigInt(5_000_000 + i - 1) },
71
+ cursor: { orderKey: currentBlockNumber - 1n },
57
72
  finality: "accepted",
58
- data: [{ data: `${5_000_000 + i}` }],
59
- endCursor: { orderKey: BigInt(5_000_000 + i) },
73
+ data: [{ data: `${baseBlockNumber + BigInt(i)}` }],
74
+ endCursor: {
75
+ orderKey: currentBlockNumber,
76
+ uniqueKey: options?.uniqueKey ? uniqueKey : undefined,
77
+ },
60
78
  production: "backfill",
61
79
  },
62
80
  });
@@ -66,6 +84,10 @@ export function generateMockMessages(
66
84
  return messages;
67
85
  }
68
86
 
87
+ function uniqueKeyFromOrderKey(orderKey: bigint): `0x${string}` {
88
+ return `0xff00${orderKey.toString()}`;
89
+ }
90
+
69
91
  type MockIndexerParams = {
70
92
  internalContext?: InternalContext;
71
93
  override?: Partial<IndexerConfig<MockFilter, MockBlock>>;
@@ -82,6 +104,7 @@ export function getMockIndexer(params?: MockIndexerParams) {
82
104
  filter: {},
83
105
  async transform() {},
84
106
  plugins: [
107
+ logger(),
85
108
  internalContext(
86
109
  contextParams ??
87
110
  ({
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
+ }
@@ -1,5 +1,6 @@
1
1
  import { createClient } from "@apibara/protocol";
2
2
  import ci from "ci-info";
3
+ import { useIndexerContext } from "../context";
3
4
  import { type IndexerWithStreamConfig, createIndexer } from "../indexer";
4
5
  import { type InternalContext, internalContext } from "../plugins/context";
5
6
  import { logger } from "../plugins/logger";
@@ -8,7 +9,11 @@ import { isCassetteAvailable } from "../vcr/helper";
8
9
  import { record } from "../vcr/record";
9
10
  import { replay } from "../vcr/replay";
10
11
 
12
+ export type VcrResult = Record<string, unknown>;
13
+
11
14
  export function createVcr() {
15
+ let result: VcrResult;
16
+
12
17
  return {
13
18
  async run<TFilter, TBlock>(
14
19
  cassetteName: string,
@@ -40,6 +45,10 @@ export function createVcr() {
40
45
 
41
46
  const indexer = createIndexer(indexerConfig);
42
47
 
48
+ indexer.hooks.hook("run:after", () => {
49
+ result = useIndexerContext();
50
+ });
51
+
43
52
  if (!isCassetteAvailable(vcrConfig, cassetteName)) {
44
53
  if (ci.isCI) {
45
54
  throw new Error("Cannot record cassette in CI");
@@ -53,6 +62,8 @@ export function createVcr() {
53
62
  } else {
54
63
  await replay(vcrConfig, indexer, cassetteName);
55
64
  }
65
+
66
+ return result;
56
67
  },
57
68
  };
58
69
  }
@@ -1,7 +0,0 @@
1
- 'use strict';
2
-
3
- function defineIndexerPlugin(def) {
4
- return def;
5
- }
6
-
7
- exports.defineIndexerPlugin = defineIndexerPlugin;
@@ -1,5 +0,0 @@
1
- function defineIndexerPlugin(def) {
2
- return def;
3
- }
4
-
5
- export { defineIndexerPlugin as d };