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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/index.cjs +271 -39
  2. package/dist/index.d.cts +1 -16
  3. package/dist/index.d.mts +1 -16
  4. package/dist/index.d.ts +1 -16
  5. package/dist/index.mjs +262 -25
  6. package/dist/internal/index.cjs +10 -0
  7. package/dist/internal/index.d.cts +3 -0
  8. package/dist/internal/index.d.mts +3 -0
  9. package/dist/internal/index.d.ts +3 -0
  10. package/dist/internal/index.mjs +8 -0
  11. package/dist/internal/plugins.cjs +38 -0
  12. package/dist/internal/plugins.d.cts +13 -0
  13. package/dist/internal/plugins.d.mts +13 -0
  14. package/dist/internal/plugins.d.ts +13 -0
  15. package/dist/internal/plugins.mjs +34 -0
  16. package/dist/internal/testing.cjs +118 -0
  17. package/dist/internal/testing.d.cts +42 -0
  18. package/dist/internal/testing.d.mts +42 -0
  19. package/dist/internal/testing.d.ts +42 -0
  20. package/dist/internal/testing.mjs +113 -0
  21. package/dist/plugins/index.cjs +39 -3
  22. package/dist/plugins/index.d.cts +16 -2
  23. package/dist/plugins/index.d.mts +16 -2
  24. package/dist/plugins/index.d.ts +16 -2
  25. package/dist/plugins/index.mjs +36 -3
  26. package/dist/shared/indexer.077335f3.cjs +15 -0
  27. package/dist/shared/indexer.2416906c.cjs +29 -0
  28. package/dist/shared/indexer.601ceab0.cjs +7 -0
  29. package/dist/shared/indexer.9b21ddd2.mjs +5 -0
  30. package/dist/shared/indexer.a55ad619.mjs +12 -0
  31. package/dist/shared/indexer.fedcd831.d.cts +100 -0
  32. package/dist/shared/indexer.fedcd831.d.mts +100 -0
  33. package/dist/shared/indexer.fedcd831.d.ts +100 -0
  34. package/dist/shared/indexer.ff25c953.mjs +26 -0
  35. package/dist/testing/index.cjs +52 -50
  36. package/dist/testing/index.d.cts +8 -36
  37. package/dist/testing/index.d.mts +8 -36
  38. package/dist/testing/index.d.ts +8 -36
  39. package/dist/testing/index.mjs +47 -47
  40. package/dist/vcr/index.cjs +84 -17
  41. package/dist/vcr/index.d.cts +16 -7
  42. package/dist/vcr/index.d.mts +16 -7
  43. package/dist/vcr/index.d.ts +16 -7
  44. package/dist/vcr/index.mjs +75 -11
  45. package/package.json +22 -42
  46. package/src/compose.test.ts +76 -0
  47. package/src/compose.ts +71 -0
  48. package/src/context.ts +14 -8
  49. package/src/index.ts +0 -5
  50. package/src/indexer.test.ts +125 -186
  51. package/src/indexer.ts +278 -151
  52. package/src/internal/index.ts +6 -0
  53. package/src/internal/plugins.ts +1 -0
  54. package/src/internal/testing.ts +148 -0
  55. package/src/plugins/config.ts +4 -4
  56. package/src/plugins/context.ts +40 -0
  57. package/src/plugins/index.ts +8 -1
  58. package/src/plugins/logger.ts +30 -0
  59. package/src/plugins/persistence.ts +24 -187
  60. package/src/testing/index.ts +58 -3
  61. package/src/vcr/record.ts +5 -3
  62. package/src/vcr/replay.ts +8 -18
  63. package/dist/plugins/kv.cjs +0 -131
  64. package/dist/plugins/kv.d.cts +0 -32
  65. package/dist/plugins/kv.d.mts +0 -32
  66. package/dist/plugins/kv.d.ts +0 -32
  67. package/dist/plugins/kv.mjs +0 -124
  68. package/dist/plugins/persistence.cjs +0 -182
  69. package/dist/plugins/persistence.d.cts +0 -50
  70. package/dist/plugins/persistence.d.mts +0 -50
  71. package/dist/plugins/persistence.d.ts +0 -50
  72. package/dist/plugins/persistence.mjs +0 -179
  73. package/dist/shared/indexer.2c23c9cd.mjs +0 -35
  74. package/dist/shared/indexer.318d3617.cjs +0 -47
  75. package/dist/shared/indexer.36530330.mjs +0 -249
  76. package/dist/shared/indexer.500fd281.d.cts +0 -23
  77. package/dist/shared/indexer.541d43eb.cjs +0 -266
  78. package/dist/shared/indexer.93d6b2eb.mjs +0 -17
  79. package/dist/shared/indexer.a8b7ab1f.cjs +0 -25
  80. package/dist/shared/indexer.b9c8f0d8.d.cts +0 -19
  81. package/dist/shared/indexer.b9c8f0d8.d.mts +0 -19
  82. package/dist/shared/indexer.b9c8f0d8.d.ts +0 -19
  83. package/dist/shared/indexer.c7ed6b83.d.cts +0 -82
  84. package/dist/shared/indexer.e1856641.d.mts +0 -23
  85. package/dist/shared/indexer.e4f2430f.d.ts +0 -23
  86. package/dist/shared/indexer.e8bd138d.d.mts +0 -82
  87. package/dist/shared/indexer.f761abcd.d.ts +0 -82
  88. package/dist/sinks/csv.cjs +0 -85
  89. package/dist/sinks/csv.d.cts +0 -66
  90. package/dist/sinks/csv.d.mts +0 -66
  91. package/dist/sinks/csv.d.ts +0 -66
  92. package/dist/sinks/csv.mjs +0 -78
  93. package/dist/sinks/drizzle/index.cjs +0 -212
  94. package/dist/sinks/drizzle/index.d.cts +0 -153
  95. package/dist/sinks/drizzle/index.d.mts +0 -153
  96. package/dist/sinks/drizzle/index.d.ts +0 -153
  97. package/dist/sinks/drizzle/index.mjs +0 -198
  98. package/dist/sinks/sqlite.cjs +0 -90
  99. package/dist/sinks/sqlite.d.cts +0 -71
  100. package/dist/sinks/sqlite.d.mts +0 -71
  101. package/dist/sinks/sqlite.d.ts +0 -71
  102. package/dist/sinks/sqlite.mjs +0 -87
  103. package/src/hooks/index.ts +0 -2
  104. package/src/hooks/useKVStore.ts +0 -12
  105. package/src/hooks/useSink.ts +0 -13
  106. package/src/plugins/kv.test.ts +0 -120
  107. package/src/plugins/kv.ts +0 -132
  108. package/src/plugins/persistence.test.ts +0 -151
  109. package/src/sink.ts +0 -36
  110. package/src/sinks/csv.test.ts +0 -65
  111. package/src/sinks/csv.ts +0 -159
  112. package/src/sinks/drizzle/Int8Range.ts +0 -52
  113. package/src/sinks/drizzle/delete.ts +0 -42
  114. package/src/sinks/drizzle/drizzle.test.ts +0 -239
  115. package/src/sinks/drizzle/drizzle.ts +0 -115
  116. package/src/sinks/drizzle/index.ts +0 -6
  117. package/src/sinks/drizzle/insert.ts +0 -42
  118. package/src/sinks/drizzle/select.ts +0 -44
  119. package/src/sinks/drizzle/transaction.ts +0 -49
  120. package/src/sinks/drizzle/update.ts +0 -47
  121. package/src/sinks/drizzle/utils.ts +0 -99
  122. package/src/sinks/sqlite.test.ts +0 -99
  123. package/src/sinks/sqlite.ts +0 -170
  124. package/src/testing/helper.ts +0 -13
  125. package/src/testing/indexer.ts +0 -35
  126. package/src/testing/setup.ts +0 -59
  127. package/src/testing/vcr.ts +0 -54
package/dist/index.cjs CHANGED
@@ -1,50 +1,282 @@
1
1
  'use strict';
2
2
 
3
- const vcr_index = require('./shared/indexer.541d43eb.cjs');
4
- const sink = require('./shared/indexer.a8b7ab1f.cjs');
5
- const helper = require('./shared/indexer.318d3617.cjs');
6
- const plugins_index = require('./plugins/index.cjs');
7
- require('node:fs/promises');
8
- require('node:path');
9
- require('klona/full');
10
- require('consola');
11
- require('hookable');
12
- require('node:assert');
13
- require('node:fs');
14
- require('@apibara/protocol/testing');
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');
15
9
  require('node:async_hooks');
16
10
  require('unctx');
17
- require('@opentelemetry/api');
18
11
 
19
- function useKVStore() {
20
- const ctx = helper.useIndexerContext();
21
- if (!ctx?.kv)
22
- throw new Error("KV Plugin is not available in context!");
23
- return ctx.kv;
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
+ };
24
44
  }
25
45
 
26
- function useSink({
27
- context
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
28
57
  }) {
29
- if (!context.sinkTransaction) {
30
- throw new Error("Transaction context doesn't exist!");
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);
31
69
  }
32
- return context.sinkTransaction;
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
+ let startingCursor;
114
+ if (indexer.options.startingCursor) {
115
+ startingCursor = indexer.options.startingCursor;
116
+ } else if (indexer.options.startingBlock !== void 0) {
117
+ if (indexer.options.startingBlock === 0n) {
118
+ startingCursor = void 0;
119
+ } else if (indexer.options.startingBlock > 0n) {
120
+ startingCursor = {
121
+ orderKey: indexer.options.startingBlock - 1n
122
+ };
123
+ }
124
+ }
125
+ const request = indexer.streamConfig.Request.make({
126
+ filter: isFactoryMode ? [indexer.options.filter, {}] : [indexer.options.filter],
127
+ finality: indexer.options.finality,
128
+ startingCursor
129
+ });
130
+ const options = {};
131
+ await indexer.hooks.callHook("connect:before", { request, options });
132
+ let mainFilter;
133
+ if (isFactoryMode) {
134
+ mainFilter = request.filter[1];
135
+ }
136
+ let stream = client.streamData(request, options)[Symbol.asyncIterator]();
137
+ await indexer.hooks.callHook("connect:after", { request });
138
+ let onConnectCalled = false;
139
+ while (true) {
140
+ const { value: message, done } = await stream.next();
141
+ if (done) {
142
+ break;
143
+ }
144
+ if (!onConnectCalled) {
145
+ onConnectCalled = true;
146
+ if (runOptions.onConnect) {
147
+ await runOptions.onConnect();
148
+ }
149
+ }
150
+ await indexer.hooks.callHook("message", { message });
151
+ switch (message._tag) {
152
+ case "data": {
153
+ await tracer.startActiveSpan("message data", async (span) => {
154
+ const blocks = message.data.data;
155
+ const { cursor, endCursor, finality } = message.data;
156
+ context$1.cursor = cursor;
157
+ context$1.endCursor = endCursor;
158
+ context$1.finality = finality;
159
+ await middleware(context$1, async () => {
160
+ let block;
161
+ if (isFactoryMode) {
162
+ assert__default(indexer.options.factory !== void 0);
163
+ const [factoryBlock, mainBlock] = blocks;
164
+ block = mainBlock;
165
+ if (factoryBlock !== null) {
166
+ const { filter } = await indexer.options.factory({
167
+ block: factoryBlock,
168
+ context: context$1
169
+ });
170
+ if (filter) {
171
+ mainFilter = indexer.streamConfig.mergeFilter(
172
+ mainFilter,
173
+ filter
174
+ );
175
+ const request2 = indexer.streamConfig.Request.make({
176
+ filter: [indexer.options.filter, mainFilter],
177
+ finality: indexer.options.finality,
178
+ startingCursor: cursor
179
+ });
180
+ await indexer.hooks.callHook("connect:factory", {
181
+ request: request2,
182
+ endCursor
183
+ });
184
+ stream = client.streamData(request2, options)[Symbol.asyncIterator]();
185
+ const { value: message2 } = await stream.next();
186
+ assert__default(message2._tag === "data");
187
+ const [_factoryBlock, _block] = message2.data.data;
188
+ block = _block;
189
+ }
190
+ }
191
+ } else {
192
+ block = blocks[0];
193
+ }
194
+ if (block) {
195
+ await tracer.startActiveSpan("handler", async (span2) => {
196
+ await indexer.options.transform({
197
+ block,
198
+ cursor,
199
+ endCursor,
200
+ finality,
201
+ context: context$1
202
+ });
203
+ span2.end();
204
+ });
205
+ }
206
+ });
207
+ span.end();
208
+ });
209
+ context$1.cursor = void 0;
210
+ context$1.endCursor = void 0;
211
+ context$1.finality = void 0;
212
+ break;
213
+ }
214
+ case "invalidate": {
215
+ await tracer.startActiveSpan("message invalidate", async (span) => {
216
+ await indexer.hooks.callHook("message:invalidate", { message });
217
+ span.end();
218
+ });
219
+ break;
220
+ }
221
+ case "finalize": {
222
+ await tracer.startActiveSpan("message finalize", async (span) => {
223
+ await indexer.hooks.callHook("message:finalize", { message });
224
+ span.end();
225
+ });
226
+ break;
227
+ }
228
+ case "heartbeat": {
229
+ await tracer.startActiveSpan("message heartbeat", async (span) => {
230
+ await indexer.hooks.callHook("message:heartbeat", { message });
231
+ span.end();
232
+ });
233
+ break;
234
+ }
235
+ case "systemMessage": {
236
+ await tracer.startActiveSpan(
237
+ "message systemMessage",
238
+ async (span) => {
239
+ switch (message.systemMessage.output?._tag) {
240
+ case "stderr": {
241
+ consola__default.warn(message.systemMessage.output.stderr);
242
+ break;
243
+ }
244
+ case "stdout": {
245
+ consola__default.info(message.systemMessage.output.stdout);
246
+ break;
247
+ }
248
+ }
249
+ await indexer.hooks.callHook("message:systemMessage", {
250
+ message
251
+ });
252
+ span.end();
253
+ }
254
+ );
255
+ break;
256
+ }
257
+ default: {
258
+ consola__default.warn("unexpected message", message);
259
+ throw new Error("not implemented");
260
+ }
261
+ }
262
+ await indexer.hooks.callHook("run:after");
263
+ }
264
+ });
265
+ }
266
+ async function registerMiddleware(indexer) {
267
+ const middleware = [];
268
+ const use = (fn) => {
269
+ middleware.push(fn);
270
+ };
271
+ await indexer.hooks.callHook("handler:middleware", { use });
272
+ const composed = compose(middleware);
273
+ return async function _composedIndexerMiddleware(context, next) {
274
+ await composed(context, next);
275
+ };
33
276
  }
34
277
 
35
- exports.createIndexer = vcr_index.createIndexer;
36
- exports.defineIndexer = vcr_index.defineIndexer;
37
- exports.loadCassette = vcr_index.loadCassette;
38
- exports.record = vcr_index.record;
39
- exports.replay = vcr_index.replay;
40
- exports.run = vcr_index.run;
41
- exports.DefaultSink = sink.DefaultSink;
42
- exports.Sink = sink.Sink;
43
- exports.defaultSink = sink.defaultSink;
44
- exports.deserialize = helper.deserialize;
45
- exports.isCassetteAvailable = helper.isCassetteAvailable;
46
- exports.serialize = helper.serialize;
47
- exports.useIndexerContext = helper.useIndexerContext;
48
- exports.defineIndexerPlugin = plugins_index.defineIndexerPlugin;
49
- exports.useKVStore = useKVStore;
50
- exports.useSink = useSink;
278
+ exports.useIndexerContext = context.useIndexerContext;
279
+ exports.createIndexer = createIndexer;
280
+ exports.defineIndexer = defineIndexer;
281
+ exports.run = run;
282
+ exports.runWithReconnect = runWithReconnect;
package/dist/index.d.cts CHANGED
@@ -1,18 +1,3 @@
1
- import { a as IndexerContext } from './shared/indexer.c7ed6b83.cjs';
2
- export { f as Indexer, c as IndexerConfig, b as IndexerHooks, I as IndexerPlugin, d as IndexerWithStreamConfig, g as createIndexer, e as defineIndexer, h as defineIndexerPlugin, r as run, u as useIndexerContext } from './shared/indexer.c7ed6b83.cjs';
3
- export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink } from './shared/indexer.b9c8f0d8.cjs';
4
- export { C as CassetteOptions, V as VcrConfig, a as VcrReplayResult, l as loadCassette, r as replay } from './shared/indexer.500fd281.cjs';
5
- export { CassetteDataType, deserialize, isCassetteAvailable, record, serialize } from './vcr/index.cjs';
6
- import { KVStore } from './plugins/kv.cjs';
1
+ export { a as Indexer, c as IndexerConfig, e as IndexerHooks, f as IndexerStartingCursor, I as IndexerWithStreamConfig, R as ReconnectOptions, i as RunOptions, U as UseMiddlewareFunction, h as createIndexer, g as defineIndexer, j as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.fedcd831.cjs';
7
2
  import '@apibara/protocol';
8
3
  import 'hookable';
9
- import 'better-sqlite3';
10
-
11
- type UseKVStoreResult = InstanceType<typeof KVStore>;
12
- declare function useKVStore(): UseKVStoreResult;
13
-
14
- declare function useSink<TTxnParams>({ context, }: {
15
- context: IndexerContext<TTxnParams>;
16
- }): NonNullable<TTxnParams>;
17
-
18
- export { type UseKVStoreResult, useKVStore, useSink };
package/dist/index.d.mts CHANGED
@@ -1,18 +1,3 @@
1
- import { a as IndexerContext } from './shared/indexer.e8bd138d.mjs';
2
- export { f as Indexer, c as IndexerConfig, b as IndexerHooks, I as IndexerPlugin, d as IndexerWithStreamConfig, g as createIndexer, e as defineIndexer, h as defineIndexerPlugin, r as run, u as useIndexerContext } from './shared/indexer.e8bd138d.mjs';
3
- export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink } from './shared/indexer.b9c8f0d8.mjs';
4
- export { C as CassetteOptions, V as VcrConfig, a as VcrReplayResult, l as loadCassette, r as replay } from './shared/indexer.e1856641.mjs';
5
- export { CassetteDataType, deserialize, isCassetteAvailable, record, serialize } from './vcr/index.mjs';
6
- import { KVStore } from './plugins/kv.mjs';
1
+ export { a as Indexer, c as IndexerConfig, e as IndexerHooks, f as IndexerStartingCursor, I as IndexerWithStreamConfig, R as ReconnectOptions, i as RunOptions, U as UseMiddlewareFunction, h as createIndexer, g as defineIndexer, j as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.fedcd831.mjs';
7
2
  import '@apibara/protocol';
8
3
  import 'hookable';
9
- import 'better-sqlite3';
10
-
11
- type UseKVStoreResult = InstanceType<typeof KVStore>;
12
- declare function useKVStore(): UseKVStoreResult;
13
-
14
- declare function useSink<TTxnParams>({ context, }: {
15
- context: IndexerContext<TTxnParams>;
16
- }): NonNullable<TTxnParams>;
17
-
18
- export { type UseKVStoreResult, useKVStore, useSink };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,3 @@
1
- import { a as IndexerContext } from './shared/indexer.f761abcd.js';
2
- export { f as Indexer, c as IndexerConfig, b as IndexerHooks, I as IndexerPlugin, d as IndexerWithStreamConfig, g as createIndexer, e as defineIndexer, h as defineIndexerPlugin, r as run, u as useIndexerContext } from './shared/indexer.f761abcd.js';
3
- export { D as DefaultSink, S as Sink, a as SinkCursorParams, b as SinkData, d as defaultSink } from './shared/indexer.b9c8f0d8.js';
4
- export { C as CassetteOptions, V as VcrConfig, a as VcrReplayResult, l as loadCassette, r as replay } from './shared/indexer.e4f2430f.js';
5
- export { CassetteDataType, deserialize, isCassetteAvailable, record, serialize } from './vcr/index.js';
6
- import { KVStore } from './plugins/kv.js';
1
+ export { a as Indexer, c as IndexerConfig, e as IndexerHooks, f as IndexerStartingCursor, I as IndexerWithStreamConfig, R as ReconnectOptions, i as RunOptions, U as UseMiddlewareFunction, h as createIndexer, g as defineIndexer, j as run, r as runWithReconnect, u as useIndexerContext } from './shared/indexer.fedcd831.js';
7
2
  import '@apibara/protocol';
8
3
  import 'hookable';
9
- import 'better-sqlite3';
10
-
11
- type UseKVStoreResult = InstanceType<typeof KVStore>;
12
- declare function useKVStore(): UseKVStoreResult;
13
-
14
- declare function useSink<TTxnParams>({ context, }: {
15
- context: IndexerContext<TTxnParams>;
16
- }): NonNullable<TTxnParams>;
17
-
18
- export { type UseKVStoreResult, useKVStore, useSink };
package/dist/index.mjs CHANGED
@@ -1,34 +1,271 @@
1
- export { c as createIndexer, d as defineIndexer, l as loadCassette, a as record, b as replay, r as run } from './shared/indexer.36530330.mjs';
2
- export { D as DefaultSink, S as Sink, d as defaultSink } from './shared/indexer.93d6b2eb.mjs';
3
- import { u as useIndexerContext } from './shared/indexer.2c23c9cd.mjs';
4
- export { d as deserialize, i as isCassetteAvailable, s as serialize } from './shared/indexer.2c23c9cd.mjs';
5
- export { defineIndexerPlugin } from './plugins/index.mjs';
6
- import 'node:fs/promises';
7
- import 'node:path';
8
- import 'klona/full';
9
- import 'consola';
10
- import 'hookable';
11
- import 'node:assert';
12
- import 'node:fs';
13
- import '@apibara/protocol/testing';
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';
14
7
  import 'node:async_hooks';
15
8
  import 'unctx';
16
- import '@opentelemetry/api';
17
9
 
18
- function useKVStore() {
19
- const ctx = useIndexerContext();
20
- if (!ctx?.kv)
21
- throw new Error("KV Plugin is not available in context!");
22
- return ctx.kv;
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
+ };
23
37
  }
24
38
 
25
- function useSink({
26
- context
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
27
50
  }) {
28
- if (!context.sinkTransaction) {
29
- throw new Error("Transaction context doesn't exist!");
51
+ const indexer = {
52
+ options,
53
+ streamConfig,
54
+ hooks: createHooks()
55
+ };
56
+ if (indexer.options.debug) {
57
+ createDebugger(indexer.hooks, { tag: "indexer" });
30
58
  }
31
- return context.sinkTransaction;
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
+ let startingCursor;
107
+ if (indexer.options.startingCursor) {
108
+ startingCursor = indexer.options.startingCursor;
109
+ } else if (indexer.options.startingBlock !== void 0) {
110
+ if (indexer.options.startingBlock === 0n) {
111
+ startingCursor = void 0;
112
+ } else if (indexer.options.startingBlock > 0n) {
113
+ startingCursor = {
114
+ orderKey: indexer.options.startingBlock - 1n
115
+ };
116
+ }
117
+ }
118
+ const request = indexer.streamConfig.Request.make({
119
+ filter: isFactoryMode ? [indexer.options.filter, {}] : [indexer.options.filter],
120
+ finality: indexer.options.finality,
121
+ startingCursor
122
+ });
123
+ const options = {};
124
+ await indexer.hooks.callHook("connect:before", { request, options });
125
+ let mainFilter;
126
+ if (isFactoryMode) {
127
+ mainFilter = request.filter[1];
128
+ }
129
+ let stream = client.streamData(request, options)[Symbol.asyncIterator]();
130
+ await indexer.hooks.callHook("connect:after", { request });
131
+ let onConnectCalled = false;
132
+ while (true) {
133
+ const { value: message, done } = await stream.next();
134
+ if (done) {
135
+ break;
136
+ }
137
+ if (!onConnectCalled) {
138
+ onConnectCalled = true;
139
+ if (runOptions.onConnect) {
140
+ await runOptions.onConnect();
141
+ }
142
+ }
143
+ await indexer.hooks.callHook("message", { message });
144
+ switch (message._tag) {
145
+ case "data": {
146
+ await tracer.startActiveSpan("message data", async (span) => {
147
+ const blocks = message.data.data;
148
+ const { cursor, endCursor, finality } = message.data;
149
+ context.cursor = cursor;
150
+ context.endCursor = endCursor;
151
+ context.finality = finality;
152
+ await middleware(context, async () => {
153
+ let block;
154
+ if (isFactoryMode) {
155
+ assert(indexer.options.factory !== void 0);
156
+ const [factoryBlock, mainBlock] = blocks;
157
+ block = mainBlock;
158
+ if (factoryBlock !== null) {
159
+ const { filter } = await indexer.options.factory({
160
+ block: factoryBlock,
161
+ context
162
+ });
163
+ if (filter) {
164
+ mainFilter = indexer.streamConfig.mergeFilter(
165
+ mainFilter,
166
+ filter
167
+ );
168
+ const request2 = indexer.streamConfig.Request.make({
169
+ filter: [indexer.options.filter, mainFilter],
170
+ finality: indexer.options.finality,
171
+ startingCursor: cursor
172
+ });
173
+ await indexer.hooks.callHook("connect:factory", {
174
+ request: request2,
175
+ endCursor
176
+ });
177
+ stream = client.streamData(request2, options)[Symbol.asyncIterator]();
178
+ const { value: message2 } = await stream.next();
179
+ assert(message2._tag === "data");
180
+ const [_factoryBlock, _block] = message2.data.data;
181
+ block = _block;
182
+ }
183
+ }
184
+ } else {
185
+ block = blocks[0];
186
+ }
187
+ if (block) {
188
+ await tracer.startActiveSpan("handler", async (span2) => {
189
+ await indexer.options.transform({
190
+ block,
191
+ cursor,
192
+ endCursor,
193
+ finality,
194
+ context
195
+ });
196
+ span2.end();
197
+ });
198
+ }
199
+ });
200
+ span.end();
201
+ });
202
+ context.cursor = void 0;
203
+ context.endCursor = void 0;
204
+ context.finality = void 0;
205
+ break;
206
+ }
207
+ case "invalidate": {
208
+ await tracer.startActiveSpan("message invalidate", async (span) => {
209
+ await indexer.hooks.callHook("message:invalidate", { message });
210
+ span.end();
211
+ });
212
+ break;
213
+ }
214
+ case "finalize": {
215
+ await tracer.startActiveSpan("message finalize", async (span) => {
216
+ await indexer.hooks.callHook("message:finalize", { message });
217
+ span.end();
218
+ });
219
+ break;
220
+ }
221
+ case "heartbeat": {
222
+ await tracer.startActiveSpan("message heartbeat", async (span) => {
223
+ await indexer.hooks.callHook("message:heartbeat", { message });
224
+ span.end();
225
+ });
226
+ break;
227
+ }
228
+ case "systemMessage": {
229
+ await tracer.startActiveSpan(
230
+ "message systemMessage",
231
+ async (span) => {
232
+ switch (message.systemMessage.output?._tag) {
233
+ case "stderr": {
234
+ consola.warn(message.systemMessage.output.stderr);
235
+ break;
236
+ }
237
+ case "stdout": {
238
+ consola.info(message.systemMessage.output.stdout);
239
+ break;
240
+ }
241
+ }
242
+ await indexer.hooks.callHook("message:systemMessage", {
243
+ message
244
+ });
245
+ span.end();
246
+ }
247
+ );
248
+ break;
249
+ }
250
+ default: {
251
+ consola.warn("unexpected message", message);
252
+ throw new Error("not implemented");
253
+ }
254
+ }
255
+ await indexer.hooks.callHook("run:after");
256
+ }
257
+ });
258
+ }
259
+ async function registerMiddleware(indexer) {
260
+ const middleware = [];
261
+ const use = (fn) => {
262
+ middleware.push(fn);
263
+ };
264
+ await indexer.hooks.callHook("handler:middleware", { use });
265
+ const composed = compose(middleware);
266
+ return async function _composedIndexerMiddleware(context, next) {
267
+ await composed(context, next);
268
+ };
32
269
  }
33
270
 
34
- export { useIndexerContext, useKVStore, useSink };
271
+ export { createIndexer, defineIndexer, run, runWithReconnect, useIndexerContext };
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ function generateIndexerId(fileBasedName, identifier) {
4
+ return `indexer_${fileBasedName}_${identifier || "default"}`.replace(
5
+ /[^a-zA-Z0-9_]/g,
6
+ "_"
7
+ );
8
+ }
9
+
10
+ exports.generateIndexerId = generateIndexerId;