envio 3.13.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.
- package/bin.mjs +10 -1
- package/index.d.ts +11 -0
- package/package.json +8 -20
- package/rescript.json +1 -4
- package/src/Config.res +69 -100
- package/src/Config.res.mjs +50 -63
- package/src/Core.res +46 -6
- package/src/Core.res.mjs +41 -4
- package/src/EventConfigBuilder.res +4 -10
- package/src/EventConfigBuilder.res.mjs +1 -3
- package/src/ExitOnCaughtUp.res +5 -1
- package/src/ExitOnCaughtUp.res.mjs +8 -3
- package/src/HandlerLoader.res +74 -32
- package/src/HandlerLoader.res.mjs +69 -23
- package/src/HandlerTypeCheck.mjs +14 -0
- package/src/HandlerTypeCheckWorker.mjs +68 -0
- package/src/Internal.res +1 -1
- package/src/Main.res +17 -12
- package/src/Main.res.mjs +9 -3
- package/src/NodeVersion.mjs +13 -0
- package/src/Persistence.res +79 -38
- package/src/Persistence.res.mjs +41 -10
- package/src/PgStorage.res +62 -34
- package/src/PgStorage.res.mjs +65 -31
- package/src/ResumePlan.res +172 -0
- package/src/ResumePlan.res.mjs +122 -0
- package/src/Supervisor.res +8 -8
- package/src/Supervisor.res.mjs +6 -1
- package/src/TestIndexer.res +14 -4
- package/src/TestIndexer.res.mjs +3 -2
- package/src/TsModuleHooks.mjs +121 -0
- package/src/Utils.res +0 -8
- package/src/Worker.res +8 -0
- package/src/Worker.res.mjs +11 -0
- package/src/bindings/NodeJs.res +8 -7
- package/src/bindings/NodeJs.res.mjs +0 -3
- package/src/bindings/Pino.res +2 -2
- package/src/db/InternalTable.res +52 -2
- package/src/db/InternalTable.res.mjs +32 -3
- package/src/sources/FuelHyperSync.res +2 -0
- package/src/sources/FuelHyperSync.res.mjs +1 -0
- package/src/sources/FuelHyperSync.resi +1 -0
- package/src/sources/FuelHyperSyncClient.res +25 -8
- package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
- package/src/sources/FuelHyperSyncSource.res +24 -22
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
- package/src/tui/InitApi.res +85 -0
- package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
- package/src/tui/Tui.res +142 -249
- package/src/tui/Tui.res.mjs +123 -332
- package/src/Js.shim.ts +0 -11
- package/src/bindings/DateFns.res +0 -74
- package/src/bindings/DateFns.res.mjs +0 -22
- package/src/sources/FuelSDK.res +0 -38
- package/src/sources/FuelSDK.res.mjs +0 -27
- package/src/sources/vendored-fuel-abi-coder.js +0 -1792
- package/src/tui/bindings/Ink.res +0 -369
- package/src/tui/bindings/Ink.res.mjs +0 -66
- package/src/tui/bindings/Style.res +0 -123
- package/src/tui/bindings/Style.res.mjs +0 -2
- package/src/tui/components/BufferedProgressBar.res +0 -40
- package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
- package/src/tui/components/CustomHooks.res +0 -122
- package/src/tui/components/Messages.res +0 -40
- package/src/tui/components/Messages.res.mjs +0 -71
- package/src/tui/components/SyncETA.res +0 -179
- package/src/tui/components/SyncETA.res.mjs +0 -249
- package/src/tui/components/TuiData.res +0 -98
- package/src/tui/components/TuiData.res.mjs +0 -73
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
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -343,7 +343,7 @@ let validateFieldsShapeOrThrow = (
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
let selectionList = (fields: unknown, key: string): option<array<string>> =>
|
|
346
|
-
switch
|
|
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
|
-
|
|
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
|
-
|
|
595
|
+
abi: rawAbi
|
|
598
596
|
};
|
|
599
597
|
break;
|
|
600
598
|
case "mint" :
|
package/src/ExitOnCaughtUp.res
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
/*
|
|
40
|
+
/* Worker Not a pure module */
|
package/src/HandlerLoader.res
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
@module("node:fs/promises")
|
|
2
2
|
external globIterator: string => Utils.asyncIterator<string> = "glob"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
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}.
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 (
|
|
112
|
+
let registerAllHandlers = async (
|
|
113
|
+
~config: Config.t,
|
|
114
|
+
~autoLoadFiles,
|
|
115
|
+
): HandlerRegister.registrationsByChainId => {
|
|
81
116
|
HandlerRegister.startRegistration(~config)
|
|
117
|
+
registerTsHooks()
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
} catch (exn) {
|
|
18
|
-
|
|
18
|
+
function registerTsHooksWith(prim) {
|
|
19
|
+
TsModuleHooksMjs.register(prim);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function
|
|
22
|
-
let
|
|
23
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
69
|
+
return Stdlib_JsError.throwWithMessage(`Failed to glob src/handlers directory for auto-loading handlers. Pattern: ` + srcPattern + `. Error: ` + Utils.prettifyExn(exn));
|
|
51
70
|
}
|
|
52
|
-
|
|
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
|
-
|
|
107
|
+
registerTsHooksWith,
|
|
108
|
+
registerTsHooks,
|
|
109
|
+
isTypeScript,
|
|
110
|
+
toAbsolutePath,
|
|
111
|
+
importHandler,
|
|
68
112
|
registerContractHandlers,
|
|
69
|
-
|
|
113
|
+
getAutoLoadFiles,
|
|
114
|
+
checkTypesInWorker,
|
|
115
|
+
typeCheck,
|
|
70
116
|
registerAllHandlers,
|
|
71
117
|
}
|
|
72
|
-
/*
|
|
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
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=
|
|
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
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
376
|
-
let
|
|
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) {
|