envio 3.13.0-rc.0 → 3.14.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 (119) hide show
  1. package/bin.mjs +10 -1
  2. package/index.d.ts +11 -0
  3. package/package.json +8 -20
  4. package/rescript.json +1 -4
  5. package/src/BatchProcessing.res +0 -2
  6. package/src/ChainState.res +39 -25
  7. package/src/ChainState.res.mjs +26 -23
  8. package/src/ChainState.resi +1 -2
  9. package/src/Config.res +78 -100
  10. package/src/Config.res.mjs +55 -64
  11. package/src/Core.res +67 -6
  12. package/src/Core.res.mjs +58 -4
  13. package/src/CrossChainState.res +13 -6
  14. package/src/CrossChainState.res.mjs +17 -4
  15. package/src/EventConfigBuilder.res +4 -10
  16. package/src/EventConfigBuilder.res.mjs +1 -3
  17. package/src/EventProcessing.res +6 -6
  18. package/src/EventProcessing.res.mjs +8 -6
  19. package/src/ExitOnCaughtUp.res +5 -1
  20. package/src/ExitOnCaughtUp.res.mjs +8 -3
  21. package/src/FinalizeBackfill.res +16 -10
  22. package/src/FinalizeBackfill.res.mjs +16 -5
  23. package/src/HandlerLoader.res +98 -36
  24. package/src/HandlerLoader.res.mjs +89 -24
  25. package/src/HandlerTypeCheck.mjs +14 -0
  26. package/src/HandlerTypeCheckWorker.mjs +68 -0
  27. package/src/Internal.res +1 -1
  28. package/src/LoadLayer.res +39 -7
  29. package/src/LoadLayer.res.mjs +65 -38
  30. package/src/LoadLayer.resi +28 -0
  31. package/src/Main.res +17 -12
  32. package/src/Main.res.mjs +9 -3
  33. package/src/NodeVersion.mjs +13 -0
  34. package/src/Persistence.res +79 -38
  35. package/src/Persistence.res.mjs +41 -10
  36. package/src/PgStorage.res +86 -39
  37. package/src/PgStorage.res.mjs +83 -32
  38. package/src/ResumePlan.res +172 -0
  39. package/src/ResumePlan.res.mjs +122 -0
  40. package/src/Supervisor.res +8 -8
  41. package/src/Supervisor.res.mjs +6 -1
  42. package/src/SyncWrites.res +86 -0
  43. package/src/SyncWrites.res.mjs +85 -0
  44. package/src/TestIndexer.res +19 -6
  45. package/src/TestIndexer.res.mjs +5 -4
  46. package/src/TsModuleHooks.mjs +121 -0
  47. package/src/UserContext.res +377 -62
  48. package/src/UserContext.res.mjs +297 -39
  49. package/src/Utils.res +0 -8
  50. package/src/Worker.res +8 -0
  51. package/src/Worker.res.mjs +11 -0
  52. package/src/bindings/NodeJs.res +8 -7
  53. package/src/bindings/NodeJs.res.mjs +0 -3
  54. package/src/bindings/Pino.res +2 -2
  55. package/src/db/InternalTable.res +52 -2
  56. package/src/db/InternalTable.res.mjs +32 -3
  57. package/src/db/Table.res +10 -0
  58. package/src/db/Table.res.mjs +22 -11
  59. package/src/sources/FuelHyperSync.res +2 -0
  60. package/src/sources/FuelHyperSync.res.mjs +1 -0
  61. package/src/sources/FuelHyperSync.resi +1 -0
  62. package/src/sources/FuelHyperSyncClient.res +25 -8
  63. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  64. package/src/sources/FuelHyperSyncSource.res +24 -22
  65. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  66. package/src/subgraph/abi-types.ts +63 -0
  67. package/src/subgraph/assemblyscript.ts +85 -0
  68. package/src/subgraph/blocks.ts +183 -0
  69. package/src/subgraph/calls.ts +149 -0
  70. package/src/subgraph/conformance.ts +83 -0
  71. package/src/subgraph/errors.ts +96 -0
  72. package/src/subgraph/graph-cli-worker.mjs +26 -0
  73. package/src/subgraph/graph-cli.ts +190 -0
  74. package/src/subgraph/graph-ts-enums.ts +28 -0
  75. package/src/subgraph/graph-ts-types/VERSION +2 -0
  76. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  77. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  78. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  79. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  80. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  81. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  82. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  83. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  84. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  85. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  86. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  87. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  88. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  89. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  90. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  91. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  92. package/src/subgraph/graph-ts.ts +2285 -0
  93. package/src/subgraph/hosts.ts +158 -0
  94. package/src/subgraph/rpc.ts +65 -0
  95. package/src/subgraph/runtime.ts +595 -0
  96. package/src/subgraph/scope.ts +68 -0
  97. package/src/tui/InitApi.res +85 -0
  98. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  99. package/src/tui/Tui.res +142 -249
  100. package/src/tui/Tui.res.mjs +123 -332
  101. package/src/Js.shim.ts +0 -11
  102. package/src/bindings/DateFns.res +0 -74
  103. package/src/bindings/DateFns.res.mjs +0 -22
  104. package/src/sources/FuelSDK.res +0 -38
  105. package/src/sources/FuelSDK.res.mjs +0 -27
  106. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  107. package/src/tui/bindings/Ink.res +0 -369
  108. package/src/tui/bindings/Ink.res.mjs +0 -66
  109. package/src/tui/bindings/Style.res +0 -123
  110. package/src/tui/bindings/Style.res.mjs +0 -2
  111. package/src/tui/components/BufferedProgressBar.res +0 -40
  112. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  113. package/src/tui/components/CustomHooks.res +0 -122
  114. package/src/tui/components/Messages.res +0 -40
  115. package/src/tui/components/Messages.res.mjs +0 -71
  116. package/src/tui/components/SyncETA.res +0 -179
  117. package/src/tui/components/SyncETA.res.mjs +0 -249
  118. package/src/tui/components/TuiData.res +0 -98
  119. package/src/tui/components/TuiData.res.mjs +0 -73
package/bin.mjs CHANGED
@@ -1,3 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { run } from "./src/Bin.res.mjs";
2
+ import { unsupportedNodeMessage } from "./src/NodeVersion.mjs";
3
+
4
+ // Checked before the rest loads, which an older Node may not even parse.
5
+ const unsupported = unsupportedNodeMessage();
6
+ if (unsupported !== undefined) {
7
+ console.error(unsupported);
8
+ process.exit(1);
9
+ }
10
+
11
+ const { run } = await import("./src/Bin.res.mjs");
3
12
  await run(process.argv.slice(2));
package/index.d.ts CHANGED
@@ -12,6 +12,17 @@ declare const bigintSchema: Sury.Schema<bigint>;
12
12
  /** Ethereum address — a 20-byte hex string prefixed with `0x`. */
13
13
  export type Address = `0x${string}`;
14
14
 
15
+ /** A value a `Json` entity field can hold. A `bigint` isn't JSON — store its
16
+ * decimal string with `.toString()`. Shadows the global `JSON` interface only
17
+ * where imported; the `JSON` object itself is unaffected. */
18
+ export type JSON =
19
+ | string
20
+ | number
21
+ | boolean
22
+ | null
23
+ | readonly JSON[]
24
+ | { readonly [key: string]: JSON };
25
+
15
26
  /** Structured logger bound to an event or handler context. Messages are
16
27
  * displayed in the console and the Envio Hosted Service. */
17
28
  export type Logger = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.13.0-rc.0",
3
+ "version": "3.14.0-subgraph",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -27,7 +27,7 @@
27
27
  "dapp"
28
28
  ],
29
29
  "engines": {
30
- "node": ">=22.0.0"
30
+ "node": ">=22.15.0"
31
31
  },
32
32
  "files": [
33
33
  "bin.mjs",
@@ -42,11 +42,6 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@elastic/ecs-pino-format": "1.4.0",
45
- "@fuel-ts/crypto": "0.96.1",
46
- "@fuel-ts/errors": "0.96.1",
47
- "@fuel-ts/hasher": "0.96.1",
48
- "@fuel-ts/math": "0.96.1",
49
- "@fuel-ts/utils": "0.96.1",
50
45
  "bignumber.js": "9.3.1",
51
46
  "eventsource": "4.1.0",
52
47
  "express": "4.19.2",
@@ -56,20 +51,13 @@
56
51
  "rescript-schema": "9.5.1",
57
52
  "viem": "2.54.0",
58
53
  "dotenv": "16.4.5",
59
- "date-fns": "3.3.1",
60
- "@rescript/react": "0.14.1",
61
- "react": "19.2.5",
62
- "ink": "6.8.0",
63
- "ink-big-text": "2.0.0",
64
- "ink-spinner": "5.0.0",
65
- "postgres": "3.4.8",
66
- "tsx": "4.21.0"
54
+ "postgres": "3.4.8"
67
55
  },
68
56
  "optionalDependencies": {
69
- "envio-linux-x64": "3.13.0-rc.0",
70
- "envio-linux-x64-musl": "3.13.0-rc.0",
71
- "envio-linux-arm64": "3.13.0-rc.0",
72
- "envio-darwin-x64": "3.13.0-rc.0",
73
- "envio-darwin-arm64": "3.13.0-rc.0"
57
+ "envio-linux-x64": "3.14.0-subgraph",
58
+ "envio-linux-x64-musl": "3.14.0-subgraph",
59
+ "envio-linux-arm64": "3.14.0-subgraph",
60
+ "envio-darwin-x64": "3.14.0-subgraph",
61
+ "envio-darwin-arm64": "3.14.0-subgraph"
74
62
  }
75
63
  }
package/rescript.json CHANGED
@@ -12,10 +12,7 @@
12
12
  "module": "esmodule",
13
13
  "in-source": true
14
14
  },
15
- "jsx": {
16
- "version": 4
17
- },
18
- "dependencies": ["rescript-schema", "@rescript/react"],
15
+ "dependencies": ["rescript-schema"],
19
16
  "compiler-flags": ["-open RescriptSchema"],
20
17
  "warnings": {
21
18
  "error": "+a"
@@ -159,8 +159,6 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
159
159
  // Can safely reset rollback state, since overwrite is not possible.
160
160
  state->IndexerState.clearRollback
161
161
  state->IndexerState.applyBatchProgress(~batch)
162
- // Before the finalize below, so a chain says where it finished ahead of
163
- // the process saying what it does about that.
164
162
  state->IndexerState.reportFinished
165
163
 
166
164
  // Backfilling → FinalizingIndexes → Ready. Awaiting here holds the
@@ -426,7 +426,6 @@ let getLatestValidScannedBlock = (cs: t, ~blockStore: BlockStore.t, ~blockNumber
426
426
  ->BlockStore.latestValidBlockFromStore(blockStore, blockNumbers)
427
427
  ->Null.toOption
428
428
  let safeCheckpointTracking = (cs: t) => cs.safeCheckpointTracking
429
- let isProgressAtHead = (cs: t) => cs.isProgressAtHead
430
429
  let committedProgressBlockNumber = (cs: t) => cs.committedProgressBlockNumber
431
430
  let committedProgressBlockTime = (cs: t) => cs.committedProgressBlockTime
432
431
  let numEventsProcessed = (cs: t) => cs.numEventsProcessed
@@ -660,30 +659,6 @@ let hasProcessedToEndblock = (cs: t) => {
660
659
  }
661
660
  }
662
661
 
663
- // Where this chain has finished indexing, the first time it gets there.
664
- // `EndBlock` is terminal: the chain indexed everything it was configured to.
665
- // `Backfill` is the rest of the history, up to the point where blocks can
666
- // still be reorged, which is as far as a chain indexes before the indexer
667
- // crosses into them.
668
- type finished = EndBlock(int) | Backfill(int)
669
-
670
- let takeFinished = (cs: t) =>
671
- if cs.reportedFinished {
672
- None
673
- } else {
674
- switch (cs.fetchState.endBlock, cs->hasProcessedToEndblock, cs.isProgressAtHead) {
675
- | (Some(endBlock), true, _) => {
676
- cs.reportedFinished = true
677
- Some(EndBlock(endBlock))
678
- }
679
- | (_, _, true) => {
680
- cs.reportedFinished = true
681
- Some(Backfill(cs.committedProgressBlockNumber))
682
- }
683
- | _ => None
684
- }
685
- }
686
-
687
662
  // Caught up as judged by persisted values alone: progress reached the endBlock,
688
663
  // or the head the previous run had already observed (less the lag that holds the
689
664
  // tip back). Unlike `isFetchingAtHead` this doesn't move when a fresh height
@@ -708,6 +683,45 @@ let isDurablyCaughtUp = (cs: t) => {
708
683
  atEndBlock || atHead
709
684
  }
710
685
 
686
+ // This chain has indexed everything there was to index: it reached its end
687
+ // block, or its progress reached the head. The single reading of that, for
688
+ // everything that turns on it — what the chain reports, and whether the whole
689
+ // process may finalize.
690
+ //
691
+ // Both spellings, because neither covers the other: `isProgressAtHead` latches
692
+ // the moment a batch's progress met the head known when it was created, which a
693
+ // head that has since moved on would read as behind; `isDurablyCaughtUp` judges
694
+ // the head as it stands, which is all a run resumed at the head has — it has no
695
+ // batch to latch anything.
696
+ let hasCaughtUp = (cs: t) => cs.isProgressAtHead || cs->isDurablyCaughtUp
697
+
698
+ // Where this chain has finished indexing, the first time it gets there.
699
+ // `EndBlock` is terminal: the chain indexed everything it was configured to.
700
+ // `Backfill` is the rest of the history, up to the point where blocks can
701
+ // still be reorged, which is as far as a chain indexes before the indexer
702
+ // crosses into them.
703
+ type finished = EndBlock(int) | Backfill(int)
704
+
705
+ let takeFinished = (cs: t) =>
706
+ if cs.reportedFinished {
707
+ None
708
+ } else {
709
+ switch (cs.fetchState.endBlock, cs->hasProcessedToEndblock, cs->hasCaughtUp) {
710
+ | (Some(endBlock), true, _) => {
711
+ cs.reportedFinished = true
712
+ Some(EndBlock(endBlock))
713
+ }
714
+ | (_, _, true) => {
715
+ cs.reportedFinished = true
716
+ // Finishing a backfill happens in a run, and a chain resumed with its
717
+ // `ready_at` already committed did none in this one. Being at the end
718
+ // block, above, stays true across runs and is said on every one.
719
+ cs->isReady ? None : Some(Backfill(cs.committedProgressBlockNumber))
720
+ }
721
+ | _ => None
722
+ }
723
+ }
724
+
711
725
  let getHighestBlockBelowThreshold = (cs: t): int => {
712
726
  let highestBlockBelowThreshold = cs.fetchState.knownHeight - cs.maxReorgDepth
713
727
  highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
@@ -220,10 +220,6 @@ function safeCheckpointTracking(cs) {
220
220
  return cs.safeCheckpointTracking;
221
221
  }
222
222
 
223
- function isProgressAtHead(cs) {
224
- return cs.isProgressAtHead;
225
- }
226
-
227
223
  function committedProgressBlockNumber(cs) {
228
224
  return cs.committedProgressBlockNumber;
229
225
  }
@@ -421,13 +417,29 @@ function hasProcessedToEndblock(cs) {
421
417
  }
422
418
  }
423
419
 
420
+ function hasCaughtUp(cs) {
421
+ if (cs.isProgressAtHead) {
422
+ return true;
423
+ } else {
424
+ let fetchState = cs.fetchState;
425
+ let committedProgressBlockNumber = cs.committedProgressBlockNumber;
426
+ let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
427
+ let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
428
+ if (atEndBlock) {
429
+ return true;
430
+ } else {
431
+ return atHead;
432
+ }
433
+ }
434
+ }
435
+
424
436
  function takeFinished(cs) {
425
437
  if (cs.reportedFinished) {
426
438
  return;
427
439
  }
428
440
  let match = cs.fetchState.endBlock;
429
441
  let match$1 = hasProcessedToEndblock(cs);
430
- let match$2 = cs.isProgressAtHead;
442
+ let match$2 = hasCaughtUp(cs);
431
443
  if (match !== undefined && match$1) {
432
444
  cs.reportedFinished = true;
433
445
  return {
@@ -437,22 +449,14 @@ function takeFinished(cs) {
437
449
  }
438
450
  if (match$2) {
439
451
  cs.reportedFinished = true;
440
- return {
441
- TAG: "Backfill",
442
- _0: cs.committedProgressBlockNumber
443
- };
444
- }
445
- }
446
-
447
- function isDurablyCaughtUp(cs) {
448
- let fetchState = cs.fetchState;
449
- let committedProgressBlockNumber = cs.committedProgressBlockNumber;
450
- let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
451
- let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
452
- if (atEndBlock) {
453
- return true;
454
- } else {
455
- return atHead;
452
+ if (cs.timestampCaughtUpToHeadOrEndblock !== undefined) {
453
+ return;
454
+ } else {
455
+ return {
456
+ TAG: "Backfill",
457
+ _0: cs.committedProgressBlockNumber
458
+ };
459
+ }
456
460
  }
457
461
  }
458
462
 
@@ -946,7 +950,6 @@ export {
946
950
  getReorgThresholdBlockNumbersBelow,
947
951
  getLatestValidScannedBlock,
948
952
  safeCheckpointTracking,
949
- isProgressAtHead,
950
953
  committedProgressBlockNumber,
951
954
  committedProgressBlockTime,
952
955
  numEventsProcessed,
@@ -980,7 +983,7 @@ export {
980
983
  reorgThresholdLiftsCeiling,
981
984
  reorgThresholdEntryMessage,
982
985
  hasProcessedToEndblock,
983
- isDurablyCaughtUp,
986
+ hasCaughtUp,
984
987
  getHighestBlockBelowThreshold,
985
988
  isActivelyIndexing,
986
989
  isReady,
@@ -70,7 +70,6 @@ let getLatestValidScannedBlock: (
70
70
  ~blockNumbers: array<int>,
71
71
  ) => option<int>
72
72
  let safeCheckpointTracking: t => option<SafeCheckpointTracking.t>
73
- let isProgressAtHead: t => bool
74
73
  let committedProgressBlockNumber: t => int
75
74
  let committedProgressBlockTime: t => option<int>
76
75
  let numEventsProcessed: t => float
@@ -132,7 +131,7 @@ let takeFinished: t => option<finished>
132
131
  let reorgThresholdLiftsCeiling: t => bool
133
132
  let reorgThresholdEntryMessage: string
134
133
  let hasProcessedToEndblock: t => bool
135
- let isDurablyCaughtUp: t => bool
134
+ let hasCaughtUp: t => bool
136
135
  let getHighestBlockBelowThreshold: t => int
137
136
  let isActivelyIndexing: t => bool
138
137
  let isReady: t => bool
package/src/Config.res CHANGED
@@ -136,6 +136,9 @@ type t = {
136
136
  reorgThresholdReadyTolerance: int,
137
137
  lowercaseAddresses: bool,
138
138
  isDev: bool,
139
+ // The operator's `-d` / `--config` flags, repeated in every command printed
140
+ // for them to run next. Empty when they gave neither.
141
+ projectFlags: string,
139
142
  // An `envio start --chain` process: drives a subset of the schema's chains
140
143
  // while sibling processes drive the rest, so it only touches what its own
141
144
  // chains own — their partitions' indexes, their `ready_at`, their resume.
@@ -147,6 +150,10 @@ type t = {
147
150
  // the schema alone: a cross-chain entity has rows any chain's reorg can
148
151
  // reach, so its checkpoints have to be comparable across chains.
149
152
  checkpointSequence: CheckpointSequence.t,
153
+ envioInfo: JSON.t,
154
+ // Set only in subgraph mode: the translated manifest, which is what makes
155
+ // the subgraph runtime take over handler registration.
156
+ subgraph: option<JSON.t>,
150
157
  }
151
158
 
152
159
  type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
@@ -595,6 +602,7 @@ let publicConfigSchema = S.schema(s =>
595
602
  "handlers": s.matches(S.option(S.string)),
596
603
  "isDev": s.matches(S.option(S.bool)),
597
604
  "isolatedChains": s.matches(S.option(S.array(ChainId.schema))),
605
+ "projectFlags": s.matches(S.option(S.string)),
598
606
  "fullBatchSize": s.matches(S.option(S.int)),
599
607
  "rollbackOnReorg": s.matches(S.option(S.bool)),
600
608
  "saveFullHistory": s.matches(S.option(S.bool)),
@@ -607,6 +615,7 @@ let publicConfigSchema = S.schema(s =>
607
615
  "svm": s.matches(S.option(publicConfigEcosystemSchema)),
608
616
  "enums": s.matches(S.option(S.dict(S.array(S.string)))),
609
617
  "entities": s.matches(S.option(S.array(entityJsonSchema))),
618
+ "subgraph": s.matches(S.option(S.json(~validate=false))),
610
619
  }
611
620
  )
612
621
 
@@ -666,6 +675,45 @@ let isolate = (config: t, ~chainIds: array<ChainId.t>) => {
666
675
  }
667
676
  }
668
677
 
678
+ // What the command decided rather than the project's files: `envio dev` vs
679
+ // `envio start`, which chains this process drives, and how it found the
680
+ // project.
681
+ let commandFields = ["isDev", "isolatedChains", "projectFlags"]
682
+
683
+ let withProjectFlags = (command, ~config: t) =>
684
+ switch config.projectFlags {
685
+ | "" => command
686
+ | flags => `${command} ${flags}`
687
+ }
688
+
689
+ let ecosystemFields = ["evm", "fuel", "svm"]
690
+
691
+ // What `envio_info` records: the public config without the command's fields
692
+ // or its chains. A chain is recorded by its own `envio_chains` row and its
693
+ // config's `envio_addresses` rows instead, so adding one never rewrites this
694
+ // record. The rest of a chain's config — its sources, its block lag, a
695
+ // contract's start block — is read from config.yaml on every start.
696
+ let toEnvioInfo = (publicConfigJson: JSON.t): JSON.t => {
697
+ let envioInfo = publicConfigJson->JSON.stringify->JSON.parseOrThrow
698
+ switch envioInfo {
699
+ | Object(obj) => {
700
+ commandFields->Array.forEach(field => obj->Utils.Dict.deleteInPlace(field))
701
+ // The subgraph blob is how the runtime finds mappings. None of it
702
+ // describes stored data — that's already in `evm` / `entities` — so a
703
+ // specVersion bump or a mapping-path edit must not force a reset.
704
+ obj->Utils.Dict.deleteInPlace("subgraph")
705
+ ecosystemFields->Array.forEach(ecosystem =>
706
+ switch obj->Dict.get(ecosystem) {
707
+ | Some(Object(ecosystemDict)) => ecosystemDict->Utils.Dict.deleteInPlace("chains")
708
+ | _ => ()
709
+ }
710
+ )
711
+ }
712
+ | _ => ()
713
+ }
714
+ envioInfo
715
+ }
716
+
669
717
  let fromPublic = (publicConfigJson: JSON.t) => {
670
718
  let maxAddrInPartition = Env.maxAddrInPartition
671
719
  // Parse public config
@@ -1136,11 +1184,14 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1136
1184
  reorgThresholdReadyTolerance: 100,
1137
1185
  lowercaseAddresses,
1138
1186
  isDev: publicConfig["isDev"]->Option.getOr(false),
1187
+ projectFlags: publicConfig["projectFlags"]->Option.getOr(""),
1139
1188
  isolated: false,
1140
1189
  userEntitiesByName,
1141
1190
  userEntities,
1142
1191
  allEnums,
1143
1192
  checkpointSequence: CheckpointSequence.fromEntities(userEntities),
1193
+ envioInfo: publicConfigJson->toEnvioInfo,
1194
+ subgraph: publicConfig["subgraph"],
1144
1195
  }
1145
1196
 
1146
1197
  switch publicConfig["isolatedChains"] {
@@ -1255,49 +1306,6 @@ let getPublicConfigJson = () =>
1255
1306
  | None => Core.getConfigJson()->JSON.parseOrThrow
1256
1307
  }
1257
1308
 
1258
- // Drops source URLs from each chain so RPC/hypersync edits don't trigger
1259
- // the resume-time compat check (and don't end up in `envio_info`). Also
1260
- // drops `isDev`, which toggles between `envio dev` and `envio start` and
1261
- // has no bearing on schema/indexing compatibility.
1262
- let stripSensitiveData = (json: JSON.t): JSON.t => {
1263
- let cloned = json->JSON.stringify->JSON.parseOrThrow
1264
- let stripChains = (ecosystem: option<JSON.t>) =>
1265
- switch ecosystem {
1266
- | Some(Object(ecosystemDict)) =>
1267
- switch ecosystemDict->Dict.get("chains") {
1268
- | Some(Object(chains)) =>
1269
- chains
1270
- ->Dict.valuesToArray
1271
- ->Array.forEach(chainJson =>
1272
- switch chainJson {
1273
- | Object(chain) => {
1274
- chain->Utils.Dict.deleteInPlace("rpcs")
1275
- chain->Utils.Dict.deleteInPlace("hypersync")
1276
- }
1277
- | _ => ()
1278
- }
1279
- )
1280
- | _ => ()
1281
- }
1282
- | _ => ()
1283
- }
1284
- switch cloned {
1285
- | Object(obj) => {
1286
- obj->Utils.Dict.deleteInPlace("isDev")
1287
- obj->Utils.Dict.deleteInPlace("isolatedChains")
1288
- stripChains(obj->Dict.get("evm"))
1289
- stripChains(obj->Dict.get("fuel"))
1290
- stripChains(obj->Dict.get("svm"))
1291
- }
1292
- | _ => ()
1293
- }
1294
- cloned
1295
- }
1296
-
1297
- // What the storage layer records as the config this schema was built from,
1298
- // and checks a resuming run against.
1299
- let envioInfo = () => getPublicConfigJson()->stripSensitiveData
1300
-
1301
1309
  // Postgres jsonb doesn't preserve key order, so canonicalize with sorted
1302
1310
  // keys before string-comparing.
1303
1311
  let rec canonicalJson = (json: JSON.t): JSON.t =>
@@ -1317,9 +1325,10 @@ let rec canonicalJson = (json: JSON.t): JSON.t =>
1317
1325
  // Returns dotted leaf paths (`a.b[i].c`) where `stored` differs from
1318
1326
  // `current`, restricted to the highest-priority top-level tier with any
1319
1327
  // diff. Tiers in order: version → name → storage → ecosystem
1320
- // (evm/fuel/svm) → entities → other top-level keys. The first tier
1321
- // containing a diff is the only one rendered; lower tiers are silenced
1322
- // so a single noisy section doesn't bury the actionable change.
1328
+ // (evm/fuel/svm, and the chains a resume puts beside them) → entities →
1329
+ // other top-level keys. The first tier containing a diff is the only one
1330
+ // rendered; lower tiers are silenced so a single noisy section doesn't bury
1331
+ // the actionable change.
1323
1332
  let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
1324
1333
  let canonEq = (a: JSON.t, b: JSON.t) =>
1325
1334
  JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b))
@@ -1387,7 +1396,7 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
1387
1396
  ["chainIdMode"],
1388
1397
  ["name"],
1389
1398
  ["storage"],
1390
- ["evm", "fuel", "svm"],
1399
+ ["evm", "fuel", "svm", "chains"],
1391
1400
  ["entities"],
1392
1401
  ]
1393
1402
  let firstHit = tiers->Array.reduce(None, (acc, tier) =>
@@ -1417,65 +1426,34 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
1417
1426
  acc
1418
1427
  }
1419
1428
 
1420
- // Throws an `incompatible config` error listing each path in `changedPaths`,
1421
- // plus the remediation options. `~resetCommand` is rendered as-is for
1422
- // option 2 (the wipe-and-redo). `~runCommand` controls option 3 (parallel
1423
- // indexer recipe): when `None`, option 3 is omitted — the migrate flow
1424
- // uses this because running a second indexer doesn't apply.
1425
- // `~hasClickhouse` adds the extra env line so users running both
1426
- // Postgres and Clickhouse get a complete override.
1427
- let throwIfIncompatible = (
1429
+ // The `incompatible config` error listing each path in `changedPaths`, plus
1430
+ // the remediation options. `~resetCommand` is rendered as-is for option 2
1431
+ // (the wipe-and-redo). `~runCommand` controls option 3 (parallel indexer
1432
+ // recipe): when `None`, option 3 is omitted — the migrate flow uses this
1433
+ // because running a second indexer doesn't apply. `~hasClickhouse` adds the
1434
+ // extra env line so users running both Postgres and Clickhouse get a complete
1435
+ // override.
1436
+ let incompatibleMessage = (
1428
1437
  changedPaths: array<string>,
1429
1438
  ~resetCommand: string,
1430
1439
  ~runCommand: option<string>,
1431
1440
  ~hasClickhouse: bool,
1432
1441
  ) => {
1433
- if changedPaths->Array.length > 0 {
1434
- let bullets = changedPaths->Array.map(p => ` - ${p}`)->Array.joinUnsafe("\n")
1435
- let option1 = "Revert the changes above"
1436
- let padTo = (s, col) => s ++ " "->String.repeat(Math.Int.max(col - String.length(s), 1))
1437
- let col = Math.Int.max(String.length(option1), String.length(resetCommand)) + 2
1438
- let option3 = switch runCommand {
1439
- | None => ""
1440
- | Some(cmd) =>
1441
- let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : ""
1442
- `\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
1443
- }
1444
- JsError.throwWithMessage(
1445
- `The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
1446
- col,
1447
- )}# resume indexing where it left off\n 2. ${resetCommand->padTo(
1448
- col,
1449
- )}# delete all indexed data and start over${option3}`,
1450
- )
1451
- }
1452
- }
1453
-
1454
- let throwIfResumeIncompatible = (
1455
- ~storedEnvioInfo: option<JSON.t>,
1456
- ~storedContractMapping: ContractMapping.t,
1457
- ~envioInfo: JSON.t,
1458
- ~contractMapping: ContractMapping.t,
1459
- ~resetCommand: string,
1460
- ~runCommand: option<string>,
1461
- ) => {
1462
- let changedPaths = switch storedEnvioInfo {
1463
- | None => ["storage was initialized by an older envio version"]
1464
- | Some(stored) => diffPaths(~stored, ~current=envioInfo)
1465
- }
1466
- let changedPaths =
1467
- storedContractMapping->ContractMapping.isEqual(contractMapping)
1468
- ? changedPaths
1469
- : changedPaths->Array.concat(["contracts"])
1470
- let hasClickhouse = switch envioInfo {
1471
- | Object(d) =>
1472
- switch d->Dict.get("storage") {
1473
- | Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
1474
- | _ => false
1475
- }
1476
- | _ => false
1442
+ let bullets = changedPaths->Array.map(p => ` - ${p}`)->Array.joinUnsafe("\n")
1443
+ let option1 = "Revert the changes above"
1444
+ let padTo = (s, col) => s ++ " "->String.repeat(Math.Int.max(col - String.length(s), 1))
1445
+ let col = Math.Int.max(String.length(option1), String.length(resetCommand)) + 2
1446
+ let option3 = switch runCommand {
1447
+ | None => ""
1448
+ | Some(cmd) =>
1449
+ let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : ""
1450
+ `\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
1477
1451
  }
1478
- throwIfIncompatible(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
1452
+ `The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
1453
+ col,
1454
+ )}# resume indexing where it left off\n 2. ${resetCommand->padTo(
1455
+ col,
1456
+ )}# delete all indexed data and start over${option3}`
1479
1457
  }
1480
1458
 
1481
1459
  // The returned value is a pure function of the JSON: it holds only event