envio 3.15.0 → 3.16.0-subgraph

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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -85,7 +85,7 @@ type t = {
85
85
  // Should take into consideration partitions fetching for previous states (before rollback)
86
86
  mutable fetchingPartitionsCount: int,
87
87
  recoveryTimeout: float,
88
- retryBackoffScale: float,
88
+ clock: Clock.t,
89
89
  // Why a source was last disabled for good. A disabled source is never
90
90
  // retried, so by the time the manager runs out of sources this is the reason
91
91
  // it did — and the only thing that tells a caller what to change.
@@ -93,11 +93,10 @@ type t = {
93
93
  mutable hasRealtime: bool,
94
94
  mutable committedRateLimitTimeMs: float,
95
95
  mutable rateLimitWaiters: int,
96
- // Wall-clock timestamp (Date.now()) when the current rate-limit window
97
- // started, or None if not currently waiting. Wall-clock so consumers
98
- // (TUI) can compute elapsed time with their own Date.now() reads.
96
+ // When the current rate-limit window started on `clock`, or None if not
97
+ // currently waiting.
99
98
  mutable activeRateLimitStartMs: option<float>,
100
- // Wall-clock timestamp by which the server expects the longest current
99
+ // Timestamp on `clock` by which the server expects the longest current
101
100
  // wait to clear. Tracks the latest reset across concurrent waiters so
102
101
  // the displayed countdown reflects when the indexer will actually retry.
103
102
  mutable activeRateLimitResetAtMs: option<float>,
@@ -206,20 +205,20 @@ let inFlightCount = sourceManager => sourceManager.fetchingPartitionsCount
206
205
  let getRateLimitTimeMs = sourceManager =>
207
206
  sourceManager.committedRateLimitTimeMs +.
208
207
  switch sourceManager.activeRateLimitStartMs {
209
- | Some(startMs) => Date.now() -. startMs
208
+ | Some(startMs) => sourceManager.clock.now() -. startMs
210
209
  | None => 0.0
211
210
  }
212
211
 
213
212
  let getRateLimitResetInMs = sourceManager =>
214
213
  switch sourceManager.activeRateLimitResetAtMs {
215
214
  | Some(resetAt) =>
216
- let remaining = resetAt -. Date.now()
215
+ let remaining = resetAt -. sourceManager.clock.now()
217
216
  remaining > 0.0 ? Some(remaining) : None
218
217
  | None => None
219
218
  }
220
219
 
221
220
  let startRateLimitTimeout = (sourceManager, ~resetMs) => {
222
- let now = Date.now()
221
+ let now = sourceManager.clock.now()
223
222
  if sourceManager.rateLimitWaiters === 0 {
224
223
  sourceManager.activeRateLimitStartMs = Some(now)
225
224
  }
@@ -237,7 +236,7 @@ let stopRateLimitTimeout = sourceManager => {
237
236
  switch sourceManager.activeRateLimitStartMs {
238
237
  | Some(startMs) =>
239
238
  sourceManager.committedRateLimitTimeMs =
240
- sourceManager.committedRateLimitTimeMs +. Date.now() -. startMs
239
+ sourceManager.committedRateLimitTimeMs +. sourceManager.clock.now() -. startMs
241
240
  sourceManager.activeRateLimitStartMs = None
242
241
  | None => ()
243
242
  }
@@ -259,16 +258,8 @@ let waitForRateLimitReset = async (sourceManager: t, ~resetMs, ~retry, ~logger)
259
258
  ->Int.toString}s. For higher limits upgrade your plan at https://envio.dev/app/api-tokens.`,
260
259
  ~params={"retry": retry, "waitMs": waitMs},
261
260
  )
262
- let waitedAt = Date.now()
263
261
  sourceManager->startRateLimitTimeout(~resetMs=waitMs)
264
- await Utils.delay(waitMs)
265
- // Timers run on the event loop's cached clock, so one can fire with Date.now()
266
- // a millisecond short of the wait it was set for, and the wait is counted by
267
- // Date.now(). Temporary until main takes the same fix.
268
- let short = waitedAt +. waitMs->Int.toFloat -. Date.now()
269
- if short > 0. {
270
- await Utils.delay(short->Math.ceil->Float.toInt)
271
- }
262
+ await sourceManager.clock->Clock.delay(~label="rate-limit reset", waitMs)
272
263
  sourceManager->stopRateLimitTimeout
273
264
  }
274
265
 
@@ -354,11 +345,12 @@ type timing = {
354
345
  reducedPollingInterval?: int,
355
346
  recoveryTimeout?: float,
356
347
  getHeightRetryInterval?: (~retry: int) => int,
357
- retryBackoffScale?: float,
358
348
  heightPollTimeout?: int,
349
+ clock?: Clock.t,
359
350
  }
360
351
 
361
352
  let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing: timing={}) => {
353
+ let clock = timing.clock->Option.getOr(Clock.real)
362
354
  let newBlockStallTimeout = timing.newBlockStallTimeout->Option.getOr(60_000)
363
355
  let newBlockStallTimeoutRealtime = timing.newBlockStallTimeoutRealtime->Option.getOr(20_000)
364
356
  let stalledPollingInterval = timing.stalledPollingInterval->Option.getOr(5_000)
@@ -392,6 +384,7 @@ let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing:
392
384
  ~recordRequestStats=stats => requestStats->recordStatsInto(stats),
393
385
  ~getHeightRetryInterval,
394
386
  ~pollTimeoutMillis=timing.heightPollTimeout->Option.getOr(60_000),
387
+ ~clock,
395
388
  ),
396
389
  disabled: false,
397
390
  lastFailedAt: None,
@@ -408,7 +401,7 @@ let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing:
408
401
  stalledPollingInterval,
409
402
  reducedPollingInterval,
410
403
  recoveryTimeout,
411
- retryBackoffScale: timing.retryBackoffScale->Option.getOr(1.),
404
+ clock,
412
405
  statusStart: Performance.now(),
413
406
  status: Idle,
414
407
  idleSeconds: 0.,
@@ -517,8 +510,8 @@ let compareByOldestFailure = (a: sourceState, b: sourceState) =>
517
510
  }
518
511
 
519
512
  // Priority: working primaries > working secondaries > all primaries.
520
- let getNextSources = (sourceManager, ~isRealtime, ~excludedSources=?) => {
521
- let now = Date.now()
513
+ let getNextSources = (sourceManager: t, ~isRealtime, ~excludedSources=?) => {
514
+ let now = sourceManager.clock.now()
522
515
  let workingPrimarySources = []
523
516
  let allPrimarySources = []
524
517
  let workingSecondarySources = []
@@ -589,10 +582,8 @@ let retryBackoffMillis = retry =>
589
582
  // 0, these must never busy-loop when there is no other source to move to.
590
583
  let minRecoverableBackoffMillis = 50
591
584
 
592
- // The logged backoff stays the unscaled one, so a scaled-down test still reads
593
- // the schedule an operator would see.
594
585
  let sleepBackoff = (sourceManager: t, millis) =>
595
- Utils.delay((millis->Int.toFloat *. sourceManager.retryBackoffScale)->Float.toInt)
586
+ sourceManager.clock->Clock.delay(~label="retry backoff", millis)
596
587
 
597
588
  // Back off before retrying the same request, failing the source over first when
598
589
  // another one can actually take over. Marking `lastFailedAt` only demotes this
@@ -614,7 +605,7 @@ let backoffBeforeRetry = async (
614
605
  let switchedToWorkingSource = if (
615
606
  retry >= failoverAfterRetries && (retry - failoverAfterRetries)->mod(2) === 0
616
607
  ) {
617
- let now = Date.now()
608
+ let now = sourceManager.clock.now()
618
609
  sourceState.lastFailedAt = Some(now)
619
610
  switch sourceManager->getNextSource(~isRealtime, ~excludedSources?) {
620
611
  | Some(next) =>
@@ -851,8 +842,8 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
851
842
  let cleanup = () => {
852
843
  watched->Array.forEach(subscription => subscription.unsubscribe())
853
844
  watched->Utils.Array.clearInPlace
854
- pokeTimeoutId->Utils.clearTimeoutRef
855
- stallTimeoutId->Utils.clearTimeoutRef
845
+ sourceManager.clock->Clock.clearRef(pokeTimeoutId)
846
+ sourceManager.clock->Clock.clearRef(stallTimeoutId)
856
847
  }
857
848
 
858
849
  let settled = ref(false)
@@ -922,32 +913,44 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
922
913
  // Re-armed because distrusting a stream is a one-shot — the next height it
923
914
  // delivers takes it back at its word — and the silence that earned it can
924
915
  // come straight back.
925
- let rec armPokeTimeout = () => pokeTimeoutId := Some(setTimeout(() => {
926
- pokeTimeoutId := None
927
- if !settled.contents {
928
- watched->Array.forEach(subscription => subscription.distrustStream())
929
- }
930
-
931
- // Re-checked: distrusting can answer the wait, and a timer armed
932
- // after the cleanup that follows is one nothing can ever clear.
933
- if !settled.contents {
934
- armPokeTimeout()
935
- }
936
- }, Utils.jitter(stallTimeout)))
916
+ let rec armPokeTimeout = () =>
917
+ pokeTimeoutId :=
918
+ Some(
919
+ sourceManager.clock.setTimeout(
920
+ ~label="stall poke",
921
+ () => {
922
+ pokeTimeoutId := None
923
+ if !settled.contents {
924
+ watched->Array.forEach(subscription => subscription.distrustStream())
925
+ }
926
+
927
+ // Re-checked: distrusting can answer the wait, and a timer armed
928
+ // after the cleanup that follows is one nothing can ever clear.
929
+ if !settled.contents {
930
+ armPokeTimeout()
931
+ }
932
+ },
933
+ sourceManager.clock->Clock.jitter(stallTimeout),
934
+ ),
935
+ )
937
936
 
938
937
  // Punctual, unlike the poke above: newBlockStallTimeout is a promise to the
939
938
  // operator about when they hear about a quiet chain, and spreading that
940
939
  // would report it early. Fires once — the warning is worth saying once per
941
940
  // wait, and a fallback stays recruited.
942
- let armStallTimeout = () => stallTimeoutId := Some(setTimeout(() => {
943
- stallTimeoutId := None
944
- if !settled.contents {
945
- stalled := true
946
-
947
- // Build fallback: non-disabled sources not in mainSources with a valid role, even with recent lastFailedAt
948
- let fallbackSources = []
949
- sourcesState->Array.forEach(
950
- sourceState => {
941
+ let armStallTimeout = () =>
942
+ stallTimeoutId :=
943
+ Some(
944
+ sourceManager.clock.setTimeout(
945
+ ~label="stall timeout",
946
+ () => {
947
+ stallTimeoutId := None
948
+ if !settled.contents {
949
+ stalled := true
950
+
951
+ // Build fallback: non-disabled sources not in mainSources with a valid role, even with recent lastFailedAt
952
+ let fallbackSources = []
953
+ sourcesState->Array.forEach(sourceState => {
951
954
  if (
952
955
  !sourceState.disabled &&
953
956
  !(mainSources->Array.includes(sourceState)) &&
@@ -959,37 +962,41 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
959
962
  ) {
960
963
  fallbackSources->Array.push(sourceState)
961
964
  }
962
- },
963
- )
964
-
965
- switch fallbackSources {
966
- | [] =>
967
- logger->Logger.warn(
968
- `No new blocks detected within ${(stallTimeout / 1000)
969
- ->Int.toString}s. Polling will continue at a reduced rate. For better reliability, refer to our RPC fallback guide: https://docs.envio.dev/docs/HyperIndex/rpc-sync`,
965
+ })
966
+
967
+ switch fallbackSources {
968
+ | [] =>
969
+ logger->Logger.warn(
970
+ `No new blocks detected within ${(stallTimeout / 1000)
971
+ ->Int.toString}s. Polling will continue at a reduced rate. For better reliability, refer to our RPC fallback guide: https://docs.envio.dev/docs/HyperIndex/rpc-sync`,
972
+ )
973
+ | _ =>
974
+ logger->Logger.warn(
975
+ `No new blocks detected within ${(stallTimeout / 1000)
976
+ ->Int.toString}s. Continuing polling with secondary RPC sources from the configuration.`,
977
+ )
978
+ }
979
+
980
+ // Recruited to poll, not to stream: see `watch`.
981
+ fallbackSources->Array.forEach(sourceState =>
982
+ sourceState->watch(~withStream=false)
970
983
  )
971
- | _ =>
972
- logger->Logger.warn(
973
- `No new blocks detected within ${(stallTimeout / 1000)
974
- ->Int.toString}s. Continuing polling with secondary RPC sources from the configuration.`,
975
- )
976
- }
977
984
 
978
- // Recruited to poll, not to stream: see `watch`.
979
- fallbackSources->Array.forEach(sourceState => sourceState->watch(~withStream=false))
980
-
981
- // A fallback recruited here can still be holding a stream from an
982
- // earlier wait that made it a primary, and a live stream is not
983
- // polled behind. It has to inherit the verdict the primaries
984
- // already earned — this wait has heard nothing for a whole window
985
- // — or it would sit silent behind that stream until the next
986
- // spread poke. Distrusting a waiter that already does so is a
987
- // no-op, so this reaches the new ones only.
988
- if !settled.contents {
989
- watched->Array.forEach(subscription => subscription.distrustStream())
985
+ // A fallback recruited here can still be holding a stream from an
986
+ // earlier wait that made it a primary, and a live stream is not
987
+ // polled behind. It has to inherit the verdict the primaries
988
+ // already earned — this wait has heard nothing for a whole window
989
+ // — or it would sit silent behind that stream until the next
990
+ // spread poke. Distrusting a waiter that already does so is a
991
+ // no-op, so this reaches the new ones only.
992
+ if !settled.contents {
993
+ watched->Array.forEach(subscription => subscription.distrustStream())
994
+ }
990
995
  }
991
- }
992
- }, stallTimeout))
996
+ },
997
+ stallTimeout,
998
+ ),
999
+ )
993
1000
 
994
1001
  armPokeTimeout()
995
1002
  armStallTimeout()
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Clock from "../Clock.res.mjs";
3
4
  import * as Utils from "../Utils.res.mjs";
4
5
  import * as Logger from "../Logger.res.mjs";
5
6
  import * as Source from "./Source.res.mjs";
@@ -153,7 +154,7 @@ function inFlightCount(sourceManager) {
153
154
  function getRateLimitTimeMs(sourceManager) {
154
155
  let startMs = sourceManager.activeRateLimitStartMs;
155
156
  return sourceManager.committedRateLimitTimeMs + (
156
- startMs !== undefined ? Date.now() - startMs : 0.0
157
+ startMs !== undefined ? sourceManager.clock.now() - startMs : 0.0
157
158
  );
158
159
  }
159
160
 
@@ -162,14 +163,14 @@ function getRateLimitResetInMs(sourceManager) {
162
163
  if (resetAt === undefined) {
163
164
  return;
164
165
  }
165
- let remaining = resetAt - Date.now();
166
+ let remaining = resetAt - sourceManager.clock.now();
166
167
  if (remaining > 0.0) {
167
168
  return remaining;
168
169
  }
169
170
  }
170
171
 
171
172
  function startRateLimitTimeout(sourceManager, resetMs) {
172
- let now = Date.now();
173
+ let now = sourceManager.clock.now();
173
174
  if (sourceManager.rateLimitWaiters === 0) {
174
175
  sourceManager.activeRateLimitStartMs = now;
175
176
  }
@@ -186,20 +187,15 @@ async function waitForRateLimitReset(sourceManager, resetMs, retry, logger) {
186
187
  retry: retry,
187
188
  waitMs: waitMs
188
189
  });
189
- let waitedAt = Date.now();
190
190
  startRateLimitTimeout(sourceManager, waitMs);
191
- await Utils.delay(waitMs);
192
- let short = waitedAt + waitMs - Date.now();
193
- if (short > 0) {
194
- await Utils.delay(Math.ceil(short) | 0);
195
- }
191
+ await Clock.delay(sourceManager.clock, "rate-limit reset", waitMs);
196
192
  sourceManager.rateLimitWaiters = sourceManager.rateLimitWaiters - 1 | 0;
197
193
  if (sourceManager.rateLimitWaiters !== 0) {
198
194
  return;
199
195
  }
200
196
  let startMs = sourceManager.activeRateLimitStartMs;
201
197
  if (startMs !== undefined) {
202
- sourceManager.committedRateLimitTimeMs = sourceManager.committedRateLimitTimeMs + Date.now() - startMs;
198
+ sourceManager.committedRateLimitTimeMs = sourceManager.committedRateLimitTimeMs + sourceManager.clock.now() - startMs;
203
199
  sourceManager.activeRateLimitStartMs = undefined;
204
200
  }
205
201
  sourceManager.activeRateLimitResetAtMs = undefined;
@@ -278,6 +274,7 @@ function makeGetHeightRetryInterval(initialRetryInterval, backoffMultiplicative,
278
274
 
279
275
  function make(sources, logger, isRealtime, timingOpt) {
280
276
  let timing = timingOpt !== undefined ? timingOpt : ({});
277
+ let clock = Stdlib_Option.getOr(timing.clock, Clock.real);
281
278
  let newBlockStallTimeout = Stdlib_Option.getOr(timing.newBlockStallTimeout, 60000);
282
279
  let newBlockStallTimeoutRealtime = Stdlib_Option.getOr(timing.newBlockStallTimeoutRealtime, 20000);
283
280
  let stalledPollingInterval = Stdlib_Option.getOr(timing.stalledPollingInterval, 5000);
@@ -293,7 +290,7 @@ function make(sources, logger, isRealtime, timingOpt) {
293
290
  let requestStats = {};
294
291
  return {
295
292
  source: source,
296
- feed: HeightFeed.make(source, logger, stats => recordStatsInto(requestStats, stats), getHeightRetryInterval$1, Stdlib_Option.getOr(timing.heightPollTimeout, 60000)),
293
+ feed: HeightFeed.make(source, logger, stats => recordStatsInto(requestStats, stats), getHeightRetryInterval$1, Stdlib_Option.getOr(timing.heightPollTimeout, 60000), clock),
297
294
  disabled: false,
298
295
  lastFailedAt: undefined,
299
296
  requestStats: requestStats
@@ -314,7 +311,7 @@ function make(sources, logger, isRealtime, timingOpt) {
314
311
  waitingLogged: false,
315
312
  fetchingPartitionsCount: 0,
316
313
  recoveryTimeout: recoveryTimeout,
317
- retryBackoffScale: Stdlib_Option.getOr(timing.retryBackoffScale, 1),
314
+ clock: clock,
318
315
  disableReason: undefined,
319
316
  hasRealtime: hasRealtime,
320
317
  committedRateLimitTimeMs: 0.0,
@@ -422,7 +419,7 @@ function compareByOldestFailure(a, b) {
422
419
  }
423
420
 
424
421
  function getNextSources(sourceManager, isRealtime, excludedSources) {
425
- let now = Date.now();
422
+ let now = sourceManager.clock.now();
426
423
  let workingPrimarySources = [];
427
424
  let allPrimarySources = [];
428
425
  let workingSecondarySources = [];
@@ -475,7 +472,7 @@ function getNextSource(sourceManager, isRealtime, excludedSources) {
475
472
  }
476
473
 
477
474
  function sleepBackoff(sourceManager, millis) {
478
- return Utils.delay(millis * sourceManager.retryBackoffScale | 0);
475
+ return Clock.delay(sourceManager.clock, "retry backoff", millis);
479
476
  }
480
477
 
481
478
  async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillisOpt, failoverAfterRetriesOpt, excludedSources) {
@@ -483,7 +480,7 @@ async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime,
483
480
  let failoverAfterRetries = failoverAfterRetriesOpt !== undefined ? failoverAfterRetriesOpt : 2;
484
481
  let switchedToWorkingSource;
485
482
  if (retry >= failoverAfterRetries && (retry - failoverAfterRetries | 0) % 2 === 0) {
486
- let now = Date.now();
483
+ let now = sourceManager.clock.now();
487
484
  sourceState.lastFailedAt = now;
488
485
  let next = getNextSource(sourceManager, isRealtime, excludedSources);
489
486
  if (next !== undefined) {
@@ -599,8 +596,8 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
599
596
  let cleanup = () => {
600
597
  watched.forEach(subscription => subscription.unsubscribe());
601
598
  Utils.$$Array.clearInPlace(watched);
602
- Utils.clearTimeoutRef(pokeTimeoutId);
603
- Utils.clearTimeoutRef(stallTimeoutId);
599
+ Clock.clearRef(sourceManager.clock, pokeTimeoutId);
600
+ Clock.clearRef(sourceManager.clock, stallTimeoutId);
604
601
  };
605
602
  let settled = {
606
603
  contents: false
@@ -648,7 +645,7 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
648
645
  return;
649
646
  }
650
647
  let armPokeTimeout = () => {
651
- pokeTimeoutId.contents = Primitive_option.some(setTimeout(() => {
648
+ pokeTimeoutId.contents = Primitive_option.some(sourceManager.clock.setTimeout("stall poke", () => {
652
649
  pokeTimeoutId.contents = undefined;
653
650
  if (!settled.contents) {
654
651
  watched.forEach(subscription => subscription.distrustStream());
@@ -656,10 +653,10 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
656
653
  if (!settled.contents) {
657
654
  return armPokeTimeout();
658
655
  }
659
- }, Utils.jitter(stallTimeout)));
656
+ }, Clock.jitter(sourceManager.clock, stallTimeout)));
660
657
  };
661
658
  let armStallTimeout = () => {
662
- stallTimeoutId.contents = Primitive_option.some(setTimeout(() => {
659
+ stallTimeoutId.contents = Primitive_option.some(sourceManager.clock.setTimeout("stall timeout", () => {
663
660
  stallTimeoutId.contents = undefined;
664
661
  if (settled.contents) {
665
662
  return;
@@ -17,11 +17,10 @@ type timing = {
17
17
  reducedPollingInterval?: int,
18
18
  recoveryTimeout?: float,
19
19
  getHeightRetryInterval?: (~retry: int) => int,
20
- // Multiplies the sleep before retrying an items query or a block-hash
21
- // lookup, but not the backoff it logs. A rate limit's wait is the server's
22
- // to set and isn't scaled.
23
- retryBackoffScale?: float,
24
20
  heightPollTimeout?: int,
21
+ // Every timer the manager and its height feeds arm, and every time they
22
+ // read. A test passes one that only moves when the test advances it.
23
+ clock?: Clock.t,
25
24
  }
26
25
 
27
26
  let make: (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime: bool, ~timing: timing=?) => t
@@ -25,6 +25,7 @@ let makeProbeSources = (chainConfig: Config.chain, ~logger, ~lowercaseAddresses)
25
25
  ~blockStore=BlockStore.make(~ecosystem=chainConfig.ecosystem),
26
26
  ~transactionStore=TransactionStore.make(~ecosystem=chainConfig.ecosystem),
27
27
  ~logger,
28
+ ~maxAddressesPerRequest=Env.maxAddrInPartition,
28
29
  )
29
30
  }
30
31
 
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Env from "../Env.res.mjs";
3
4
  import * as Utils from "../Utils.res.mjs";
4
5
  import * as Logger from "../Logger.res.mjs";
5
6
  import * as ChainId from "../ChainId.res.mjs";
@@ -14,7 +15,7 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
14
15
 
15
16
  function makeProbeSources(chainConfig, logger, lowercaseAddresses) {
16
17
  let addressStore = AddressStore.make(chainConfig.ecosystem, []);
17
- return ChainSources.make(chainConfig, [], undefined, addressStore, lowercaseAddresses, BlockStore.make(chainConfig.ecosystem), TransactionStore.make(chainConfig.ecosystem), logger);
18
+ return ChainSources.make(chainConfig, [], undefined, addressStore, lowercaseAddresses, BlockStore.make(chainConfig.ecosystem), TransactionStore.make(chainConfig.ecosystem), logger, Env.maxAddrInPartition);
18
19
  }
19
20
 
20
21
  function candidateSources(sources) {
@@ -132,4 +133,4 @@ export {
132
133
  resolveHeadOrThrow,
133
134
  resolveAllOrThrow,
134
135
  }
135
- /* Utils Not a pure module */
136
+ /* Env Not a pure module */
@@ -277,7 +277,7 @@ let make = (
277
277
  {height, requestStats: [{method: "getHeight", seconds}]}
278
278
  },
279
279
  getItemsOrThrow,
280
- createHeightSubscription: (~onHeight, ~onStatus) =>
281
- HyperSyncSSE.subscribe(~hyperSyncUrl=endpointUrl, ~apiToken, ~onHeight, ~onStatus),
280
+ createHeightSubscription: (~clock, ~onHeight, ~onStatus) =>
281
+ HyperSyncSSE.subscribe(~clock, ~hyperSyncUrl=endpointUrl, ~apiToken, ~onHeight, ~onStatus),
282
282
  }
283
283
  }
@@ -205,7 +205,7 @@ function make(param) {
205
205
  };
206
206
  },
207
207
  getItemsOrThrow: getItemsOrThrow,
208
- createHeightSubscription: (onHeight, onStatus) => HyperSyncSSE.subscribe(endpointUrl, apiToken, onHeight, onStatus)
208
+ createHeightSubscription: (clock, onHeight, onStatus) => HyperSyncSSE.subscribe(clock, endpointUrl, apiToken, onHeight, onStatus)
209
209
  };
210
210
  }
211
211
 
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Contract-call signatures and ABI type lists as graph codegen writes them.
3
+ * Free of envio imports: the graph-ts shim reads them too.
4
+ */
5
+
6
+ export type ParsedSignature = {
7
+ name: string;
8
+ inputs: string;
9
+ outputs: string;
10
+ };
11
+
12
+ /**
13
+ * graph codegen emits `"balanceOf(address):(uint256)"`. Types only, no names —
14
+ * which is all the ABI codec needs.
15
+ */
16
+ export function parseSignature(signature: string): ParsedSignature {
17
+ const open = signature.indexOf("(");
18
+ if (open === -1) {
19
+ throw new Error(`Unreadable contract call signature "${signature}"`);
20
+ }
21
+ const name = signature.slice(0, open);
22
+
23
+ let depth = 0;
24
+ let close = -1;
25
+ for (let index = open; index < signature.length; index++) {
26
+ const char = signature[index];
27
+ if (char === "(") depth++;
28
+ if (char === ")") {
29
+ depth--;
30
+ if (depth === 0) {
31
+ close = index;
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ if (close === -1) {
37
+ throw new Error(`Unreadable contract call signature "${signature}"`);
38
+ }
39
+
40
+ const inputs = signature.slice(open + 1, close);
41
+ const rest = signature.slice(close + 1).replace(/^:/, "");
42
+ const outputs = rest.startsWith("(") ? rest.slice(1, -1) : rest;
43
+
44
+ return { name, inputs, outputs };
45
+ }
46
+
47
+ /** `address,(uint256,bytes)` → `["address", "(uint256,bytes)"]`. */
48
+ export function splitTypes(list: string): string[] {
49
+ const types: string[] = [];
50
+ let depth = 0;
51
+ let start = 0;
52
+ for (let index = 0; index < list.length; index++) {
53
+ const char = list[index];
54
+ if (char === "(") depth++;
55
+ else if (char === ")") depth--;
56
+ else if (char === "," && depth === 0) {
57
+ types.push(list.slice(start, index));
58
+ start = index + 1;
59
+ }
60
+ }
61
+ if (list.length > 0) types.push(list.slice(start));
62
+ return types;
63
+ }
64
+
65
+ type AbiParam = { type: string; components?: AbiParam[] };
66
+
67
+ /** An ABI parameter's type as graph codegen spells it: a tuple as `(…)`, keeping its array suffix. */
68
+ function abiTypeString(param: AbiParam): string {
69
+ if (!param.type.startsWith("tuple")) return param.type;
70
+ return `(${(param.components ?? []).map(abiTypeString).join(",")})${param.type.slice("tuple".length)}`;
71
+ }
72
+
73
+ /** The first function named `name` in an ABI, as graph codegen's call signature: `name(inputs):(outputs)`. */
74
+ export function functionSignature(abi: unknown, name: string): string | null {
75
+ const record = abi as { abi?: unknown; compilerOutput?: { abi?: unknown } } | null;
76
+ const entries = Array.isArray(abi) ? abi : (record?.abi ?? record?.compilerOutput?.abi);
77
+ if (!Array.isArray(entries)) return null;
78
+ const found = entries.find(
79
+ (entry) => (entry?.type ?? "function") === "function" && entry?.name === name,
80
+ ) as { inputs?: AbiParam[]; outputs?: AbiParam[] } | undefined;
81
+ if (!found) return null;
82
+ const list = (params: AbiParam[] | undefined) => (params ?? []).map(abiTypeString).join(",");
83
+ return `${name}(${list(found.inputs)}):(${list(found.outputs)})`;
84
+ }