@apibara/indexer 2.1.0-beta.4 → 2.1.0-beta.41

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
@@ -1,6 +1,12 @@
1
- import { createClient } from "@apibara/protocol";
1
+ import { createAuthenticatedClient } from "@apibara/protocol";
2
2
  import ci from "ci-info";
3
- import { type IndexerWithStreamConfig, createIndexer } from "../indexer";
3
+ import { type NestedHooks, mergeHooks } from "hookable";
4
+ import { useIndexerContext } from "../context";
5
+ import {
6
+ type IndexerHooks,
7
+ type IndexerWithStreamConfig,
8
+ createIndexer,
9
+ } from "../indexer";
4
10
  import { type InternalContext, internalContext } from "../plugins/context";
5
11
  import { logger } from "../plugins/logger";
6
12
  import type { CassetteOptions, VcrConfig } from "../vcr/config";
@@ -8,24 +14,36 @@ import { isCassetteAvailable } from "../vcr/helper";
8
14
  import { record } from "../vcr/record";
9
15
  import { replay } from "../vcr/replay";
10
16
 
17
+ export type VcrResult = Record<string, unknown>;
18
+
11
19
  export function createVcr() {
20
+ let result: VcrResult;
21
+
12
22
  return {
13
23
  async run<TFilter, TBlock>(
14
24
  cassetteName: string,
15
25
  indexerConfig: IndexerWithStreamConfig<TFilter, TBlock>,
16
- range: { fromBlock: bigint; toBlock: bigint },
26
+ config: {
27
+ range: { fromBlock: bigint; toBlock: bigint };
28
+ hooks?: NestedHooks<IndexerHooks<TFilter, TBlock>>;
29
+ },
17
30
  ) {
18
31
  const vcrConfig: VcrConfig = {
19
32
  cassetteDir: "cassettes",
20
33
  };
21
34
 
35
+ indexerConfig.hooks = mergeHooks(
36
+ indexerConfig.hooks ?? {},
37
+ config.hooks ?? {},
38
+ );
39
+
22
40
  const cassetteOptions: CassetteOptions = {
23
41
  name: cassetteName,
24
42
  startingCursor: {
25
- orderKey: range.fromBlock,
43
+ orderKey: config.range.fromBlock,
26
44
  },
27
45
  endingCursor: {
28
- orderKey: range.toBlock,
46
+ orderKey: config.range.toBlock,
29
47
  },
30
48
  };
31
49
 
@@ -40,12 +58,16 @@ export function createVcr() {
40
58
 
41
59
  const indexer = createIndexer(indexerConfig);
42
60
 
61
+ indexer.hooks.hook("run:after", () => {
62
+ result = useIndexerContext();
63
+ });
64
+
43
65
  if (!isCassetteAvailable(vcrConfig, cassetteName)) {
44
66
  if (ci.isCI) {
45
67
  throw new Error("Cannot record cassette in CI");
46
68
  }
47
69
 
48
- const client = createClient(
70
+ const client = createAuthenticatedClient(
49
71
  indexer.streamConfig,
50
72
  indexer.options.streamUrl,
51
73
  );
@@ -53,6 +75,8 @@ export function createVcr() {
53
75
  } else {
54
76
  await replay(vcrConfig, indexer, cassetteName);
55
77
  }
78
+
79
+ return result;
56
80
  },
57
81
  };
58
82
  }
@@ -0,0 +1,21 @@
1
+ import { useIndexerContext } from "../context";
2
+
3
+ export function reloadIndexer() {
4
+ const context = useIndexerContext();
5
+ context._reload = true;
6
+ }
7
+
8
+ export function reloadIfNeeded() {
9
+ const context = useIndexerContext();
10
+ if (context._reload) {
11
+ context._reload = false;
12
+ throw new ReloadIndexerRequest();
13
+ }
14
+ }
15
+
16
+ export class ReloadIndexerRequest extends Error {
17
+ constructor(message?: string) {
18
+ super(message);
19
+ this.name = "ReloadIndexerRequest";
20
+ }
21
+ }
@@ -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 };
@@ -1,76 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { type MiddlewareFunction, type NextFunction, compose } from "./compose";
3
-
4
- type C = {
5
- bag: Record<string, unknown>;
6
- finalized: boolean;
7
- };
8
-
9
- type MiddlewareTuple = MiddlewareFunction<C>;
10
-
11
- describe("compose", () => {
12
- async function a(context: C, next: NextFunction) {
13
- context.bag.log = "log";
14
- await next();
15
- }
16
-
17
- async function b(context: C, next: NextFunction) {
18
- await next();
19
- context.bag.headers = "custom-header";
20
- }
21
-
22
- async function c(context: C, next: NextFunction) {
23
- context.bag.xxx = "yyy";
24
- await next();
25
- context.bag.zzz = context.bag.xxx;
26
- }
27
-
28
- async function handler(context: C, next: NextFunction) {
29
- context.bag.log = `${context.bag.log} message`;
30
- await next();
31
- context.bag.message = "new response";
32
- }
33
-
34
- const middleware: MiddlewareTuple[] = [];
35
-
36
- middleware.push(a);
37
- middleware.push(b);
38
- middleware.push(c);
39
- middleware.push(handler);
40
-
41
- it("composes", async () => {
42
- const context: C = {
43
- bag: {},
44
- finalized: false,
45
- };
46
-
47
- const composed = compose<C>(middleware);
48
- await composed(context);
49
-
50
- expect(context.bag.log).toBeDefined();
51
- expect(context.bag.log).toBe("log message");
52
- expect(context.bag.headers).toBe("custom-header");
53
- expect(context.bag.xxx).toBe("yyy");
54
- expect(context.bag.zzz).toBe("yyy");
55
- expect(context.finalized).toBe(false);
56
- });
57
-
58
- it("accepts a next function", async () => {
59
- const context: C = {
60
- bag: {},
61
- finalized: false,
62
- };
63
-
64
- const composed = compose<C>(middleware);
65
- await composed(context, async () => {
66
- context.finalized = true;
67
- });
68
-
69
- expect(context.bag.log).toBeDefined();
70
- expect(context.bag.log).toBe("log message");
71
- expect(context.bag.headers).toBe("custom-header");
72
- expect(context.bag.xxx).toBe("yyy");
73
- expect(context.bag.zzz).toBe("yyy");
74
- expect(context.finalized).toBe(true);
75
- });
76
- });
@@ -1,430 +0,0 @@
1
- import type { Cursor, DataFinality } from "@apibara/protocol";
2
- import {
3
- type MockBlock,
4
- MockClient,
5
- type MockFilter,
6
- } from "@apibara/protocol/testing";
7
- import { describe, expect, it } from "vitest";
8
- import { type IndexerContext, useMessageMetadataContext } from "./context";
9
- import { run } from "./indexer";
10
- import {
11
- generateMockMessages,
12
- getMockIndexer,
13
- mockSink,
14
- useMockSink,
15
- } from "./internal/testing";
16
-
17
- async function transform<TData>({
18
- block: { data },
19
- }: {
20
- block: { data?: TData };
21
- cursor?: Cursor;
22
- endCursor?: Cursor;
23
- finality?: DataFinality;
24
- context: IndexerContext;
25
- }) {
26
- const { cursor, endCursor, finality } = useMessageMetadataContext();
27
- const { output } = useMockSink();
28
- output.push({
29
- data,
30
- cursor: cursor?.orderKey,
31
- endCursor: endCursor?.orderKey,
32
- finality,
33
- });
34
- }
35
-
36
- describe("Run Test", () => {
37
- it("should stream messages", async () => {
38
- const client = new MockClient<MockFilter, MockBlock>((request, options) => {
39
- return generateMockMessages();
40
- });
41
-
42
- const output: unknown[] = [];
43
-
44
- const indexer = getMockIndexer({
45
- override: {
46
- plugins: [mockSink({ output })],
47
- transform,
48
- },
49
- });
50
-
51
- await run(client, indexer);
52
-
53
- expect(output).toMatchInlineSnapshot(`
54
- [
55
- {
56
- "cursor": 4999999n,
57
- "data": "5000000",
58
- "endCursor": 5000000n,
59
- "finality": "accepted",
60
- },
61
- {
62
- "cursor": 5000000n,
63
- "data": "5000001",
64
- "endCursor": 5000001n,
65
- "finality": "accepted",
66
- },
67
- {
68
- "cursor": 5000001n,
69
- "data": "5000002",
70
- "endCursor": 5000002n,
71
- "finality": "accepted",
72
- },
73
- {
74
- "cursor": 5000002n,
75
- "data": "5000003",
76
- "endCursor": 5000003n,
77
- "finality": "accepted",
78
- },
79
- {
80
- "cursor": 5000003n,
81
- "data": "5000004",
82
- "endCursor": 5000004n,
83
- "finality": "accepted",
84
- },
85
- {
86
- "cursor": 5000004n,
87
- "data": "5000005",
88
- "endCursor": 5000005n,
89
- "finality": "accepted",
90
- },
91
- {
92
- "cursor": 5000005n,
93
- "data": "5000006",
94
- "endCursor": 5000006n,
95
- "finality": "accepted",
96
- },
97
- {
98
- "cursor": 5000006n,
99
- "data": "5000007",
100
- "endCursor": 5000007n,
101
- "finality": "accepted",
102
- },
103
- {
104
- "cursor": 5000007n,
105
- "data": "5000008",
106
- "endCursor": 5000008n,
107
- "finality": "accepted",
108
- },
109
- {
110
- "cursor": 5000008n,
111
- "data": "5000009",
112
- "endCursor": 5000009n,
113
- "finality": "accepted",
114
- },
115
- ]
116
- `);
117
- });
118
-
119
- it("factory mode: indexer should merge filters and restart when needed", async () => {
120
- const client = new MockClient<MockFilter, MockBlock>((request, options) => {
121
- const [_factoryFilter, mainFilter] = request.filter;
122
-
123
- if (Object.keys(mainFilter).length === 0) {
124
- expect(request.startingCursor?.orderKey).toEqual(100n);
125
-
126
- return [
127
- {
128
- _tag: "data",
129
- data: {
130
- finality: "accepted",
131
- cursor: { orderKey: 100n },
132
- endCursor: { orderKey: 101n },
133
- data: [null, null],
134
- production: "backfill",
135
- },
136
- },
137
- {
138
- _tag: "data",
139
- data: {
140
- finality: "accepted",
141
- cursor: { orderKey: 101n },
142
- endCursor: { orderKey: 102n },
143
- data: [null, null],
144
- production: "backfill",
145
- },
146
- },
147
- {
148
- _tag: "data",
149
- data: {
150
- finality: "accepted",
151
- cursor: { orderKey: 102n },
152
- endCursor: { orderKey: 103n },
153
- data: [{ data: "B" }, null],
154
- production: "backfill",
155
- },
156
- },
157
- ];
158
- }
159
-
160
- if (mainFilter.filter === "B") {
161
- expect(request.startingCursor?.orderKey).toEqual(102n);
162
-
163
- return [
164
- {
165
- _tag: "data",
166
- data: {
167
- finality: "accepted",
168
- cursor: { orderKey: 102n },
169
- endCursor: { orderKey: 103n },
170
- data: [{ data: "B" }, { data: "103B" }],
171
- production: "backfill",
172
- },
173
- },
174
- {
175
- _tag: "data",
176
- data: {
177
- finality: "accepted",
178
- cursor: { orderKey: 103n },
179
- endCursor: { orderKey: 104n },
180
- data: [null, { data: "104B" }],
181
- production: "backfill",
182
- },
183
- },
184
- {
185
- _tag: "data",
186
- data: {
187
- finality: "accepted",
188
- cursor: { orderKey: 104n },
189
- endCursor: { orderKey: 105n },
190
- data: [null, { data: "105B" }],
191
- production: "backfill",
192
- },
193
- },
194
- {
195
- _tag: "data",
196
- data: {
197
- finality: "accepted",
198
- cursor: { orderKey: 105n },
199
- endCursor: { orderKey: 106n },
200
- data: [{ data: "C" }, { data: "106B" }],
201
- production: "backfill",
202
- },
203
- },
204
- ];
205
- }
206
-
207
- if (mainFilter.filter === "BC") {
208
- expect(request.startingCursor?.orderKey).toEqual(105n);
209
-
210
- return [
211
- {
212
- _tag: "data",
213
- data: {
214
- finality: "accepted",
215
- cursor: { orderKey: 105n },
216
- endCursor: { orderKey: 106n },
217
- data: [{ data: "C" }, { data: "106BC" }],
218
- production: "backfill",
219
- },
220
- },
221
- {
222
- _tag: "data",
223
- data: {
224
- finality: "accepted",
225
- cursor: { orderKey: 106n },
226
- endCursor: { orderKey: 107n },
227
- data: [null, { data: "107BC" }],
228
- production: "backfill",
229
- },
230
- },
231
- {
232
- _tag: "data",
233
- data: {
234
- finality: "accepted",
235
- cursor: { orderKey: 107n },
236
- endCursor: { orderKey: 108n },
237
- data: [null, { data: "108BC" }],
238
- production: "backfill",
239
- },
240
- },
241
- ];
242
- }
243
-
244
- return [];
245
- });
246
-
247
- const output: unknown[] = [];
248
- const metadata: Record<string, unknown> = {};
249
-
250
- const indexer = getMockIndexer({
251
- override: {
252
- plugins: [mockSink({ output, metadata })],
253
- startingCursor: { orderKey: 100n },
254
- factory: async ({ block }) => {
255
- if (block.data === "B") {
256
- return { filter: { filter: "B" } };
257
- }
258
-
259
- if (block.data === "C") {
260
- return { filter: { filter: "C" } };
261
- }
262
-
263
- return {};
264
- },
265
- transform,
266
- },
267
- });
268
-
269
- await run(client, indexer);
270
-
271
- expect((metadata.lastCursor as Cursor).orderKey).toEqual(108n);
272
- expect((metadata.lastFilter as { filter: unknown }).filter).toEqual("BC");
273
-
274
- expect(output).toMatchInlineSnapshot(`
275
- [
276
- {
277
- "cursor": 102n,
278
- "data": "103B",
279
- "endCursor": 103n,
280
- "finality": "accepted",
281
- },
282
- {
283
- "cursor": 103n,
284
- "data": "104B",
285
- "endCursor": 104n,
286
- "finality": "accepted",
287
- },
288
- {
289
- "cursor": 104n,
290
- "data": "105B",
291
- "endCursor": 105n,
292
- "finality": "accepted",
293
- },
294
- {
295
- "cursor": 105n,
296
- "data": "106BC",
297
- "endCursor": 106n,
298
- "finality": "accepted",
299
- },
300
- {
301
- "cursor": 106n,
302
- "data": "107BC",
303
- "endCursor": 107n,
304
- "finality": "accepted",
305
- },
306
- {
307
- "cursor": 107n,
308
- "data": "108BC",
309
- "endCursor": 108n,
310
- "finality": "accepted",
311
- },
312
- ]
313
- `);
314
- });
315
-
316
- it("factory mode: last cursor should persist when error is thrown in indexer", async () => {
317
- const client = new MockClient<MockFilter, MockBlock>((request, options) => {
318
- const [_factoryFilter, mainFilter] = request.filter;
319
-
320
- if (Object.keys(mainFilter).length === 0) {
321
- expect(request.startingCursor?.orderKey).toEqual(100n);
322
-
323
- return [
324
- {
325
- _tag: "data",
326
- data: {
327
- finality: "accepted",
328
- cursor: { orderKey: 100n },
329
- endCursor: { orderKey: 101n },
330
- data: [null, null],
331
- production: "backfill",
332
- },
333
- },
334
- {
335
- _tag: "data",
336
- data: {
337
- finality: "accepted",
338
- cursor: { orderKey: 101n },
339
- endCursor: { orderKey: 102n },
340
- data: [null, null],
341
- production: "backfill",
342
- },
343
- },
344
- {
345
- _tag: "data",
346
- data: {
347
- finality: "accepted",
348
- cursor: { orderKey: 102n },
349
- endCursor: { orderKey: 103n },
350
- data: [{ data: "B" }, null],
351
- production: "backfill",
352
- },
353
- },
354
- Error("this error should not occurr!"),
355
- ];
356
- }
357
-
358
- if (mainFilter.filter === "B") {
359
- expect(request.startingCursor?.orderKey).toEqual(102n);
360
-
361
- return [
362
- Error("this error should occurr!"),
363
- {
364
- _tag: "data",
365
- data: {
366
- finality: "accepted",
367
- cursor: { orderKey: 103n },
368
- endCursor: { orderKey: 104n },
369
- data: [null, { data: "104B" }],
370
- production: "backfill",
371
- },
372
- },
373
- {
374
- _tag: "data",
375
- data: {
376
- finality: "accepted",
377
- cursor: { orderKey: 104n },
378
- endCursor: { orderKey: 105n },
379
- data: [null, { data: "105B" }],
380
- production: "backfill",
381
- },
382
- },
383
- {
384
- _tag: "data",
385
- data: {
386
- finality: "accepted",
387
- cursor: { orderKey: 105n },
388
- endCursor: { orderKey: 106n },
389
- data: [{ data: "C" }, { data: "106B" }],
390
- production: "backfill",
391
- },
392
- },
393
- ];
394
- }
395
-
396
- return [];
397
- });
398
-
399
- const output: unknown[] = [];
400
- const metadata: Record<string, unknown> = {};
401
-
402
- const indexer = getMockIndexer({
403
- override: {
404
- plugins: [mockSink({ output, metadata })],
405
- startingCursor: { orderKey: 100n },
406
- factory: async ({ block }) => {
407
- if (block.data === "B") {
408
- return { filter: { filter: "B" } };
409
- }
410
-
411
- if (block.data === "C") {
412
- return { filter: { filter: "C" } };
413
- }
414
-
415
- return {};
416
- },
417
- transform,
418
- },
419
- });
420
-
421
- await expect(() => run(client, indexer)).rejects.toThrowError(
422
- "this error should occurr!",
423
- );
424
-
425
- expect((metadata.lastCursor as Cursor).orderKey).toEqual(103n);
426
- expect((metadata.lastFilter as { filter: unknown }).filter).toEqual("B");
427
-
428
- expect(output).toMatchInlineSnapshot("[]");
429
- });
430
- });