@apibara/indexer 0.4.1 → 2.0.0-beta.3

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 (112) hide show
  1. package/README.md +2 -9
  2. package/package.json +66 -45
  3. package/src/context.ts +19 -0
  4. package/src/hooks/index.ts +2 -0
  5. package/src/hooks/useKVStore.ts +12 -0
  6. package/src/hooks/useSink.ts +13 -0
  7. package/src/index.ts +7 -1
  8. package/src/indexer.test.ts +491 -0
  9. package/src/indexer.ts +322 -0
  10. package/src/otel.ts +3 -0
  11. package/src/plugins/config.ts +11 -0
  12. package/src/plugins/kv.test.ts +120 -0
  13. package/src/plugins/kv.ts +132 -0
  14. package/src/plugins/persistence.test.ts +151 -0
  15. package/src/plugins/persistence.ts +202 -0
  16. package/src/sink.ts +36 -0
  17. package/src/sinks/csv.test.ts +65 -0
  18. package/src/sinks/csv.ts +159 -0
  19. package/src/sinks/drizzle/Int8Range.ts +52 -0
  20. package/src/sinks/drizzle/delete.ts +42 -0
  21. package/src/sinks/drizzle/drizzle.test.ts +239 -0
  22. package/src/sinks/drizzle/drizzle.ts +115 -0
  23. package/src/sinks/drizzle/index.ts +6 -0
  24. package/src/sinks/drizzle/insert.ts +39 -0
  25. package/src/sinks/drizzle/select.ts +44 -0
  26. package/src/sinks/drizzle/transaction.ts +49 -0
  27. package/src/sinks/drizzle/update.ts +47 -0
  28. package/src/sinks/drizzle/utils.ts +36 -0
  29. package/src/sinks/sqlite.test.ts +99 -0
  30. package/src/sinks/sqlite.ts +170 -0
  31. package/src/testing/helper.ts +13 -0
  32. package/src/testing/index.ts +3 -0
  33. package/src/testing/indexer.ts +35 -0
  34. package/src/testing/setup.ts +59 -0
  35. package/src/testing/vcr.ts +54 -0
  36. package/src/vcr/config.ts +11 -0
  37. package/src/vcr/helper.ts +27 -0
  38. package/src/vcr/index.ts +4 -0
  39. package/src/vcr/record.ts +45 -0
  40. package/src/vcr/replay.ts +51 -0
  41. package/LICENSE.txt +0 -202
  42. package/dist/config.d.ts +0 -35
  43. package/dist/config.js +0 -2
  44. package/dist/config.js.map +0 -1
  45. package/dist/config.test-d.d.ts +0 -1
  46. package/dist/config.test-d.js +0 -38
  47. package/dist/config.test-d.js.map +0 -1
  48. package/dist/index.js +0 -2
  49. package/dist/index.js.map +0 -1
  50. package/dist/sink/console.d.ts +0 -10
  51. package/dist/sink/console.js +0 -2
  52. package/dist/sink/console.js.map +0 -1
  53. package/dist/sink/console.test-d.d.ts +0 -1
  54. package/dist/sink/console.test-d.js +0 -12
  55. package/dist/sink/console.test-d.js.map +0 -1
  56. package/dist/sink/mongo.d.ts +0 -14
  57. package/dist/sink/mongo.js +0 -2
  58. package/dist/sink/mongo.js.map +0 -1
  59. package/dist/sink/parquet.d.ts +0 -9
  60. package/dist/sink/parquet.js +0 -2
  61. package/dist/sink/parquet.js.map +0 -1
  62. package/dist/sink/postgres.d.ts +0 -10
  63. package/dist/sink/postgres.js +0 -2
  64. package/dist/sink/postgres.js.map +0 -1
  65. package/dist/sink/webhook.d.ts +0 -12
  66. package/dist/sink/webhook.js +0 -2
  67. package/dist/sink/webhook.js.map +0 -1
  68. package/dist/starknet/block.d.ts +0 -409
  69. package/dist/starknet/block.js +0 -2
  70. package/dist/starknet/block.js.map +0 -1
  71. package/dist/starknet/block.test-d.d.ts +0 -1
  72. package/dist/starknet/block.test-d.js +0 -95
  73. package/dist/starknet/block.test-d.js.map +0 -1
  74. package/dist/starknet/felt.d.ts +0 -11
  75. package/dist/starknet/felt.js +0 -25
  76. package/dist/starknet/felt.js.map +0 -1
  77. package/dist/starknet/felt.test.d.ts +0 -1
  78. package/dist/starknet/felt.test.js +0 -16
  79. package/dist/starknet/felt.test.js.map +0 -1
  80. package/dist/starknet/filter.d.ts +0 -175
  81. package/dist/starknet/filter.js +0 -2
  82. package/dist/starknet/filter.js.map +0 -1
  83. package/dist/starknet/filter.test-d.d.ts +0 -1
  84. package/dist/starknet/filter.test-d.js +0 -166
  85. package/dist/starknet/filter.test-d.js.map +0 -1
  86. package/dist/starknet/index.d.ts +0 -10
  87. package/dist/starknet/index.js +0 -5
  88. package/dist/starknet/index.js.map +0 -1
  89. package/dist/starknet/parser.d.ts +0 -16
  90. package/dist/starknet/parser.js +0 -49
  91. package/dist/starknet/parser.js.map +0 -1
  92. package/dist/starknet/parser.test.d.ts +0 -1
  93. package/dist/starknet/parser.test.js +0 -50
  94. package/dist/starknet/parser.test.js.map +0 -1
  95. package/src/config.test-d.ts +0 -55
  96. package/src/config.ts +0 -47
  97. package/src/sink/console.test-d.ts +0 -14
  98. package/src/sink/console.ts +0 -11
  99. package/src/sink/mongo.ts +0 -14
  100. package/src/sink/parquet.ts +0 -9
  101. package/src/sink/postgres.ts +0 -10
  102. package/src/sink/webhook.ts +0 -12
  103. package/src/starknet/block.test-d.ts +0 -112
  104. package/src/starknet/block.ts +0 -469
  105. package/src/starknet/felt.test.ts +0 -25
  106. package/src/starknet/felt.ts +0 -30
  107. package/src/starknet/filter.test-d.ts +0 -197
  108. package/src/starknet/filter.ts +0 -205
  109. package/src/starknet/index.ts +0 -12
  110. package/src/starknet/parser.test.ts +0 -67
  111. package/src/starknet/parser.ts +0 -69
  112. /package/{dist/index.d.ts → src/plugins/index.ts} +0 -0
package/README.md CHANGED
@@ -1,14 +1,7 @@
1
1
  # `@apibara/indexer`
2
2
 
3
- This package provides type definitions and helper functions to write Apibara
4
- indexers.
3
+ TODO
5
4
 
6
5
  ## Installation
7
6
 
8
- Apibara indexers use the Deno javascript runtime. Install the package by importing it from a CDN,
9
- for example:
10
-
11
- ```ts
12
- import { Config } from "https://esm.run/@apibara/indexer";
13
- ```
14
-
7
+ TODO
package/package.json CHANGED
@@ -1,62 +1,83 @@
1
1
  {
2
2
  "name": "@apibara/indexer",
3
- "version": "0.4.1",
3
+ "version": "2.0.0-beta.3",
4
4
  "type": "module",
5
+ "source": "./src/index.ts",
6
+ "main": "./dist/index.mjs",
5
7
  "exports": {
6
8
  ".": {
7
9
  "types": "./dist/index.d.ts",
8
- "import": "./dist/index.js",
9
- "default": "./dist/index.js"
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.cjs",
12
+ "default": "./dist/index.mjs"
10
13
  },
11
- "./sink/console": {
12
- "types": "./dist/sink/console.d.ts",
13
- "import": "./dist/sink/console.js",
14
- "default": "./dist/sink/console.js"
14
+ "./sinks/sqlite": {
15
+ "types": "./dist/sinks/sqlite.d.ts",
16
+ "import": "./dist/sinks/sqlite.mjs",
17
+ "require": "./dist/sinks/sqlite.cjs",
18
+ "default": "./dist/sinks/sqlite.mjs"
15
19
  },
16
- "./sink/mongo": {
17
- "types": "./dist/sink/mongo.d.ts",
18
- "import": "./dist/sink/mongo.js",
19
- "default": "./dist/sink/mongo.js"
20
+ "./sinks/csv": {
21
+ "types": "./dist/sinks/csv.d.ts",
22
+ "import": "./dist/sinks/csv.mjs",
23
+ "require": "./dist/sinks/csv.cjs",
24
+ "default": "./dist/sinks/csv.mjs"
20
25
  },
21
- "./sink/parquet": {
22
- "types": "./dist/sink/parquet.d.ts",
23
- "import": "./dist/sink/parquet.js",
24
- "default": "./dist/sink/parquet.js"
26
+ "./sinks/drizzle": {
27
+ "types": "./dist/sinks/drizzle/index.d.ts",
28
+ "import": "./dist/sinks/drizzle/index.mjs",
29
+ "require": "./dist/sinks/drizzle/index.cjs",
30
+ "default": "./dist/sinks/drizzle/index.mjs"
25
31
  },
26
- "./sink/postgres": {
27
- "types": "./dist/sink/postgres.d.ts",
28
- "import": "./dist/sink/postgres.js",
29
- "default": "./dist/sink/postgres.js"
30
- },
31
- "./sink/webhook": {
32
- "types": "./dist/sink/webhook.d.ts",
33
- "import": "./dist/sink/webhook.js",
34
- "default": "./dist/sink/webhook.js"
35
- },
36
- "./starknet": {
37
- "types": "./dist/starknet/index.d.ts",
38
- "import": "./dist/starknet/index.js",
39
- "default": "./dist/starknet/index.js"
32
+ "./testing": {
33
+ "types": "./dist/testing/index.d.ts",
34
+ "import": "./dist/testing/index.mjs",
35
+ "require": "./dist/testing/index.cjs",
36
+ "default": "./dist/testing/index.mjs"
40
37
  }
41
38
  },
39
+ "publishConfig": {},
40
+ "scripts": {
41
+ "build": "unbuild",
42
+ "lint": "biome check .",
43
+ "typecheck": "tsc --noEmit",
44
+ "lint:fix": "pnpm lint --write",
45
+ "test": "vitest",
46
+ "test:ci": "vitest run",
47
+ "format": "biome format . --write"
48
+ },
49
+ "devDependencies": {
50
+ "@types/better-sqlite3": "^7.6.11",
51
+ "@types/node": "^20.14.0",
52
+ "@types/pg": "^8.11.10",
53
+ "better-sqlite3": "^11.1.2",
54
+ "csv-stringify": "^6.5.0",
55
+ "drizzle-orm": "^0.33.0",
56
+ "pg": "^8.12.0",
57
+ "postgres-range": "^1.1.4",
58
+ "unbuild": "^2.0.0",
59
+ "vitest": "^1.6.0"
60
+ },
61
+ "dependencies": {
62
+ "@apibara/protocol": "2.0.0-beta.3",
63
+ "@opentelemetry/api": "^1.9.0",
64
+ "consola": "^3.2.3",
65
+ "hookable": "^5.5.3",
66
+ "klona": "^2.0.6",
67
+ "nice-grpc": "^2.1.8",
68
+ "unctx": "^2.3.1"
69
+ },
70
+ "peerDependencies": {
71
+ "better-sqlite3": "^11.1.2",
72
+ "csv-stringify": "^6.5.0",
73
+ "drizzle-orm": "^0.33.0",
74
+ "postgres-range": "^1.1.4",
75
+ "vitest": "^1.6.0"
76
+ },
42
77
  "files": [
43
78
  "dist",
44
79
  "src",
45
80
  "README.md"
46
81
  ],
47
- "devDependencies": {
48
- "vitest": "^0.34.3"
49
- },
50
- "dependencies": {
51
- "starknet": "^5.24.3",
52
- "zod": "^3.22.2"
53
- },
54
- "scripts": {
55
- "build": "tsc",
56
- "test": "vitest",
57
- "test:typecheck": "vitest typecheck",
58
- "lint": "biome check .",
59
- "lint:fix": "pnpm lint --apply",
60
- "format": "biome format . --write"
61
- }
62
- }
82
+ "types": "./dist/index.d.ts"
83
+ }
package/src/context.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { getContext } from "unctx";
3
+ import type { Sink } from "./sink";
4
+
5
+ // biome-ignore lint/suspicious/noExplicitAny: context type
6
+ export interface IndexerContext<TTxnParams = any> extends Record<string, any> {
7
+ sink?: Sink<TTxnParams>;
8
+ sinkTransaction?: TTxnParams;
9
+ }
10
+
11
+ export const indexerAsyncContext = getContext<IndexerContext>("indexer", {
12
+ asyncContext: true,
13
+ AsyncLocalStorage,
14
+ });
15
+
16
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
17
+ export function useIndexerContext<TTxnParams = any>() {
18
+ return indexerAsyncContext.use() as IndexerContext<TTxnParams>;
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./useKVStore";
2
+ export * from "./useSink";
@@ -0,0 +1,12 @@
1
+ import { useIndexerContext } from "../context";
2
+ import type { KVStore } from "../plugins/kv";
3
+
4
+ export type UseKVStoreResult = InstanceType<typeof KVStore>;
5
+
6
+ export function useKVStore(): UseKVStoreResult {
7
+ const ctx = useIndexerContext();
8
+
9
+ if (!ctx?.kv) throw new Error("KV Plugin is not available in context!");
10
+
11
+ return ctx.kv;
12
+ }
@@ -0,0 +1,13 @@
1
+ import type { IndexerContext } from "../context";
2
+
3
+ export function useSink<TTxnParams>({
4
+ context,
5
+ }: {
6
+ context: IndexerContext<TTxnParams>;
7
+ }) {
8
+ if (!context.sinkTransaction) {
9
+ throw new Error("Transaction context doesn't exist!");
10
+ }
11
+
12
+ return context.sinkTransaction;
13
+ }
package/src/index.ts CHANGED
@@ -1 +1,7 @@
1
- export * from "./config";
1
+ export * from "./indexer";
2
+ export * from "./sink";
3
+ export { useIndexerContext } from "./context";
4
+
5
+ export * from "./plugins";
6
+ export * from "./vcr";
7
+ export * from "./hooks";
@@ -0,0 +1,491 @@
1
+ import {
2
+ type MockBlock,
3
+ MockClient,
4
+ type MockFilter,
5
+ } from "@apibara/protocol/testing";
6
+ import Database from "better-sqlite3";
7
+ import { describe, expect, it } from "vitest";
8
+ import { useSink } from "./hooks";
9
+ import { run } from "./indexer";
10
+ import { SqlitePersistence, sqlitePersistence } from "./plugins/persistence";
11
+ import { generateMockMessages, vcr } from "./testing";
12
+ import { getMockIndexer } from "./testing/indexer";
13
+
14
+ describe("Run Test", () => {
15
+ it("should stream messages", async () => {
16
+ const client = new MockClient<MockFilter, MockBlock>((request, options) => {
17
+ return generateMockMessages();
18
+ });
19
+
20
+ const sink = vcr();
21
+
22
+ const indexer = getMockIndexer({
23
+ sink,
24
+ override: {
25
+ transform: async ({ context, endCursor, block: { data } }) => {
26
+ const { writer } = useSink({ context });
27
+ writer.insert([{ data }]);
28
+ },
29
+ },
30
+ });
31
+
32
+ await run(client, indexer);
33
+
34
+ expect(sink.result).toMatchInlineSnapshot(`
35
+ [
36
+ {
37
+ "data": [
38
+ {
39
+ "data": "5000000",
40
+ },
41
+ ],
42
+ "endCursor": {
43
+ "orderKey": 5000000n,
44
+ },
45
+ },
46
+ {
47
+ "data": [
48
+ {
49
+ "data": "5000001",
50
+ },
51
+ ],
52
+ "endCursor": {
53
+ "orderKey": 5000001n,
54
+ },
55
+ },
56
+ {
57
+ "data": [
58
+ {
59
+ "data": "5000002",
60
+ },
61
+ ],
62
+ "endCursor": {
63
+ "orderKey": 5000002n,
64
+ },
65
+ },
66
+ {
67
+ "data": [
68
+ {
69
+ "data": "5000003",
70
+ },
71
+ ],
72
+ "endCursor": {
73
+ "orderKey": 5000003n,
74
+ },
75
+ },
76
+ {
77
+ "data": [
78
+ {
79
+ "data": "5000004",
80
+ },
81
+ ],
82
+ "endCursor": {
83
+ "orderKey": 5000004n,
84
+ },
85
+ },
86
+ {
87
+ "data": [
88
+ {
89
+ "data": "5000005",
90
+ },
91
+ ],
92
+ "endCursor": {
93
+ "orderKey": 5000005n,
94
+ },
95
+ },
96
+ {
97
+ "data": [
98
+ {
99
+ "data": "5000006",
100
+ },
101
+ ],
102
+ "endCursor": {
103
+ "orderKey": 5000006n,
104
+ },
105
+ },
106
+ {
107
+ "data": [
108
+ {
109
+ "data": "5000007",
110
+ },
111
+ ],
112
+ "endCursor": {
113
+ "orderKey": 5000007n,
114
+ },
115
+ },
116
+ {
117
+ "data": [
118
+ {
119
+ "data": "5000008",
120
+ },
121
+ ],
122
+ "endCursor": {
123
+ "orderKey": 5000008n,
124
+ },
125
+ },
126
+ {
127
+ "data": [
128
+ {
129
+ "data": "5000009",
130
+ },
131
+ ],
132
+ "endCursor": {
133
+ "orderKey": 5000009n,
134
+ },
135
+ },
136
+ ]
137
+ `);
138
+ });
139
+
140
+ it("factory mode: indexer should merge filters and restart when needed", async () => {
141
+ const client = new MockClient<MockFilter, MockBlock>((request, options) => {
142
+ const [_factoryFilter, mainFilter] = request.filter;
143
+
144
+ if (Object.keys(mainFilter).length === 0) {
145
+ expect(request.startingCursor?.orderKey).toEqual(100n);
146
+
147
+ return [
148
+ {
149
+ _tag: "data",
150
+ data: {
151
+ finality: "accepted",
152
+ cursor: { orderKey: 100n },
153
+ endCursor: { orderKey: 101n },
154
+ data: [null, null],
155
+ },
156
+ },
157
+ {
158
+ _tag: "data",
159
+ data: {
160
+ finality: "accepted",
161
+ cursor: { orderKey: 101n },
162
+ endCursor: { orderKey: 102n },
163
+ data: [null, null],
164
+ },
165
+ },
166
+ {
167
+ _tag: "data",
168
+ data: {
169
+ finality: "accepted",
170
+ cursor: { orderKey: 102n },
171
+ endCursor: { orderKey: 103n },
172
+ data: [{ data: "B" }, null],
173
+ },
174
+ },
175
+ ];
176
+ }
177
+
178
+ if (mainFilter.filter === "B") {
179
+ expect(request.startingCursor?.orderKey).toEqual(102n);
180
+
181
+ return [
182
+ {
183
+ _tag: "data",
184
+ data: {
185
+ finality: "accepted",
186
+ cursor: { orderKey: 102n },
187
+ endCursor: { orderKey: 103n },
188
+ data: [{ data: "B" }, { data: "103B" }],
189
+ },
190
+ },
191
+ {
192
+ _tag: "data",
193
+ data: {
194
+ finality: "accepted",
195
+ cursor: { orderKey: 103n },
196
+ endCursor: { orderKey: 104n },
197
+ data: [null, { data: "104B" }],
198
+ },
199
+ },
200
+ {
201
+ _tag: "data",
202
+ data: {
203
+ finality: "accepted",
204
+ cursor: { orderKey: 104n },
205
+ endCursor: { orderKey: 105n },
206
+ data: [null, { data: "105B" }],
207
+ },
208
+ },
209
+ {
210
+ _tag: "data",
211
+ data: {
212
+ finality: "accepted",
213
+ cursor: { orderKey: 105n },
214
+ endCursor: { orderKey: 106n },
215
+ data: [{ data: "C" }, { data: "106B" }],
216
+ },
217
+ },
218
+ ];
219
+ }
220
+
221
+ if (mainFilter.filter === "BC") {
222
+ expect(request.startingCursor?.orderKey).toEqual(105n);
223
+
224
+ return [
225
+ {
226
+ _tag: "data",
227
+ data: {
228
+ finality: "accepted",
229
+ cursor: { orderKey: 105n },
230
+ endCursor: { orderKey: 106n },
231
+ data: [{ data: "C" }, { data: "106BC" }],
232
+ },
233
+ },
234
+ {
235
+ _tag: "data",
236
+ data: {
237
+ finality: "accepted",
238
+ cursor: { orderKey: 106n },
239
+ endCursor: { orderKey: 107n },
240
+ data: [null, { data: "107BC" }],
241
+ },
242
+ },
243
+ {
244
+ _tag: "data",
245
+ data: {
246
+ finality: "accepted",
247
+ cursor: { orderKey: 107n },
248
+ endCursor: { orderKey: 108n },
249
+ data: [null, { data: "108BC" }],
250
+ },
251
+ },
252
+ ];
253
+ }
254
+
255
+ return [];
256
+ });
257
+
258
+ const db = Database(":memory:");
259
+
260
+ const sink = vcr();
261
+
262
+ // create mock indexer with persistence plugin
263
+ const indexer = getMockIndexer({
264
+ plugins: [
265
+ sqlitePersistence({
266
+ database: db,
267
+ }),
268
+ ],
269
+ sink,
270
+ override: {
271
+ startingCursor: { orderKey: 100n },
272
+ factory: async ({ block }) => {
273
+ if (block.data === "B") {
274
+ return { filter: { filter: "B" } };
275
+ }
276
+
277
+ if (block.data === "C") {
278
+ return { filter: { filter: "C" } };
279
+ }
280
+
281
+ return {};
282
+ },
283
+ transform: async ({ context, endCursor, block: { data } }) => {
284
+ const { writer } = useSink({ context });
285
+ writer.insert([{ data }]);
286
+ },
287
+ },
288
+ });
289
+
290
+ await run(client, indexer);
291
+
292
+ const store = new SqlitePersistence<MockFilter>(db);
293
+
294
+ const latest = store.get();
295
+
296
+ expect(latest.cursor?.orderKey).toEqual(108n);
297
+ expect(latest.filter?.filter).toEqual("BC");
298
+
299
+ expect(sink.result).toMatchInlineSnapshot(`
300
+ [
301
+ {
302
+ "data": [
303
+ {
304
+ "data": "103B",
305
+ },
306
+ ],
307
+ "endCursor": {
308
+ "orderKey": 103n,
309
+ },
310
+ },
311
+ {
312
+ "data": [
313
+ {
314
+ "data": "104B",
315
+ },
316
+ ],
317
+ "endCursor": {
318
+ "orderKey": 104n,
319
+ },
320
+ },
321
+ {
322
+ "data": [
323
+ {
324
+ "data": "105B",
325
+ },
326
+ ],
327
+ "endCursor": {
328
+ "orderKey": 105n,
329
+ },
330
+ },
331
+ {
332
+ "data": [
333
+ {
334
+ "data": "106BC",
335
+ },
336
+ ],
337
+ "endCursor": {
338
+ "orderKey": 106n,
339
+ },
340
+ },
341
+ {
342
+ "data": [
343
+ {
344
+ "data": "107BC",
345
+ },
346
+ ],
347
+ "endCursor": {
348
+ "orderKey": 107n,
349
+ },
350
+ },
351
+ {
352
+ "data": [
353
+ {
354
+ "data": "108BC",
355
+ },
356
+ ],
357
+ "endCursor": {
358
+ "orderKey": 108n,
359
+ },
360
+ },
361
+ ]
362
+ `);
363
+
364
+ db.close();
365
+ });
366
+
367
+ it("factory mode: last cursor should persist when error is thrown in indexer", async () => {
368
+ const client = new MockClient<MockFilter, MockBlock>((request, options) => {
369
+ const [_factoryFilter, mainFilter] = request.filter;
370
+
371
+ if (Object.keys(mainFilter).length === 0) {
372
+ expect(request.startingCursor?.orderKey).toEqual(100n);
373
+
374
+ return [
375
+ {
376
+ _tag: "data",
377
+ data: {
378
+ finality: "accepted",
379
+ cursor: { orderKey: 100n },
380
+ endCursor: { orderKey: 101n },
381
+ data: [null, null],
382
+ },
383
+ },
384
+ {
385
+ _tag: "data",
386
+ data: {
387
+ finality: "accepted",
388
+ cursor: { orderKey: 101n },
389
+ endCursor: { orderKey: 102n },
390
+ data: [null, null],
391
+ },
392
+ },
393
+ {
394
+ _tag: "data",
395
+ data: {
396
+ finality: "accepted",
397
+ cursor: { orderKey: 102n },
398
+ endCursor: { orderKey: 103n },
399
+ data: [{ data: "B" }, null],
400
+ },
401
+ },
402
+ Error("this error should not occurr!"),
403
+ ];
404
+ }
405
+
406
+ if (mainFilter.filter === "B") {
407
+ expect(request.startingCursor?.orderKey).toEqual(102n);
408
+
409
+ return [
410
+ Error("this error should occurr!"),
411
+ {
412
+ _tag: "data",
413
+ data: {
414
+ finality: "accepted",
415
+ cursor: { orderKey: 103n },
416
+ endCursor: { orderKey: 104n },
417
+ data: [null, { data: "104B" }],
418
+ },
419
+ },
420
+ {
421
+ _tag: "data",
422
+ data: {
423
+ finality: "accepted",
424
+ cursor: { orderKey: 104n },
425
+ endCursor: { orderKey: 105n },
426
+ data: [null, { data: "105B" }],
427
+ },
428
+ },
429
+ {
430
+ _tag: "data",
431
+ data: {
432
+ finality: "accepted",
433
+ cursor: { orderKey: 105n },
434
+ endCursor: { orderKey: 106n },
435
+ data: [{ data: "C" }, { data: "106B" }],
436
+ },
437
+ },
438
+ ];
439
+ }
440
+
441
+ return [];
442
+ });
443
+
444
+ const db = Database(":memory:");
445
+
446
+ const sink = vcr();
447
+
448
+ // create mock indexer with persistence plugin
449
+ const indexer = getMockIndexer({
450
+ plugins: [
451
+ sqlitePersistence({
452
+ database: db,
453
+ }),
454
+ ],
455
+ sink,
456
+ override: {
457
+ startingCursor: { orderKey: 100n },
458
+ factory: async ({ block }) => {
459
+ if (block.data === "B") {
460
+ return { filter: { filter: "B" } };
461
+ }
462
+
463
+ if (block.data === "C") {
464
+ return { filter: { filter: "C" } };
465
+ }
466
+
467
+ return {};
468
+ },
469
+ transform: async ({ context, endCursor, block: { data } }) => {
470
+ const { writer } = useSink({ context });
471
+ writer.insert([{ data }]);
472
+ },
473
+ },
474
+ });
475
+
476
+ await expect(() => run(client, indexer)).rejects.toThrowError(
477
+ "this error should occurr!",
478
+ );
479
+
480
+ const store = new SqlitePersistence<MockFilter>(db);
481
+
482
+ const latest = store.get();
483
+
484
+ expect(latest.cursor?.orderKey).toEqual(103n);
485
+ expect(latest.filter?.filter).toEqual("B");
486
+
487
+ expect(sink.result).toMatchInlineSnapshot("[]");
488
+
489
+ db.close();
490
+ });
491
+ });