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
@@ -1,20 +1,37 @@
1
1
  @module("node:fs/promises")
2
2
  external globIterator: string => Utils.asyncIterator<string> = "glob"
3
3
 
4
- // Register tsx for TypeScript handler support
5
- // Wrapped in try-catch because if tsx is already loaded via --import (e.g., in tests),
6
- // calling module.register again will throw an error
7
- try {
8
- NodeJs.Module.register("tsx/esm", NodeJs.ImportMeta.url)
9
- } catch {
10
- | _ => () // tsx already loaded, ignore
4
+ type tsHooksAddon = {
5
+ loadTs: string => string,
6
+ tsResolveCandidates: (string, Null.t<string>) => array<string>,
7
+ tsNotFoundCandidates: (string, Null.t<string>, string) => array<string>,
11
8
  }
12
9
 
13
- // Convert a relative path to a file:// URL for dynamic import
14
- // Paths are resolved relative to process.cwd() (project root)
15
- let toImportUrl = (relativePath: string) => {
16
- let absolutePath = NodeJs.Path.resolve([NodeJs.Process.cwd(), relativePath])->NodeJs.Path.toString
17
- NodeJs.Url.pathToFileURL(absolutePath)->NodeJs.Url.toString
10
+ @module("./TsModuleHooks.mjs")
11
+ external registerTsHooksWith: tsHooksAddon => unit = "register"
12
+
13
+ // The resolve hook calls into the addon, and loading the addon goes through
14
+ // the resolve hook, so the addon has to be loaded before the hooks exist.
15
+ let registerTsHooks = () => {
16
+ let addon = Core.getAddon()
17
+ registerTsHooksWith({
18
+ loadTs: addon.loadTs,
19
+ tsResolveCandidates: addon.tsResolveCandidates,
20
+ tsNotFoundCandidates: addon.tsNotFoundCandidates,
21
+ })
22
+ }
23
+
24
+ let isTypeScript = file => /\.m?tsx?$/->RegExp.test(file)
25
+
26
+ // Paths are relative to the project root, which is the working directory.
27
+ let toAbsolutePath = (file: string) =>
28
+ NodeJs.Path.resolve([NodeJs.Process.cwd(), file])->NodeJs.Path.toString
29
+
30
+ let importHandler = async file => {
31
+ if isTypeScript(file) {
32
+ Core.getAddon().tsCheckHandlerFormat(toAbsolutePath(file))
33
+ }
34
+ await Utils.importPath(toAbsolutePath(file)->NodeJs.Url.pathToFileURL->NodeJs.Url.toString)
18
35
  }
19
36
 
20
37
  let registerContractHandlers = async (~contractName, ~handler: option<string>) => {
@@ -22,7 +39,7 @@ let registerContractHandlers = async (~contractName, ~handler: option<string>) =
22
39
  | None => ()
23
40
  | Some(handlerPath) =>
24
41
  try {
25
- let _ = await Utils.importPath(toImportUrl(handlerPath))
42
+ let _ = await importHandler(handlerPath)
26
43
  } catch {
27
44
  | exn =>
28
45
  let cause = exn->Utils.prettifyExn->Obj.magic
@@ -37,10 +54,9 @@ let registerContractHandlers = async (~contractName, ~handler: option<string>) =
37
54
  }
38
55
  }
39
56
 
40
- let autoLoadFromSrcHandlers = async (~handlers: string) => {
41
- // Relative to cwd (project root)
42
- let srcPattern = `./${handlers}/**/*.{js,mjs,ts}`
43
- let handlerFiles = try {
57
+ let getAutoLoadFiles = async (~config: Config.t) => {
58
+ let srcPattern = `./${config.handlers}/**/*.{js,mjs,ts}`
59
+ try {
44
60
  let iterator = globIterator(srcPattern)
45
61
  let files = await iterator->Utils.Array.fromAsyncIterator
46
62
  // Filter out test and spec files
@@ -54,22 +70,38 @@ let autoLoadFromSrcHandlers = async (~handlers: string) => {
54
70
  } catch {
55
71
  | exn =>
56
72
  JsError.throwWithMessage(
57
- `Failed to glob src/handlers directory for auto-loading handlers. Pattern: ${srcPattern}. Before continuing, check that you're using Node.js >=22 version. Error: ${exn
73
+ `Failed to glob src/handlers directory for auto-loading handlers. Pattern: ${srcPattern}. Error: ${exn
58
74
  ->Utils.prettifyExn
59
75
  ->Obj.magic}`,
60
76
  )
61
77
  }
78
+ }
62
79
 
63
- // Import handler files using absolute file:// URLs resolved from cwd
64
- let _ = await handlerFiles
65
- ->Array.map(file => {
66
- Utils.importPath(toImportUrl(file))->Promise.catch(exn => {
67
- let cause = exn->Utils.prettifyExn->Obj.magic
68
- Logging.errorWithExn(exn, `Failed to auto-load handler file: ${file}`)
69
- JsError.throwWithMessage(`Failed to auto-load handler file: ${file}. Cause: ${cause}`)
70
- })
71
- })
72
- ->Promise.all
80
+ type typeCheckResult = {
81
+ skipped?: string,
82
+ errors?: string,
83
+ }
84
+
85
+ @module("./HandlerTypeCheck.mjs")
86
+ external checkTypesInWorker: (~cwd: string, ~files: array<string>) => promise<typeCheckResult> =
87
+ "check"
88
+
89
+ let typeCheck = async (~config: Config.t, ~autoLoadFiles) => {
90
+ // A contract's `handler:` may also be an auto-loaded file.
91
+ let files =
92
+ autoLoadFiles
93
+ ->Array.concat(config.contractHandlers->Array.filterMap(({handler}) => handler))
94
+ ->Array.filter(isTypeScript)
95
+ ->Array.map(toAbsolutePath)
96
+ ->Utils.Set.fromArray
97
+ ->Utils.Set.toArray
98
+ if files->Array.length > 0 {
99
+ switch await checkTypesInWorker(~cwd=NodeJs.Process.cwd(), ~files) {
100
+ | {errors} => JsError.throwWithMessage(`Handler files have type errors:\n\n${errors}`)
101
+ | {skipped} => Logging.warn(skipped)
102
+ | _ => ()
103
+ }
104
+ }
73
105
  }
74
106
 
75
107
  // `Config` holds only event definitions; handler + per-chain `where`
@@ -77,13 +109,23 @@ let autoLoadFromSrcHandlers = async (~handlers: string) => {
77
109
  // `HandlerRegister.finishRegistration`. This loads the user handler files
78
110
  // (populating the global `HandlerRegister` registry as a side effect) and
79
111
  // returns the resulting per-chain registrations.
80
- let registerAllHandlers = async (~config: Config.t): HandlerRegister.registrationsByChainId => {
112
+ let registerAllHandlers = async (
113
+ ~config: Config.t,
114
+ ~autoLoadFiles,
115
+ ): HandlerRegister.registrationsByChainId => {
81
116
  HandlerRegister.startRegistration(~config)
117
+ registerTsHooks()
82
118
 
83
- // Auto-load all .js files from src/handlers directory
84
- await autoLoadFromSrcHandlers(~handlers=config.handlers)
119
+ let _ = await autoLoadFiles
120
+ ->Array.map(file => {
121
+ importHandler(file)->Promise.catch(exn => {
122
+ let cause = exn->Utils.prettifyExn->Obj.magic
123
+ Logging.errorWithExn(exn, `Failed to auto-load handler file: ${file}`)
124
+ JsError.throwWithMessage(`Failed to auto-load handler file: ${file}. Cause: ${cause}`)
125
+ })
126
+ })
127
+ ->Promise.all
85
128
 
86
- // Load contract-specific handlers
87
129
  let _ = await config.contractHandlers
88
130
  ->Array.map(({name, handler}) => {
89
131
  registerContractHandlers(~contractName=name, ~handler)
@@ -1,26 +1,46 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Url from "url";
4
+ import * as Core from "./Core.res.mjs";
4
5
  import * as Path from "path";
5
6
  import * as Utils from "./Utils.res.mjs";
6
7
  import * as Logging from "./Logging.res.mjs";
7
8
  import * as Process from "process";
8
- import * as Nodemodule from "node:module";
9
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
9
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
11
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
11
12
  import * as HandlerRegister from "./HandlerRegister.res.mjs";
12
13
  import * as Promises from "node:fs/promises";
14
+ import * as TsModuleHooksMjs from "./TsModuleHooks.mjs";
13
15
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
+ import * as HandlerTypeCheckMjs from "./HandlerTypeCheck.mjs";
14
17
 
15
- try {
16
- Nodemodule.register("tsx/esm", import.meta.url);
17
- } catch (exn) {
18
-
18
+ function registerTsHooksWith(prim) {
19
+ TsModuleHooksMjs.register(prim);
19
20
  }
20
21
 
21
- function toImportUrl(relativePath) {
22
- let absolutePath = Path.resolve(Process.cwd(), relativePath);
23
- return Url.pathToFileURL(absolutePath).toString();
22
+ function registerTsHooks() {
23
+ let addon = Core.getAddon();
24
+ TsModuleHooksMjs.register({
25
+ loadTs: addon.loadTs,
26
+ tsResolveCandidates: addon.tsResolveCandidates,
27
+ tsNotFoundCandidates: addon.tsNotFoundCandidates
28
+ });
29
+ }
30
+
31
+ function isTypeScript(file) {
32
+ return /\.m?tsx?$/.test(file);
33
+ }
34
+
35
+ function toAbsolutePath(file) {
36
+ return Path.resolve(Process.cwd(), file);
37
+ }
38
+
39
+ async function importHandler(file) {
40
+ if (isTypeScript(file)) {
41
+ Core.getAddon().tsCheckHandlerFormat(toAbsolutePath(file));
42
+ }
43
+ return await import(Url.pathToFileURL(toAbsolutePath(file)).toString());
24
44
  }
25
45
 
26
46
  async function registerContractHandlers(contractName, handler) {
@@ -28,7 +48,7 @@ async function registerContractHandlers(contractName, handler) {
28
48
  return;
29
49
  }
30
50
  try {
31
- await import(toImportUrl(handler));
51
+ await importHandler(handler);
32
52
  return;
33
53
  } catch (raw_exn) {
34
54
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -38,35 +58,61 @@ async function registerContractHandlers(contractName, handler) {
38
58
  }
39
59
  }
40
60
 
41
- async function autoLoadFromSrcHandlers(handlers) {
42
- let srcPattern = `./` + handlers + `/**/*.{js,mjs,ts}`;
43
- let handlerFiles;
61
+ async function getAutoLoadFiles(config) {
62
+ let srcPattern = `./` + config.handlers + `/**/*.{js,mjs,ts}`;
44
63
  try {
45
64
  let iterator = Promises.glob(srcPattern);
46
65
  let files = await Utils.$$Array.fromAsyncIterator(iterator);
47
- handlerFiles = files.filter(file => !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test.")));
66
+ return files.filter(file => !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test.")));
48
67
  } catch (raw_exn) {
49
68
  let exn = Primitive_exceptions.internalToException(raw_exn);
50
- handlerFiles = Stdlib_JsError.throwWithMessage(`Failed to glob src/handlers directory for auto-loading handlers. Pattern: ` + srcPattern + `. Before continuing, check that you're using Node.js >=22 version. Error: ` + Utils.prettifyExn(exn));
69
+ return Stdlib_JsError.throwWithMessage(`Failed to glob src/handlers directory for auto-loading handlers. Pattern: ` + srcPattern + `. Error: ` + Utils.prettifyExn(exn));
51
70
  }
52
- await Promise.all(handlerFiles.map(file => Stdlib_Promise.$$catch(import(toImportUrl(file)), exn => {
71
+ }
72
+
73
+ function checkTypesInWorker(prim0, prim1) {
74
+ return HandlerTypeCheckMjs.check(prim0, prim1);
75
+ }
76
+
77
+ async function typeCheck(config, autoLoadFiles) {
78
+ let files = Array.from(new Set(autoLoadFiles.concat(Stdlib_Array.filterMap(config.contractHandlers, param => param.handler)).filter(isTypeScript).map(toAbsolutePath)));
79
+ if (files.length === 0) {
80
+ return;
81
+ }
82
+ let match = await HandlerTypeCheckMjs.check(Process.cwd(), files);
83
+ let skipped = match.skipped;
84
+ let errors = match.errors;
85
+ if (errors !== undefined) {
86
+ return Stdlib_JsError.throwWithMessage(`Handler files have type errors:\n\n` + errors);
87
+ } else if (skipped !== undefined) {
88
+ return Logging.warn(skipped);
89
+ } else {
90
+ return;
91
+ }
92
+ }
93
+
94
+ async function registerAllHandlers(config, autoLoadFiles) {
95
+ HandlerRegister.startRegistration(config);
96
+ registerTsHooks();
97
+ await Promise.all(autoLoadFiles.map(file => Stdlib_Promise.$$catch(importHandler(file), exn => {
53
98
  let cause = Utils.prettifyExn(exn);
54
99
  Logging.errorWithExn(exn, `Failed to auto-load handler file: ` + file);
55
100
  return Stdlib_JsError.throwWithMessage(`Failed to auto-load handler file: ` + file + `. Cause: ` + cause);
56
101
  })));
57
- }
58
-
59
- async function registerAllHandlers(config) {
60
- HandlerRegister.startRegistration(config);
61
- await autoLoadFromSrcHandlers(config.handlers);
62
102
  await Promise.all(config.contractHandlers.map(param => registerContractHandlers(param.name, param.handler)));
63
103
  return HandlerRegister.finishRegistration(config);
64
104
  }
65
105
 
66
106
  export {
67
- toImportUrl,
107
+ registerTsHooksWith,
108
+ registerTsHooks,
109
+ isTypeScript,
110
+ toAbsolutePath,
111
+ importHandler,
68
112
  registerContractHandlers,
69
- autoLoadFromSrcHandlers,
113
+ getAutoLoadFiles,
114
+ checkTypesInWorker,
115
+ typeCheck,
70
116
  registerAllHandlers,
71
117
  }
72
- /* Not a pure module */
118
+ /* url Not a pure module */
@@ -0,0 +1,14 @@
1
+ import { Worker } from "node:worker_threads";
2
+
3
+ // The compiler blocks the thread it runs on, so it gets its own and the
4
+ // database setup carries on meanwhile.
5
+ export const check = (cwd, files) =>
6
+ new Promise((resolve, reject) => {
7
+ const worker = new Worker(new URL("./HandlerTypeCheckWorker.mjs", import.meta.url), {
8
+ workerData: { cwd, files },
9
+ });
10
+ worker.once("message", resolve);
11
+ worker.once("error", reject);
12
+ // Settles nothing once the result has arrived.
13
+ worker.once("exit", () => reject(new Error("The handler type check exited without a result.")));
14
+ });
@@ -0,0 +1,68 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { stripVTControlCharacters } from "node:util";
5
+ import { parentPort, workerData } from "node:worker_threads";
6
+
7
+ const check = ({ cwd, files }) => {
8
+ // The ReScript compiler checks a ReScript project's handlers, so it has no
9
+ // reason to set up TypeScript, and the warning would only be noise there.
10
+ // `rescript.json` at the root is what makes codegen treat it as ReScript.
11
+ const skip = (warning) => (existsSync(path.join(cwd, "rescript.json")) ? {} : { skipped: warning });
12
+
13
+ let ts;
14
+ try {
15
+ // The project's own compiler, so handlers are checked exactly as the
16
+ // user's editor and `tsc` check them.
17
+ ts = createRequire(path.join(cwd, "package.json"))("typescript");
18
+ } catch {
19
+ return skip("Skipped the handler type check: the project doesn't depend on typescript.");
20
+ }
21
+
22
+ const configPath = ts.findConfigFile(cwd, ts.sys.fileExists);
23
+ if (configPath === undefined) {
24
+ return skip(
25
+ "Skipped the handler type check: no tsconfig.json found. Add one to type-check handlers on start, like the one envio init creates."
26
+ );
27
+ }
28
+
29
+ const host = {
30
+ getCanonicalFileName: (file) => file,
31
+ getCurrentDirectory: () => cwd,
32
+ getNewLine: () => "\n",
33
+ };
34
+ const format = (diagnostics) =>
35
+ stripVTControlCharacters(ts.formatDiagnosticsWithColorAndContext(diagnostics, host)).trimEnd();
36
+
37
+ const parsed = ts.getParsedCommandLineOfConfigFile(
38
+ configPath,
39
+ { noEmit: true },
40
+ {
41
+ ...ts.sys,
42
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
43
+ throw new Error(format([diagnostic]));
44
+ },
45
+ }
46
+ );
47
+
48
+ // The tsconfig's files are roots, as tsc makes them, since any of them can
49
+ // declare globals a handler relies on. Only the handlers' errors count.
50
+ const handlerFiles = files.map((file) => path.resolve(cwd, file));
51
+ const program = ts.createProgram({
52
+ rootNames: [...parsed.fileNames, ...handlerFiles],
53
+ options: parsed.options,
54
+ projectReferences: parsed.projectReferences,
55
+ });
56
+
57
+ // Every file is a root, so each has a source file. Without one,
58
+ // `getSemanticDiagnostics` would check the whole program.
59
+ const diagnostics = handlerFiles
60
+ .map((file) => program.getSourceFile(file))
61
+ .flatMap((sourceFile) => [
62
+ ...program.getSyntacticDiagnostics(sourceFile),
63
+ ...program.getSemanticDiagnostics(sourceFile),
64
+ ]);
65
+ return diagnostics.length === 0 ? {} : { errors: format(diagnostics) };
66
+ };
67
+
68
+ parentPort.postMessage(check(workerData));
package/src/Internal.res CHANGED
@@ -491,7 +491,7 @@ type eventConfig = private {
491
491
  }
492
492
 
493
493
  type fuelEventKind =
494
- | LogData({logId: string, decode: string => eventParams})
494
+ | LogData({logId: string, abi: JSON.t})
495
495
  | Mint
496
496
  | Burn
497
497
  | Transfer
package/src/Main.res CHANGED
@@ -411,8 +411,6 @@ let getGlobalIndexer = (): 'indexer => {
411
411
  Utils.Proxy.make(Utils.Object.createNullObject(), traps)->(Utils.magic: {..} => 'indexer)
412
412
  }
413
413
 
414
- // The RPC-stripped public config that the storage layer persists in
415
- // `envio_info` (on initialize) and validates against (on resume).
416
414
  let migrate = async (~reset) => {
417
415
  let config = Config.load()
418
416
  let persistence = PgStorage.makePersistenceFromConfig(~config)
@@ -420,8 +418,8 @@ let migrate = async (~reset) => {
420
418
  ~reset,
421
419
  ~chainConfigs=config.chainMap->ChainMap.values,
422
420
  ~contractMapping=config.contractMapping,
423
- ~envioInfo=Config.envioInfo(),
424
- ~resetCommand="envio local db-migrate setup",
421
+ ~envioInfo=config.envioInfo,
422
+ ~resetCommand="envio local db-migrate setup"->Config.withProjectFlags(~config),
425
423
  ~runCommand=None,
426
424
  ~lowercaseAddresses=config.lowercaseAddresses,
427
425
  // A migration command runs once and exits, with nobody watching it recover:
@@ -466,18 +464,23 @@ exception FatalError(exn)
466
464
  | None => PgStorage.makePersistenceFromConfig(~config)
467
465
  }
468
466
  setGlobalPersistence(persistence)
469
- await persistence->Persistence.initForRun(
470
- ~config,
471
- ~reset,
472
- ~isDevelopmentMode,
473
- ~requireInitialized=config.isolated,
474
- )
467
+ let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(~config)
468
+ // A worker's supervisor has checked the handlers already, and the test
469
+ // indexer leaves type checking to the user's test setup.
470
+ let typeCheck =
471
+ isTest || Worker.isEnabled
472
+ ? Promise.resolve()
473
+ : HandlerLoader.typeCheck(~config, ~autoLoadFiles)
474
+ let _ = await Promise.all2((
475
+ persistence->Persistence.initForRun(~config, ~reset, ~isDevelopmentMode),
476
+ typeCheck,
477
+ ))
475
478
 
476
479
  // Loads user handler files, which register handler/contractRegister/where
477
480
  // state into the global `HandlerRegister` registry as a side effect; this
478
481
  // returns that state resolved into per-chain registrations. `config` itself
479
482
  // is never mutated by registration — it holds only event definitions.
480
- let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
483
+ let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config, ~autoLoadFiles)
481
484
  let config = if isTest {
482
485
  {...config, shouldRollbackOnReorg: false}
483
486
  } else {
@@ -535,7 +538,7 @@ exception FatalError(exn)
535
538
  ~onError,
536
539
  )
537
540
  if shouldUseTui {
538
- let _rerender = Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
541
+ Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
539
542
  }
540
543
  Worker.bindRun(
541
544
  ~getMetrics=() => state->IndexerState.toMetrics,
@@ -565,6 +568,8 @@ let start = async (
565
568
  Worker.config->Option.forEach(({chainIds, isDev}) =>
566
569
  Config.prime(Config.getPublicConfigJson()->Config.withCommandFields(~chainIds, ~isDev))
567
570
  )
571
+ // An unsupported Node fails here, before the database is touched.
572
+ HandlerLoader.registerTsHooks()
568
573
  let config = Config.load()
569
574
  switch isTest ? None : Supervisor.planForRun(~config) {
570
575
  | Some(workers) => await Supervisor.run(~config, ~workers, ~reset)
package/src/Main.res.mjs CHANGED
@@ -351,7 +351,7 @@ function getGlobalIndexer() {
351
351
  async function migrate(reset) {
352
352
  let config = Config.load();
353
353
  let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
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");
354
+ await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, config.envioInfo, Config.withProjectFlags("envio local db-migrate setup", config), undefined, reset, config.lowercaseAddresses, undefined, undefined, "Once");
355
355
  return await persistence.storage.close();
356
356
  }
357
357
 
@@ -372,8 +372,13 @@ async function startIndexer(config, persistence, resetOpt, isTestOpt, exitAfterF
372
372
  let isDevelopmentMode = !isTest && config.isDev;
373
373
  let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
374
374
  EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
375
- await Persistence.initForRun(persistence$1, config, reset, isDevelopmentMode, config.isolated);
376
- let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
375
+ let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(config);
376
+ let typeCheck = isTest || Worker.isEnabled ? Promise.resolve() : HandlerLoader.typeCheck(config, autoLoadFiles);
377
+ await Promise.all([
378
+ Persistence.initForRun(persistence$1, config, reset, isDevelopmentMode),
379
+ typeCheck
380
+ ]);
381
+ let registrationsByChainId = await HandlerLoader.registerAllHandlers(config, autoLoadFiles);
377
382
  let config$1;
378
383
  if (isTest) {
379
384
  let newrecord = {...config};
@@ -417,6 +422,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
417
422
  let isTest = isTestOpt !== undefined ? isTestOpt : false;
418
423
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
419
424
  Stdlib_Option.forEach(Worker.config, param => Config.prime(Config.withCommandFields(Config.getPublicConfigJson(), param.chainIds, param.isDev)));
425
+ HandlerLoader.registerTsHooks();
420
426
  let config = Config.load();
421
427
  let workers = isTest ? undefined : Supervisor.planForRun(config, undefined);
422
428
  if (workers !== undefined) {
@@ -0,0 +1,13 @@
1
+ const MINIMUM = [22, 15, 0];
2
+
3
+ // The message for a Node.js older than envio supports, or undefined.
4
+ export const unsupportedNodeMessage = () => {
5
+ const current = process.versions.node;
6
+ const parts = current.split(".").map(Number);
7
+ const index = MINIMUM.findIndex((minimum, i) => parts[i] !== minimum);
8
+ if (index === -1 || parts[index] > MINIMUM[index]) return undefined;
9
+ return [
10
+ `envio needs Node.js ${MINIMUM.join(".")} or newer, and this is Node.js ${current}.`,
11
+ "Install a newer one from https://nodejs.org/en/download, or with your version manager, for example: nvm install --lts",
12
+ ].join("\n");
13
+ };