envio 3.0.2 → 3.1.0-rc.1

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 (101) hide show
  1. package/README.md +0 -1
  2. package/evm.schema.json +15 -8
  3. package/fuel.schema.json +19 -12
  4. package/index.d.ts +0 -2
  5. package/package.json +6 -7
  6. package/rescript.json +1 -1
  7. package/src/Batch.res +4 -214
  8. package/src/Batch.res.mjs +6 -165
  9. package/src/ChainFetcher.res +12 -28
  10. package/src/ChainFetcher.res.mjs +8 -17
  11. package/src/ChainManager.res +10 -9
  12. package/src/ChainManager.res.mjs +6 -10
  13. package/src/Config.res +9 -25
  14. package/src/Config.res.mjs +17 -27
  15. package/src/Core.res +7 -0
  16. package/src/Ctx.res +1 -0
  17. package/src/Env.res +0 -8
  18. package/src/Env.res.mjs +0 -6
  19. package/src/EventConfigBuilder.res +13 -123
  20. package/src/EventConfigBuilder.res.mjs +6 -73
  21. package/src/EventProcessing.res +5 -29
  22. package/src/EventProcessing.res.mjs +11 -20
  23. package/src/EventUtils.res +0 -27
  24. package/src/EventUtils.res.mjs +0 -24
  25. package/src/FetchState.res +2 -15
  26. package/src/FetchState.res.mjs +3 -18
  27. package/src/GlobalState.res +26 -39
  28. package/src/GlobalState.res.mjs +12 -40
  29. package/src/HandlerLoader.res +6 -5
  30. package/src/HandlerLoader.res.mjs +27 -9
  31. package/src/HandlerRegister.res +1 -12
  32. package/src/HandlerRegister.res.mjs +1 -6
  33. package/src/HandlerRegister.resi +1 -1
  34. package/src/Hasura.res +96 -32
  35. package/src/Hasura.res.mjs +93 -38
  36. package/src/InMemoryStore.res +205 -45
  37. package/src/InMemoryStore.res.mjs +157 -40
  38. package/src/InMemoryTable.res +165 -249
  39. package/src/InMemoryTable.res.mjs +156 -227
  40. package/src/Internal.res +10 -34
  41. package/src/Internal.res.mjs +9 -3
  42. package/src/LoadLayer.res +5 -5
  43. package/src/LoadLayer.res.mjs +5 -5
  44. package/src/LogSelection.res +15 -19
  45. package/src/LogSelection.res.mjs +5 -6
  46. package/src/Main.res +4 -6
  47. package/src/Main.res.mjs +26 -15
  48. package/src/Persistence.res +7 -132
  49. package/src/Persistence.res.mjs +1 -102
  50. package/src/PgStorage.res +57 -40
  51. package/src/PgStorage.res.mjs +60 -34
  52. package/src/ReorgDetection.res +35 -58
  53. package/src/ReorgDetection.res.mjs +21 -29
  54. package/src/SimulateItems.res.mjs +21 -3
  55. package/src/Sink.res +2 -2
  56. package/src/Sink.res.mjs +1 -1
  57. package/src/TableIndices.res +9 -2
  58. package/src/TableIndices.res.mjs +7 -1
  59. package/src/TestIndexer.res +53 -60
  60. package/src/TestIndexer.res.mjs +77 -63
  61. package/src/TestIndexerProxyStorage.res +4 -14
  62. package/src/TestIndexerProxyStorage.res.mjs +1 -5
  63. package/src/UserContext.res +2 -4
  64. package/src/UserContext.res.mjs +4 -5
  65. package/src/Utils.res +0 -2
  66. package/src/Utils.res.mjs +0 -3
  67. package/src/bindings/ClickHouse.res +45 -38
  68. package/src/bindings/ClickHouse.res.mjs +16 -17
  69. package/src/bindings/Vitest.res +3 -0
  70. package/src/db/InternalTable.res +59 -18
  71. package/src/db/InternalTable.res.mjs +82 -51
  72. package/src/db/Table.res +9 -2
  73. package/src/db/Table.res.mjs +10 -7
  74. package/src/sources/EnvioApiClient.res +15 -0
  75. package/src/sources/EnvioApiClient.res.mjs +24 -0
  76. package/src/sources/EvmChain.res +32 -10
  77. package/src/sources/EvmChain.res.mjs +31 -5
  78. package/src/sources/HyperFuelSource.res +15 -58
  79. package/src/sources/HyperFuelSource.res.mjs +20 -39
  80. package/src/sources/HyperSync.res +54 -100
  81. package/src/sources/HyperSync.res.mjs +67 -96
  82. package/src/sources/HyperSync.resi +4 -22
  83. package/src/sources/HyperSyncClient.res +70 -247
  84. package/src/sources/HyperSyncClient.res.mjs +47 -46
  85. package/src/sources/HyperSyncSource.res +94 -166
  86. package/src/sources/HyperSyncSource.res.mjs +100 -127
  87. package/src/sources/RpcSource.res +43 -22
  88. package/src/sources/RpcSource.res.mjs +50 -35
  89. package/src/sources/SimulateSource.res +1 -7
  90. package/src/sources/SimulateSource.res.mjs +1 -7
  91. package/src/sources/Source.res +10 -1
  92. package/src/sources/Source.res.mjs +3 -0
  93. package/src/sources/SourceManager.res +177 -8
  94. package/src/sources/SourceManager.res.mjs +141 -3
  95. package/src/sources/SourceManager.resi +19 -0
  96. package/src/tui/Tui.res +44 -6
  97. package/src/tui/Tui.res.mjs +56 -8
  98. package/src/tui/components/TuiData.res +3 -0
  99. package/svm.schema.json +11 -4
  100. package/src/sources/HyperSyncJsonApi.res +0 -390
  101. package/src/sources/HyperSyncJsonApi.res.mjs +0 -237
@@ -225,7 +225,6 @@ let make = ({chain, endpointUrl}: options): t => {
225
225
  ~retry,
226
226
  ~logger,
227
227
  ) => {
228
- let mkLogAndRaise = ErrorHandling.mkLogAndRaise(~logger, ...)
229
228
  let totalTimeRef = Hrtime.makeTimer()
230
229
 
231
230
  let selectionConfig = getSelectionConfig(selection)
@@ -299,55 +298,6 @@ let make = ({chain, endpointUrl}: options): t => {
299
298
  //In the query
300
299
  let heighestBlockQueried = pageUnsafe.nextBlock - 1
301
300
 
302
- let lastBlockQueriedPromise = // switch pageUnsafe.rollbackGuard {
303
- // //In the case a rollbackGuard is returned (this only happens at the head for unconfirmed blocks)
304
- // //use these values
305
- // | Some({blockNumber, timestamp, hash}) =>
306
- // {
307
- // ReorgDetection.blockNumber,
308
- // blockTimestamp: timestamp,
309
- // blockHash: hash,
310
- // }->Promise.resolve
311
- // | None =>
312
- //The optional block and timestamp of the last item returned by the query
313
- //(Optional in the case that there are no logs returned in the query)
314
- switch pageUnsafe.items->Belt.Array.get(pageUnsafe.items->Belt.Array.length - 1) {
315
- | Some({block}) if block.height == heighestBlockQueried =>
316
- //If the last log item in the current page is equal to the
317
- //heighest block acounted for in the query. Simply return this
318
- //value without making an extra query
319
-
320
- (
321
- {
322
- blockNumber: block.height,
323
- blockTimestamp: block.time,
324
- blockHash: block.id,
325
- }: ReorgDetection.blockDataWithTimestamp
326
- )->Promise.resolve
327
- //If it does not match it means that there were no matching logs in the last
328
- //block so we should fetch the block data
329
- | Some(_)
330
- | None =>
331
- //If there were no logs at all in the current page query then fetch the
332
- //timestamp of the heighest block accounted for
333
- HyperFuel.queryBlockData(~serverUrl=endpointUrl, ~blockNumber=heighestBlockQueried, ~logger)
334
- ->Promise.thenResolve(res => {
335
- switch res {
336
- | Some(blockData) => blockData
337
- | None =>
338
- mkLogAndRaise(
339
- Not_found,
340
- ~msg=`Failure, blockData for block ${heighestBlockQueried->Int.toString} unexpectedly returned None`,
341
- )
342
- }
343
- })
344
- ->Promise.catch(exn => {
345
- exn->mkLogAndRaise(
346
- ~msg=`Failed to query blockData for block ${heighestBlockQueried->Int.toString}`,
347
- )
348
- })
349
- }
350
-
351
301
  let parsingTimeRef = Hrtime.makeTimer()
352
302
 
353
303
  let parsedQueueItems = pageUnsafe.items->Array.map(item => {
@@ -463,11 +413,18 @@ let make = ({chain, endpointUrl}: options): t => {
463
413
 
464
414
  let parsingTimeElapsed = parsingTimeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
465
415
 
466
- let rangeLastBlock = await lastBlockQueriedPromise
416
+ // Fuel never rolls back on reorg, so block hashes here are purely informational
417
+ // for detect-only logging via ReorgDetection.
418
+ let blockHashes = pageUnsafe.items->Array.map(({block}) => {
419
+ ReorgDetection.blockNumber: block.height,
420
+ blockHash: block.id,
421
+ })
467
422
 
468
- let reorgGuard: ReorgDetection.reorgGuard = {
469
- rangeLastBlock: rangeLastBlock->ReorgDetection.generalizeBlockDataWithTimestamp,
470
- prevRangeLastBlock: None,
423
+ let latestFetchedBlockTimestamp = switch pageUnsafe.items->Belt.Array.get(
424
+ pageUnsafe.items->Belt.Array.length - 1,
425
+ ) {
426
+ | Some({block}) if block.height == heighestBlockQueried => block.time
427
+ | _ => 0
471
428
  }
472
429
 
473
430
  let totalTimeElapsed = totalTimeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
@@ -479,12 +436,12 @@ let make = ({chain, endpointUrl}: options): t => {
479
436
  }
480
437
 
481
438
  {
482
- latestFetchedBlockTimestamp: rangeLastBlock.blockTimestamp,
439
+ latestFetchedBlockTimestamp,
483
440
  parsedQueueItems,
484
- latestFetchedBlockNumber: rangeLastBlock.blockNumber,
441
+ latestFetchedBlockNumber: heighestBlockQueried,
485
442
  stats,
486
443
  knownHeight,
487
- reorgGuard,
444
+ blockHashes,
488
445
  fromBlockQueried: fromBlock,
489
446
  }
490
447
  }
@@ -492,7 +449,7 @@ let make = ({chain, endpointUrl}: options): t => {
492
449
  let getBlockHashes = (~blockNumbers as _, ~logger as _) =>
493
450
  JsError.throwWithMessage("HyperFuel does not support getting block hashes")
494
451
 
495
- let jsonApiClient = Rest.client(endpointUrl)
452
+ let jsonApiClient = EnvioApiClient.make(endpointUrl)
496
453
 
497
454
  {
498
455
  name,
@@ -11,8 +11,8 @@ import * as Prometheus from "../Prometheus.res.mjs";
11
11
  import * as EventRouter from "./EventRouter.res.mjs";
12
12
  import * as ErrorHandling from "../ErrorHandling.res.mjs";
13
13
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
14
+ import * as EnvioApiClient from "./EnvioApiClient.res.mjs";
14
15
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
15
- import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
16
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
17
17
 
18
18
  let EventRoutingFailed = /* @__PURE__ */Primitive_exceptions.create("HyperFuelSource.EventRoutingFailed");
@@ -182,7 +182,6 @@ function make(param) {
182
182
  let name = "HyperFuel";
183
183
  let getSelectionConfig = memoGetSelectionConfig(chain);
184
184
  let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, indexingAddresses, knownHeight, param, selection, retry, logger) => {
185
- let mkLogAndRaise = (extra, extra$1) => ErrorHandling.mkLogAndRaise(logger, extra, extra$1);
186
185
  let totalTimeRef = Hrtime.makeTimer();
187
186
  let selectionConfig = getSelectionConfig(selection);
188
187
  let recieptsSelection = selectionConfig.getRecieptsSelection(addressesByContractName);
@@ -238,34 +237,6 @@ function make(param) {
238
237
  let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(startFetchingBatchTimeRef));
239
238
  let knownHeight$1 = pageUnsafe.archiveHeight;
240
239
  let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
241
- let match = Belt_Array.get(pageUnsafe.items, pageUnsafe.items.length - 1 | 0);
242
- let lastBlockQueriedPromise;
243
- let exit = 0;
244
- if (match !== undefined) {
245
- let block = match.block;
246
- if (block.height === heighestBlockQueried) {
247
- lastBlockQueriedPromise = Promise.resolve({
248
- blockHash: block.id,
249
- blockNumber: block.height,
250
- blockTimestamp: block.time
251
- });
252
- } else {
253
- exit = 1;
254
- }
255
- } else {
256
- exit = 1;
257
- }
258
- if (exit === 1) {
259
- lastBlockQueriedPromise = Stdlib_Promise.$$catch(HyperFuel.queryBlockData(endpointUrl, heighestBlockQueried, logger).then(res => {
260
- if (res !== undefined) {
261
- return res;
262
- } else {
263
- return mkLogAndRaise(`Failure, blockData for block ` + heighestBlockQueried.toString() + ` unexpectedly returned None`, {
264
- RE_EXN_ID: "Not_found"
265
- });
266
- }
267
- }), exn => mkLogAndRaise(`Failed to query blockData for block ` + heighestBlockQueried.toString(), exn));
268
- }
269
240
  let parsingTimeRef = Hrtime.makeTimer();
270
241
  let parsedQueueItems = pageUnsafe.items.map(item => {
271
242
  let block = item.block;
@@ -383,11 +354,21 @@ function make(param) {
383
354
  };
384
355
  });
385
356
  let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingTimeRef));
386
- let rangeLastBlock = await lastBlockQueriedPromise;
387
- let reorgGuard = {
388
- rangeLastBlock: rangeLastBlock,
389
- prevRangeLastBlock: undefined
390
- };
357
+ let blockHashes = pageUnsafe.items.map(param => {
358
+ let block = param.block;
359
+ return {
360
+ blockHash: block.id,
361
+ blockNumber: block.height
362
+ };
363
+ });
364
+ let match = Belt_Array.get(pageUnsafe.items, pageUnsafe.items.length - 1 | 0);
365
+ let latestFetchedBlockTimestamp;
366
+ if (match !== undefined) {
367
+ let block = match.block;
368
+ latestFetchedBlockTimestamp = block.height === heighestBlockQueried ? block.time : 0;
369
+ } else {
370
+ latestFetchedBlockTimestamp = 0;
371
+ }
391
372
  let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef));
392
373
  let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed;
393
374
  let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime;
@@ -398,16 +379,16 @@ function make(param) {
398
379
  };
399
380
  return {
400
381
  knownHeight: knownHeight$1,
401
- reorgGuard: reorgGuard,
382
+ blockHashes: blockHashes,
402
383
  parsedQueueItems: parsedQueueItems,
403
384
  fromBlockQueried: fromBlock,
404
- latestFetchedBlockNumber: rangeLastBlock.blockNumber,
405
- latestFetchedBlockTimestamp: rangeLastBlock.blockTimestamp,
385
+ latestFetchedBlockNumber: heighestBlockQueried,
386
+ latestFetchedBlockTimestamp: latestFetchedBlockTimestamp,
406
387
  stats: stats
407
388
  };
408
389
  };
409
390
  let getBlockHashes = (param, param$1) => Stdlib_JsError.throwWithMessage("HyperFuel does not support getting block hashes");
410
- let jsonApiClient = Rest.client(endpointUrl, undefined);
391
+ let jsonApiClient = EnvioApiClient.make(endpointUrl);
411
392
  return {
412
393
  name: name,
413
394
  sourceFor: "Sync",
@@ -1,30 +1,26 @@
1
- module Log = {
2
- type t = {
3
- address: Address.t,
4
- data: string,
5
- topics: array<EvmTypes.Hex.t>,
6
- logIndex: int,
1
+ let reraisIfRateLimited = exn =>
2
+ switch exn->JsExn.anyToExnInternal {
3
+ | JsExn(e) =>
4
+ switch e->JsExn.message {
5
+ | Some(msg) if msg->String.startsWith("RATE_LIMITED:") =>
6
+ let resetMs =
7
+ msg
8
+ ->String.slice(~start=13, ~end=msg->String.length)
9
+ ->Int.fromString
10
+ ->Option.getOr(1000)
11
+ throw(Source.RateLimited({resetMs: resetMs}))
12
+ | _ => ()
13
+ }
14
+ | _ => ()
7
15
  }
8
16
 
9
- let fieldNames = ["address", "data", "topics", "logIndex"]
10
- }
11
-
12
- type hyperSyncPage<'item> = {
13
- items: array<'item>,
17
+ type logsQueryPage = {
18
+ items: array<HyperSyncClient.EventItems.item>,
14
19
  nextBlock: int,
15
20
  archiveHeight: int,
16
21
  rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
17
- events: array<HyperSyncClient.ResponseTypes.event>,
18
22
  }
19
23
 
20
- type logsQueryPageItem = {
21
- log: Log.t,
22
- block: HyperSyncClient.ResponseTypes.block,
23
- transaction: Internal.eventTransaction,
24
- }
25
-
26
- type logsQueryPage = hyperSyncPage<logsQueryPageItem>
27
-
28
24
  type missingParams = {
29
25
  queryName: string,
30
26
  missingParams: array<string>,
@@ -74,94 +70,37 @@ module GetLogs = {
74
70
  fieldSelection,
75
71
  }
76
72
 
77
- @inline
78
- let addMissingParams = (acc, fieldNames, returnedObj, ~prefix) => {
79
- if fieldNames->Utils.Array.notEmpty {
80
- if !(returnedObj->Obj.magic) {
81
- acc->Array.push(prefix)->ignore
82
- } else {
83
- for idx in 0 to fieldNames->Array.length - 1 {
84
- let fieldName = fieldNames->Array.getUnsafe(idx)
85
- switch returnedObj
86
- ->(Utils.magic: 'a => dict<unknown>)
87
- ->Utils.Dict.dangerouslyGetNonOption(fieldName) {
88
- | Some(_) => ()
89
- | None => acc->Array.push(prefix ++ "." ++ fieldName)->ignore
90
- }
73
+ // Rust encodes structured failures as a JSON payload in the napi error's
74
+ // message: `{"kind":"MissingFields","fields":["block.timestamp", ...]}`.
75
+ // JSON.parse + shape check is the recovery protocol — no string-grepping
76
+ // on anyhow's Debug format.
77
+ let extractMissingParams = (exn: exn): option<array<string>> => {
78
+ let message = switch exn {
79
+ | JsExn(jsExn) => jsExn->JsExn.message
80
+ | _ => None
81
+ }
82
+ switch message {
83
+ | None => None
84
+ | Some(msg) =>
85
+ switch msg->JSON.parseOrThrow->JSON.Decode.object {
86
+ | exception _ => None
87
+ | None => None
88
+ | Some(obj) =>
89
+ switch (obj->Dict.get("kind"), obj->Dict.get("fields")) {
90
+ | (Some(String("MissingFields")), Some(Array(fields))) =>
91
+ Some(fields->Array.filterMap(JSON.Decode.string))
92
+ | _ => None
91
93
  }
92
94
  }
93
95
  }
94
96
  }
95
97
 
96
- //Note this function can throw an error
97
- let convertEvent = (
98
- event: HyperSyncClient.ResponseTypes.event,
99
- ~nonOptionalBlockFieldNames,
100
- ~nonOptionalTransactionFieldNames,
101
- ): logsQueryPageItem => {
102
- let missingParams = []
103
- missingParams->addMissingParams(Log.fieldNames, event.log, ~prefix="log")
104
- missingParams->addMissingParams(nonOptionalBlockFieldNames, event.block, ~prefix="block")
105
- missingParams->addMissingParams(
106
- nonOptionalTransactionFieldNames,
107
- event.transaction,
108
- ~prefix="transaction",
109
- )
110
- if missingParams->Array.length > 0 {
111
- throw(Error(UnexpectedMissingParams({missingParams: missingParams})))
112
- }
113
-
114
- //Topics can be nullable and still need to be filtered
115
- let logUnsanitized: Log.t = event.log->(Utils.magic: HyperSyncClient.ResponseTypes.log => Log.t)
116
- let topics = event.log.topics->Option.getUnsafe->Array.filterMap(Nullable.toOption)
117
- let address = event.log.address->Option.getUnsafe
118
- let log = {
119
- ...logUnsanitized,
120
- topics,
121
- address,
122
- }
123
-
124
- {
125
- log,
126
- block: event.block->(
127
- Utils.magic: option<
128
- HyperSyncClient.ResponseTypes.block,
129
- > => HyperSyncClient.ResponseTypes.block
130
- ),
131
- transaction: event.transaction->(
132
- Utils.magic: option<HyperSyncClient.ResponseTypes.transaction> => Internal.eventTransaction
133
- ),
134
- }
135
- }
136
-
137
- let convertResponse = (
138
- res: HyperSyncClient.ResponseTypes.eventResponse,
139
- ~nonOptionalBlockFieldNames,
140
- ~nonOptionalTransactionFieldNames,
141
- ): logsQueryPage => {
142
- let {nextBlock, archiveHeight, rollbackGuard} = res
143
- let items =
144
- res.data->Array.map(item =>
145
- item->convertEvent(~nonOptionalBlockFieldNames, ~nonOptionalTransactionFieldNames)
146
- )
147
- let page: logsQueryPage = {
148
- items,
149
- nextBlock,
150
- archiveHeight: archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
151
- events: res.data,
152
- rollbackGuard,
153
- }
154
- page
155
- }
156
-
157
98
  let query = async (
158
99
  ~client: HyperSyncClient.t,
159
100
  ~fromBlock,
160
101
  ~toBlock,
161
102
  ~logSelections: array<LogSelection.t>,
162
103
  ~fieldSelection,
163
- ~nonOptionalBlockFieldNames,
164
- ~nonOptionalTransactionFieldNames,
165
104
  ): logsQueryPage => {
166
105
  let addressesWithTopics = logSelections->Array.flatMap(({addresses, topicSelections}) =>
167
106
  topicSelections->Array.map(({topic0, topic1, topic2, topic3}) => {
@@ -182,13 +121,26 @@ module GetLogs = {
182
121
  ~fieldSelection,
183
122
  )
184
123
 
185
- let res = await client.getEvents(~query)
124
+ let res = switch await client.getEventItems(~query) {
125
+ | res => res
126
+ | exception exn =>
127
+ reraisIfRateLimited(exn)
128
+ switch extractMissingParams(exn) {
129
+ | Some(missingParams) => throw(Error(UnexpectedMissingParams({missingParams: missingParams})))
130
+ | None => throw(exn)
131
+ }
132
+ }
186
133
  if res.nextBlock <= fromBlock {
187
134
  // Might happen when /height response was from another instance of HyperSync
188
135
  throw(Error(WrongInstance))
189
136
  }
190
137
 
191
- res->convertResponse(~nonOptionalBlockFieldNames, ~nonOptionalTransactionFieldNames)
138
+ {
139
+ items: res.items,
140
+ nextBlock: res.nextBlock,
141
+ archiveHeight: res.archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
142
+ rollbackGuard: res.rollbackGuard,
143
+ }
192
144
  }
193
145
  }
194
146
 
@@ -258,7 +210,9 @@ module BlockData = {
258
210
 
259
211
  Prometheus.SourceRequestCount.increment(~sourceName, ~chainId, ~method="getBlockHashes")
260
212
  let maybeSuccessfulRes = switch await client.get(~query=body) {
261
- | exception _ => None
213
+ | exception exn =>
214
+ reraisIfRateLimited(exn)
215
+ None
262
216
  | res if res.nextBlock <= fromBlock => None
263
217
  | res => Some(res)
264
218
  }
@@ -2,22 +2,38 @@
2
2
 
3
3
  import * as Time from "../Time.res.mjs";
4
4
  import * as Utils from "../Utils.res.mjs";
5
+ import * as Source from "./Source.res.mjs";
5
6
  import * as Logging from "../Logging.res.mjs";
6
7
  import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
9
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
7
10
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
11
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
12
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
13
  import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
10
14
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
15
  import * as HyperSyncClient from "./HyperSyncClient.res.mjs";
12
- import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
13
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
17
 
15
- let fieldNames = [
16
- "address",
17
- "data",
18
- "topics",
19
- "logIndex"
20
- ];
18
+ function reraisIfRateLimited(exn) {
19
+ let e = Primitive_exceptions.internalToException(exn);
20
+ if (e.RE_EXN_ID !== "JsExn") {
21
+ return;
22
+ }
23
+ let msg = Stdlib_JsExn.message(e._1);
24
+ if (msg === undefined) {
25
+ return;
26
+ }
27
+ if (!msg.startsWith("RATE_LIMITED:")) {
28
+ return;
29
+ }
30
+ let resetMs = Stdlib_Option.getOr(Stdlib_Int.fromString(msg.slice(13, msg.length), undefined), 1000);
31
+ throw {
32
+ RE_EXN_ID: Source.RateLimited,
33
+ resetMs: resetMs,
34
+ Error: new Error()
35
+ };
36
+ }
21
37
 
22
38
  let HyperSyncQueryError = /* @__PURE__ */Primitive_exceptions.create("HyperSync.HyperSyncQueryError");
23
39
 
@@ -55,7 +71,28 @@ function makeRequestBody(fromBlock, toBlockInclusive, addressesWithTopics, field
55
71
  };
56
72
  }
57
73
 
58
- async function query(client, fromBlock, toBlock, logSelections, fieldSelection, nonOptionalBlockFieldNames, nonOptionalTransactionFieldNames) {
74
+ function extractMissingParams(exn) {
75
+ let message = exn.RE_EXN_ID === "JsExn" ? Stdlib_JsExn.message(exn._1) : undefined;
76
+ if (message === undefined) {
77
+ return;
78
+ }
79
+ let obj;
80
+ try {
81
+ obj = Stdlib_JSON.Decode.object(JSON.parse(message));
82
+ } catch (exn$1) {
83
+ return;
84
+ }
85
+ if (obj === undefined) {
86
+ return;
87
+ }
88
+ let match = obj["kind"];
89
+ let match$1 = obj["fields"];
90
+ if (match === "MissingFields" && Array.isArray(match$1)) {
91
+ return Stdlib_Array.filterMap(match$1, Stdlib_JSON.Decode.string);
92
+ }
93
+ }
94
+
95
+ async function query(client, fromBlock, toBlock, logSelections, fieldSelection) {
59
96
  let addressesWithTopics = logSelections.flatMap(param => {
60
97
  let addresses = param.addresses;
61
98
  return param.topicSelections.map(param => {
@@ -64,65 +101,14 @@ async function query(client, fromBlock, toBlock, logSelections, fieldSelection,
64
101
  });
65
102
  });
66
103
  let query$1 = makeRequestBody(fromBlock, toBlock, addressesWithTopics, fieldSelection);
67
- let res = await client.getEvents(query$1);
68
- if (res.nextBlock <= fromBlock) {
69
- throw {
70
- RE_EXN_ID: $$Error,
71
- _1: "WrongInstance",
72
- Error: new Error()
73
- };
74
- }
75
- let items = res.data.map(item => {
76
- let missingParams = [];
77
- let returnedObj = item.log;
78
- if (Utils.$$Array.notEmpty(fieldNames)) {
79
- if (returnedObj) {
80
- for (let idx = 0, idx_finish = fieldNames.length; idx < idx_finish; ++idx) {
81
- let fieldName = fieldNames[idx];
82
- let match = returnedObj[fieldName];
83
- if (match !== undefined) {
84
-
85
- } else {
86
- missingParams.push("log." + fieldName);
87
- }
88
- }
89
- } else {
90
- missingParams.push("log");
91
- }
92
- }
93
- let returnedObj$1 = item.block;
94
- if (Utils.$$Array.notEmpty(nonOptionalBlockFieldNames)) {
95
- if (returnedObj$1) {
96
- for (let idx$1 = 0, idx_finish$1 = nonOptionalBlockFieldNames.length; idx$1 < idx_finish$1; ++idx$1) {
97
- let fieldName$1 = nonOptionalBlockFieldNames[idx$1];
98
- let match$1 = returnedObj$1[fieldName$1];
99
- if (match$1 !== undefined) {
100
-
101
- } else {
102
- missingParams.push("block." + fieldName$1);
103
- }
104
- }
105
- } else {
106
- missingParams.push("block");
107
- }
108
- }
109
- let returnedObj$2 = item.transaction;
110
- if (Utils.$$Array.notEmpty(nonOptionalTransactionFieldNames)) {
111
- if (returnedObj$2) {
112
- for (let idx$2 = 0, idx_finish$2 = nonOptionalTransactionFieldNames.length; idx$2 < idx_finish$2; ++idx$2) {
113
- let fieldName$2 = nonOptionalTransactionFieldNames[idx$2];
114
- let match$2 = returnedObj$2[fieldName$2];
115
- if (match$2 !== undefined) {
116
-
117
- } else {
118
- missingParams.push("transaction." + fieldName$2);
119
- }
120
- }
121
- } else {
122
- missingParams.push("transaction");
123
- }
124
- }
125
- if (missingParams.length !== 0) {
104
+ let res;
105
+ try {
106
+ res = await client.getEventItems(query$1);
107
+ } catch (raw_exn) {
108
+ let exn = Primitive_exceptions.internalToException(raw_exn);
109
+ reraisIfRateLimited(exn);
110
+ let missingParams = extractMissingParams(exn);
111
+ if (missingParams !== undefined) {
126
112
  throw {
127
113
  RE_EXN_ID: $$Error,
128
114
  _1: {
@@ -132,35 +118,20 @@ async function query(client, fromBlock, toBlock, logSelections, fieldSelection,
132
118
  Error: new Error()
133
119
  };
134
120
  }
135
- let logUnsanitized = item.log;
136
- let topics = Stdlib_Array.filterMap(item.log.topics, prim => {
137
- if (prim == null) {
138
- return;
139
- } else {
140
- return Primitive_option.some(prim);
141
- }
142
- });
143
- let address = item.log.address;
144
- let log_data = logUnsanitized.data;
145
- let log_logIndex = logUnsanitized.logIndex;
146
- let log = {
147
- address: address,
148
- data: log_data,
149
- topics: topics,
150
- logIndex: log_logIndex
151
- };
152
- return {
153
- log: log,
154
- block: item.block,
155
- transaction: item.transaction
121
+ throw exn;
122
+ }
123
+ if (res.nextBlock <= fromBlock) {
124
+ throw {
125
+ RE_EXN_ID: $$Error,
126
+ _1: "WrongInstance",
127
+ Error: new Error()
156
128
  };
157
- });
129
+ }
158
130
  return {
159
- items: items,
131
+ items: res.items,
160
132
  nextBlock: res.nextBlock,
161
133
  archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
162
- rollbackGuard: res.rollbackGuard,
163
- events: res.data
134
+ rollbackGuard: res.rollbackGuard
164
135
  };
165
136
  }
166
137
 
@@ -230,7 +201,9 @@ async function queryBlockData(client, fromBlock, toBlock, sourceName, chainId, l
230
201
  try {
231
202
  res = await client.get(body);
232
203
  exit = 1;
233
- } catch (exn) {
204
+ } catch (raw_exn) {
205
+ let exn = Primitive_exceptions.internalToException(raw_exn);
206
+ reraisIfRateLimited(exn);
234
207
  maybeSuccessfulRes = undefined;
235
208
  }
236
209
  if (exit === 1) {
@@ -289,15 +262,13 @@ function queryBlockData$1(client, blockNumber, sourceName, chainId, logger) {
289
262
  return queryBlockData(client, blockNumber, blockNumber, sourceName, chainId, logger).then(res => Stdlib_Result.map(res, res => res[0]));
290
263
  }
291
264
 
292
- let Log = {};
293
-
294
265
  let GetLogs = {
295
266
  $$Error: $$Error,
267
+ extractMissingParams: extractMissingParams,
296
268
  query: query
297
269
  };
298
270
 
299
271
  export {
300
- Log,
301
272
  queryErrorToMsq,
302
273
  GetLogs,
303
274
  queryBlockData$1 as queryBlockData,
@@ -1,28 +1,10 @@
1
- type hyperSyncPage<'item> = {
2
- items: array<'item>,
1
+ type logsQueryPage = {
2
+ items: array<HyperSyncClient.EventItems.item>,
3
3
  nextBlock: int,
4
4
  archiveHeight: int,
5
5
  rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
6
- events: array<HyperSyncClient.ResponseTypes.event>,
7
6
  }
8
7
 
9
- module Log: {
10
- type t = {
11
- address: Address.t,
12
- data: string,
13
- topics: array<EvmTypes.Hex.t>,
14
- logIndex: int,
15
- }
16
- }
17
-
18
- type logsQueryPageItem = {
19
- log: Log.t,
20
- block: HyperSyncClient.ResponseTypes.block,
21
- transaction: Internal.eventTransaction,
22
- }
23
-
24
- type logsQueryPage = hyperSyncPage<logsQueryPageItem>
25
-
26
8
  type missingParams = {
27
9
  queryName: string,
28
10
  missingParams: array<string>,
@@ -41,14 +23,14 @@ module GetLogs: {
41
23
 
42
24
  exception Error(error)
43
25
 
26
+ let extractMissingParams: exn => option<array<string>>
27
+
44
28
  let query: (
45
29
  ~client: HyperSyncClient.t,
46
30
  ~fromBlock: int,
47
31
  ~toBlock: option<int>,
48
32
  ~logSelections: array<LogSelection.t>,
49
33
  ~fieldSelection: HyperSyncClient.QueryTypes.fieldSelection,
50
- ~nonOptionalBlockFieldNames: array<string>,
51
- ~nonOptionalTransactionFieldNames: array<string>,
52
34
  ) => promise<logsQueryPage>
53
35
  }
54
36