@apibara/indexer 2.0.0-beta.9 → 2.1.0-beta.1

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 (127) hide show
  1. package/dist/index.cjs +267 -39
  2. package/dist/index.d.cts +1 -16
  3. package/dist/index.d.mts +1 -16
  4. package/dist/index.d.ts +1 -16
  5. package/dist/index.mjs +258 -25
  6. package/dist/internal/index.cjs +10 -0
  7. package/dist/internal/index.d.cts +3 -0
  8. package/dist/internal/index.d.mts +3 -0
  9. package/dist/internal/index.d.ts +3 -0
  10. package/dist/internal/index.mjs +8 -0
  11. package/dist/internal/plugins.cjs +38 -0
  12. package/dist/internal/plugins.d.cts +13 -0
  13. package/dist/internal/plugins.d.mts +13 -0
  14. package/dist/internal/plugins.d.ts +13 -0
  15. package/dist/internal/plugins.mjs +34 -0
  16. package/dist/internal/testing.cjs +118 -0
  17. package/dist/internal/testing.d.cts +42 -0
  18. package/dist/internal/testing.d.mts +42 -0
  19. package/dist/internal/testing.d.ts +42 -0
  20. package/dist/internal/testing.mjs +113 -0
  21. package/dist/plugins/index.cjs +39 -3
  22. package/dist/plugins/index.d.cts +16 -2
  23. package/dist/plugins/index.d.mts +16 -2
  24. package/dist/plugins/index.d.ts +16 -2
  25. package/dist/plugins/index.mjs +36 -3
  26. package/dist/shared/indexer.077335f3.cjs +15 -0
  27. package/dist/shared/indexer.2416906c.cjs +29 -0
  28. package/dist/shared/indexer.601ceab0.cjs +7 -0
  29. package/dist/shared/indexer.9b21ddd2.mjs +5 -0
  30. package/dist/shared/indexer.a55ad619.mjs +12 -0
  31. package/dist/shared/indexer.fedcd831.d.cts +100 -0
  32. package/dist/shared/indexer.fedcd831.d.mts +100 -0
  33. package/dist/shared/indexer.fedcd831.d.ts +100 -0
  34. package/dist/shared/indexer.ff25c953.mjs +26 -0
  35. package/dist/testing/index.cjs +52 -50
  36. package/dist/testing/index.d.cts +8 -36
  37. package/dist/testing/index.d.mts +8 -36
  38. package/dist/testing/index.d.ts +8 -36
  39. package/dist/testing/index.mjs +47 -47
  40. package/dist/vcr/index.cjs +84 -17
  41. package/dist/vcr/index.d.cts +16 -7
  42. package/dist/vcr/index.d.mts +16 -7
  43. package/dist/vcr/index.d.ts +16 -7
  44. package/dist/vcr/index.mjs +75 -11
  45. package/package.json +22 -42
  46. package/src/compose.test.ts +76 -0
  47. package/src/compose.ts +71 -0
  48. package/src/context.ts +14 -8
  49. package/src/index.ts +0 -5
  50. package/src/indexer.test.ts +125 -186
  51. package/src/indexer.ts +274 -151
  52. package/src/internal/index.ts +6 -0
  53. package/src/internal/plugins.ts +1 -0
  54. package/src/internal/testing.ts +148 -0
  55. package/src/plugins/config.ts +4 -4
  56. package/src/plugins/context.ts +40 -0
  57. package/src/plugins/index.ts +8 -1
  58. package/src/plugins/logger.ts +30 -0
  59. package/src/plugins/persistence.ts +24 -187
  60. package/src/testing/index.ts +58 -3
  61. package/src/vcr/record.ts +5 -3
  62. package/src/vcr/replay.ts +8 -18
  63. package/dist/plugins/kv.cjs +0 -131
  64. package/dist/plugins/kv.d.cts +0 -32
  65. package/dist/plugins/kv.d.mts +0 -32
  66. package/dist/plugins/kv.d.ts +0 -32
  67. package/dist/plugins/kv.mjs +0 -124
  68. package/dist/plugins/persistence.cjs +0 -182
  69. package/dist/plugins/persistence.d.cts +0 -50
  70. package/dist/plugins/persistence.d.mts +0 -50
  71. package/dist/plugins/persistence.d.ts +0 -50
  72. package/dist/plugins/persistence.mjs +0 -179
  73. package/dist/shared/indexer.2c23c9cd.mjs +0 -35
  74. package/dist/shared/indexer.318d3617.cjs +0 -47
  75. package/dist/shared/indexer.36530330.mjs +0 -249
  76. package/dist/shared/indexer.500fd281.d.cts +0 -23
  77. package/dist/shared/indexer.541d43eb.cjs +0 -266
  78. package/dist/shared/indexer.93d6b2eb.mjs +0 -17
  79. package/dist/shared/indexer.a8b7ab1f.cjs +0 -25
  80. package/dist/shared/indexer.b9c8f0d8.d.cts +0 -19
  81. package/dist/shared/indexer.b9c8f0d8.d.mts +0 -19
  82. package/dist/shared/indexer.b9c8f0d8.d.ts +0 -19
  83. package/dist/shared/indexer.c7ed6b83.d.cts +0 -82
  84. package/dist/shared/indexer.e1856641.d.mts +0 -23
  85. package/dist/shared/indexer.e4f2430f.d.ts +0 -23
  86. package/dist/shared/indexer.e8bd138d.d.mts +0 -82
  87. package/dist/shared/indexer.f761abcd.d.ts +0 -82
  88. package/dist/sinks/csv.cjs +0 -85
  89. package/dist/sinks/csv.d.cts +0 -66
  90. package/dist/sinks/csv.d.mts +0 -66
  91. package/dist/sinks/csv.d.ts +0 -66
  92. package/dist/sinks/csv.mjs +0 -78
  93. package/dist/sinks/drizzle/index.cjs +0 -212
  94. package/dist/sinks/drizzle/index.d.cts +0 -153
  95. package/dist/sinks/drizzle/index.d.mts +0 -153
  96. package/dist/sinks/drizzle/index.d.ts +0 -153
  97. package/dist/sinks/drizzle/index.mjs +0 -198
  98. package/dist/sinks/sqlite.cjs +0 -90
  99. package/dist/sinks/sqlite.d.cts +0 -71
  100. package/dist/sinks/sqlite.d.mts +0 -71
  101. package/dist/sinks/sqlite.d.ts +0 -71
  102. package/dist/sinks/sqlite.mjs +0 -87
  103. package/src/hooks/index.ts +0 -2
  104. package/src/hooks/useKVStore.ts +0 -12
  105. package/src/hooks/useSink.ts +0 -13
  106. package/src/plugins/kv.test.ts +0 -120
  107. package/src/plugins/kv.ts +0 -132
  108. package/src/plugins/persistence.test.ts +0 -151
  109. package/src/sink.ts +0 -36
  110. package/src/sinks/csv.test.ts +0 -65
  111. package/src/sinks/csv.ts +0 -159
  112. package/src/sinks/drizzle/Int8Range.ts +0 -52
  113. package/src/sinks/drizzle/delete.ts +0 -42
  114. package/src/sinks/drizzle/drizzle.test.ts +0 -239
  115. package/src/sinks/drizzle/drizzle.ts +0 -115
  116. package/src/sinks/drizzle/index.ts +0 -6
  117. package/src/sinks/drizzle/insert.ts +0 -42
  118. package/src/sinks/drizzle/select.ts +0 -44
  119. package/src/sinks/drizzle/transaction.ts +0 -49
  120. package/src/sinks/drizzle/update.ts +0 -47
  121. package/src/sinks/drizzle/utils.ts +0 -99
  122. package/src/sinks/sqlite.test.ts +0 -99
  123. package/src/sinks/sqlite.ts +0 -170
  124. package/src/testing/helper.ts +0 -13
  125. package/src/testing/indexer.ts +0 -35
  126. package/src/testing/setup.ts +0 -59
  127. package/src/testing/vcr.ts +0 -54
package/src/indexer.ts CHANGED
@@ -1,11 +1,17 @@
1
- import type {
2
- Client,
3
- Cursor,
4
- DataFinality,
5
- StreamConfig,
6
- StreamDataOptions,
7
- StreamDataRequest,
8
- StreamDataResponse,
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,
9
15
  } from "@apibara/protocol";
10
16
  import consola from "consola";
11
17
  import {
@@ -16,6 +22,7 @@ import {
16
22
  } from "hookable";
17
23
 
18
24
  import assert from "node:assert";
25
+ import { type MiddlewareFunction, type NextFunction, compose } from "./compose";
19
26
  import {
20
27
  type IndexerContext,
21
28
  indexerAsyncContext,
@@ -23,7 +30,10 @@ import {
23
30
  } from "./context";
24
31
  import { tracer } from "./otel";
25
32
  import type { IndexerPlugin } from "./plugins";
26
- import { type Sink, defaultSink } from "./sink";
33
+
34
+ export type UseMiddlewareFunction = (
35
+ fn: MiddlewareFunction<IndexerContext>,
36
+ ) => void;
27
37
 
28
38
  export interface IndexerHooks<TFilter, TBlock> {
29
39
  "run:before": () => void;
@@ -35,7 +45,9 @@ export interface IndexerHooks<TFilter, TBlock> {
35
45
  request: StreamDataRequest<TFilter>;
36
46
  options: StreamDataOptions;
37
47
  }) => void;
38
- "connect:after": () => void;
48
+ "connect:after": ({
49
+ request,
50
+ }: { request: StreamDataRequest<TFilter> }) => void;
39
51
  "connect:factory": ({
40
52
  request,
41
53
  endCursor,
@@ -43,45 +55,36 @@ export interface IndexerHooks<TFilter, TBlock> {
43
55
  request: StreamDataRequest<TFilter>;
44
56
  endCursor?: Cursor;
45
57
  }) => void;
46
- "handler:before": ({
47
- block,
48
- finality,
49
- endCursor,
50
- }: {
51
- block: TBlock;
52
- finality: DataFinality;
53
- endCursor?: Cursor;
54
- }) => void;
55
- "handler:after": ({
56
- block,
57
- finality,
58
- endCursor,
59
- }: {
60
- block: TBlock;
61
- finality: DataFinality;
62
- endCursor?: Cursor;
63
- }) => void;
64
- "transaction:commit": ({
65
- finality,
66
- endCursor,
67
- }: {
68
- finality: DataFinality;
69
- endCursor?: Cursor;
70
- }) => void;
71
- "handler:exception": ({ error }: { error: Error }) => void;
58
+ "handler:middleware": ({ use }: { use: UseMiddlewareFunction }) => void;
72
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;
73
64
  }
74
65
 
75
- export interface IndexerConfig<TFilter, TBlock, TTxnParams> {
66
+ export type IndexerStartingCursor =
67
+ | {
68
+ startingCursor?: never;
69
+ startingBlock: bigint;
70
+ }
71
+ | {
72
+ startingCursor: Cursor;
73
+ startingBlock?: never;
74
+ }
75
+ | {
76
+ startingCursor?: never;
77
+ startingBlock?: never;
78
+ };
79
+
80
+ export type IndexerConfig<TFilter, TBlock> = {
76
81
  streamUrl: string;
77
82
  filter: TFilter;
78
83
  finality?: DataFinality;
79
- startingCursor?: Cursor;
80
- sink?: Sink<TTxnParams>;
81
84
  factory?: ({
82
85
  block,
83
86
  context,
84
- }: { block: TBlock; context: IndexerContext<TTxnParams> }) => Promise<{
87
+ }: { block: TBlock; context: IndexerContext }) => Promise<{
85
88
  filter?: TFilter;
86
89
  }>;
87
90
  transform: (args: {
@@ -89,40 +92,42 @@ export interface IndexerConfig<TFilter, TBlock, TTxnParams> {
89
92
  cursor?: Cursor | undefined;
90
93
  endCursor?: Cursor | undefined;
91
94
  finality: DataFinality;
92
- context: IndexerContext<TTxnParams>;
95
+ context: IndexerContext;
93
96
  }) => Promise<void>;
94
97
  hooks?: NestedHooks<IndexerHooks<TFilter, TBlock>>;
95
- plugins?: ReadonlyArray<IndexerPlugin<TFilter, TBlock, TTxnParams>>;
98
+ plugins?: ReadonlyArray<IndexerPlugin<TFilter, TBlock>>;
96
99
  debug?: boolean;
97
- }
100
+ } & IndexerStartingCursor;
98
101
 
99
- export interface IndexerWithStreamConfig<TFilter, TBlock, TTxnParams>
100
- extends IndexerConfig<TFilter, TBlock, TTxnParams> {
102
+ export type IndexerWithStreamConfig<TFilter, TBlock> = IndexerConfig<
103
+ TFilter,
104
+ TBlock
105
+ > & {
101
106
  streamConfig: StreamConfig<TFilter, TBlock>;
102
- }
107
+ };
103
108
 
104
109
  export function defineIndexer<TFilter, TBlock>(
105
110
  streamConfig: StreamConfig<TFilter, TBlock>,
106
111
  ) {
107
- return <TTxnParams>(
108
- config: IndexerConfig<TFilter, TBlock, TTxnParams>,
109
- ): IndexerWithStreamConfig<TFilter, TBlock, TTxnParams> => ({
112
+ return (
113
+ config: IndexerConfig<TFilter, TBlock>,
114
+ ): IndexerWithStreamConfig<TFilter, TBlock> => ({
110
115
  streamConfig,
111
116
  ...config,
112
117
  });
113
118
  }
114
119
 
115
- export interface Indexer<TFilter, TBlock, TTxnParams> {
120
+ export interface Indexer<TFilter, TBlock> {
116
121
  streamConfig: StreamConfig<TFilter, TBlock>;
117
- options: IndexerConfig<TFilter, TBlock, TTxnParams>;
122
+ options: IndexerConfig<TFilter, TBlock>;
118
123
  hooks: Hookable<IndexerHooks<TFilter, TBlock>>;
119
124
  }
120
125
 
121
- export function createIndexer<TFilter, TBlock, TTxnParams>({
126
+ export function createIndexer<TFilter, TBlock>({
122
127
  streamConfig,
123
128
  ...options
124
- }: IndexerWithStreamConfig<TFilter, TBlock, TTxnParams>) {
125
- const indexer: Indexer<TFilter, TBlock, TTxnParams> = {
129
+ }: IndexerWithStreamConfig<TFilter, TBlock>) {
130
+ const indexer: Indexer<TFilter, TBlock> = {
126
131
  options,
127
132
  streamConfig,
128
133
  hooks: createHooks<IndexerHooks<TFilter, TBlock>>(),
@@ -141,29 +146,97 @@ export function createIndexer<TFilter, TBlock, TTxnParams>({
141
146
  return indexer;
142
147
  }
143
148
 
144
- export async function run<TFilter, TBlock, TTxnParams>(
149
+ export interface ReconnectOptions {
150
+ maxRetries?: number;
151
+ retryDelay?: number;
152
+ maxWait?: number;
153
+ }
154
+
155
+ export async function runWithReconnect<TFilter, TBlock>(
145
156
  client: Client<TFilter, TBlock>,
146
- indexer: Indexer<TFilter, TBlock, TTxnParams>,
157
+ indexer: Indexer<TFilter, TBlock>,
158
+ options: ReconnectOptions = {},
147
159
  ) {
148
- await indexerAsyncContext.callAsync({}, async () => {
149
- const context = useIndexerContext<TTxnParams>();
160
+ let retryCount = 0;
161
+
162
+ const maxRetries = options.maxRetries ?? 10;
163
+ const retryDelay = options.retryDelay ?? 1_000;
164
+ const maxWait = options.maxWait ?? 30_000;
165
+
166
+ const runOptions: RunOptions = {
167
+ onConnect() {
168
+ retryCount = 0;
169
+ },
170
+ };
150
171
 
151
- const sink = indexer.options.sink ?? defaultSink();
172
+ while (true) {
173
+ try {
174
+ await run(client, indexer, runOptions);
175
+ return;
176
+ } catch (error) {
177
+ // Only reconnect on internal/server errors.
178
+ // All other errors should be rethrown.
179
+
180
+ retryCount++;
181
+
182
+ if (error instanceof ClientError) {
183
+ if (error.code === Status.INTERNAL) {
184
+ if (retryCount < maxRetries) {
185
+ consola.error(
186
+ "Internal server error, reconnecting...",
187
+ error.message,
188
+ );
152
189
 
153
- context.sink = sink as Sink<TTxnParams>;
190
+ // Add jitter to the retry delay to avoid all clients retrying at the same time.
191
+ const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
192
+ await new Promise((resolve) =>
193
+ setTimeout(resolve, Math.min(retryCount * delay, maxWait)),
194
+ );
195
+
196
+ continue;
197
+ }
198
+ }
199
+ }
200
+
201
+ throw error;
202
+ }
203
+ }
204
+ }
205
+
206
+ export interface RunOptions {
207
+ onConnect?: () => void | Promise<void>;
208
+ }
209
+
210
+ export async function run<TFilter, TBlock>(
211
+ client: Client<TFilter, TBlock>,
212
+ indexer: Indexer<TFilter, TBlock>,
213
+ runOptions: RunOptions = {},
214
+ ) {
215
+ await indexerAsyncContext.callAsync({}, async () => {
216
+ const context = useIndexerContext();
217
+ const middleware = await registerMiddleware(indexer);
154
218
 
155
219
  await indexer.hooks.callHook("run:before");
156
220
 
157
- // Check if the it's factory mode or not
158
221
  const isFactoryMode = indexer.options.factory !== undefined;
159
222
 
160
- // if factory mode we add a empty filter
223
+ // Give priority to startingCursor over startingBlock.
224
+ let startingCursor: Cursor | undefined;
225
+ if (indexer.options.startingCursor) {
226
+ startingCursor = indexer.options.startingCursor;
227
+ } else if (indexer.options.startingBlock !== undefined) {
228
+ startingCursor = {
229
+ orderKey: indexer.options.startingBlock,
230
+ };
231
+ }
232
+
233
+ // if factory mode we add a empty filter at the end of the filter array.
161
234
  const request = indexer.streamConfig.Request.make({
162
235
  filter: isFactoryMode
163
236
  ? [indexer.options.filter, {} as TFilter]
164
237
  : [indexer.options.filter],
165
238
  finality: indexer.options.finality,
166
- startingCursor: indexer.options.startingCursor,
239
+ startingCursor,
167
240
  });
168
241
 
169
242
  const options: StreamDataOptions = {};
@@ -176,13 +249,14 @@ export async function run<TFilter, TBlock, TTxnParams>(
176
249
  mainFilter = request.filter[1];
177
250
  }
178
251
 
179
- // create stream
180
252
  let stream: AsyncIterator<
181
253
  StreamDataResponse<TBlock>,
182
254
  StreamDataResponse<TBlock>
183
255
  > = client.streamData(request, options)[Symbol.asyncIterator]();
184
256
 
185
- await indexer.hooks.callHook("connect:after");
257
+ await indexer.hooks.callHook("connect:after", { request });
258
+
259
+ let onConnectCalled = false;
186
260
 
187
261
  while (true) {
188
262
  const { value: message, done } = await stream.next();
@@ -191,6 +265,13 @@ export async function run<TFilter, TBlock, TTxnParams>(
191
265
  break;
192
266
  }
193
267
 
268
+ if (!onConnectCalled) {
269
+ onConnectCalled = true;
270
+ if (runOptions.onConnect) {
271
+ await runOptions.onConnect();
272
+ }
273
+ }
274
+
194
275
  await indexer.hooks.callHook("message", { message });
195
276
 
196
277
  switch (message._tag) {
@@ -199,117 +280,138 @@ export async function run<TFilter, TBlock, TTxnParams>(
199
280
  const blocks = message.data.data;
200
281
  const { cursor, endCursor, finality } = message.data;
201
282
 
202
- await sink.transaction(
203
- { cursor, endCursor, finality },
204
- async (txn) => {
205
- // attach transaction to context
206
- context.sinkTransaction = txn as TTxnParams;
207
-
208
- let block: TBlock | null;
283
+ context.cursor = cursor;
284
+ context.endCursor = endCursor;
285
+ context.finality = finality;
209
286
 
210
- // when factory mode
211
- if (isFactoryMode) {
212
- assert(indexer.options.factory !== undefined);
287
+ await middleware(context, async () => {
288
+ let block: TBlock | null;
213
289
 
214
- const [factoryBlock, mainBlock] = blocks;
290
+ // when factory mode
291
+ if (isFactoryMode) {
292
+ assert(indexer.options.factory !== undefined);
215
293
 
216
- block = mainBlock;
294
+ const [factoryBlock, mainBlock] = blocks;
217
295
 
218
- if (factoryBlock !== null) {
219
- const { filter } = await indexer.options.factory({
220
- block: factoryBlock,
221
- context,
222
- });
296
+ block = mainBlock;
223
297
 
224
- // write returned data from factory function if filter is not defined
225
- if (filter) {
226
- // when filter is defined
227
- // merge old and new filters
228
- mainFilter = indexer.streamConfig.mergeFilter(
229
- mainFilter,
230
- filter,
231
- );
298
+ if (factoryBlock !== null) {
299
+ const { filter } = await indexer.options.factory({
300
+ block: factoryBlock,
301
+ context,
302
+ });
232
303
 
233
- // create request with new filters
234
- const request = indexer.streamConfig.Request.make({
235
- filter: [indexer.options.filter, mainFilter],
236
- finality: indexer.options.finality,
237
- startingCursor: cursor,
238
- });
304
+ // write returned data from factory function if filter is not defined
305
+ if (filter) {
306
+ // when filter is defined
307
+ // merge old and new filters
308
+ mainFilter = indexer.streamConfig.mergeFilter(
309
+ mainFilter,
310
+ filter,
311
+ );
312
+
313
+ // create request with new filters
314
+ const request = indexer.streamConfig.Request.make({
315
+ filter: [indexer.options.filter, mainFilter],
316
+ finality: indexer.options.finality,
317
+ startingCursor: cursor,
318
+ });
239
319
 
240
- await indexer.hooks.callHook("connect:factory", {
241
- request,
242
- endCursor,
243
- });
320
+ await indexer.hooks.callHook("connect:factory", {
321
+ request,
322
+ endCursor,
323
+ });
244
324
 
245
- // create new stream with new request
246
- stream = client
247
- .streamData(request, options)
248
- [Symbol.asyncIterator]();
325
+ // create new stream with new request
326
+ stream = client
327
+ .streamData(request, options)
328
+ [Symbol.asyncIterator]();
249
329
 
250
- const { value: message } = await stream.next();
330
+ const { value: message } = await stream.next();
251
331
 
252
- assert(message._tag === "data");
332
+ assert(message._tag === "data");
253
333
 
254
- const [_factoryBlock, _block] = message.data.data;
334
+ const [_factoryBlock, _block] = message.data.data;
255
335
 
256
- block = _block;
257
- }
336
+ block = _block;
258
337
  }
259
- } else {
260
- // when not in factory mode
261
- block = blocks[0];
262
338
  }
263
-
264
- // if block is not null
265
- if (block) {
266
- await tracer.startActiveSpan("handler", async (span) => {
267
- await indexer.hooks.callHook("handler:before", {
268
- block,
269
- endCursor,
270
- finality,
271
- });
272
-
273
- try {
274
- await indexer.options.transform({
275
- block,
276
- cursor,
277
- endCursor,
278
- finality,
279
- context,
280
- });
281
- await indexer.hooks.callHook("handler:after", {
282
- block,
283
- finality,
284
- endCursor,
285
- });
286
- } catch (error) {
287
- assert(error instanceof Error);
288
- await indexer.hooks.callHook("handler:exception", {
289
- error,
290
- });
291
- throw error;
292
- }
293
-
294
- span.end();
339
+ } else {
340
+ // when not in factory mode
341
+ block = blocks[0];
342
+ }
343
+
344
+ // if block is not null
345
+ if (block) {
346
+ await tracer.startActiveSpan("handler", async (span) => {
347
+ await indexer.options.transform({
348
+ block,
349
+ cursor,
350
+ endCursor,
351
+ finality,
352
+ context,
295
353
  });
296
- }
297
- },
298
- );
299
- await indexer.hooks.callHook("transaction:commit", {
300
- finality,
301
- endCursor,
354
+
355
+ span.end();
356
+ });
357
+ }
302
358
  });
359
+
303
360
  span.end();
304
361
  });
362
+
363
+ context.cursor = undefined;
364
+ context.endCursor = undefined;
365
+ context.finality = undefined;
366
+
305
367
  break;
306
368
  }
307
369
  case "invalidate": {
308
370
  await tracer.startActiveSpan("message invalidate", async (span) => {
309
- await sink.invalidate(message.invalidate.cursor);
371
+ await indexer.hooks.callHook("message:invalidate", { message });
372
+ span.end();
373
+ });
374
+ break;
375
+ }
376
+ case "finalize": {
377
+ await tracer.startActiveSpan("message finalize", async (span) => {
378
+ await indexer.hooks.callHook("message:finalize", { message });
379
+ span.end();
310
380
  });
311
381
  break;
312
382
  }
383
+ case "heartbeat": {
384
+ await tracer.startActiveSpan("message heartbeat", async (span) => {
385
+ await indexer.hooks.callHook("message:heartbeat", { message });
386
+ span.end();
387
+ });
388
+ break;
389
+ }
390
+ case "systemMessage": {
391
+ await tracer.startActiveSpan(
392
+ "message systemMessage",
393
+ async (span) => {
394
+ switch (message.systemMessage.output?._tag) {
395
+ case "stderr": {
396
+ consola.warn(message.systemMessage.output.stderr);
397
+ break;
398
+ }
399
+ case "stdout": {
400
+ consola.info(message.systemMessage.output.stdout);
401
+ break;
402
+ }
403
+ default: {
404
+ }
405
+ }
406
+
407
+ await indexer.hooks.callHook("message:systemMessage", {
408
+ message,
409
+ });
410
+ span.end();
411
+ },
412
+ );
413
+ break;
414
+ }
313
415
  default: {
314
416
  consola.warn("unexpected message", message);
315
417
  throw new Error("not implemented");
@@ -320,3 +422,24 @@ export async function run<TFilter, TBlock, TTxnParams>(
320
422
  }
321
423
  });
322
424
  }
425
+
426
+ async function registerMiddleware<TFilter, TBlock>(
427
+ indexer: Indexer<TFilter, TBlock>,
428
+ ): Promise<MiddlewareFunction<IndexerContext>> {
429
+ const middleware: MiddlewareFunction<IndexerContext>[] = [];
430
+ const use = (fn: MiddlewareFunction<IndexerContext>) => {
431
+ middleware.push(fn);
432
+ };
433
+
434
+ await indexer.hooks.callHook("handler:middleware", { use });
435
+
436
+ const composed = compose(middleware);
437
+
438
+ // Return a named function to help debugging
439
+ return async function _composedIndexerMiddleware(
440
+ context: IndexerContext,
441
+ next?: NextFunction,
442
+ ) {
443
+ await composed(context, next);
444
+ };
445
+ }
@@ -0,0 +1,6 @@
1
+ export function generateIndexerId(fileBasedName: string, identifier?: string) {
2
+ return `indexer_${fileBasedName}_${identifier || "default"}`.replace(
3
+ /[^a-zA-Z0-9_]/g,
4
+ "_",
5
+ );
6
+ }
@@ -0,0 +1 @@
1
+ export * from "../plugins/context";