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

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 (68) hide show
  1. package/dist/index.cjs +81 -27
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.mts +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.mjs +71 -17
  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 +25 -10
  20. package/dist/internal/testing.cjs.map +1 -0
  21. package/dist/internal/testing.d.cts +5 -3
  22. package/dist/internal/testing.d.mts +5 -3
  23. package/dist/internal/testing.d.ts +5 -3
  24. package/dist/internal/testing.mjs +23 -8
  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.a55ad619.mjs → indexer.75773ef1.mjs} +6 -1
  40. package/dist/shared/indexer.75773ef1.mjs.map +1 -0
  41. package/dist/shared/{indexer.fedcd831.d.cts → indexer.806c605c.d.cts} +15 -16
  42. package/dist/shared/{indexer.fedcd831.d.mts → indexer.806c605c.d.mts} +15 -16
  43. package/dist/shared/{indexer.fedcd831.d.ts → indexer.806c605c.d.ts} +15 -16
  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/indexer.ts +78 -28
  60. package/src/internal/testing.ts +34 -11
  61. package/src/otel.ts +29 -2
  62. package/src/plugins/context.ts +1 -1
  63. package/src/plugins/logger.ts +11 -2
  64. package/src/testing/index.ts +30 -6
  65. package/dist/shared/indexer.601ceab0.cjs +0 -7
  66. package/dist/shared/indexer.9b21ddd2.mjs +0 -5
  67. package/src/compose.test.ts +0 -76
  68. package/src/indexer.test.ts +0 -430
package/src/indexer.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  type Client,
3
3
  ClientError,
4
+ type CreateClientOptions,
4
5
  type Cursor,
5
6
  type DataFinality,
7
+ type DataProduction,
6
8
  type Finalize,
7
- type Heartbeat,
8
9
  type Invalidate,
10
+ ServerError,
9
11
  Status,
10
12
  type StreamConfig,
11
13
  type StreamDataOptions,
@@ -28,14 +30,16 @@ import {
28
30
  indexerAsyncContext,
29
31
  useIndexerContext,
30
32
  } from "./context";
31
- import { tracer } from "./otel";
33
+ import { createIndexerMetrics, createTracer } from "./otel";
32
34
  import type { IndexerPlugin } from "./plugins";
35
+ import { useInternalContext } from "./plugins/context";
33
36
 
34
37
  export type UseMiddlewareFunction = (
35
38
  fn: MiddlewareFunction<IndexerContext>,
36
39
  ) => void;
37
40
 
38
41
  export interface IndexerHooks<TFilter, TBlock> {
42
+ "plugins:init": () => void;
39
43
  "run:before": () => void;
40
44
  "run:after": () => void;
41
45
  "connect:before": ({
@@ -47,7 +51,9 @@ export interface IndexerHooks<TFilter, TBlock> {
47
51
  }) => void;
48
52
  "connect:after": ({
49
53
  request,
50
- }: { request: StreamDataRequest<TFilter> }) => void;
54
+ }: {
55
+ request: StreamDataRequest<TFilter>;
56
+ }) => void;
51
57
  "connect:factory": ({
52
58
  request,
53
59
  endCursor,
@@ -59,7 +65,7 @@ export interface IndexerHooks<TFilter, TBlock> {
59
65
  message: ({ message }: { message: StreamDataResponse<TBlock> }) => void;
60
66
  "message:invalidate": ({ message }: { message: Invalidate }) => void;
61
67
  "message:finalize": ({ message }: { message: Finalize }) => void;
62
- "message:heartbeat": ({ message }: { message: Heartbeat }) => void;
68
+ "message:heartbeat": () => void;
63
69
  "message:systemMessage": ({ message }: { message: SystemMessage }) => void;
64
70
  }
65
71
 
@@ -77,23 +83,24 @@ export type IndexerStartingCursor =
77
83
  startingBlock?: never;
78
84
  };
79
85
 
86
+ export type HandlerArgs<TBlock> = {
87
+ block: TBlock;
88
+ cursor?: Cursor | undefined;
89
+ endCursor?: Cursor | undefined;
90
+ finality: DataFinality;
91
+ production: DataProduction;
92
+ context: IndexerContext;
93
+ };
94
+
80
95
  export type IndexerConfig<TFilter, TBlock> = {
81
96
  streamUrl: string;
82
97
  filter: TFilter;
83
98
  finality?: DataFinality;
84
- factory?: ({
85
- block,
86
- context,
87
- }: { block: TBlock; context: IndexerContext }) => Promise<{
99
+ clientOptions?: CreateClientOptions;
100
+ factory?: (args: HandlerArgs<TBlock>) => Promise<{
88
101
  filter?: TFilter;
89
102
  }>;
90
- transform: (args: {
91
- block: TBlock;
92
- cursor?: Cursor | undefined;
93
- endCursor?: Cursor | undefined;
94
- finality: DataFinality;
95
- context: IndexerContext;
96
- }) => Promise<void>;
103
+ transform: (args: HandlerArgs<TBlock>) => Promise<void>;
97
104
  hooks?: NestedHooks<IndexerHooks<TFilter, TBlock>>;
98
105
  plugins?: ReadonlyArray<IndexerPlugin<TFilter, TBlock>>;
99
106
  debug?: boolean;
@@ -179,13 +186,18 @@ export async function runWithReconnect<TFilter, TBlock>(
179
186
 
180
187
  retryCount++;
181
188
 
182
- if (error instanceof ClientError) {
189
+ if (error instanceof ClientError || error instanceof ServerError) {
190
+ const isServerError = error instanceof ServerError;
191
+
183
192
  if (error.code === Status.INTERNAL) {
184
193
  if (retryCount < maxRetries) {
185
194
  consola.error(
186
- "Internal server error, reconnecting...",
187
- error.message,
195
+ `Internal ${isServerError ? "server" : "client"} error: ${
196
+ error.message
197
+ }`,
188
198
  );
199
+ consola.start("Reconnecting...");
200
+ console.log();
189
201
 
190
202
  // Add jitter to the retry delay to avoid all clients retrying at the same time.
191
203
  const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
@@ -214,10 +226,22 @@ export async function run<TFilter, TBlock>(
214
226
  ) {
215
227
  await indexerAsyncContext.callAsync({}, async () => {
216
228
  const context = useIndexerContext();
229
+
230
+ if (indexer.options.debug) {
231
+ context.debug = true;
232
+ }
233
+
234
+ await indexer.hooks.callHook("plugins:init");
235
+
217
236
  const middleware = await registerMiddleware(indexer);
218
237
 
238
+ const indexerMetrics = createIndexerMetrics();
239
+ const tracer = createTracer();
240
+
219
241
  await indexer.hooks.callHook("run:before");
220
242
 
243
+ const { indexerName: indexerId } = useInternalContext();
244
+
221
245
  const isFactoryMode = indexer.options.factory !== undefined;
222
246
 
223
247
  // Give priority to startingCursor over startingBlock.
@@ -235,13 +259,13 @@ export async function run<TFilter, TBlock>(
235
259
  }
236
260
 
237
261
  // if factory mode we add a empty filter at the end of the filter array.
238
- const request = indexer.streamConfig.Request.make({
262
+ const request = {
239
263
  filter: isFactoryMode
240
264
  ? [indexer.options.filter, {} as TFilter]
241
265
  : [indexer.options.filter],
242
266
  finality: indexer.options.finality,
243
267
  startingCursor,
244
- });
268
+ } as StreamDataRequest<TFilter>;
245
269
 
246
270
  const options: StreamDataOptions = {};
247
271
 
@@ -282,17 +306,25 @@ export async function run<TFilter, TBlock>(
282
306
  case "data": {
283
307
  await tracer.startActiveSpan("message data", async (span) => {
284
308
  const blocks = message.data.data;
285
- const { cursor, endCursor, finality } = message.data;
309
+ const { cursor, endCursor, finality, production } = message.data;
286
310
 
287
311
  context.cursor = cursor;
288
312
  context.endCursor = endCursor;
289
313
  context.finality = finality;
290
314
 
315
+ // Record current block number being processed
316
+ indexerMetrics.currentBlockGauge.record(
317
+ Number(endCursor?.orderKey),
318
+ {
319
+ indexer_id: indexerId,
320
+ },
321
+ );
322
+
291
323
  await middleware(context, async () => {
292
324
  let block: TBlock | null;
293
325
 
294
326
  // when factory mode
295
- if (isFactoryMode) {
327
+ if (isFactoryMode && finality !== "pending") {
296
328
  assert(indexer.options.factory !== undefined);
297
329
 
298
330
  const [factoryBlock, mainBlock] = blocks;
@@ -302,6 +334,10 @@ export async function run<TFilter, TBlock>(
302
334
  if (factoryBlock !== null) {
303
335
  const { filter } = await indexer.options.factory({
304
336
  block: factoryBlock,
337
+ cursor,
338
+ endCursor,
339
+ finality,
340
+ production,
305
341
  context,
306
342
  });
307
343
 
@@ -315,11 +351,11 @@ export async function run<TFilter, TBlock>(
315
351
  );
316
352
 
317
353
  // create request with new filters
318
- const request = indexer.streamConfig.Request.make({
354
+ const request = {
319
355
  filter: [indexer.options.filter, mainFilter],
320
356
  finality: indexer.options.finality,
321
357
  startingCursor: cursor,
322
- });
358
+ } as StreamDataRequest<TFilter>;
323
359
 
324
360
  await indexer.hooks.callHook("connect:factory", {
325
361
  request,
@@ -353,6 +389,7 @@ export async function run<TFilter, TBlock>(
353
389
  cursor,
354
390
  endCursor,
355
391
  finality,
392
+ production,
356
393
  context,
357
394
  });
358
395
 
@@ -364,6 +401,11 @@ export async function run<TFilter, TBlock>(
364
401
  span.end();
365
402
  });
366
403
 
404
+ // Record processed block metric
405
+ indexerMetrics.processedBlockCounter.add(1, {
406
+ indexer_id: indexerId,
407
+ });
408
+
367
409
  context.cursor = undefined;
368
410
  context.endCursor = undefined;
369
411
  context.finality = undefined;
@@ -372,21 +414,29 @@ export async function run<TFilter, TBlock>(
372
414
  }
373
415
  case "invalidate": {
374
416
  await tracer.startActiveSpan("message invalidate", async (span) => {
375
- await indexer.hooks.callHook("message:invalidate", { message });
417
+ // Record reorg metric
418
+ indexerMetrics.reorgCounter.add(1, {
419
+ indexer_id: indexerId,
420
+ });
421
+ await indexer.hooks.callHook("message:invalidate", {
422
+ message: message.invalidate,
423
+ });
376
424
  span.end();
377
425
  });
378
426
  break;
379
427
  }
380
428
  case "finalize": {
381
429
  await tracer.startActiveSpan("message finalize", async (span) => {
382
- await indexer.hooks.callHook("message:finalize", { message });
430
+ await indexer.hooks.callHook("message:finalize", {
431
+ message: message.finalize,
432
+ });
383
433
  span.end();
384
434
  });
385
435
  break;
386
436
  }
387
437
  case "heartbeat": {
388
438
  await tracer.startActiveSpan("message heartbeat", async (span) => {
389
- await indexer.hooks.callHook("message:heartbeat", { message });
439
+ await indexer.hooks.callHook("message:heartbeat");
390
440
  span.end();
391
441
  });
392
442
  break;
@@ -409,7 +459,7 @@ export async function run<TFilter, TBlock>(
409
459
  }
410
460
 
411
461
  await indexer.hooks.callHook("message:systemMessage", {
412
- message,
462
+ message: message.systemMessage,
413
463
  });
414
464
  span.end();
415
465
  },
@@ -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,33 +31,50 @@ 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
- },
42
- } as Invalidate);
51
+ } as Invalidate,
52
+ });
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
- },
51
- } as Finalize);
65
+ } as Finalize,
66
+ });
52
67
  } else {
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
+ }
@@ -7,7 +7,7 @@ export function internalContext<TFilter, TBlock, TTxnParams>(
7
7
  values: Record<string, unknown>,
8
8
  ) {
9
9
  return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
10
- indexer.hooks.hook("run:before", () => {
10
+ indexer.hooks.hook("plugins:init", () => {
11
11
  try {
12
12
  const ctx = useIndexerContext();
13
13
  ctx[INTERNAL_CONTEXT_PROPERTY] = {
@@ -1,4 +1,9 @@
1
- import { type ConsolaInstance, type ConsolaReporter, consola } from "consola";
1
+ import {
2
+ type ConsolaInstance,
3
+ type ConsolaReporter,
4
+ LogLevels,
5
+ consola,
6
+ } from "consola";
2
7
  import { useIndexerContext } from "../context";
3
8
  import { defineIndexerPlugin } from "./config";
4
9
 
@@ -8,7 +13,7 @@ export function logger<TFilter, TBlock, TTxnParams>({
8
13
  logger,
9
14
  }: { logger?: ConsolaReporter } = {}) {
10
15
  return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
11
- indexer.hooks.hook("run:before", () => {
16
+ indexer.hooks.hook("plugins:init", () => {
12
17
  const ctx = useIndexerContext();
13
18
 
14
19
  if (logger) {
@@ -16,6 +21,10 @@ export function logger<TFilter, TBlock, TTxnParams>({
16
21
  } else {
17
22
  ctx.logger = consola.create({});
18
23
  }
24
+
25
+ if (ctx.debug) {
26
+ ctx.logger.level = LogLevels.debug;
27
+ }
19
28
  });
20
29
  });
21
30
  }
@@ -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
  }
@@ -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
- });