envio 3.12.1 → 3.13.0-rc.0

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 (103) hide show
  1. package/package.json +6 -8
  2. package/src/BatchProcessing.res +15 -14
  3. package/src/BatchProcessing.res.mjs +8 -15
  4. package/src/Bin.res +24 -17
  5. package/src/Bin.res.mjs +5 -0
  6. package/src/ChainFetching.res +18 -24
  7. package/src/ChainFetching.res.mjs +14 -15
  8. package/src/ChainState.res +103 -38
  9. package/src/ChainState.res.mjs +79 -22
  10. package/src/ChainState.resi +14 -2
  11. package/src/Config.res +32 -0
  12. package/src/Config.res.mjs +24 -0
  13. package/src/Core.res +4 -0
  14. package/src/Core.res.mjs +4 -0
  15. package/src/CrossChainState.res +109 -11
  16. package/src/CrossChainState.res.mjs +71 -7
  17. package/src/CrossChainState.resi +11 -1
  18. package/src/Env.res +4 -0
  19. package/src/ErrorHandling.res +15 -4
  20. package/src/ErrorHandling.res.mjs +16 -5
  21. package/src/FinalizeBackfill.res +10 -4
  22. package/src/FinalizeBackfill.res.mjs +5 -3
  23. package/src/HandlerRegister.res +0 -37
  24. package/src/HandlerRegister.res.mjs +0 -18
  25. package/src/IndexerLoop.res +2 -0
  26. package/src/IndexerLoop.res.mjs +1 -0
  27. package/src/IndexerState.res +43 -1
  28. package/src/IndexerState.res.mjs +48 -4
  29. package/src/IndexerState.resi +11 -0
  30. package/src/Main.res +128 -268
  31. package/src/Main.res.mjs +27 -153
  32. package/src/Metrics.res +268 -102
  33. package/src/Metrics.res.mjs +227 -48
  34. package/src/Persistence.res +30 -2
  35. package/src/Persistence.res.mjs +12 -3
  36. package/src/PgStorage.res +19 -11
  37. package/src/PgStorage.res.mjs +13 -10
  38. package/src/Rollback.res +3 -3
  39. package/src/Server.res +178 -0
  40. package/src/Server.res.mjs +143 -0
  41. package/src/SimulateItems.res +1 -1
  42. package/src/SimulateItems.res.mjs +1 -1
  43. package/src/Supervisor.res +542 -0
  44. package/src/Supervisor.res.mjs +455 -0
  45. package/src/TestIndexer.res +1 -1
  46. package/src/TestIndexer.res.mjs +2 -2
  47. package/src/Worker.res +101 -0
  48. package/src/Worker.res.mjs +81 -0
  49. package/src/bindings/NodeJs.res +50 -0
  50. package/src/bindings/NodeJs.res.mjs +8 -1
  51. package/src/db/InternalTable.res +8 -1
  52. package/src/db/InternalTable.res.mjs +5 -1
  53. package/src/sources/AddressSet.res +5 -3
  54. package/src/sources/AddressStore.res +19 -11
  55. package/src/sources/AddressStore.res.mjs +2 -7
  56. package/src/sources/BlockStore.res +10 -9
  57. package/src/sources/BlockStore.res.mjs +4 -4
  58. package/src/sources/ChainSources.res +4 -0
  59. package/src/sources/ChainSources.res.mjs +2 -2
  60. package/src/sources/EvmChain.res +4 -0
  61. package/src/sources/EvmChain.res.mjs +3 -1
  62. package/src/sources/EvmEventItem.res +56 -0
  63. package/src/sources/EvmEventItem.res.mjs +32 -0
  64. package/src/sources/EvmHyperSyncSource.res +7 -37
  65. package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
  66. package/src/sources/EvmRpcClient.res +41 -59
  67. package/src/sources/EvmRpcClient.res.mjs +4 -54
  68. package/src/sources/EvmRpcWs.res +4 -1
  69. package/src/sources/EvmRpcWs.res.mjs +6 -2
  70. package/src/sources/FuelHyperSyncSource.res +6 -4
  71. package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
  72. package/src/sources/HeightFeed.res +7 -1
  73. package/src/sources/HeightFeed.res.mjs +9 -7
  74. package/src/sources/HyperSyncClient.res +1 -14
  75. package/src/sources/RequestStat.res +7 -0
  76. package/src/sources/RequestStat.res.mjs +14 -1
  77. package/src/sources/RpcSource.res +100 -1043
  78. package/src/sources/RpcSource.res.mjs +81 -1056
  79. package/src/sources/SimulateSource.res +2 -5
  80. package/src/sources/SimulateSource.res.mjs +2 -3
  81. package/src/sources/Source.res +34 -12
  82. package/src/sources/Source.res.mjs +11 -0
  83. package/src/sources/SourceManager.res +111 -31
  84. package/src/sources/SourceManager.res.mjs +65 -31
  85. package/src/sources/SourceManager.resi +4 -0
  86. package/src/sources/StartBlockResolver.res +8 -5
  87. package/src/sources/StartBlockResolver.res.mjs +4 -2
  88. package/src/sources/SvmHyperSyncSource.res +4 -2
  89. package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
  90. package/src/sources/TransactionStore.res +9 -6
  91. package/src/sources/TransactionStore.res.mjs +2 -2
  92. package/src/tui/Tui.res +10 -0
  93. package/src/tui/Tui.res.mjs +15 -0
  94. package/src/tui/components/SyncETA.res +12 -6
  95. package/src/tui/components/SyncETA.res.mjs +12 -8
  96. package/src/LazyLoader.res +0 -135
  97. package/src/LazyLoader.res.mjs +0 -118
  98. package/src/bindings/SDSL.res +0 -12
  99. package/src/bindings/SDSL.res.mjs +0 -9
  100. package/src/bindings/Yargs.res +0 -8
  101. package/src/bindings/Yargs.res.mjs +0 -2
  102. package/src/sources/Rpc.res +0 -185
  103. package/src/sources/Rpc.res.mjs +0 -183
package/src/Main.res CHANGED
@@ -1,80 +1,3 @@
1
- // The public console/state chain shape. Kept to exactly this field set for
2
- // backward compatibility with consumers like RACE — new metric fields stay off
3
- // the HTTP response.
4
- type chainData = {
5
- chainId: ChainId.t,
6
- poweredByHyperSync: bool,
7
- firstEventBlockNumber: option<int>,
8
- latestProcessedBlock: option<int>,
9
- timestampCaughtUpToHeadOrEndblock: option<Date.t>,
10
- numEventsProcessed: float,
11
- latestFetchedBlockNumber: int,
12
- // Need this for API backwards compatibility
13
- @as("currentBlockHeight")
14
- knownHeight: int,
15
- numBatchesFetched: int,
16
- startBlock: int,
17
- endBlock: option<int>,
18
- numAddresses: int,
19
- }
20
- @tag("status")
21
- type state =
22
- | @as("disabled") Disabled({})
23
- | @as("initializing") Initializing({})
24
- | @as("active")
25
- Active({
26
- envioVersion: string,
27
- chains: array<chainData>,
28
- indexerStartTime: Date.t,
29
- isPreRegisteringDynamicContracts: bool,
30
- rollbackOnReorg: bool,
31
- })
32
-
33
- let toChainData = (m: Metrics.chainMetrics): chainData => {
34
- chainId: m.chainId,
35
- poweredByHyperSync: m.poweredByHyperSync,
36
- firstEventBlockNumber: m.firstEventBlockNumber,
37
- latestProcessedBlock: m.latestProcessedBlock,
38
- timestampCaughtUpToHeadOrEndblock: m.timestampCaughtUpToHeadOrEndblock,
39
- numEventsProcessed: m.numEventsProcessed,
40
- latestFetchedBlockNumber: m.latestFetchedBlockNumber,
41
- knownHeight: m.knownHeight,
42
- numBatchesFetched: m.numBatchesFetched,
43
- startBlock: m.startBlock,
44
- endBlock: m.endBlock,
45
- numAddresses: m.numAddresses,
46
- }
47
-
48
- let chainDataSchema = S.schema((s): chainData => {
49
- chainId: s.matches(ChainId.schema),
50
- poweredByHyperSync: s.matches(S.bool),
51
- firstEventBlockNumber: s.matches(S.option(S.int)),
52
- latestProcessedBlock: s.matches(S.option(S.int)),
53
- timestampCaughtUpToHeadOrEndblock: s.matches(S.option(S.datetime(S.string))),
54
- numEventsProcessed: s.matches(S.float),
55
- latestFetchedBlockNumber: s.matches(S.int),
56
- knownHeight: s.matches(S.int),
57
- numBatchesFetched: s.matches(S.int),
58
- startBlock: s.matches(S.int),
59
- endBlock: s.matches(S.option(S.int)),
60
- numAddresses: s.matches(S.int),
61
- })
62
- let stateSchema = S.union([
63
- S.literal(Disabled({})),
64
- S.literal(Initializing({})),
65
- S.schema(s => Active({
66
- envioVersion: s.matches(S.string),
67
- chains: s.matches(S.array(chainDataSchema)),
68
- indexerStartTime: s.matches(S.datetime(S.string)),
69
- // Keep the field, since Dev Console expects it to be present
70
- isPreRegisteringDynamicContracts: false,
71
- rollbackOnReorg: s.matches(S.bool),
72
- })),
73
- ])
74
-
75
- // Runtime state lives in the process-wide `EnvioGlobal` record (shared
76
- // across duplicate envio module instances); the slots are opaque there, so
77
- // cast them to the real types here.
78
1
  let getIndexerState = () =>
79
2
  EnvioGlobal.value.indexerState->(Utils.magic: option<unknown> => option<IndexerState.t>)
80
3
  let setIndexerState = (state: IndexerState.t) =>
@@ -488,111 +411,8 @@ let getGlobalIndexer = (): 'indexer => {
488
411
  Utils.Proxy.make(Utils.Object.createNullObject(), traps)->(Utils.magic: {..} => 'indexer)
489
412
  }
490
413
 
491
- let startServer = (
492
- ~getMetrics: unit => option<Metrics.t>,
493
- ~envioVersion: string,
494
- ~persistence: Persistence.t,
495
- ~isDevelopmentMode: bool,
496
- ) => {
497
- open Express
498
-
499
- let app = make()
500
-
501
- let consoleCorsMiddleware = (req, res, next) => {
502
- switch req.headers->Dict.get("origin") {
503
- | Some(origin) if origin === Env.prodEnvioAppUrl || origin === Env.envioAppUrl =>
504
- res->setHeader("Access-Control-Allow-Origin", origin)
505
- | _ => ()
506
- }
507
-
508
- res->setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
509
- res->setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
510
-
511
- if req.method === Rest.Options {
512
- res->sendStatus(200)
513
- } else {
514
- next()
515
- }
516
- }
517
- app->useFor("/console", consoleCorsMiddleware)
518
- app->useFor("/metrics", consoleCorsMiddleware)
519
- app->useFor("/metrics/runtime", consoleCorsMiddleware)
520
-
521
- app->get("/healthz", (_req, res) => {
522
- // this is the machine readable port used in kubernetes to check the health of this service.
523
- // aditional health information could be added in the future (info about errors, back-offs, etc).
524
- res->sendStatus(200)
525
- })
526
-
527
- app->get("/console/state", (_req, res) => {
528
- let state = if !isDevelopmentMode {
529
- Disabled({})
530
- } else {
531
- switch getMetrics() {
532
- | None => Initializing({})
533
- | Some(metrics) =>
534
- Active({
535
- envioVersion,
536
- chains: metrics.chains->Array.map(toChainData),
537
- indexerStartTime: metrics.startTime,
538
- isPreRegisteringDynamicContracts: false,
539
- rollbackOnReorg: metrics.rollbackEnabled,
540
- })
541
- }
542
- }
543
-
544
- res->json(state->S.reverseConvertToJsonOrThrow(stateSchema))
545
- })
546
-
547
- app->post("/console/syncCache", (_req, res) => {
548
- if isDevelopmentMode {
549
- (persistence->Persistence.getInitializedStorageOrThrow).dumpEffectCache()
550
- ->Promise.thenResolve(_ => res->json(Boolean(true)))
551
- ->Promise.ignore
552
- } else {
553
- res->json(Boolean(false))
554
- }
555
- })
556
-
557
- Metrics.startRuntimeCollectors()
558
-
559
- app->get("/metrics", (_req, res) => {
560
- res->set("Content-Type", Metrics.contentType)
561
- let _ = res->endWithData(Metrics.collect(~metrics=getMetrics()))
562
- })
563
-
564
- app->get("/metrics/runtime", (_req, res) => {
565
- res->set("Content-Type", Metrics.contentType)
566
- let _ = res->endWithData(Metrics.collectRuntime())
567
- })
568
-
569
- let server = app->listen(Env.serverPort)
570
- server->Express.onError(err => {
571
- let code = (err->(Utils.magic: JsExn.t => {..}))["code"]
572
- if code === "EADDRINUSE" {
573
- Logging.error(
574
- `Port ${Env.serverPort->Int.toString} is already in use. To fix this either:` ++
575
- `\n 1. Kill the process using the port: lsof -ti :${Env.serverPort->Int.toString} | xargs kill -9` ++ `\n 2. Use a different port by setting the ENVIO_INDEXER_PORT environment variable: ENVIO_INDEXER_PORT=9899 envio start`,
576
- )
577
- } else {
578
- Logging.errorWithExn(err, "Failed to start indexer server")
579
- }
580
- NodeJs.process->NodeJs.exitWithCode(Failure)
581
- })
582
- }
583
-
584
- type args = {@as("tui-off") tuiOff?: bool}
585
-
586
- type process
587
- @val external process: process = "process"
588
- @get external argv: process => 'a = "argv"
589
-
590
- type mainArgs = Yargs.parsedArgs<args>
591
-
592
414
  // The RPC-stripped public config that the storage layer persists in
593
415
  // `envio_info` (on initialize) and validates against (on resume).
594
- let getEnvioInfo = () => Config.getPublicConfigJson()->Config.stripSensitiveData
595
-
596
416
  let migrate = async (~reset) => {
597
417
  let config = Config.load()
598
418
  let persistence = PgStorage.makePersistenceFromConfig(~config)
@@ -600,7 +420,7 @@ let migrate = async (~reset) => {
600
420
  ~reset,
601
421
  ~chainConfigs=config.chainMap->ChainMap.values,
602
422
  ~contractMapping=config.contractMapping,
603
- ~envioInfo=getEnvioInfo(),
423
+ ~envioInfo=Config.envioInfo(),
604
424
  ~resetCommand="envio local db-migrate setup",
605
425
  ~runCommand=None,
606
426
  ~lowercaseAddresses=config.lowercaseAddresses,
@@ -622,6 +442,115 @@ let dropSchema = async () => {
622
442
  // context, so callers should act on it (exit / re-throw) without logging again.
623
443
  exception FatalError(exn)
624
444
 
445
+ %%private(
446
+ let startIndexer = async (
447
+ ~config: Config.t,
448
+ ~persistence: option<Persistence.t>=?,
449
+ ~reset=false,
450
+ ~isTest=false,
451
+ ~exitAfterFirstEventBlock=false,
452
+ ~patchConfig: option<(Config.t, HandlerRegister.registrationsByChainId) => Config.t>=?,
453
+ ) => {
454
+ // A worker reports to its supervisor, which draws for the whole run.
455
+ let shouldUseTui = Tui.shouldUse(~suppressed=isTest || Worker.isEnabled)
456
+ // isDevelopmentMode controls whether the indexer stays alive after all
457
+ // chains finish (keepProcessAlive) and whether the console API is exposed.
458
+ // Set by `envio dev` via the public config's `isDev` field; `envio start`
459
+ // leaves it false so the process exits cleanly when indexing completes.
460
+ let isDevelopmentMode = !isTest && config.isDev
461
+ // Initialized first so the exported indexer value contains state from the
462
+ // database when handler files are loaded (they may access the indexer at
463
+ // module top level).
464
+ let persistence = switch persistence {
465
+ | Some(p) => p
466
+ | None => PgStorage.makePersistenceFromConfig(~config)
467
+ }
468
+ setGlobalPersistence(persistence)
469
+ await persistence->Persistence.initForRun(
470
+ ~config,
471
+ ~reset,
472
+ ~isDevelopmentMode,
473
+ ~requireInitialized=config.isolated,
474
+ )
475
+
476
+ // Loads user handler files, which register handler/contractRegister/where
477
+ // state into the global `HandlerRegister` registry as a side effect; this
478
+ // returns that state resolved into per-chain registrations. `config` itself
479
+ // is never mutated by registration — it holds only event definitions.
480
+ let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
481
+ let config = if isTest {
482
+ {...config, shouldRollbackOnReorg: false}
483
+ } else {
484
+ config
485
+ }
486
+
487
+ let config = switch patchConfig {
488
+ | Some(patchConfig) => patchConfig(config, registrationsByChainId)
489
+ | None => config
490
+ }
491
+ // The single fatal-error handler, invoked once via IndexerState.errorExit.
492
+ // It logs the failure once (with chain context) and rejects the run wrapped in
493
+ // `FatalError` so callers know it's already logged — `Bin.res` just exits, the
494
+ // test worker unwraps and re-throws it to the parent thread. `runUntilFatalError`
495
+ // only ever rejects: on a clean run it stays pending and the process exits via
496
+ // ExitOnCaughtUp / when the indexer loop drains.
497
+ let onErrorReject = ref(None)
498
+ let runUntilFatalError: promise<unit> = Promise.make((_resolve, reject) =>
499
+ onErrorReject := Some(reject)
500
+ )
501
+ // `onErrorReject` is filled synchronously by `Promise.make` above, before the
502
+ // indexer can run and call `onError`, so it's always present here.
503
+ let onError = (errHandler: ErrorHandling.t) => {
504
+ errHandler->ErrorHandling.log
505
+ (onErrorReject.contents->Option.getUnsafe)(FatalError(errHandler.exn->Utils.prettifyExn))
506
+ }
507
+ let envioVersion = Utils.EnvioPackage.value.version
508
+
509
+ let getMetrics = () => getIndexerState()->Option.map(IndexerState.toMetrics)
510
+ let dumpEffectCache = () =>
511
+ (persistence->Persistence.getInitializedStorageOrThrow).dumpEffectCache()
512
+
513
+ // A worker reports through its supervisor, which owns the one server and the
514
+ // one display the run has.
515
+ if !isTest && !Worker.isEnabled {
516
+ Metrics.startRuntimeCollectors()
517
+ Server.startServer(
518
+ ~onSyncCache=() => dumpEffectCache()->Promise.thenResolve(ignore),
519
+ ~collectRuntime=Metrics.collectRuntime,
520
+ ~isDevelopmentMode,
521
+ ~envioVersion,
522
+ ~getMetrics,
523
+ )
524
+ }
525
+
526
+ let state = IndexerState.makeFromDbState(
527
+ ~config,
528
+ ~persistence,
529
+ ~initialState=persistence->Persistence.getInitializedState,
530
+ ~registrationsByChainId,
531
+ ~isDevelopmentMode,
532
+ ~shouldUseTui,
533
+ ~exitAfterFirstEventBlock,
534
+ ~holdRealtime=Worker.config->Option.mapOr(false, worker => worker.holdRealtime),
535
+ ~onError,
536
+ )
537
+ if shouldUseTui {
538
+ let _rerender = Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
539
+ }
540
+ Worker.bindRun(
541
+ ~getMetrics=() => state->IndexerState.toMetrics,
542
+ ~onReleaseRealtime=() => state->IndexerState.releaseRealtime,
543
+ )
544
+ setIndexerState(state)
545
+ state->IndexerLoop.start
546
+ await runUntilFatalError
547
+ }
548
+ )
549
+
550
+ // Starts this process's part of a run: the group's supervisor when the budget
551
+ // and the schema afford splitting the chains across processes, and the indexer
552
+ // itself otherwise. A worker is already one process's part, so it never splits
553
+ // again — `planForRun` refuses an isolated config.
625
554
  let start = async (
626
555
  ~persistence: option<Persistence.t>=?,
627
556
  ~reset=false,
@@ -629,93 +558,24 @@ let start = async (
629
558
  ~exitAfterFirstEventBlock=false,
630
559
  ~patchConfig: option<(Config.t, HandlerRegister.registrationsByChainId) => Config.t>=?,
631
560
  ) => {
632
- let mainArgs: mainArgs = process->argv->Yargs.hideBin->Yargs.yargs->Yargs.argv
633
- let explicitTui = switch mainArgs.tuiOff {
634
- | Some(off) => Some(!off)
635
- | None => Env.tuiEnvVar
636
- }
637
- let shouldUseTui = switch (isTest, explicitTui) {
638
- | (true, _) => false
639
- | (_, Some(tui)) => tui
640
- | (_, None) => !Envio.isNonInteractive()
641
- }
642
- // Initialize persistence first so the exported indexer value contains state from the database
643
- // when handler files are loaded (they may access the indexer at module top level).
644
- let config = Config.load()
645
- // isDevelopmentMode controls whether the indexer stays alive after all
646
- // chains finish (keepProcessAlive) and whether the console API is exposed.
647
- // Set by `envio dev` via the public config's `isDev` field; `envio start`
648
- // leaves it false so the process exits cleanly when indexing completes.
649
- let isDevelopmentMode = !isTest && config.isDev
650
- let persistence = switch persistence {
651
- | Some(p) => p
652
- | None => PgStorage.makePersistenceFromConfig(~config)
653
- }
654
- setGlobalPersistence(persistence)
655
- await persistence->Persistence.init(
656
- ~reset,
657
- ~chainConfigs=config.chainMap->ChainMap.values,
658
- ~contractMapping=config.contractMapping,
659
- ~envioInfo=getEnvioInfo(),
660
- ~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
661
- ~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
662
- ~lowercaseAddresses=config.lowercaseAddresses,
663
- ~requireInitialized=config.isolated,
561
+ // A worker parses the project's files itself rather than being handed the
562
+ // config: a public config carries every contract's ABI, which is far more
563
+ // than a spawn environment should. What the command decided rides along
564
+ // instead, and re-applying it is what makes the two configs the same one.
565
+ Worker.config->Option.forEach(({chainIds, isDev}) =>
566
+ Config.prime(Config.getPublicConfigJson()->Config.withCommandFields(~chainIds, ~isDev))
664
567
  )
665
-
666
- // Loads user handler files, which register handler/contractRegister/where
667
- // state into the global `HandlerRegister` registry as a side effect; this
668
- // returns that state resolved into per-chain registrations. `config` itself
669
- // is never mutated by registration — it holds only event definitions.
670
- let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
671
- let config = if isTest {
672
- {...config, shouldRollbackOnReorg: false}
673
- } else {
674
- config
675
- }
676
-
677
- let config = switch patchConfig {
678
- | Some(patchConfig) => patchConfig(config, registrationsByChainId)
679
- | None => config
680
- }
681
- // The single fatal-error handler, invoked once via IndexerState.errorExit.
682
- // It logs the failure once (with chain context) and rejects the run wrapped in
683
- // `FatalError` so callers know it's already logged — `Bin.res` just exits, the
684
- // test worker unwraps and re-throws it to the parent thread. `runUntilFatalError`
685
- // only ever rejects: on a clean run it stays pending and the process exits via
686
- // ExitOnCaughtUp / when the indexer loop drains.
687
- let onErrorReject = ref(None)
688
- let runUntilFatalError: promise<unit> = Promise.make((_resolve, reject) =>
689
- onErrorReject := Some(reject)
690
- )
691
- // `onErrorReject` is filled synchronously by `Promise.make` above, before the
692
- // indexer can run and call `onError`, so it's always present here.
693
- let onError = (errHandler: ErrorHandling.t) => {
694
- errHandler->ErrorHandling.log
695
- (onErrorReject.contents->Option.getUnsafe)(FatalError(errHandler.exn->Utils.prettifyExn))
696
- }
697
- let envioVersion = Utils.EnvioPackage.value.version
698
-
699
- let getMetrics = () => getIndexerState()->Option.map(IndexerState.toMetrics)
700
-
701
- if !isTest {
702
- startServer(~persistence, ~isDevelopmentMode, ~envioVersion, ~getMetrics)
703
- }
704
-
705
- let state = IndexerState.makeFromDbState(
706
- ~config,
707
- ~persistence,
708
- ~initialState=persistence->Persistence.getInitializedState,
709
- ~registrationsByChainId,
710
- ~isDevelopmentMode,
711
- ~shouldUseTui,
712
- ~exitAfterFirstEventBlock,
713
- ~onError,
714
- )
715
- if shouldUseTui {
716
- let _rerender = Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
568
+ let config = Config.load()
569
+ switch isTest ? None : Supervisor.planForRun(~config) {
570
+ | Some(workers) => await Supervisor.run(~config, ~workers, ~reset)
571
+ | None =>
572
+ await startIndexer(
573
+ ~config,
574
+ ~persistence?,
575
+ ~reset,
576
+ ~isTest,
577
+ ~exitAfterFirstEventBlock,
578
+ ~patchConfig?,
579
+ )
717
580
  }
718
- setIndexerState(state)
719
- state->IndexerLoop.start
720
- await runUntilFatalError
721
581
  }
package/src/Main.res.mjs CHANGED
@@ -1,85 +1,31 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Env from "./Env.res.mjs";
4
3
  import * as Tui from "./tui/Tui.res.mjs";
5
- import * as Envio from "./Envio.res.mjs";
6
4
  import * as Utils from "./Utils.res.mjs";
7
5
  import * as Config from "./Config.res.mjs";
6
+ import * as Server from "./Server.res.mjs";
7
+ import * as Worker from "./Worker.res.mjs";
8
8
  import * as ChainId from "./ChainId.res.mjs";
9
- import * as Logging from "./Logging.res.mjs";
10
9
  import * as Metrics from "./Metrics.res.mjs";
11
- import Express from "express";
12
- import * as Process from "process";
13
10
  import * as ChainMap from "./ChainMap.res.mjs";
14
11
  import * as PgStorage from "./PgStorage.res.mjs";
15
12
  import * as ChainState from "./ChainState.res.mjs";
13
+ import * as Supervisor from "./Supervisor.res.mjs";
16
14
  import * as AddressRows from "./AddressRows.res.mjs";
17
15
  import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
18
16
  import * as IndexerLoop from "./IndexerLoop.res.mjs";
19
17
  import * as Persistence from "./Persistence.res.mjs";
20
- import Yargs from "yargs/yargs";
21
18
  import * as IndexerState from "./IndexerState.res.mjs";
22
19
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
23
20
  import * as HandlerLoader from "./HandlerLoader.res.mjs";
24
21
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
25
- import * as Helpers from "yargs/helpers";
26
22
  import * as RollbackCommit from "./RollbackCommit.res.mjs";
27
23
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
28
24
  import * as ContractMapping from "./ContractMapping.res.mjs";
29
25
  import * as HandlerRegister from "./HandlerRegister.res.mjs";
30
26
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
31
- import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
32
27
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
33
28
 
34
- function toChainData(m) {
35
- return {
36
- chainId: m.chainId,
37
- poweredByHyperSync: m.poweredByHyperSync,
38
- firstEventBlockNumber: m.firstEventBlockNumber,
39
- latestProcessedBlock: m.latestProcessedBlock,
40
- timestampCaughtUpToHeadOrEndblock: m.timestampCaughtUpToHeadOrEndblock,
41
- numEventsProcessed: m.numEventsProcessed,
42
- latestFetchedBlockNumber: m.latestFetchedBlockNumber,
43
- currentBlockHeight: m.knownHeight,
44
- numBatchesFetched: m.numBatchesFetched,
45
- startBlock: m.startBlock,
46
- endBlock: m.endBlock,
47
- numAddresses: m.numAddresses
48
- };
49
- }
50
-
51
- let chainDataSchema = S$RescriptSchema.schema(s => ({
52
- chainId: s.m(ChainId.schema),
53
- poweredByHyperSync: s.m(S$RescriptSchema.bool),
54
- firstEventBlockNumber: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
55
- latestProcessedBlock: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
56
- timestampCaughtUpToHeadOrEndblock: s.m(S$RescriptSchema.option(S$RescriptSchema.datetime(S$RescriptSchema.string, undefined))),
57
- numEventsProcessed: s.m(S$RescriptSchema.float),
58
- latestFetchedBlockNumber: s.m(S$RescriptSchema.int),
59
- currentBlockHeight: s.m(S$RescriptSchema.int),
60
- numBatchesFetched: s.m(S$RescriptSchema.int),
61
- startBlock: s.m(S$RescriptSchema.int),
62
- endBlock: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
63
- numAddresses: s.m(S$RescriptSchema.int)
64
- }));
65
-
66
- let stateSchema = S$RescriptSchema.union([
67
- S$RescriptSchema.literal({
68
- status: "disabled"
69
- }),
70
- S$RescriptSchema.literal({
71
- status: "initializing"
72
- }),
73
- S$RescriptSchema.schema(s => ({
74
- status: "active",
75
- envioVersion: s.m(S$RescriptSchema.string),
76
- chains: s.m(S$RescriptSchema.array(chainDataSchema)),
77
- indexerStartTime: s.m(S$RescriptSchema.datetime(S$RescriptSchema.string, undefined)),
78
- isPreRegisteringDynamicContracts: false,
79
- rollbackOnReorg: s.m(S$RescriptSchema.bool)
80
- }))
81
- ]);
82
-
83
29
  function getIndexerState() {
84
30
  return EnvioGlobal.value.indexerState;
85
31
  }
@@ -402,87 +348,10 @@ function getGlobalIndexer() {
402
348
  return new Proxy(Object.create(null), traps);
403
349
  }
404
350
 
405
- function startServer(getMetrics, envioVersion, persistence, isDevelopmentMode) {
406
- let app = Express();
407
- let consoleCorsMiddleware = (req, res, next) => {
408
- let origin = req.headers["origin"];
409
- if (origin !== undefined && (origin === Env.prodEnvioAppUrl || origin === Env.envioAppUrl)) {
410
- res.setHeader("Access-Control-Allow-Origin", origin);
411
- }
412
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
413
- res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
414
- if (req.method === "OPTIONS") {
415
- res.sendStatus(200);
416
- return;
417
- } else {
418
- return next();
419
- }
420
- };
421
- app.use("/console", consoleCorsMiddleware);
422
- app.use("/metrics", consoleCorsMiddleware);
423
- app.use("/metrics/runtime", consoleCorsMiddleware);
424
- app.get("/healthz", (_req, res) => {
425
- res.sendStatus(200);
426
- });
427
- app.get("/console/state", (_req, res) => {
428
- let state;
429
- if (isDevelopmentMode) {
430
- let metrics = getMetrics();
431
- state = metrics !== undefined ? ({
432
- status: "active",
433
- envioVersion: envioVersion,
434
- chains: metrics.chains.map(toChainData),
435
- indexerStartTime: metrics.startTime,
436
- isPreRegisteringDynamicContracts: false,
437
- rollbackOnReorg: metrics.rollbackEnabled
438
- }) : ({
439
- status: "initializing"
440
- });
441
- } else {
442
- state = {
443
- status: "disabled"
444
- };
445
- }
446
- res.json(S$RescriptSchema.reverseConvertToJsonOrThrow(state, stateSchema));
447
- });
448
- app.post("/console/syncCache", (_req, res) => {
449
- if (isDevelopmentMode) {
450
- Persistence.getInitializedStorageOrThrow(persistence).dumpEffectCache().then(() => {
451
- res.json(true);
452
- });
453
- } else {
454
- res.json(false);
455
- }
456
- });
457
- Metrics.startRuntimeCollectors();
458
- app.get("/metrics", (_req, res) => {
459
- res.set("Content-Type", Metrics.contentType);
460
- res.end(Metrics.collect(getMetrics()));
461
- });
462
- app.get("/metrics/runtime", (_req, res) => {
463
- res.set("Content-Type", Metrics.contentType);
464
- res.end(Metrics.collectRuntime());
465
- });
466
- let server = app.listen(Env.serverPort);
467
- server.on("error", err => {
468
- let code = err.code;
469
- if (code === "EADDRINUSE") {
470
- Logging.error(`Port ` + Env.serverPort.toString() + ` is already in use. To fix this either:` + (`\n 1. Kill the process using the port: lsof -ti :` + Env.serverPort.toString() + ` | xargs kill -9`) + `\n 2. Use a different port by setting the ENVIO_INDEXER_PORT environment variable: ENVIO_INDEXER_PORT=9899 envio start`);
471
- } else {
472
- Logging.errorWithExn(err, "Failed to start indexer server");
473
- }
474
- Process.exit(1);
475
- });
476
- }
477
-
478
- function getEnvioInfo() {
479
- return Config.stripSensitiveData(Config.getPublicConfigJson());
480
- }
481
-
482
351
  async function migrate(reset) {
483
352
  let config = Config.load();
484
353
  let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
485
- await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset, config.lowercaseAddresses, undefined, "Once");
354
+ await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.envioInfo(), "envio local db-migrate setup", undefined, reset, config.lowercaseAddresses, undefined, undefined, "Once");
486
355
  return await persistence.storage.close();
487
356
  }
488
357
 
@@ -495,21 +364,15 @@ async function dropSchema() {
495
364
 
496
365
  let FatalError = /* @__PURE__ */Primitive_exceptions.create("Main.FatalError");
497
366
 
498
- async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockOpt, patchConfig) {
367
+ async function startIndexer(config, persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockOpt, patchConfig) {
499
368
  let reset = resetOpt !== undefined ? resetOpt : false;
500
369
  let isTest = isTestOpt !== undefined ? isTestOpt : false;
501
370
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
502
- let mainArgs = Yargs(Helpers.hideBin(process.argv)).argv;
503
- let off = mainArgs["tui-off"];
504
- let explicitTui = off !== undefined ? !off : Env.tuiEnvVar;
505
- let shouldUseTui = isTest ? false : (
506
- explicitTui !== undefined ? explicitTui : !Envio.isNonInteractive()
507
- );
508
- let config = Config.load();
371
+ let shouldUseTui = Tui.shouldUse(isTest || Worker.isEnabled, undefined);
509
372
  let isDevelopmentMode = !isTest && config.isDev;
510
373
  let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
511
374
  EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
512
- await Persistence.init(persistence$1, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset, config.lowercaseAddresses, config.isolated, undefined);
375
+ await Persistence.initForRun(persistence$1, config, reset, isDevelopmentMode, config.isolated);
513
376
  let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
514
377
  let config$1;
515
378
  if (isTest) {
@@ -535,22 +398,35 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
535
398
  };
536
399
  let envioVersion = Utils.EnvioPackage.value.version;
537
400
  let getMetrics = () => Stdlib_Option.map(EnvioGlobal.value.indexerState, IndexerState.toMetrics);
538
- if (!isTest) {
539
- startServer(getMetrics, envioVersion, persistence$1, isDevelopmentMode);
401
+ if (!isTest && !Worker.isEnabled) {
402
+ Metrics.startRuntimeCollectors();
403
+ Server.startServer(getMetrics, envioVersion, () => Persistence.getInitializedStorageOrThrow(persistence$1).dumpEffectCache().then(prim => {}), Metrics.collectRuntime, isDevelopmentMode);
540
404
  }
541
- let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, onError, undefined);
405
+ let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, Stdlib_Option.mapOr(Worker.config, false, worker => worker.holdRealtime), onError, undefined);
542
406
  if (shouldUseTui) {
543
407
  Tui.start(config$2, () => IndexerState.toMetrics(state));
544
408
  }
409
+ Worker.bindRun(() => IndexerState.toMetrics(state), () => IndexerState.releaseRealtime(state));
545
410
  EnvioGlobal.value.indexerState = Primitive_option.some(state);
546
411
  IndexerLoop.start(state);
547
412
  return await runUntilFatalError;
548
413
  }
549
414
 
415
+ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockOpt, patchConfig) {
416
+ let reset = resetOpt !== undefined ? resetOpt : false;
417
+ let isTest = isTestOpt !== undefined ? isTestOpt : false;
418
+ let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
419
+ Stdlib_Option.forEach(Worker.config, param => Config.prime(Config.withCommandFields(Config.getPublicConfigJson(), param.chainIds, param.isDev)));
420
+ let config = Config.load();
421
+ let workers = isTest ? undefined : Supervisor.planForRun(config, undefined);
422
+ if (workers !== undefined) {
423
+ return await Supervisor.run(config, workers, reset);
424
+ } else {
425
+ return await startIndexer(config, persistence, reset, isTest, exitAfterFirstEventBlock, patchConfig);
426
+ }
427
+ }
428
+
550
429
  export {
551
- toChainData,
552
- chainDataSchema,
553
- stateSchema,
554
430
  getIndexerState,
555
431
  setIndexerState,
556
432
  getGlobalPersistence,
@@ -559,11 +435,9 @@ export {
559
435
  getInitialChainState,
560
436
  buildChainsObject,
561
437
  getGlobalIndexer,
562
- startServer,
563
- getEnvioInfo,
564
438
  migrate,
565
439
  dropSchema,
566
440
  FatalError,
567
441
  start,
568
442
  }
569
- /* chainDataSchema Not a pure module */
443
+ /* Tui Not a pure module */