envio 3.13.0-rc.0 → 3.14.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 (77) 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 +69 -100
  10. package/src/Config.res.mjs +50 -63
  11. package/src/Core.res +46 -6
  12. package/src/Core.res.mjs +41 -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/ExitOnCaughtUp.res +5 -1
  18. package/src/ExitOnCaughtUp.res.mjs +8 -3
  19. package/src/FinalizeBackfill.res +16 -10
  20. package/src/FinalizeBackfill.res.mjs +16 -5
  21. package/src/HandlerLoader.res +74 -32
  22. package/src/HandlerLoader.res.mjs +69 -23
  23. package/src/HandlerTypeCheck.mjs +14 -0
  24. package/src/HandlerTypeCheckWorker.mjs +68 -0
  25. package/src/Internal.res +1 -1
  26. package/src/Main.res +17 -12
  27. package/src/Main.res.mjs +9 -3
  28. package/src/NodeVersion.mjs +13 -0
  29. package/src/Persistence.res +79 -38
  30. package/src/Persistence.res.mjs +41 -10
  31. package/src/PgStorage.res +62 -34
  32. package/src/PgStorage.res.mjs +65 -31
  33. package/src/ResumePlan.res +172 -0
  34. package/src/ResumePlan.res.mjs +122 -0
  35. package/src/Supervisor.res +8 -8
  36. package/src/Supervisor.res.mjs +6 -1
  37. package/src/TestIndexer.res +19 -6
  38. package/src/TestIndexer.res.mjs +5 -4
  39. package/src/TsModuleHooks.mjs +121 -0
  40. package/src/Utils.res +0 -8
  41. package/src/Worker.res +8 -0
  42. package/src/Worker.res.mjs +11 -0
  43. package/src/bindings/NodeJs.res +8 -7
  44. package/src/bindings/NodeJs.res.mjs +0 -3
  45. package/src/bindings/Pino.res +2 -2
  46. package/src/db/InternalTable.res +52 -2
  47. package/src/db/InternalTable.res.mjs +32 -3
  48. package/src/sources/FuelHyperSync.res +2 -0
  49. package/src/sources/FuelHyperSync.res.mjs +1 -0
  50. package/src/sources/FuelHyperSync.resi +1 -0
  51. package/src/sources/FuelHyperSyncClient.res +25 -8
  52. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  53. package/src/sources/FuelHyperSyncSource.res +24 -22
  54. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  55. package/src/tui/InitApi.res +85 -0
  56. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  57. package/src/tui/Tui.res +142 -249
  58. package/src/tui/Tui.res.mjs +123 -332
  59. package/src/Js.shim.ts +0 -11
  60. package/src/bindings/DateFns.res +0 -74
  61. package/src/bindings/DateFns.res.mjs +0 -22
  62. package/src/sources/FuelSDK.res +0 -38
  63. package/src/sources/FuelSDK.res.mjs +0 -27
  64. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  65. package/src/tui/bindings/Ink.res +0 -369
  66. package/src/tui/bindings/Ink.res.mjs +0 -66
  67. package/src/tui/bindings/Style.res +0 -123
  68. package/src/tui/bindings/Style.res.mjs +0 -2
  69. package/src/tui/components/BufferedProgressBar.res +0 -40
  70. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  71. package/src/tui/components/CustomHooks.res +0 -122
  72. package/src/tui/components/Messages.res +0 -40
  73. package/src/tui/components/Messages.res.mjs +0 -71
  74. package/src/tui/components/SyncETA.res +0 -179
  75. package/src/tui/components/SyncETA.res.mjs +0 -249
  76. package/src/tui/components/TuiData.res +0 -98
  77. package/src/tui/components/TuiData.res.mjs +0 -73
@@ -18,7 +18,6 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
18
18
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
19
19
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
20
20
  import * as ContractMapping from "./ContractMapping.res.mjs";
21
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
22
21
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
23
22
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
24
23
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -439,6 +438,7 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
439
438
  handlers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
440
439
  isDev: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
441
440
  isolatedChains: s.m(S$RescriptSchema.option(S$RescriptSchema.array(ChainId.schema))),
441
+ projectFlags: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
442
442
  fullBatchSize: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
443
443
  rollbackOnReorg: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
444
444
  saveFullHistory: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
@@ -495,6 +495,41 @@ function isolate(config, chainIds) {
495
495
  return newrecord;
496
496
  }
497
497
 
498
+ let commandFields = [
499
+ "isDev",
500
+ "isolatedChains",
501
+ "projectFlags"
502
+ ];
503
+
504
+ function withProjectFlags(command, config) {
505
+ let flags = config.projectFlags;
506
+ if (flags === "") {
507
+ return command;
508
+ } else {
509
+ return command + ` ` + flags;
510
+ }
511
+ }
512
+
513
+ let ecosystemFields = [
514
+ "evm",
515
+ "fuel",
516
+ "svm"
517
+ ];
518
+
519
+ function toEnvioInfo(publicConfigJson) {
520
+ let envioInfo = JSON.parse(JSON.stringify(publicConfigJson));
521
+ if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
522
+ commandFields.forEach(field => Utils.Dict.deleteInPlace(envioInfo, field));
523
+ ecosystemFields.forEach(ecosystem => {
524
+ let match = envioInfo[ecosystem];
525
+ if (typeof match === "object" && match !== null && !Array.isArray(match)) {
526
+ return Utils.Dict.deleteInPlace(match, "chains");
527
+ }
528
+ });
529
+ }
530
+ return envioInfo;
531
+ }
532
+
498
533
  function fromPublic(publicConfigJson) {
499
534
  let publicConfig;
500
535
  try {
@@ -801,7 +836,9 @@ function fromPublic(publicConfigJson) {
801
836
  let config_enableRawEvents = Stdlib_Option.getOr(publicConfig.rawEvents, false);
802
837
  let config_batchSize = Stdlib_Option.getOr(publicConfig.fullBatchSize, 5000);
803
838
  let config_isDev = Stdlib_Option.getOr(publicConfig.isDev, false);
839
+ let config_projectFlags = Stdlib_Option.getOr(publicConfig.projectFlags, "");
804
840
  let config_checkpointSequence = CheckpointSequence.fromEntities(userEntities);
841
+ let config_envioInfo = toEnvioInfo(publicConfigJson);
805
842
  let config = {
806
843
  name: config_name,
807
844
  description: config_description,
@@ -823,11 +860,13 @@ function fromPublic(publicConfigJson) {
823
860
  reorgThresholdReadyTolerance: 100,
824
861
  lowercaseAddresses: lowercaseAddresses,
825
862
  isDev: config_isDev,
863
+ projectFlags: config_projectFlags,
826
864
  isolated: false,
827
865
  userEntitiesByName: userEntitiesByName,
828
866
  userEntities: userEntities,
829
867
  allEnums: allEnums,
830
- checkpointSequence: config_checkpointSequence
868
+ checkpointSequence: config_checkpointSequence,
869
+ envioInfo: config_envioInfo
831
870
  };
832
871
  let chainIds = publicConfig.isolatedChains;
833
872
  if (chainIds !== undefined) {
@@ -933,44 +972,6 @@ function getPublicConfigJson() {
933
972
  }
934
973
  }
935
974
 
936
- function stripSensitiveData(json) {
937
- let cloned = JSON.parse(JSON.stringify(json));
938
- let stripChains = ecosystem => {
939
- if (ecosystem === undefined) {
940
- return;
941
- }
942
- if (typeof ecosystem !== "object" || ecosystem === null || Array.isArray(ecosystem)) {
943
- return;
944
- }
945
- let match = ecosystem["chains"];
946
- if (match === undefined) {
947
- return;
948
- }
949
- if (typeof match !== "object" || match === null || Array.isArray(match)) {
950
- return;
951
- }
952
- Object.values(match).forEach(chainJson => {
953
- if (typeof chainJson !== "object" || chainJson === null || Array.isArray(chainJson)) {
954
- return;
955
- }
956
- Utils.Dict.deleteInPlace(chainJson, "rpcs");
957
- Utils.Dict.deleteInPlace(chainJson, "hypersync");
958
- });
959
- };
960
- if (typeof cloned === "object" && cloned !== null && !Array.isArray(cloned)) {
961
- Utils.Dict.deleteInPlace(cloned, "isDev");
962
- Utils.Dict.deleteInPlace(cloned, "isolatedChains");
963
- stripChains(cloned["evm"]);
964
- stripChains(cloned["fuel"]);
965
- stripChains(cloned["svm"]);
966
- }
967
- return cloned;
968
- }
969
-
970
- function envioInfo() {
971
- return stripSensitiveData(getPublicConfigJson());
972
- }
973
-
974
975
  function canonicalJson(json) {
975
976
  if (Array.isArray(json)) {
976
977
  return json.map(canonicalJson);
@@ -1088,7 +1089,8 @@ function diffPaths(stored, current) {
1088
1089
  [
1089
1090
  "evm",
1090
1091
  "fuel",
1091
- "svm"
1092
+ "svm",
1093
+ "chains"
1092
1094
  ],
1093
1095
  ["entities"]
1094
1096
  ];
@@ -1113,10 +1115,7 @@ function diffPaths(stored, current) {
1113
1115
  return acc;
1114
1116
  }
1115
1117
 
1116
- function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhouse) {
1117
- if (changedPaths.length === 0) {
1118
- return;
1119
- }
1118
+ function incompatibleMessage(changedPaths, resetCommand, runCommand, hasClickhouse) {
1120
1119
  let bullets = changedPaths.map(p => ` - ` + p).join("\n");
1121
1120
  let option1 = "Revert the changes above";
1122
1121
  let padTo = (s, col) => s + " ".repeat(Math.max(col - s.length | 0, 1));
@@ -1128,20 +1127,7 @@ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhou
1128
1127
  } else {
1129
1128
  option3 = "";
1130
1129
  }
1131
- Stdlib_JsError.throwWithMessage(`The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3);
1132
- }
1133
-
1134
- function throwIfResumeIncompatible(storedEnvioInfo, storedContractMapping, envioInfo, contractMapping, resetCommand, runCommand) {
1135
- let changedPaths = storedEnvioInfo !== undefined ? diffPaths(storedEnvioInfo, envioInfo) : ["storage was initialized by an older envio version"];
1136
- let changedPaths$1 = ContractMapping.isEqual(storedContractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
1137
- let hasClickhouse;
1138
- if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
1139
- let match = envioInfo["storage"];
1140
- hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
1141
- } else {
1142
- hasClickhouse = false;
1143
- }
1144
- throwIfIncompatible(changedPaths$1, resetCommand, runCommand, hasClickhouse);
1130
+ return `The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3;
1145
1131
  }
1146
1132
 
1147
1133
  function load() {
@@ -1195,6 +1181,10 @@ export {
1195
1181
  getChain,
1196
1182
  isPerChain,
1197
1183
  isolate,
1184
+ commandFields,
1185
+ withProjectFlags,
1186
+ ecosystemFields,
1187
+ toEnvioInfo,
1198
1188
  fromPublic,
1199
1189
  normalizeUserAddress,
1200
1190
  normalizeSimulateAddress,
@@ -1202,12 +1192,9 @@ export {
1202
1192
  prime,
1203
1193
  withCommandFields,
1204
1194
  getPublicConfigJson,
1205
- stripSensitiveData,
1206
- envioInfo,
1207
1195
  canonicalJson,
1208
1196
  diffPaths,
1209
- throwIfIncompatible,
1210
- throwIfResumeIncompatible,
1197
+ incompatibleMessage,
1211
1198
  load,
1212
1199
  getPgUserEntities,
1213
1200
  }
package/src/Core.res CHANGED
@@ -12,6 +12,7 @@ type transactionStoreCtor
12
12
  type blockStoreCtor
13
13
  type clickHouseSinkCtor
14
14
  type addressStoreCtor
15
+ type tuiCtor
15
16
  // Test-only: a local HyperSync server, bound by MockHyperSyncServer in envio-tests.
16
17
  type mockHyperSyncServerCtor
17
18
  type fromUserApiOptions = {
@@ -33,6 +34,10 @@ type addon = {
33
34
  isSvmPubkey: (~value: string) => bool,
34
35
  fromUserApi: (string, fromUserApiOptions) => fromUserApiResult,
35
36
  runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
37
+ loadTs: string => string,
38
+ tsCheckHandlerFormat: string => unit,
39
+ tsResolveCandidates: (string, Null.t<string>) => array<string>,
40
+ tsNotFoundCandidates: (string, Null.t<string>, string) => array<string>,
36
41
  @as("EvmHyperSyncClient")
37
42
  evmHyperSyncClient: evmHyperSyncClientCtor,
38
43
  @as("EvmRpcClient")
@@ -49,6 +54,8 @@ type addon = {
49
54
  addressStore: addressStoreCtor,
50
55
  @as("ClickHouseSink")
51
56
  clickHouseSink: clickHouseSinkCtor,
57
+ @as("Tui")
58
+ tui: tuiCtor,
52
59
  @as("MockHyperSyncServer")
53
60
  mockHyperSyncServer: mockHyperSyncServerCtor,
54
61
  encodeAddresses: (~ecosystem: string, ~addresses: array<Address.t>) => array<NodeJs.Buffer.t>,
@@ -99,7 +106,7 @@ let callRequire: ({..}, string) => addon = %raw(`(req, id) => req(id)`)
99
106
  let envioPackageDir = pathDirname(pathDirname(fileURLToPath(importMetaUrl)))
100
107
 
101
108
  // Runs `cargo build` on every invocation (like `cargo run`).
102
- let loadDevAddon: ({..}, string) => addon = %raw(`function(req, envioDir) {
109
+ let loadDevAddon: ({..}, string) => Null.t<addon> = %raw(`function(req, envioDir) {
103
110
  var cp = Nodechild_process;
104
111
  var path = Nodepath;
105
112
  var fs = Nodefs;
@@ -177,6 +184,22 @@ let loadDevAddon: ({..}, string) => addon = %raw(`function(req, envioDir) {
177
184
  // `code`, and any other fields a diagnostic might rely on.
178
185
  let rethrow: JsExn.t => 'a = %raw(`function(e) { throw e }`)
179
186
 
187
+ type packageJson = {version: string}
188
+ let requirePackageJson: ({..}, string) => packageJson = %raw(`(req, p) => req(p)`)
189
+ let devVersion = "0.0.1-dev"
190
+
191
+ let isGlibc: unit => bool = %raw(`() => Boolean(process.report?.getReport().header.glibcVersionRuntime)`)
192
+
193
+ @val external npmUserAgent: option<string> = "process.env.npm_config_user_agent"
194
+
195
+ let addCommand = () =>
196
+ switch npmUserAgent {
197
+ | Some(ua) if ua->String.startsWith("pnpm/") => "pnpm add"
198
+ | Some(ua) if ua->String.startsWith("yarn/") => "yarn add"
199
+ | Some(ua) if ua->String.startsWith("bun/") => "bun add"
200
+ | _ => "npm install"
201
+ }
202
+
180
203
  let loadAddon = () => {
181
204
  let req = createRequire(importMetaUrl)
182
205
 
@@ -212,15 +235,32 @@ let loadAddon = () => {
212
235
  switch tryRequire(0) {
213
236
  | Some(addon) => addon
214
237
  | None =>
215
- // Dev build fallback (cargo build on every run)
216
- switch loadDevAddon(req, envioPackageDir)->(Utils.magic: addon => option<addon>) {
238
+ let version = requirePackageJson(req, "../package.json").version
239
+ // Publishing stamps the real version, so only a monorepo checkout (or a
240
+ // `file:` link to one) can have a dev build to fall back to. Skipping it
241
+ // spares published installs a `pnpm list` spawn before the error below.
242
+ let devAddon = if version === devVersion {
243
+ loadDevAddon(req, envioPackageDir)->Null.toOption
244
+ } else {
245
+ None
246
+ }
247
+ switch devAddon {
217
248
  | Some(addon) => addon
218
249
  | None =>
219
250
  let host = `${processPlatform}-${processArch}`
220
- let msg = if candidates->Array.length === 0 {
251
+ let msg = switch candidates {
252
+ | []
253
+ if processPlatform === "win32" => `envio doesn't run natively on Windows. Use WSL 2 instead: https://learn.microsoft.com/windows/wsl/install`
254
+ | [] =>
221
255
  `envio doesn't support ${host}. Supported: linux-x64 (glibc/musl), linux-arm64, darwin-x64, darwin-arm64.`
222
- } else {
223
- `Couldn't load the envio native addon for ${host}. Reinstall envio (ensure optional dependencies aren't skipped).`
256
+ | _ =>
257
+ let pkg = if processPlatform === "linux" && processArch === "x64" && !isGlibc() {
258
+ `envio-linux-x64-musl`
259
+ } else {
260
+ candidates->Array.getUnsafe(0)
261
+ }
262
+ `envio's native binary for ${host} isn't installed (package "${pkg}"). This happens when optional dependencies are skipped (--omit=optional, --no-optional) or the lockfile was generated on another platform.
263
+ Reinstall dependencies, or add the package explicitly: ${addCommand()} ${pkg}@${version}`
224
264
  }
225
265
  JsError.throwWithMessage(msg)
226
266
  }
package/src/Core.res.mjs CHANGED
@@ -6,6 +6,7 @@ import * as Nodepath from "node:path";
6
6
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
7
7
  import * as Nodemodule from "node:module";
8
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
9
10
  import * as Nodechild_process from "node:child_process";
10
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
12
 
@@ -97,6 +98,29 @@ let loadDevAddon = (function(req, envioDir) {
97
98
 
98
99
  let rethrow = (function(e) { throw e });
99
100
 
101
+ let requirePackageJson = ((req, p) => req(p));
102
+
103
+ let devVersion = "0.0.1-dev";
104
+
105
+ let isGlibc = (() => Boolean(process.report?.getReport().header.glibcVersionRuntime));
106
+
107
+ function addCommand() {
108
+ let ua = process.env.npm_config_user_agent;
109
+ if (ua !== undefined) {
110
+ if (ua.startsWith("pnpm/")) {
111
+ return "pnpm add";
112
+ } else if (ua.startsWith("yarn/")) {
113
+ return "yarn add";
114
+ } else if (ua.startsWith("bun/")) {
115
+ return "bun add";
116
+ } else {
117
+ return "npm install";
118
+ }
119
+ } else {
120
+ return "npm install";
121
+ }
122
+ }
123
+
100
124
  function loadAddon() {
101
125
  let req = Nodemodule.createRequire(import.meta.url);
102
126
  let match = process.platform;
@@ -161,12 +185,21 @@ function loadAddon() {
161
185
  if (addon !== undefined) {
162
186
  return addon;
163
187
  }
164
- let addon$1 = loadDevAddon(req, envioPackageDir);
165
- if (addon$1 !== undefined) {
166
- return addon$1;
188
+ let version = requirePackageJson(req, "../package.json").version;
189
+ let devAddon = version === devVersion ? Primitive_option.fromNull(loadDevAddon(req, envioPackageDir)) : undefined;
190
+ if (devAddon !== undefined) {
191
+ return devAddon;
167
192
  }
168
193
  let host = process.platform + `-` + process.arch;
169
- return Stdlib_JsError.throwWithMessage(candidates.length === 0 ? `envio doesn't support ` + host + `. Supported: linux-x64 (glibc/musl), linux-arm64, darwin-x64, darwin-arm64.` : `Couldn't load the envio native addon for ` + host + `. Reinstall envio (ensure optional dependencies aren't skipped).`);
194
+ let tmp;
195
+ if (candidates.length !== 0) {
196
+ let pkg = process.platform === "linux" && process.arch === "x64" && !isGlibc() ? `envio-linux-x64-musl` : candidates[0];
197
+ tmp = `envio's native binary for ` + host + ` isn't installed (package "` + pkg + `"). This happens when optional dependencies are skipped (--omit=optional, --no-optional) or the lockfile was generated on another platform.
198
+ Reinstall dependencies, or add the package explicitly: ` + addCommand() + ` ` + pkg + `@` + version;
199
+ } else {
200
+ tmp = process.platform === "win32" ? `envio doesn't run natively on Windows. Use WSL 2 instead: https://learn.microsoft.com/windows/wsl/install` : `envio doesn't support ` + host + `. Supported: linux-x64 (glibc/musl), linux-arm64, darwin-x64, darwin-arm64.`;
201
+ }
202
+ return Stdlib_JsError.throwWithMessage(tmp);
170
203
  }
171
204
 
172
205
  let addonRef = {
@@ -211,6 +244,10 @@ export {
211
244
  envioPackageDir,
212
245
  loadDevAddon,
213
246
  rethrow,
247
+ requirePackageJson,
248
+ devVersion,
249
+ isGlibc,
250
+ addCommand,
214
251
  loadAddon,
215
252
  addonRef,
216
253
  getAddon,
@@ -199,7 +199,7 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampNa
199
199
  for i in 0 to chainIds->Array.length - 1 {
200
200
  let cs = crossChainState->getChainState(chainIds->Array.getUnsafe(i))
201
201
  cs->ChainState.applyBatchProgress(~batch, ~blockTimestampName)
202
- if !(cs->ChainState.hasProcessedToEndblock || cs->ChainState.isProgressAtHead) {
202
+ if !(cs->ChainState.hasCaughtUp) {
203
203
  everyChainCaughtUp := false
204
204
  }
205
205
  }
@@ -254,7 +254,7 @@ let markCaughtUpOnResume = (crossChainState: t) => {
254
254
  let everyChainCaughtUp = ref(crossChainState.chainIds->Array.length > 0)
255
255
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
256
256
  let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
257
- if !(cs->ChainState.isDurablyCaughtUp) {
257
+ if !(cs->ChainState.hasCaughtUp) {
258
258
  everyChainCaughtUp := false
259
259
  }
260
260
  }
@@ -284,11 +284,19 @@ let markReady = (crossChainState: t, ~readyAt) => {
284
284
  crossChainState.isRealtime = true
285
285
  }
286
286
 
287
+ // What a caught-up chain is waiting on before its process can finalize: the run
288
+ // holding the process back, or another chain it drives still behind. Read when
289
+ // the chain speaks, so "waiting" is said only while it is true.
290
+ let isWaitingOnOthers = (crossChainState: t, cs: ChainState.t) =>
291
+ crossChainState.holdRealtime ||
292
+ crossChainState.chainStates
293
+ ->Dict.valuesToArray
294
+ ->Array.some(other => other !== cs && !(other->ChainState.hasCaughtUp))
295
+
287
296
  // Each chain that has just finished indexing, said once, by the chain it is
288
297
  // about — so a chain that finishes early says so then, rather than when the
289
298
  // last chain in its process catches up.
290
- let reportFinished = (crossChainState: t) => {
291
- let waitingOnOthers = crossChainState.holdRealtime || crossChainState.chainIds->Array.length > 1
299
+ let reportFinished = (crossChainState: t) =>
292
300
  crossChainState.chainStates
293
301
  ->Dict.valuesToArray
294
302
  ->Array.forEach(cs =>
@@ -301,7 +309,7 @@ let reportFinished = (crossChainState: t) => {
301
309
  cs
302
310
  ->ChainState.logger
303
311
  ->Logging.childInfo({
304
- "msg": waitingOnOthers
312
+ "msg": crossChainState->isWaitingOnOthers(cs)
305
313
  ? "Finished backfill. Waiting for the other chains."
306
314
  : "Finished backfill.",
307
315
  "block": block,
@@ -309,7 +317,6 @@ let reportFinished = (crossChainState: t) => {
309
317
  | None => ()
310
318
  }
311
319
  )
312
- }
313
320
 
314
321
  // --- Fetch control. ---
315
322
 
@@ -141,7 +141,7 @@ function applyBatchProgress(crossChainState, batch, blockTimestampName) {
141
141
  for (let i = 0, i_finish = chainIds.length; i < i_finish; ++i) {
142
142
  let cs = crossChainState.chainStates[chainIds[i]];
143
143
  ChainState.applyBatchProgress(cs, batch, blockTimestampName);
144
- if (!(ChainState.hasProcessedToEndblock(cs) || ChainState.isProgressAtHead(cs))) {
144
+ if (!ChainState.hasCaughtUp(cs)) {
145
145
  everyChainCaughtUp = false;
146
146
  }
147
147
  }
@@ -169,7 +169,7 @@ function markCaughtUpOnResume(crossChainState) {
169
169
  let everyChainCaughtUp = crossChainState.chainIds.length !== 0;
170
170
  for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
171
171
  let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
172
- if (!ChainState.isDurablyCaughtUp(cs)) {
172
+ if (!ChainState.hasCaughtUp(cs)) {
173
173
  everyChainCaughtUp = false;
174
174
  }
175
175
  }
@@ -191,8 +191,21 @@ function markReady(crossChainState, readyAt) {
191
191
  crossChainState.isRealtime = true;
192
192
  }
193
193
 
194
+ function isWaitingOnOthers(crossChainState, cs) {
195
+ if (crossChainState.holdRealtime) {
196
+ return true;
197
+ } else {
198
+ return Object.values(crossChainState.chainStates).some(other => {
199
+ if (other !== cs) {
200
+ return !ChainState.hasCaughtUp(other);
201
+ } else {
202
+ return false;
203
+ }
204
+ });
205
+ }
206
+ }
207
+
194
208
  function reportFinished(crossChainState) {
195
- let waitingOnOthers = crossChainState.holdRealtime || crossChainState.chainIds.length > 1;
196
209
  Object.values(crossChainState.chainStates).forEach(cs => {
197
210
  let match = ChainState.takeFinished(cs);
198
211
  if (match !== undefined) {
@@ -203,7 +216,7 @@ function reportFinished(crossChainState) {
203
216
  });
204
217
  } else {
205
218
  return Logging.childInfo(ChainState.logger(cs), {
206
- msg: waitingOnOthers ? "Finished backfill. Waiting for the other chains." : "Finished backfill.",
219
+ msg: isWaitingOnOthers(crossChainState, cs) ? "Finished backfill. Waiting for the other chains." : "Finished backfill.",
207
220
  block: match._0
208
221
  });
209
222
  }
@@ -343,7 +343,7 @@ let validateFieldsShapeOrThrow = (
343
343
  }
344
344
 
345
345
  let selectionList = (fields: unknown, key: string): option<array<string>> =>
346
- switch (fields->(Utils.magic: unknown => dict<unknown>))->Dict.get(key) {
346
+ switch fields->(Utils.magic: unknown => dict<unknown>)->Dict.get(key) {
347
347
  | None => None
348
348
  | Some(value) => Some(value->(Utils.magic: unknown => array<string>))
349
349
  }
@@ -371,8 +371,8 @@ let parseFieldsOrThrow = (
371
371
  if !(valid->Utils.Set.has(name)) {
372
372
  JsError.throwWithMessage(
373
373
  `Invalid "${name}" field in the fields.${kind} option of ${registration}. Valid ${kind} fields: ${Utils.Array.quotedJoin(
374
- valid->Utils.Set.toArray,
375
- )}.`,
374
+ valid->Utils.Set.toArray,
375
+ )}.`,
376
376
  )
377
377
  }
378
378
  if seen->Utils.Set.has(name) {
@@ -858,13 +858,7 @@ let buildFuelEventConfig = (
858
858
  ~rawAbi: JSON.t,
859
859
  ): Internal.fuelEventConfig => {
860
860
  let fuelKind = switch kind {
861
- | "logData" =>
862
- // Transpile raw Fuel ABI to the format expected by the vendored ABI coder
863
- let abi = FuelSDK.transpileAbi(rawAbi)
864
- Internal.LogData({
865
- logId: sighash,
866
- decode: FuelSDK.Receipt.getLogDataDecoder(~abi, ~logId=sighash),
867
- })
861
+ | "logData" => Internal.LogData({logId: sighash, abi: rawAbi})
868
862
  | "mint" => Mint
869
863
  | "burn" => Burn
870
864
  | "transfer" => Transfer
@@ -6,7 +6,6 @@ import * as Core from "./Core.res.mjs";
6
6
  import * as Fuel from "./sources/Fuel.res.mjs";
7
7
  import * as Utils from "./Utils.res.mjs";
8
8
  import * as Address from "./Address.res.mjs";
9
- import * as FuelSDK from "./sources/FuelSDK.res.mjs";
10
9
  import * as Internal from "./Internal.res.mjs";
11
10
  import * as LogSelection from "./LogSelection.res.mjs";
12
11
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -590,11 +589,10 @@ function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi) {
590
589
  fuelKind = "Call";
591
590
  break;
592
591
  case "logData" :
593
- let abi = FuelSDK.transpileAbi(rawAbi);
594
592
  fuelKind = {
595
593
  TAG: "LogData",
596
594
  logId: sighash,
597
- decode: FuelSDK.Receipt.getLogDataDecoder(abi, sighash)
595
+ abi: rawAbi
598
596
  };
599
597
  break;
600
598
  case "mint" :
@@ -14,7 +14,11 @@ let run = async (state: IndexerState.t) => {
14
14
  | Some(onExit) => onExit()
15
15
  | None =>
16
16
  Logging.info("Exiting with success")
17
- NodeJs.process->NodeJs.exitWithCode(Success)
17
+ if Worker.isEnabled {
18
+ Worker.exitAfterReporting(~metrics=state->IndexerState.toMetrics)
19
+ } else {
20
+ NodeJs.process->NodeJs.exitWithCode(Success)
21
+ }
18
22
  }
19
23
  | Some(message) => state->IndexerState.errorExit(ErrorHandling.make(Utils.Error.make(message)))
20
24
  }
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Worker from "./Worker.res.mjs";
3
4
  import * as Logging from "./Logging.res.mjs";
4
5
  import * as Writing from "./Writing.res.mjs";
5
6
  import * as Process from "process";
@@ -24,12 +25,16 @@ async function run(state) {
24
25
  return onExit();
25
26
  } else {
26
27
  Logging.info("Exiting with success");
27
- Process.exit(0);
28
- return;
28
+ if (Worker.isEnabled) {
29
+ return Worker.exitAfterReporting(IndexerState.toMetrics(state));
30
+ } else {
31
+ Process.exit(0);
32
+ return;
33
+ }
29
34
  }
30
35
  }
31
36
 
32
37
  export {
33
38
  run,
34
39
  }
35
- /* Logging Not a pure module */
40
+ /* Worker Not a pure module */
@@ -11,15 +11,25 @@
11
11
  // `envio start --chain` process is indexing and never waits on one.
12
12
 
13
13
  let runOnce = async (state: IndexerState.t) => {
14
+ let chainIds = state->IndexerState.crossChainState->CrossChainState.chainIds
15
+
16
+ // Whatever brought the process here — a batch, a tick that progressed
17
+ // nothing, a supervisor's release — its chains say where they finished
18
+ // before it says what it does about that. Said here rather than left to each
19
+ // caller to order: a chain that has already spoken says nothing again.
20
+ state->IndexerState.reportFinished
21
+
14
22
  // Said by the process rather than by each of its chains: the indexes are one
15
23
  // build over the tables, and the pause is the whole process's. A chain has
16
24
  // already said it caught up, and says it is ready once this commits. The
17
25
  // chains are named because the pause is theirs, and a split run has a process
18
- // saying this for each part of it.
19
- Logging.info({
20
- "msg": "Building database indexes. Indexing is paused until they are ready, which can take a while on a large database.",
21
- "chainIds": state->IndexerState.crossChainState->CrossChainState.chainIds,
22
- })
26
+ // saying this for each part of it — one of them, for a process driving a
27
+ // single chain.
28
+ let msg = "Building database indexes. Indexing is paused until they are ready, which can take a while on a large database."
29
+ switch chainIds {
30
+ | [chainId] => Logging.info({"msg": msg, "chainId": chainId})
31
+ | chainIds => Logging.info({"msg": msg, "chainIds": chainIds})
32
+ }
23
33
 
24
34
  await Writing.flush(state)
25
35
 
@@ -30,11 +40,7 @@ let runOnce = async (state: IndexerState.t) => {
30
40
  let storage = persistence->Persistence.getInitializedStorageOrThrow
31
41
  let readyAt = Date.make()
32
42
 
33
- await storage.finalizeBackfill(
34
- ~entities=persistence.allEntities,
35
- ~chainIds=state->IndexerState.crossChainState->CrossChainState.chainIds,
36
- ~readyAt,
37
- )
43
+ await storage.finalizeBackfill(~entities=persistence.allEntities, ~chainIds, ~readyAt)
38
44
 
39
45
  // Only after the commit: in-memory readiness must never run ahead of the
40
46
  // `ready_at` a restart would read back.
@@ -7,10 +7,21 @@ import * as IndexerState from "./IndexerState.res.mjs";
7
7
  import * as CrossChainState from "./CrossChainState.res.mjs";
8
8
 
9
9
  async function runOnce(state) {
10
- Logging.info({
11
- msg: "Building database indexes. Indexing is paused until they are ready, which can take a while on a large database.",
12
- chainIds: CrossChainState.chainIds(IndexerState.crossChainState(state))
13
- });
10
+ let chainIds = CrossChainState.chainIds(IndexerState.crossChainState(state));
11
+ IndexerState.reportFinished(state);
12
+ let msg = "Building database indexes. Indexing is paused until they are ready, which can take a while on a large database.";
13
+ if (chainIds.length !== 1) {
14
+ Logging.info({
15
+ msg: msg,
16
+ chainIds: chainIds
17
+ });
18
+ } else {
19
+ let chainId = chainIds[0];
20
+ Logging.info({
21
+ msg: msg,
22
+ chainId: chainId
23
+ });
24
+ }
14
25
  await Writing.flush(state);
15
26
  if (IndexerState.hasFailedWrite(state)) {
16
27
  return;
@@ -18,7 +29,7 @@ async function runOnce(state) {
18
29
  let persistence = IndexerState.persistence(state);
19
30
  let storage = Persistence.getInitializedStorageOrThrow(persistence);
20
31
  let readyAt = new Date();
21
- await storage.finalizeBackfill(persistence.allEntities, CrossChainState.chainIds(IndexerState.crossChainState(state)), readyAt);
32
+ await storage.finalizeBackfill(persistence.allEntities, chainIds, readyAt);
22
33
  return IndexerState.markReady(state, readyAt);
23
34
  }
24
35