@apibara/indexer 2.0.0-beta.3 → 2.0.0-beta.31
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.
- package/dist/index.cjs +270 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +259 -0
- package/dist/internal/testing.cjs +109 -0
- package/dist/internal/testing.d.cts +40 -0
- package/dist/internal/testing.d.mts +40 -0
- package/dist/internal/testing.d.ts +40 -0
- package/dist/internal/testing.mjs +104 -0
- package/dist/plugins/index.cjs +43 -0
- package/dist/plugins/index.d.cts +18 -0
- package/dist/plugins/index.d.mts +18 -0
- package/dist/plugins/index.d.ts +18 -0
- package/dist/plugins/index.mjs +38 -0
- package/dist/shared/indexer.077335f3.cjs +15 -0
- package/dist/shared/indexer.2416906c.cjs +29 -0
- package/dist/shared/indexer.601ceab0.cjs +7 -0
- package/dist/shared/indexer.8939ecc8.d.cts +91 -0
- package/dist/shared/indexer.8939ecc8.d.mts +91 -0
- package/dist/shared/indexer.8939ecc8.d.ts +91 -0
- package/dist/shared/indexer.9b21ddd2.mjs +5 -0
- package/dist/shared/indexer.a55ad619.mjs +12 -0
- package/dist/shared/indexer.ff25c953.mjs +26 -0
- package/dist/testing/index.cjs +58 -0
- package/dist/testing/index.d.cts +12 -0
- package/dist/testing/index.d.mts +12 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.mjs +52 -0
- package/dist/vcr/index.cjs +92 -0
- package/dist/vcr/index.d.cts +27 -0
- package/dist/vcr/index.d.mts +27 -0
- package/dist/vcr/index.d.ts +27 -0
- package/dist/vcr/index.mjs +78 -0
- package/package.json +31 -41
- package/src/compose.test.ts +76 -0
- package/src/compose.ts +71 -0
- package/src/context.ts +14 -8
- package/src/index.ts +0 -5
- package/src/indexer.test.ts +109 -186
- package/src/indexer.ts +244 -144
- package/src/internal/testing.ts +135 -0
- package/src/plugins/config.ts +4 -4
- package/src/plugins/index.ts +8 -1
- package/src/plugins/logger.ts +30 -0
- package/src/plugins/persistence.ts +24 -187
- package/src/testing/index.ts +50 -3
- package/src/vcr/record.ts +6 -4
- package/src/vcr/replay.ts +8 -18
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useKVStore.ts +0 -12
- package/src/hooks/useSink.ts +0 -13
- package/src/plugins/kv.test.ts +0 -120
- package/src/plugins/kv.ts +0 -132
- package/src/plugins/persistence.test.ts +0 -151
- package/src/sink.ts +0 -36
- package/src/sinks/csv.test.ts +0 -65
- package/src/sinks/csv.ts +0 -159
- package/src/sinks/drizzle/Int8Range.ts +0 -52
- package/src/sinks/drizzle/delete.ts +0 -42
- package/src/sinks/drizzle/drizzle.test.ts +0 -239
- package/src/sinks/drizzle/drizzle.ts +0 -115
- package/src/sinks/drizzle/index.ts +0 -6
- package/src/sinks/drizzle/insert.ts +0 -39
- package/src/sinks/drizzle/select.ts +0 -44
- package/src/sinks/drizzle/transaction.ts +0 -49
- package/src/sinks/drizzle/update.ts +0 -47
- package/src/sinks/drizzle/utils.ts +0 -36
- package/src/sinks/sqlite.test.ts +0 -99
- package/src/sinks/sqlite.ts +0 -170
- package/src/testing/helper.ts +0 -13
- package/src/testing/indexer.ts +0 -35
- package/src/testing/setup.ts +0 -59
- package/src/testing/vcr.ts +0 -54
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const protocol = require('@apibara/protocol');
|
|
4
|
+
const consola = require('consola');
|
|
5
|
+
const hookable = require('hookable');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const context = require('./shared/indexer.077335f3.cjs');
|
|
8
|
+
const api = require('@opentelemetry/api');
|
|
9
|
+
require('node:async_hooks');
|
|
10
|
+
require('unctx');
|
|
11
|
+
|
|
12
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
13
|
+
|
|
14
|
+
const consola__default = /*#__PURE__*/_interopDefaultCompat(consola);
|
|
15
|
+
const assert__default = /*#__PURE__*/_interopDefaultCompat(assert);
|
|
16
|
+
|
|
17
|
+
function compose(middleware) {
|
|
18
|
+
return (context, next) => {
|
|
19
|
+
let index = -1;
|
|
20
|
+
return dispatch(0);
|
|
21
|
+
async function dispatch(i) {
|
|
22
|
+
if (i <= index) {
|
|
23
|
+
throw new Error("next() called multiple times");
|
|
24
|
+
}
|
|
25
|
+
index = i;
|
|
26
|
+
let handler;
|
|
27
|
+
if (i >= middleware.length) {
|
|
28
|
+
if (next) {
|
|
29
|
+
await next();
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (middleware[i]) {
|
|
34
|
+
handler = middleware[i];
|
|
35
|
+
} else {
|
|
36
|
+
handler = i === middleware.length ? next : void 0;
|
|
37
|
+
}
|
|
38
|
+
if (!handler) {
|
|
39
|
+
throw new Error("Handler not found");
|
|
40
|
+
}
|
|
41
|
+
await handler(context, () => dispatch(i + 1));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tracer = api.trace.getTracer("@apibara/indexer");
|
|
47
|
+
|
|
48
|
+
function defineIndexer(streamConfig) {
|
|
49
|
+
return (config) => ({
|
|
50
|
+
streamConfig,
|
|
51
|
+
...config
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function createIndexer({
|
|
55
|
+
streamConfig,
|
|
56
|
+
...options
|
|
57
|
+
}) {
|
|
58
|
+
const indexer = {
|
|
59
|
+
options,
|
|
60
|
+
streamConfig,
|
|
61
|
+
hooks: hookable.createHooks()
|
|
62
|
+
};
|
|
63
|
+
if (indexer.options.debug) {
|
|
64
|
+
hookable.createDebugger(indexer.hooks, { tag: "indexer" });
|
|
65
|
+
}
|
|
66
|
+
indexer.hooks.addHooks(indexer.options.hooks ?? {});
|
|
67
|
+
for (const plugin of indexer.options.plugins ?? []) {
|
|
68
|
+
plugin(indexer);
|
|
69
|
+
}
|
|
70
|
+
return indexer;
|
|
71
|
+
}
|
|
72
|
+
async function runWithReconnect(client, indexer, options = {}) {
|
|
73
|
+
let retryCount = 0;
|
|
74
|
+
const maxRetries = options.maxRetries ?? 10;
|
|
75
|
+
const retryDelay = options.retryDelay ?? 1e3;
|
|
76
|
+
const maxWait = options.maxWait ?? 3e4;
|
|
77
|
+
const runOptions = {
|
|
78
|
+
onConnect() {
|
|
79
|
+
retryCount = 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
while (true) {
|
|
83
|
+
try {
|
|
84
|
+
await run(client, indexer, runOptions);
|
|
85
|
+
return;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
retryCount++;
|
|
88
|
+
if (error instanceof protocol.ClientError) {
|
|
89
|
+
if (error.code === protocol.Status.INTERNAL) {
|
|
90
|
+
if (retryCount < maxRetries) {
|
|
91
|
+
consola__default.error(
|
|
92
|
+
"Internal server error, reconnecting...",
|
|
93
|
+
error.message
|
|
94
|
+
);
|
|
95
|
+
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
96
|
+
await new Promise(
|
|
97
|
+
(resolve) => setTimeout(resolve, Math.min(retryCount * delay, maxWait))
|
|
98
|
+
);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function run(client, indexer, runOptions = {}) {
|
|
108
|
+
await context.indexerAsyncContext.callAsync({}, async () => {
|
|
109
|
+
const context$1 = context.useIndexerContext();
|
|
110
|
+
const middleware = await registerMiddleware(indexer);
|
|
111
|
+
await indexer.hooks.callHook("run:before");
|
|
112
|
+
const isFactoryMode = indexer.options.factory !== void 0;
|
|
113
|
+
const request = indexer.streamConfig.Request.make({
|
|
114
|
+
filter: isFactoryMode ? [indexer.options.filter, {}] : [indexer.options.filter],
|
|
115
|
+
finality: indexer.options.finality,
|
|
116
|
+
startingCursor: indexer.options.startingCursor
|
|
117
|
+
});
|
|
118
|
+
const options = {};
|
|
119
|
+
await indexer.hooks.callHook("connect:before", { request, options });
|
|
120
|
+
let mainFilter;
|
|
121
|
+
if (isFactoryMode) {
|
|
122
|
+
mainFilter = request.filter[1];
|
|
123
|
+
}
|
|
124
|
+
let stream = client.streamData(request, options)[Symbol.asyncIterator]();
|
|
125
|
+
await indexer.hooks.callHook("connect:after", { request });
|
|
126
|
+
let onConnectCalled = false;
|
|
127
|
+
while (true) {
|
|
128
|
+
const { value: message, done } = await stream.next();
|
|
129
|
+
if (done) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (!onConnectCalled) {
|
|
133
|
+
onConnectCalled = true;
|
|
134
|
+
if (runOptions.onConnect) {
|
|
135
|
+
await runOptions.onConnect();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await indexer.hooks.callHook("message", { message });
|
|
139
|
+
switch (message._tag) {
|
|
140
|
+
case "data": {
|
|
141
|
+
await tracer.startActiveSpan("message data", async (span) => {
|
|
142
|
+
const blocks = message.data.data;
|
|
143
|
+
const { cursor, endCursor, finality } = message.data;
|
|
144
|
+
context$1.cursor = cursor;
|
|
145
|
+
context$1.endCursor = endCursor;
|
|
146
|
+
context$1.finality = finality;
|
|
147
|
+
await middleware(context$1, async () => {
|
|
148
|
+
let block;
|
|
149
|
+
if (isFactoryMode) {
|
|
150
|
+
assert__default(indexer.options.factory !== void 0);
|
|
151
|
+
const [factoryBlock, mainBlock] = blocks;
|
|
152
|
+
block = mainBlock;
|
|
153
|
+
if (factoryBlock !== null) {
|
|
154
|
+
const { filter } = await indexer.options.factory({
|
|
155
|
+
block: factoryBlock,
|
|
156
|
+
context: context$1
|
|
157
|
+
});
|
|
158
|
+
if (filter) {
|
|
159
|
+
mainFilter = indexer.streamConfig.mergeFilter(
|
|
160
|
+
mainFilter,
|
|
161
|
+
filter
|
|
162
|
+
);
|
|
163
|
+
const request2 = indexer.streamConfig.Request.make({
|
|
164
|
+
filter: [indexer.options.filter, mainFilter],
|
|
165
|
+
finality: indexer.options.finality,
|
|
166
|
+
startingCursor: cursor
|
|
167
|
+
});
|
|
168
|
+
await indexer.hooks.callHook("connect:factory", {
|
|
169
|
+
request: request2,
|
|
170
|
+
endCursor
|
|
171
|
+
});
|
|
172
|
+
stream = client.streamData(request2, options)[Symbol.asyncIterator]();
|
|
173
|
+
const { value: message2 } = await stream.next();
|
|
174
|
+
assert__default(message2._tag === "data");
|
|
175
|
+
const [_factoryBlock, _block] = message2.data.data;
|
|
176
|
+
block = _block;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
block = blocks[0];
|
|
181
|
+
}
|
|
182
|
+
if (block) {
|
|
183
|
+
await tracer.startActiveSpan("handler", async (span2) => {
|
|
184
|
+
await indexer.options.transform({
|
|
185
|
+
block,
|
|
186
|
+
cursor,
|
|
187
|
+
endCursor,
|
|
188
|
+
finality,
|
|
189
|
+
context: context$1
|
|
190
|
+
});
|
|
191
|
+
span2.end();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
span.end();
|
|
196
|
+
});
|
|
197
|
+
context$1.cursor = void 0;
|
|
198
|
+
context$1.endCursor = void 0;
|
|
199
|
+
context$1.finality = void 0;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "invalidate": {
|
|
203
|
+
await tracer.startActiveSpan("message invalidate", async (span) => {
|
|
204
|
+
await indexer.hooks.callHook("message:invalidate", { message });
|
|
205
|
+
span.end();
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "finalize": {
|
|
210
|
+
await tracer.startActiveSpan("message finalize", async (span) => {
|
|
211
|
+
await indexer.hooks.callHook("message:finalize", { message });
|
|
212
|
+
span.end();
|
|
213
|
+
});
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case "heartbeat": {
|
|
217
|
+
await tracer.startActiveSpan("message heartbeat", async (span) => {
|
|
218
|
+
await indexer.hooks.callHook("message:heartbeat", { message });
|
|
219
|
+
span.end();
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "systemMessage": {
|
|
224
|
+
await tracer.startActiveSpan(
|
|
225
|
+
"message systemMessage",
|
|
226
|
+
async (span) => {
|
|
227
|
+
switch (message.systemMessage.output?._tag) {
|
|
228
|
+
case "stderr": {
|
|
229
|
+
consola__default.warn(message.systemMessage.output.stderr);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "stdout": {
|
|
233
|
+
consola__default.info(message.systemMessage.output.stdout);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
await indexer.hooks.callHook("message:systemMessage", {
|
|
238
|
+
message
|
|
239
|
+
});
|
|
240
|
+
span.end();
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
default: {
|
|
246
|
+
consola__default.warn("unexpected message", message);
|
|
247
|
+
throw new Error("not implemented");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await indexer.hooks.callHook("run:after");
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async function registerMiddleware(indexer) {
|
|
255
|
+
const middleware = [];
|
|
256
|
+
const use = (fn) => {
|
|
257
|
+
middleware.push(fn);
|
|
258
|
+
};
|
|
259
|
+
await indexer.hooks.callHook("handler:middleware", { use });
|
|
260
|
+
const composed = compose(middleware);
|
|
261
|
+
return async function _composedIndexerMiddleware(context, next) {
|
|
262
|
+
await composed(context, next);
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
exports.useIndexerContext = context.useIndexerContext;
|
|
267
|
+
exports.createIndexer = createIndexer;
|
|
268
|
+
exports.defineIndexer = defineIndexer;
|
|
269
|
+
exports.run = run;
|
|
270
|
+
exports.runWithReconnect = runWithReconnect;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { c as Indexer, b as IndexerConfig, e as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, U as UseMiddlewareFunction, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.8939ecc8.cjs';
|
|
2
|
+
import '@apibara/protocol';
|
|
3
|
+
import 'hookable';
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { c as Indexer, b as IndexerConfig, e as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, U as UseMiddlewareFunction, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.8939ecc8.mjs';
|
|
2
|
+
import '@apibara/protocol';
|
|
3
|
+
import 'hookable';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { c as Indexer, b as IndexerConfig, e as IndexerHooks, I as IndexerWithStreamConfig, R as ReconnectOptions, h as RunOptions, U as UseMiddlewareFunction, g as createIndexer, f as defineIndexer, i as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.8939ecc8.js';
|
|
2
|
+
import '@apibara/protocol';
|
|
3
|
+
import 'hookable';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { ClientError, Status } from '@apibara/protocol';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { createHooks, createDebugger } from 'hookable';
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
import { i as indexerAsyncContext, u as useIndexerContext } from './shared/indexer.a55ad619.mjs';
|
|
6
|
+
import { trace } from '@opentelemetry/api';
|
|
7
|
+
import 'node:async_hooks';
|
|
8
|
+
import 'unctx';
|
|
9
|
+
|
|
10
|
+
function compose(middleware) {
|
|
11
|
+
return (context, next) => {
|
|
12
|
+
let index = -1;
|
|
13
|
+
return dispatch(0);
|
|
14
|
+
async function dispatch(i) {
|
|
15
|
+
if (i <= index) {
|
|
16
|
+
throw new Error("next() called multiple times");
|
|
17
|
+
}
|
|
18
|
+
index = i;
|
|
19
|
+
let handler;
|
|
20
|
+
if (i >= middleware.length) {
|
|
21
|
+
if (next) {
|
|
22
|
+
await next();
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (middleware[i]) {
|
|
27
|
+
handler = middleware[i];
|
|
28
|
+
} else {
|
|
29
|
+
handler = i === middleware.length ? next : void 0;
|
|
30
|
+
}
|
|
31
|
+
if (!handler) {
|
|
32
|
+
throw new Error("Handler not found");
|
|
33
|
+
}
|
|
34
|
+
await handler(context, () => dispatch(i + 1));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const tracer = trace.getTracer("@apibara/indexer");
|
|
40
|
+
|
|
41
|
+
function defineIndexer(streamConfig) {
|
|
42
|
+
return (config) => ({
|
|
43
|
+
streamConfig,
|
|
44
|
+
...config
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createIndexer({
|
|
48
|
+
streamConfig,
|
|
49
|
+
...options
|
|
50
|
+
}) {
|
|
51
|
+
const indexer = {
|
|
52
|
+
options,
|
|
53
|
+
streamConfig,
|
|
54
|
+
hooks: createHooks()
|
|
55
|
+
};
|
|
56
|
+
if (indexer.options.debug) {
|
|
57
|
+
createDebugger(indexer.hooks, { tag: "indexer" });
|
|
58
|
+
}
|
|
59
|
+
indexer.hooks.addHooks(indexer.options.hooks ?? {});
|
|
60
|
+
for (const plugin of indexer.options.plugins ?? []) {
|
|
61
|
+
plugin(indexer);
|
|
62
|
+
}
|
|
63
|
+
return indexer;
|
|
64
|
+
}
|
|
65
|
+
async function runWithReconnect(client, indexer, options = {}) {
|
|
66
|
+
let retryCount = 0;
|
|
67
|
+
const maxRetries = options.maxRetries ?? 10;
|
|
68
|
+
const retryDelay = options.retryDelay ?? 1e3;
|
|
69
|
+
const maxWait = options.maxWait ?? 3e4;
|
|
70
|
+
const runOptions = {
|
|
71
|
+
onConnect() {
|
|
72
|
+
retryCount = 0;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
while (true) {
|
|
76
|
+
try {
|
|
77
|
+
await run(client, indexer, runOptions);
|
|
78
|
+
return;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
retryCount++;
|
|
81
|
+
if (error instanceof ClientError) {
|
|
82
|
+
if (error.code === Status.INTERNAL) {
|
|
83
|
+
if (retryCount < maxRetries) {
|
|
84
|
+
consola.error(
|
|
85
|
+
"Internal server error, reconnecting...",
|
|
86
|
+
error.message
|
|
87
|
+
);
|
|
88
|
+
const delay = Math.random() * (retryDelay * 0.2) + retryDelay;
|
|
89
|
+
await new Promise(
|
|
90
|
+
(resolve) => setTimeout(resolve, Math.min(retryCount * delay, maxWait))
|
|
91
|
+
);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function run(client, indexer, runOptions = {}) {
|
|
101
|
+
await indexerAsyncContext.callAsync({}, async () => {
|
|
102
|
+
const context = useIndexerContext();
|
|
103
|
+
const middleware = await registerMiddleware(indexer);
|
|
104
|
+
await indexer.hooks.callHook("run:before");
|
|
105
|
+
const isFactoryMode = indexer.options.factory !== void 0;
|
|
106
|
+
const request = indexer.streamConfig.Request.make({
|
|
107
|
+
filter: isFactoryMode ? [indexer.options.filter, {}] : [indexer.options.filter],
|
|
108
|
+
finality: indexer.options.finality,
|
|
109
|
+
startingCursor: indexer.options.startingCursor
|
|
110
|
+
});
|
|
111
|
+
const options = {};
|
|
112
|
+
await indexer.hooks.callHook("connect:before", { request, options });
|
|
113
|
+
let mainFilter;
|
|
114
|
+
if (isFactoryMode) {
|
|
115
|
+
mainFilter = request.filter[1];
|
|
116
|
+
}
|
|
117
|
+
let stream = client.streamData(request, options)[Symbol.asyncIterator]();
|
|
118
|
+
await indexer.hooks.callHook("connect:after", { request });
|
|
119
|
+
let onConnectCalled = false;
|
|
120
|
+
while (true) {
|
|
121
|
+
const { value: message, done } = await stream.next();
|
|
122
|
+
if (done) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
if (!onConnectCalled) {
|
|
126
|
+
onConnectCalled = true;
|
|
127
|
+
if (runOptions.onConnect) {
|
|
128
|
+
await runOptions.onConnect();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await indexer.hooks.callHook("message", { message });
|
|
132
|
+
switch (message._tag) {
|
|
133
|
+
case "data": {
|
|
134
|
+
await tracer.startActiveSpan("message data", async (span) => {
|
|
135
|
+
const blocks = message.data.data;
|
|
136
|
+
const { cursor, endCursor, finality } = message.data;
|
|
137
|
+
context.cursor = cursor;
|
|
138
|
+
context.endCursor = endCursor;
|
|
139
|
+
context.finality = finality;
|
|
140
|
+
await middleware(context, async () => {
|
|
141
|
+
let block;
|
|
142
|
+
if (isFactoryMode) {
|
|
143
|
+
assert(indexer.options.factory !== void 0);
|
|
144
|
+
const [factoryBlock, mainBlock] = blocks;
|
|
145
|
+
block = mainBlock;
|
|
146
|
+
if (factoryBlock !== null) {
|
|
147
|
+
const { filter } = await indexer.options.factory({
|
|
148
|
+
block: factoryBlock,
|
|
149
|
+
context
|
|
150
|
+
});
|
|
151
|
+
if (filter) {
|
|
152
|
+
mainFilter = indexer.streamConfig.mergeFilter(
|
|
153
|
+
mainFilter,
|
|
154
|
+
filter
|
|
155
|
+
);
|
|
156
|
+
const request2 = indexer.streamConfig.Request.make({
|
|
157
|
+
filter: [indexer.options.filter, mainFilter],
|
|
158
|
+
finality: indexer.options.finality,
|
|
159
|
+
startingCursor: cursor
|
|
160
|
+
});
|
|
161
|
+
await indexer.hooks.callHook("connect:factory", {
|
|
162
|
+
request: request2,
|
|
163
|
+
endCursor
|
|
164
|
+
});
|
|
165
|
+
stream = client.streamData(request2, options)[Symbol.asyncIterator]();
|
|
166
|
+
const { value: message2 } = await stream.next();
|
|
167
|
+
assert(message2._tag === "data");
|
|
168
|
+
const [_factoryBlock, _block] = message2.data.data;
|
|
169
|
+
block = _block;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
block = blocks[0];
|
|
174
|
+
}
|
|
175
|
+
if (block) {
|
|
176
|
+
await tracer.startActiveSpan("handler", async (span2) => {
|
|
177
|
+
await indexer.options.transform({
|
|
178
|
+
block,
|
|
179
|
+
cursor,
|
|
180
|
+
endCursor,
|
|
181
|
+
finality,
|
|
182
|
+
context
|
|
183
|
+
});
|
|
184
|
+
span2.end();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
span.end();
|
|
189
|
+
});
|
|
190
|
+
context.cursor = void 0;
|
|
191
|
+
context.endCursor = void 0;
|
|
192
|
+
context.finality = void 0;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "invalidate": {
|
|
196
|
+
await tracer.startActiveSpan("message invalidate", async (span) => {
|
|
197
|
+
await indexer.hooks.callHook("message:invalidate", { message });
|
|
198
|
+
span.end();
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "finalize": {
|
|
203
|
+
await tracer.startActiveSpan("message finalize", async (span) => {
|
|
204
|
+
await indexer.hooks.callHook("message:finalize", { message });
|
|
205
|
+
span.end();
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "heartbeat": {
|
|
210
|
+
await tracer.startActiveSpan("message heartbeat", async (span) => {
|
|
211
|
+
await indexer.hooks.callHook("message:heartbeat", { message });
|
|
212
|
+
span.end();
|
|
213
|
+
});
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case "systemMessage": {
|
|
217
|
+
await tracer.startActiveSpan(
|
|
218
|
+
"message systemMessage",
|
|
219
|
+
async (span) => {
|
|
220
|
+
switch (message.systemMessage.output?._tag) {
|
|
221
|
+
case "stderr": {
|
|
222
|
+
consola.warn(message.systemMessage.output.stderr);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case "stdout": {
|
|
226
|
+
consola.info(message.systemMessage.output.stdout);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
await indexer.hooks.callHook("message:systemMessage", {
|
|
231
|
+
message
|
|
232
|
+
});
|
|
233
|
+
span.end();
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
default: {
|
|
239
|
+
consola.warn("unexpected message", message);
|
|
240
|
+
throw new Error("not implemented");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
await indexer.hooks.callHook("run:after");
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async function registerMiddleware(indexer) {
|
|
248
|
+
const middleware = [];
|
|
249
|
+
const use = (fn) => {
|
|
250
|
+
middleware.push(fn);
|
|
251
|
+
};
|
|
252
|
+
await indexer.hooks.callHook("handler:middleware", { use });
|
|
253
|
+
const composed = compose(middleware);
|
|
254
|
+
return async function _composedIndexerMiddleware(context, next) {
|
|
255
|
+
await composed(context, next);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export { createIndexer, defineIndexer, run, runWithReconnect, useIndexerContext };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const protocol = require('@apibara/protocol');
|
|
4
|
+
const testing = require('@apibara/protocol/testing');
|
|
5
|
+
const context = require('../shared/indexer.077335f3.cjs');
|
|
6
|
+
const index = require('../index.cjs');
|
|
7
|
+
const config = require('../shared/indexer.601ceab0.cjs');
|
|
8
|
+
require('consola');
|
|
9
|
+
require('node:async_hooks');
|
|
10
|
+
require('unctx');
|
|
11
|
+
require('hookable');
|
|
12
|
+
require('node:assert');
|
|
13
|
+
require('@opentelemetry/api');
|
|
14
|
+
|
|
15
|
+
function generateMockMessages(count = 10, options) {
|
|
16
|
+
const invalidateAt = options?.invalidate;
|
|
17
|
+
const finalizeAt = options?.finalize;
|
|
18
|
+
const messages = [];
|
|
19
|
+
for (let i = 0; i < count; i++) {
|
|
20
|
+
if (invalidateAt && i === invalidateAt.invalidateTriggerIndex) {
|
|
21
|
+
messages.push({
|
|
22
|
+
_tag: "invalidate",
|
|
23
|
+
invalidate: {
|
|
24
|
+
cursor: {
|
|
25
|
+
orderKey: BigInt(5e6 + invalidateAt.invalidateFromIndex)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} else if (finalizeAt && i === finalizeAt.finalizeTriggerIndex) {
|
|
30
|
+
messages.push({
|
|
31
|
+
_tag: "finalize",
|
|
32
|
+
finalize: {
|
|
33
|
+
cursor: {
|
|
34
|
+
orderKey: BigInt(5e6 + finalizeAt.finalizeToIndex)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
messages.push({
|
|
40
|
+
_tag: "data",
|
|
41
|
+
data: {
|
|
42
|
+
cursor: { orderKey: BigInt(5e6 + i - 1) },
|
|
43
|
+
finality: "accepted",
|
|
44
|
+
data: [{ data: `${5e6 + i}` }],
|
|
45
|
+
endCursor: { orderKey: BigInt(5e6 + i) }
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return messages;
|
|
51
|
+
}
|
|
52
|
+
function getMockIndexer({
|
|
53
|
+
plugins,
|
|
54
|
+
override
|
|
55
|
+
} = {}) {
|
|
56
|
+
return index.createIndexer(
|
|
57
|
+
index.defineIndexer(testing.MockStream)({
|
|
58
|
+
streamUrl: "https://sepolia.ethereum.a5a.ch",
|
|
59
|
+
finality: "accepted",
|
|
60
|
+
filter: {},
|
|
61
|
+
async transform({ block: { data }, context }) {
|
|
62
|
+
},
|
|
63
|
+
plugins,
|
|
64
|
+
...override
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function mockSink({
|
|
69
|
+
output,
|
|
70
|
+
metadata
|
|
71
|
+
}) {
|
|
72
|
+
return config.defineIndexerPlugin((indexer) => {
|
|
73
|
+
indexer.hooks.hook("connect:before", ({ request }) => {
|
|
74
|
+
if (metadata?.lastCursor && protocol.isCursor(metadata.lastCursor)) {
|
|
75
|
+
request.startingCursor = metadata.lastCursor;
|
|
76
|
+
}
|
|
77
|
+
if (metadata?.lastFilter) {
|
|
78
|
+
request.filter[1] = metadata.lastFilter;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
82
|
+
if (request.filter[1]) {
|
|
83
|
+
if (metadata) {
|
|
84
|
+
metadata.lastCursor = endCursor;
|
|
85
|
+
metadata.lastFilter = request.filter[1];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
indexer.hooks.hook("handler:middleware", ({ use }) => {
|
|
90
|
+
use(async (context, next) => {
|
|
91
|
+
context.output = output;
|
|
92
|
+
await next();
|
|
93
|
+
context.output = null;
|
|
94
|
+
if (metadata) {
|
|
95
|
+
metadata.lastCursor = context.endCursor;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function useMockSink() {
|
|
102
|
+
const context$1 = context.useIndexerContext();
|
|
103
|
+
return { output: context$1.output };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
exports.generateMockMessages = generateMockMessages;
|
|
107
|
+
exports.getMockIndexer = getMockIndexer;
|
|
108
|
+
exports.mockSink = mockSink;
|
|
109
|
+
exports.useMockSink = useMockSink;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { a as IndexerPlugin, b as IndexerConfig, c as Indexer } from '../shared/indexer.8939ecc8.cjs';
|
|
2
|
+
import { MockStreamResponse, MockFilter, MockBlock } from '@apibara/protocol/testing';
|
|
3
|
+
import '@apibara/protocol';
|
|
4
|
+
import 'hookable';
|
|
5
|
+
|
|
6
|
+
type MockMessagesOptions = {
|
|
7
|
+
invalidate?: {
|
|
8
|
+
invalidateFromIndex: number;
|
|
9
|
+
invalidateTriggerIndex: number;
|
|
10
|
+
};
|
|
11
|
+
finalize?: {
|
|
12
|
+
finalizeToIndex: number;
|
|
13
|
+
finalizeTriggerIndex: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
declare function generateMockMessages(count?: number, options?: MockMessagesOptions): MockStreamResponse[];
|
|
17
|
+
declare function getMockIndexer({ plugins, override, }?: {
|
|
18
|
+
plugins?: ReadonlyArray<IndexerPlugin<MockFilter, MockBlock>>;
|
|
19
|
+
override?: Partial<IndexerConfig<MockFilter, MockBlock>>;
|
|
20
|
+
}): Indexer<{
|
|
21
|
+
readonly filter?: string | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
readonly data?: string | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
type MockRet = {
|
|
26
|
+
data: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A mock sink used for testing. The indexer function can write to the output array.
|
|
30
|
+
* The indexer context is optionally written to the metadata object.
|
|
31
|
+
*/
|
|
32
|
+
declare function mockSink<TFilter, TBlock>({ output, metadata, }: {
|
|
33
|
+
output: unknown[];
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}): IndexerPlugin<TFilter, TBlock>;
|
|
36
|
+
declare function useMockSink(): {
|
|
37
|
+
output: unknown[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { type MockMessagesOptions, type MockRet, generateMockMessages, getMockIndexer, mockSink, useMockSink };
|