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

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 +271 -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 +262 -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 +278 -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,101 @@ 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
+ if (indexer.options.startingBlock === 0n) {
229
+ startingCursor = undefined;
230
+ } else if (indexer.options.startingBlock > 0n) {
231
+ startingCursor = {
232
+ orderKey: indexer.options.startingBlock - 1n,
233
+ };
234
+ }
235
+ }
236
+
237
+ // if factory mode we add a empty filter at the end of the filter array.
161
238
  const request = indexer.streamConfig.Request.make({
162
239
  filter: isFactoryMode
163
240
  ? [indexer.options.filter, {} as TFilter]
164
241
  : [indexer.options.filter],
165
242
  finality: indexer.options.finality,
166
- startingCursor: indexer.options.startingCursor,
243
+ startingCursor,
167
244
  });
168
245
 
169
246
  const options: StreamDataOptions = {};
@@ -176,13 +253,14 @@ export async function run<TFilter, TBlock, TTxnParams>(
176
253
  mainFilter = request.filter[1];
177
254
  }
178
255
 
179
- // create stream
180
256
  let stream: AsyncIterator<
181
257
  StreamDataResponse<TBlock>,
182
258
  StreamDataResponse<TBlock>
183
259
  > = client.streamData(request, options)[Symbol.asyncIterator]();
184
260
 
185
- await indexer.hooks.callHook("connect:after");
261
+ await indexer.hooks.callHook("connect:after", { request });
262
+
263
+ let onConnectCalled = false;
186
264
 
187
265
  while (true) {
188
266
  const { value: message, done } = await stream.next();
@@ -191,6 +269,13 @@ export async function run<TFilter, TBlock, TTxnParams>(
191
269
  break;
192
270
  }
193
271
 
272
+ if (!onConnectCalled) {
273
+ onConnectCalled = true;
274
+ if (runOptions.onConnect) {
275
+ await runOptions.onConnect();
276
+ }
277
+ }
278
+
194
279
  await indexer.hooks.callHook("message", { message });
195
280
 
196
281
  switch (message._tag) {
@@ -199,117 +284,138 @@ export async function run<TFilter, TBlock, TTxnParams>(
199
284
  const blocks = message.data.data;
200
285
  const { cursor, endCursor, finality } = message.data;
201
286
 
202
- await sink.transaction(
203
- { cursor, endCursor, finality },
204
- async (txn) => {
205
- // attach transaction to context
206
- context.sinkTransaction = txn as TTxnParams;
287
+ context.cursor = cursor;
288
+ context.endCursor = endCursor;
289
+ context.finality = finality;
207
290
 
208
- let block: TBlock | null;
291
+ await middleware(context, async () => {
292
+ let block: TBlock | null;
209
293
 
210
- // when factory mode
211
- if (isFactoryMode) {
212
- assert(indexer.options.factory !== undefined);
294
+ // when factory mode
295
+ if (isFactoryMode) {
296
+ assert(indexer.options.factory !== undefined);
213
297
 
214
- const [factoryBlock, mainBlock] = blocks;
298
+ const [factoryBlock, mainBlock] = blocks;
215
299
 
216
- block = mainBlock;
300
+ block = mainBlock;
217
301
 
218
- if (factoryBlock !== null) {
219
- const { filter } = await indexer.options.factory({
220
- block: factoryBlock,
221
- context,
222
- });
223
-
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
- );
302
+ if (factoryBlock !== null) {
303
+ const { filter } = await indexer.options.factory({
304
+ block: factoryBlock,
305
+ context,
306
+ });
232
307
 
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
- });
308
+ // write returned data from factory function if filter is not defined
309
+ if (filter) {
310
+ // when filter is defined
311
+ // merge old and new filters
312
+ mainFilter = indexer.streamConfig.mergeFilter(
313
+ mainFilter,
314
+ filter,
315
+ );
316
+
317
+ // create request with new filters
318
+ const request = indexer.streamConfig.Request.make({
319
+ filter: [indexer.options.filter, mainFilter],
320
+ finality: indexer.options.finality,
321
+ startingCursor: cursor,
322
+ });
239
323
 
240
- await indexer.hooks.callHook("connect:factory", {
241
- request,
242
- endCursor,
243
- });
324
+ await indexer.hooks.callHook("connect:factory", {
325
+ request,
326
+ endCursor,
327
+ });
244
328
 
245
- // create new stream with new request
246
- stream = client
247
- .streamData(request, options)
248
- [Symbol.asyncIterator]();
329
+ // create new stream with new request
330
+ stream = client
331
+ .streamData(request, options)
332
+ [Symbol.asyncIterator]();
249
333
 
250
- const { value: message } = await stream.next();
334
+ const { value: message } = await stream.next();
251
335
 
252
- assert(message._tag === "data");
336
+ assert(message._tag === "data");
253
337
 
254
- const [_factoryBlock, _block] = message.data.data;
338
+ const [_factoryBlock, _block] = message.data.data;
255
339
 
256
- block = _block;
257
- }
340
+ block = _block;
258
341
  }
259
- } else {
260
- // when not in factory mode
261
- block = blocks[0];
262
342
  }
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();
343
+ } else {
344
+ // when not in factory mode
345
+ block = blocks[0];
346
+ }
347
+
348
+ // if block is not null
349
+ if (block) {
350
+ await tracer.startActiveSpan("handler", async (span) => {
351
+ await indexer.options.transform({
352
+ block,
353
+ cursor,
354
+ endCursor,
355
+ finality,
356
+ context,
295
357
  });
296
- }
297
- },
298
- );
299
- await indexer.hooks.callHook("transaction:commit", {
300
- finality,
301
- endCursor,
358
+
359
+ span.end();
360
+ });
361
+ }
302
362
  });
363
+
303
364
  span.end();
304
365
  });
366
+
367
+ context.cursor = undefined;
368
+ context.endCursor = undefined;
369
+ context.finality = undefined;
370
+
305
371
  break;
306
372
  }
307
373
  case "invalidate": {
308
374
  await tracer.startActiveSpan("message invalidate", async (span) => {
309
- await sink.invalidate(message.invalidate.cursor);
375
+ await indexer.hooks.callHook("message:invalidate", { message });
376
+ span.end();
377
+ });
378
+ break;
379
+ }
380
+ case "finalize": {
381
+ await tracer.startActiveSpan("message finalize", async (span) => {
382
+ await indexer.hooks.callHook("message:finalize", { message });
383
+ span.end();
384
+ });
385
+ break;
386
+ }
387
+ case "heartbeat": {
388
+ await tracer.startActiveSpan("message heartbeat", async (span) => {
389
+ await indexer.hooks.callHook("message:heartbeat", { message });
390
+ span.end();
310
391
  });
311
392
  break;
312
393
  }
394
+ case "systemMessage": {
395
+ await tracer.startActiveSpan(
396
+ "message systemMessage",
397
+ async (span) => {
398
+ switch (message.systemMessage.output?._tag) {
399
+ case "stderr": {
400
+ consola.warn(message.systemMessage.output.stderr);
401
+ break;
402
+ }
403
+ case "stdout": {
404
+ consola.info(message.systemMessage.output.stdout);
405
+ break;
406
+ }
407
+ default: {
408
+ }
409
+ }
410
+
411
+ await indexer.hooks.callHook("message:systemMessage", {
412
+ message,
413
+ });
414
+ span.end();
415
+ },
416
+ );
417
+ break;
418
+ }
313
419
  default: {
314
420
  consola.warn("unexpected message", message);
315
421
  throw new Error("not implemented");
@@ -320,3 +426,24 @@ export async function run<TFilter, TBlock, TTxnParams>(
320
426
  }
321
427
  });
322
428
  }
429
+
430
+ async function registerMiddleware<TFilter, TBlock>(
431
+ indexer: Indexer<TFilter, TBlock>,
432
+ ): Promise<MiddlewareFunction<IndexerContext>> {
433
+ const middleware: MiddlewareFunction<IndexerContext>[] = [];
434
+ const use = (fn: MiddlewareFunction<IndexerContext>) => {
435
+ middleware.push(fn);
436
+ };
437
+
438
+ await indexer.hooks.callHook("handler:middleware", { use });
439
+
440
+ const composed = compose(middleware);
441
+
442
+ // Return a named function to help debugging
443
+ return async function _composedIndexerMiddleware(
444
+ context: IndexerContext,
445
+ next?: NextFunction,
446
+ ) {
447
+ await composed(context, next);
448
+ };
449
+ }
@@ -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";