envio 3.14.0 → 3.14.1

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/evm.schema.json CHANGED
@@ -543,7 +543,7 @@
543
543
  "type": "object",
544
544
  "properties": {
545
545
  "url": {
546
- "description": "The RPC endpoint URL.",
546
+ "description": "The RPC endpoint URL. An entry whose url is empty, for example `${RPC_URL:-}` with the variable unset, is ignored.",
547
547
  "type": "string"
548
548
  },
549
549
  "for": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.14.0",
3
+ "version": "3.14.1",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -54,10 +54,10 @@
54
54
  "postgres": "3.4.8"
55
55
  },
56
56
  "optionalDependencies": {
57
- "envio-linux-x64": "3.14.0",
58
- "envio-linux-x64-musl": "3.14.0",
59
- "envio-linux-arm64": "3.14.0",
60
- "envio-darwin-x64": "3.14.0",
61
- "envio-darwin-arm64": "3.14.0"
57
+ "envio-linux-x64": "3.14.1",
58
+ "envio-linux-x64-musl": "3.14.1",
59
+ "envio-linux-arm64": "3.14.1",
60
+ "envio-darwin-x64": "3.14.1",
61
+ "envio-darwin-arm64": "3.14.1"
62
62
  }
63
63
  }
@@ -77,15 +77,6 @@ let getAutoLoadFiles = async (~config: Config.t) => {
77
77
  }
78
78
  }
79
79
 
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
80
  let typeCheck = async (~config: Config.t, ~autoLoadFiles) => {
90
81
  // A contract's `handler:` may also be an auto-loaded file.
91
82
  let files =
@@ -96,10 +87,13 @@ let typeCheck = async (~config: Config.t, ~autoLoadFiles) => {
96
87
  ->Utils.Set.fromArray
97
88
  ->Utils.Set.toArray
98
89
  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
- | _ => ()
90
+ switch await HandlerTypeCheck.check(~cwd=NodeJs.Process.cwd(), ~files) {
91
+ | TypeErrors(errors) =>
92
+ JsError.throwWithMessage(
93
+ `The handler type check found errors. Fix them to start the indexer:\n\n${errors}`,
94
+ )
95
+ | Skipped(warning) => Logging.warn(warning)
96
+ | Passed => ()
103
97
  }
104
98
  }
105
99
  }
@@ -10,10 +10,10 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
10
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
11
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
12
12
  import * as HandlerRegister from "./HandlerRegister.res.mjs";
13
+ import * as HandlerTypeCheck from "./HandlerTypeCheck.res.mjs";
13
14
  import * as Promises from "node:fs/promises";
14
15
  import * as TsModuleHooksMjs from "./TsModuleHooks.mjs";
15
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
- import * as HandlerTypeCheckMjs from "./HandlerTypeCheck.mjs";
17
17
 
18
18
  function registerTsHooksWith(prim) {
19
19
  TsModuleHooksMjs.register(prim);
@@ -70,24 +70,18 @@ async function getAutoLoadFiles(config) {
70
70
  }
71
71
  }
72
72
 
73
- function checkTypesInWorker(prim0, prim1) {
74
- return HandlerTypeCheckMjs.check(prim0, prim1);
75
- }
76
-
77
73
  async function typeCheck(config, autoLoadFiles) {
78
74
  let files = Array.from(new Set(autoLoadFiles.concat(Stdlib_Array.filterMap(config.contractHandlers, param => param.handler)).filter(isTypeScript).map(toAbsolutePath)));
79
75
  if (files.length === 0) {
80
76
  return;
81
77
  }
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 {
78
+ let errors = await HandlerTypeCheck.check(Process.cwd(), files);
79
+ if (typeof errors !== "object") {
90
80
  return;
81
+ } else if (errors.TAG === "TypeErrors") {
82
+ return Stdlib_JsError.throwWithMessage(`The handler type check found errors. Fix them to start the indexer:\n\n` + errors._0);
83
+ } else {
84
+ return Logging.warn(errors._0);
91
85
  }
92
86
  }
93
87
 
@@ -111,7 +105,6 @@ export {
111
105
  importHandler,
112
106
  registerContractHandlers,
113
107
  getAutoLoadFiles,
114
- checkTypesInWorker,
115
108
  typeCheck,
116
109
  registerAllHandlers,
117
110
  }
@@ -0,0 +1,207 @@
1
+ type result =
2
+ | Passed
3
+ | TypeErrors(string)
4
+ | Skipped(string)
5
+
6
+ let timeoutSeconds = 30
7
+
8
+ // tsc's pretty output reads the same from TypeScript 5 to 7: a diagnostic
9
+ // starts unindented with its location, its code frame and related locations
10
+ // follow indented, and `--listFiles` prints the program's files after the
11
+ // last one, before the closing summary.
12
+ let fileDiagnostic = /^(.+):\d+:\d+ - error TS(\d+): /
13
+ let globalDiagnostic = /^error TS(\d+): /
14
+ let summary = /^Found \d+ errors?\b/
15
+ let thrownError = /^\w*Error: /
16
+
17
+ let lines = output => output->String.replaceAll("\r\n", "\n")->String.split("\n")
18
+
19
+ let rec findUp = (directory, name) => {
20
+ let candidate = NodeJs.Path.resolve([directory, name])->NodeJs.Path.toString
21
+ if NodeJs.Fs.existsSync(candidate) {
22
+ Some(candidate)
23
+ } else {
24
+ let parent = NodeJs.Path.dirname(directory)->NodeJs.Path.toString
25
+ parent === directory ? None : findUp(parent, name)
26
+ }
27
+ }
28
+
29
+ // The `tsc` the project's own scripts run. TypeScript 7 has no JavaScript API
30
+ // to call instead, only this command.
31
+ let resolveTsc = (~cwd) =>
32
+ try {
33
+ let manifest =
34
+ NodeJs.Module.createRequire(
35
+ NodeJs.Path.resolve([cwd, "package.json"])->NodeJs.Path.toString,
36
+ )->NodeJs.Module.resolve("typescript/package.json")
37
+ let bin = switch NodeJs.Fs.readFileSync(manifest)->JSON.parseOrThrow {
38
+ | JSON.Object(fields) =>
39
+ switch fields->Dict.get("bin") {
40
+ | Some(JSON.String(bin)) => Some(bin)
41
+ | Some(JSON.Object(bins)) =>
42
+ switch bins->Dict.get("tsc") {
43
+ | Some(JSON.String(bin)) => Some(bin)
44
+ | _ => None
45
+ }
46
+ | _ => None
47
+ }
48
+ | _ => None
49
+ }
50
+ bin->Option.map(bin =>
51
+ NodeJs.Path.resolve([
52
+ NodeJs.Path.dirname(manifest)->NodeJs.Path.toString,
53
+ bin,
54
+ ])->NodeJs.Path.toString
55
+ )
56
+ } catch {
57
+ | _ => None
58
+ }
59
+
60
+ type run = {error: option<NodeJs.ChildProcess.execFileError>, output: string}
61
+
62
+ let runTsc = (~tsc, ~cwd, args) =>
63
+ Promise.make((resolve, _) =>
64
+ NodeJs.ChildProcess.execFile(
65
+ NodeJs.Process.process->NodeJs.Process.execPath,
66
+ [tsc]->Array.concat(args),
67
+ {cwd, timeout: timeoutSeconds * 1000, maxBuffer: Float.Constants.positiveInfinity},
68
+ (error, stdout, stderr) =>
69
+ resolve({
70
+ error: error->Null.toOption,
71
+ output: NodeJs.Util.stripVTControlCharacters(stdout ++ stderr),
72
+ }),
73
+ )
74
+ )
75
+
76
+ type diagnostic = {file: option<string>, code: int, lines: array<string>}
77
+
78
+ // tsc type-checks nothing after a syntax error, an options error or a global
79
+ // error, and an options error is reported at its line in tsconfig.json.
80
+ let canStopTypeChecking = ({file, code}) =>
81
+ file->Option.isNone || code < 2000 || (code >= 5000 && code < 7000)
82
+
83
+ // Paths compare relative to the project, which on Windows also ignores the
84
+ // case of a drive letter tsc may print differently.
85
+ let parse = (output, ~cwd) => {
86
+ let relative = file =>
87
+ NodeJs.Path.relative(cwd, NodeJs.Path.resolve([cwd, file])->NodeJs.Path.toString)
88
+ let diagnostics = []
89
+ let programFiles = Utils.Set.make()
90
+ let current = ref(None)
91
+ let start = (~file, ~code, line) => {
92
+ let diagnostic = {
93
+ file,
94
+ code: code->Int.fromString->Option.getOr(0),
95
+ lines: [line],
96
+ }
97
+ diagnostics->Array.push(diagnostic)
98
+ current := Some(diagnostic)
99
+ }
100
+ output
101
+ ->lines
102
+ ->Array.forEach(line =>
103
+ switch (
104
+ fileDiagnostic->RegExp.exec(line)->Option.map(RegExp.Result.matches),
105
+ globalDiagnostic->RegExp.exec(line)->Option.map(RegExp.Result.matches),
106
+ ) {
107
+ | (Some([Some(file), Some(code)]), _) => start(~file=Some(relative(file)), ~code, line)
108
+ | (_, Some([Some(code)])) => start(~file=None, ~code, line)
109
+ | _ if NodeJs.Path.isAbsolute(line) =>
110
+ programFiles->Utils.Set.add(relative(line))->ignore
111
+ current := None
112
+ | _ if summary->RegExp.test(line) => current := None
113
+ | _ => current.contents->Option.forEach(diagnostic => diagnostic.lines->Array.push(line))
114
+ }
115
+ )
116
+ (diagnostics, programFiles)
117
+ }
118
+
119
+ // Exit codes say nothing here: TypeScript 5 and 6 exit with 2 on type errors,
120
+ // 7 with 1, and 1 is also how its launcher fails when the native compiler
121
+ // isn't installed.
122
+ let failure = ({error, output}) =>
123
+ switch error {
124
+ | Some({killed: true}) =>
125
+ `Skipped the handler type check: tsc didn't finish within ${timeoutSeconds->Int.toString} seconds.`
126
+ | _ =>
127
+ let reason =
128
+ output
129
+ ->lines
130
+ ->Array.find(line => thrownError->RegExp.test(line))
131
+ ->Option.getOr(output->String.trim)
132
+ `Skipped the handler type check: tsc failed to run:\n\n${reason}`
133
+ }
134
+
135
+ let check = async (~cwd, ~files) => {
136
+ // The ReScript compiler checks a ReScript project's handlers, so it has no
137
+ // reason to set up TypeScript, and the warning would only be noise there.
138
+ // `rescript.json` at the root is what makes codegen treat it as ReScript.
139
+ let skip = warning =>
140
+ NodeJs.Fs.existsSync(NodeJs.Path.resolve([cwd, "rescript.json"])->NodeJs.Path.toString)
141
+ ? Passed
142
+ : Skipped(warning)
143
+
144
+ switch (resolveTsc(~cwd), findUp(cwd, "tsconfig.json")) {
145
+ | (None, _) =>
146
+ skip(
147
+ "Skipped the handler type check: typescript isn't installed. Add it to devDependencies to type-check handlers on start.",
148
+ )
149
+ | (_, None) =>
150
+ skip(
151
+ "Skipped the handler type check: no tsconfig.json found. Add one, like the one envio init creates, to type-check handlers on start.",
152
+ )
153
+ | (Some(tsc), Some(tsconfig)) =>
154
+ // The project's own `tsc --noEmit`, minus the build info it would write.
155
+ // `explainFiles` would replace the listed files' absolute paths with
156
+ // relative ones and the reasons each is included.
157
+ let run = await runTsc(
158
+ ~tsc,
159
+ ~cwd,
160
+ [
161
+ "--project",
162
+ tsconfig,
163
+ "--noEmit",
164
+ "--incremental",
165
+ "false",
166
+ "--composite",
167
+ "false",
168
+ "--listFiles",
169
+ "--explainFiles",
170
+ "false",
171
+ "--pretty",
172
+ ],
173
+ )
174
+ let (diagnostics, programFiles) = run.output->parse(~cwd)
175
+ let report = diagnostics =>
176
+ diagnostics
177
+ ->Array.map(({lines}) => lines->Array.join("\n")->String.trimEnd)
178
+ ->Array.join("\n\n")
179
+ let handlers = files->Array.map(file => NodeJs.Path.relative(cwd, file))
180
+ let isHandler = ({file}) => file->Option.mapOr(false, file => handlers->Array.includes(file))
181
+ let errors = diagnostics->Array.filter(isHandler)
182
+ let stopping =
183
+ diagnostics->Array.filter(diagnostic =>
184
+ !isHandler(diagnostic) && canStopTypeChecking(diagnostic)
185
+ )
186
+ let unchecked = handlers->Array.filter(file => !(programFiles->Utils.Set.has(file)))
187
+ if run.error->Option.isSome && diagnostics->Array.length === 0 {
188
+ Skipped(failure(run))
189
+ } else if errors->Array.length > 0 {
190
+ TypeErrors(report(errors))
191
+ } else if stopping->Array.length > 0 {
192
+ let reason = report(stopping)
193
+ Skipped(
194
+ `Skipped the handler type check: tsc can't check the handlers until these errors are fixed:\n\n${reason}`,
195
+ )
196
+ } else if unchecked->Array.length > 0 {
197
+ let names = unchecked->Array.join(", ")
198
+ let tsconfig = NodeJs.Path.relative(cwd, tsconfig)
199
+ let pronoun = unchecked->Array.length === 1 ? "it" : "them"
200
+ Skipped(
201
+ `Skipped the handler type check: ${tsconfig} doesn't include ${names}. Add ${pronoun} to "include" to type-check ${pronoun} on start.`,
202
+ )
203
+ } else {
204
+ Passed
205
+ }
206
+ }
207
+ }
@@ -0,0 +1,262 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Fs from "fs";
4
+ import * as Path from "path";
5
+ import * as Util from "util";
6
+ import * as Module from "module";
7
+ import * as Process from "process";
8
+ import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
9
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
+ import * as Child_process from "child_process";
11
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
12
+
13
+ let fileDiagnostic = /^(.+):\d+:\d+ - error TS(\d+): /;
14
+
15
+ let globalDiagnostic = /^error TS(\d+): /;
16
+
17
+ let summary = /^Found \d+ errors?\b/;
18
+
19
+ let thrownError = /^\w*Error: /;
20
+
21
+ function lines(output) {
22
+ return output.replaceAll("\r\n", "\n").split("\n");
23
+ }
24
+
25
+ function findUp(_directory, name) {
26
+ while (true) {
27
+ let directory = _directory;
28
+ let candidate = Path.resolve(directory, name);
29
+ if (Fs.existsSync(candidate)) {
30
+ return candidate;
31
+ }
32
+ let parent = Path.dirname(directory);
33
+ if (parent === directory) {
34
+ return;
35
+ }
36
+ _directory = parent;
37
+ continue;
38
+ };
39
+ }
40
+
41
+ function resolveTsc(cwd) {
42
+ try {
43
+ let manifest = Module.createRequire(Path.resolve(cwd, "package.json")).resolve("typescript/package.json");
44
+ let fields = JSON.parse(Fs.readFileSync(manifest, "utf8"));
45
+ let bin;
46
+ if (typeof fields === "object" && fields !== null && !Array.isArray(fields)) {
47
+ let match = fields["bin"];
48
+ if (match !== undefined && !(match === null || Array.isArray(match))) {
49
+ switch (typeof match) {
50
+ case "string" :
51
+ bin = match;
52
+ break;
53
+ case "object" :
54
+ let match$1 = match["tsc"];
55
+ bin = typeof match$1 === "string" ? match$1 : undefined;
56
+ break;
57
+ default:
58
+ bin = undefined;
59
+ }
60
+ } else {
61
+ bin = undefined;
62
+ }
63
+ } else {
64
+ bin = undefined;
65
+ }
66
+ return Stdlib_Option.map(bin, bin => Path.resolve(Path.dirname(manifest), bin));
67
+ } catch (exn) {
68
+ return;
69
+ }
70
+ }
71
+
72
+ function runTsc(tsc, cwd, args) {
73
+ return new Promise((resolve, param) => {
74
+ Child_process.execFile(Process.execPath, [tsc].concat(args), {
75
+ cwd: cwd,
76
+ timeout: 30000,
77
+ maxBuffer: Number.POSITIVE_INFINITY
78
+ }, (error, stdout, stderr) => resolve({
79
+ error: error === null ? undefined : Primitive_option.some(error),
80
+ output: Util.stripVTControlCharacters(stdout + stderr)
81
+ }));
82
+ });
83
+ }
84
+
85
+ function canStopTypeChecking(param) {
86
+ let code = param.code;
87
+ if (Stdlib_Option.isNone(param.file) || code < 2000) {
88
+ return true;
89
+ } else if (code >= 5000) {
90
+ return code < 7000;
91
+ } else {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ function parse(output, cwd) {
97
+ let relative = file => Path.relative(cwd, Path.resolve(cwd, file));
98
+ let diagnostics = [];
99
+ let programFiles = new Set();
100
+ let current = {
101
+ contents: undefined
102
+ };
103
+ let start = (file, code, line) => {
104
+ let diagnostic_code = Stdlib_Option.getOr(Stdlib_Int.fromString(code, undefined), 0);
105
+ let diagnostic_lines = [line];
106
+ let diagnostic = {
107
+ file: file,
108
+ code: diagnostic_code,
109
+ lines: diagnostic_lines
110
+ };
111
+ diagnostics.push(diagnostic);
112
+ current.contents = diagnostic;
113
+ };
114
+ lines(output).forEach(line => {
115
+ let match = Stdlib_Option.map(Primitive_option.fromNullable(fileDiagnostic.exec(line)), prim => prim.slice(1));
116
+ let match$1 = Stdlib_Option.map(Primitive_option.fromNullable(globalDiagnostic.exec(line)), prim => prim.slice(1));
117
+ let exit = 0;
118
+ if (match !== undefined && match.length === 2) {
119
+ let file = match[0];
120
+ if (file !== undefined) {
121
+ let code = match[1];
122
+ if (code !== undefined) {
123
+ return start(relative(file), code, line);
124
+ }
125
+ exit = 2;
126
+ } else {
127
+ exit = 2;
128
+ }
129
+ } else {
130
+ exit = 2;
131
+ }
132
+ if (exit === 2 && match$1 !== undefined && match$1.length === 1) {
133
+ let code$1 = match$1[0];
134
+ if (code$1 !== undefined) {
135
+ return start(undefined, code$1, line);
136
+ }
137
+ }
138
+ if (Path.isAbsolute(line)) {
139
+ programFiles.add(relative(line));
140
+ current.contents = undefined;
141
+ return;
142
+ } else if (summary.test(line)) {
143
+ current.contents = undefined;
144
+ return;
145
+ } else {
146
+ return Stdlib_Option.forEach(current.contents, diagnostic => {
147
+ diagnostic.lines.push(line);
148
+ });
149
+ }
150
+ });
151
+ return [
152
+ diagnostics,
153
+ programFiles
154
+ ];
155
+ }
156
+
157
+ function failure(param) {
158
+ let output = param.output;
159
+ let error = param.error;
160
+ if (error !== undefined && error.killed) {
161
+ return `Skipped the handler type check: tsc didn't finish within ` + (30).toString() + ` seconds.`;
162
+ }
163
+ let reason = Stdlib_Option.getOr(lines(output).find(line => thrownError.test(line)), output.trim());
164
+ return `Skipped the handler type check: tsc failed to run:\n\n` + reason;
165
+ }
166
+
167
+ async function check(cwd, files) {
168
+ let skip = warning => {
169
+ if (Fs.existsSync(Path.resolve(cwd, "rescript.json"))) {
170
+ return "Passed";
171
+ } else {
172
+ return {
173
+ TAG: "Skipped",
174
+ _0: warning
175
+ };
176
+ }
177
+ };
178
+ let match = resolveTsc(cwd);
179
+ let match$1 = findUp(cwd, "tsconfig.json");
180
+ if (match === undefined) {
181
+ return skip("Skipped the handler type check: typescript isn't installed. Add it to devDependencies to type-check handlers on start.");
182
+ }
183
+ if (match$1 === undefined) {
184
+ return skip("Skipped the handler type check: no tsconfig.json found. Add one, like the one envio init creates, to type-check handlers on start.");
185
+ }
186
+ let run = await runTsc(match, cwd, [
187
+ "--project",
188
+ match$1,
189
+ "--noEmit",
190
+ "--incremental",
191
+ "false",
192
+ "--composite",
193
+ "false",
194
+ "--listFiles",
195
+ "--explainFiles",
196
+ "false",
197
+ "--pretty"
198
+ ]);
199
+ let match$2 = parse(run.output, cwd);
200
+ let programFiles = match$2[1];
201
+ let diagnostics = match$2[0];
202
+ let report = diagnostics => diagnostics.map(param => param.lines.join("\n").trimEnd()).join("\n\n");
203
+ let handlers = files.map(file => Path.relative(cwd, file));
204
+ let isHandler = param => Stdlib_Option.mapOr(param.file, false, file => handlers.includes(file));
205
+ let errors = diagnostics.filter(isHandler);
206
+ let stopping = diagnostics.filter(diagnostic => {
207
+ if (isHandler(diagnostic)) {
208
+ return false;
209
+ } else {
210
+ return canStopTypeChecking(diagnostic);
211
+ }
212
+ });
213
+ let unchecked = handlers.filter(file => !programFiles.has(file));
214
+ if (Stdlib_Option.isSome(run.error) && diagnostics.length === 0) {
215
+ return {
216
+ TAG: "Skipped",
217
+ _0: failure(run)
218
+ };
219
+ }
220
+ if (errors.length !== 0) {
221
+ return {
222
+ TAG: "TypeErrors",
223
+ _0: report(errors)
224
+ };
225
+ }
226
+ if (stopping.length !== 0) {
227
+ let reason = report(stopping);
228
+ return {
229
+ TAG: "Skipped",
230
+ _0: `Skipped the handler type check: tsc can't check the handlers until these errors are fixed:\n\n` + reason
231
+ };
232
+ }
233
+ if (unchecked.length === 0) {
234
+ return "Passed";
235
+ }
236
+ let names = unchecked.join(", ");
237
+ let tsconfig = Path.relative(cwd, match$1);
238
+ let pronoun = unchecked.length === 1 ? "it" : "them";
239
+ return {
240
+ TAG: "Skipped",
241
+ _0: `Skipped the handler type check: ` + tsconfig + ` doesn't include ` + names + `. Add ` + pronoun + ` to "include" to type-check ` + pronoun + ` on start.`
242
+ };
243
+ }
244
+
245
+ let timeoutSeconds = 30;
246
+
247
+ export {
248
+ timeoutSeconds,
249
+ fileDiagnostic,
250
+ globalDiagnostic,
251
+ summary,
252
+ thrownError,
253
+ lines,
254
+ findUp,
255
+ resolveTsc,
256
+ runTsc,
257
+ canStopTypeChecking,
258
+ parse,
259
+ failure,
260
+ check,
261
+ }
262
+ /* fs Not a pure module */
@@ -39,6 +39,15 @@ module Util = {
39
39
  @module("util") external inspect: ('a, inspectOptions) => string = "inspect"
40
40
 
41
41
  let inspectObj = a => inspect(a, {showHidden: false, depth: Null, colors: true})
42
+
43
+ @module("util") external stripVTControlCharacters: string => string = "stripVTControlCharacters"
44
+ }
45
+
46
+ module Module = {
47
+ type require
48
+
49
+ @module("module") external createRequire: string => require = "createRequire"
50
+ @send external resolve: (require, string) => string = "resolve"
42
51
  }
43
52
 
44
53
  module Process = {
@@ -165,6 +174,23 @@ module ChildProcess = {
165
174
  @module("child_process")
166
175
  external execWithOptions: (string, execOptions, callback) => unit = "exec"
167
176
 
177
+ type execFileOptions = {
178
+ cwd?: string,
179
+ timeout?: int,
180
+ maxBuffer?: float,
181
+ }
182
+
183
+ // `killed` is set when the timeout stopped the child.
184
+ type execFileError = {killed: bool}
185
+
186
+ @module("child_process")
187
+ external execFile: (
188
+ string,
189
+ array<string>,
190
+ execFileOptions,
191
+ (Null.t<execFileError>, string, string) => unit,
192
+ ) => unit = "execFile"
193
+
168
194
  // One of a child's stdio slots, present only for a slot the parent asked to
169
195
  // pipe rather than inherit.
170
196
  module Stream = {
@@ -225,6 +251,8 @@ module Path = {
225
251
 
226
252
  @module("path") external join: (t, string) => t = "join"
227
253
  @module("path") external dirname: string => t = "dirname"
254
+ @module("path") external relative: (string, string) => string = "relative"
255
+ @module("path") external isAbsolute: string => bool = "isAbsolute"
228
256
 
229
257
  external toString: t => string = "%identity"
230
258
 
@@ -275,6 +303,9 @@ module Fs = {
275
303
  mode?: int,
276
304
  }
277
305
 
306
+ @module("fs") external existsSync: string => bool = "existsSync"
307
+ @module("fs") external readFileSync: (string, @as("utf8") _) => string = "readFileSync"
308
+
278
309
  module Promises = {
279
310
  @module("fs") @scope("promises")
280
311
  external writeFile: (
@@ -16,6 +16,8 @@ let Util$1 = {
16
16
  inspectObj: inspectObj
17
17
  };
18
18
 
19
+ let Module = {};
20
+
19
21
  let Process = {};
20
22
 
21
23
  let empty = globalThis.Buffer.alloc(0);
@@ -59,6 +61,7 @@ let Fs = {
59
61
 
60
62
  export {
61
63
  Util$1 as Util,
64
+ Module,
62
65
  Process,
63
66
  Buffer,
64
67
  V8,
@@ -28,6 +28,7 @@ type pageOutcome =
28
28
  | @as("SuggestedToBlock") SuggestedToBlock
29
29
  | @as("Backoff") Backoff
30
30
  | @as("FieldSelection") FieldSelection
31
+ | @as("BehindHead") BehindHead
31
32
 
32
33
  type nextPageResult = {
33
34
  kind: pageOutcome,
@@ -45,6 +46,7 @@ type nextPageResult = {
45
46
  // alongside it is empty.
46
47
  type blockHashResult = {
47
48
  message: option<string>,
49
+ behindHeadBlock: option<int>,
48
50
  requestStats: array<Source.requestStat>,
49
51
  }
50
52
 
@@ -99,7 +99,8 @@ let make = (
99
99
 
100
100
  // The cause travels as a real error so it reaches the logs the way every
101
101
  // other source's does. Absent where the message is the whole story.
102
- let exn = result.providerMessage->Option.map(message => JsError.make(message)->JsExn.anyToExnInternal)
102
+ let exn =
103
+ result.providerMessage->Option.map(message => JsError.make(message)->JsExn.anyToExnInternal)
103
104
 
104
105
  let failedGettingItems = (decision): exn => Source.GetItemsError(
105
106
  FailedGettingItems({
@@ -134,6 +135,16 @@ let make = (
134
135
  }),
135
136
  ),
136
137
  )
138
+ | BehindHead =>
139
+ throw(
140
+ Source.SourceBehindHead({
141
+ blockNumber: switch result.blockNumber {
142
+ | Some(blockNumber) => blockNumber
143
+ | None => missing("blockNumber")
144
+ },
145
+ requestStats: result.requestStats,
146
+ }),
147
+ )
137
148
  | SuggestedToBlock =>
138
149
  throw(
139
150
  failedGettingItems(
@@ -185,9 +196,11 @@ let make = (
185
196
  let getBlockHashes = async (~blockNumbers, ~logger as _) => {
186
197
  let (result, pageBlockStore) = await rpcClient.getBlockHashes(blockNumbers)
187
198
  {
188
- Source.result: switch result.message {
189
- | None => Ok(pageBlockStore)
190
- | Some(message) => Error(JsError.make(message)->JsExn.anyToExnInternal)
199
+ Source.result: switch (result.message, result.behindHeadBlock) {
200
+ | (None, _) => Ok(pageBlockStore)
201
+ | (Some(_), Some(blockNumber)) =>
202
+ Error(Source.SourceBehindHead({blockNumber, requestStats: result.requestStats}))
203
+ | (Some(message), None) => Error(JsError.make(message)->JsExn.anyToExnInternal)
191
204
  },
192
205
  requestStats: result.requestStats,
193
206
  }
@@ -93,6 +93,14 @@ function make(param) {
93
93
  },
94
94
  Error: new Error()
95
95
  };
96
+ case "BehindHead" :
97
+ let blockNumber$1 = result.blockNumber;
98
+ throw {
99
+ RE_EXN_ID: Source.SourceBehindHead,
100
+ blockNumber: blockNumber$1 !== undefined ? blockNumber$1 : missing("blockNumber"),
101
+ requestStats: result.requestStats,
102
+ Error: new Error()
103
+ };
96
104
  }
97
105
  let parsingTimeRef = Performance.now();
98
106
  let parsedQueueItems = EvmEventItem.toInternalItems(result.items, onEventRegistrations, chainId);
@@ -114,12 +122,22 @@ function make(param) {
114
122
  let getBlockHashes = async (blockNumbers, param) => {
115
123
  let match = await rpcClient.getBlockHashes(blockNumbers);
116
124
  let result = match[0];
117
- let message = result.message;
125
+ let match$1 = result.message;
126
+ let match$2 = result.behindHeadBlock;
118
127
  return {
119
- result: message !== undefined ? ({
120
- TAG: "Error",
121
- _0: Primitive_exceptions.internalToException(new Error(message))
122
- }) : ({
128
+ result: match$1 !== undefined ? (
129
+ match$2 !== undefined ? ({
130
+ TAG: "Error",
131
+ _0: {
132
+ RE_EXN_ID: Source.SourceBehindHead,
133
+ blockNumber: match$2,
134
+ requestStats: result.requestStats
135
+ }
136
+ }) : ({
137
+ TAG: "Error",
138
+ _0: Primitive_exceptions.internalToException(new Error(match$1))
139
+ })
140
+ ) : ({
123
141
  TAG: "Ok",
124
142
  _0: match[1]
125
143
  }),
@@ -24,7 +24,7 @@ let recordStatsInto = (
24
24
  aggregates: dict<requestStatAgg>,
25
25
  requestStats: array<Source.requestStat>,
26
26
  ) => {
27
- requestStats->Array.forEach(({method, seconds, responseBlocks: ?responseBlocks}) => {
27
+ requestStats->Array.forEach(({method, seconds, ?responseBlocks}) => {
28
28
  let agg = switch aggregates->Utils.Dict.dangerouslyGetNonOption(method) {
29
29
  | Some(agg) => agg
30
30
  | None =>
@@ -577,11 +577,14 @@ let backoffBeforeRetry = async (
577
577
  ~isRealtime,
578
578
  ~backoffMillis,
579
579
  ~minBackoffMillis=0,
580
+ ~failoverAfterRetries=2,
580
581
  ~excludedSources=?,
581
582
  ) => {
582
- // Give the source two attempts before demoting it, then re-try a failover
583
+ // Give the source a few attempts before demoting it, then re-try a failover
583
584
  // every second attempt.
584
- let switchedToWorkingSource = if retry >= 2 && retry->mod(2) === 0 {
585
+ let switchedToWorkingSource = if (
586
+ retry >= failoverAfterRetries && (retry - failoverAfterRetries)->mod(2) === 0
587
+ ) {
585
588
  let now = Date.now()
586
589
  sourceState.lastFailedAt = Some(now)
587
590
  switch sourceManager->getNextSource(~isRealtime, ~excludedSources?) {
@@ -608,6 +611,25 @@ let backoffBeforeRetry = async (
608
611
  }
609
612
  }
610
613
 
614
+ // A replica behind its siblings usually catches up within seconds, and failing
615
+ // over demotes an otherwise healthy source for the whole recovery timeout. So
616
+ // the source keeps the query for about 10s (100ms doubling, capped at 1s per
617
+ // attempt) before a failover is tried. Past that window the source is behind
618
+ // for real, and with nowhere to fail over to it is polled less and less often,
619
+ // doubling from 1s up to the shared cap.
620
+ let behindHeadFailoverRetries = 12
621
+
622
+ let behindHeadBackoffMillis = retry =>
623
+ if retry < behindHeadFailoverRetries {
624
+ Utils.expBackoff(~base=100, ~exp=retry, ~maxMillis=1_000)
625
+ } else {
626
+ Utils.expBackoff(
627
+ ~base=1_000,
628
+ ~exp=retry - behindHeadFailoverRetries,
629
+ ~maxMillis=maxRetryBackoffMillis,
630
+ )
631
+ }
632
+
611
633
  // The queried block hasn't reached the backend instance that served the
612
634
  // request. Expected around the head of a load-balanced backend, so early
613
635
  // attempts stay quiet and short; a source that stays behind fails over like any
@@ -619,18 +641,28 @@ let retryBehindHead = async (
619
641
  ~isRealtime,
620
642
  ~logger: Pino.t,
621
643
  ~blockNumber: int,
622
- ~method: string,
623
- ~err: exn,
624
644
  ~excludedSources=?,
625
645
  ) => {
626
- let backoffMillis = retry->retryBackoffMillis
627
- let log = retry >= 4 ? Logging.childWarn : Logging.childTrace
646
+ let backoffMillis = retry->behindHeadBackoffMillis
647
+ let source = sourceState.source.name
648
+ let block = blockNumber->Int.toString
649
+ // The condition is routine and resolves without the user doing anything, so
650
+ // the wording says so; the error object would only add internals.
651
+ let (log, msg) = if retry < behindHeadFailoverRetries {
652
+ (
653
+ Logging.childTrace,
654
+ `${source} hasn't reached block ${block} on all of its servers yet. This is normal near the latest block and resolves by itself - retrying shortly.`,
655
+ )
656
+ } else {
657
+ (
658
+ Logging.childWarn,
659
+ `${source} is still catching up to block ${block}. This is a delay on the provider's side; indexing will continue automatically, using another data source if one is configured.`,
660
+ )
661
+ }
628
662
  logger->log({
629
- "msg": `Block #${blockNumber->Int.toString} is not available on the ${sourceState.source.name} source yet. Instances of a load-balanced backend drift slightly around the head, so this is expected - indexing continues after an automatic retry.`,
630
- "method": method,
663
+ "msg": msg,
631
664
  "retry": retry,
632
665
  "backOffMilliseconds": backoffMillis,
633
- "err": err->Utils.prettifyExn,
634
666
  })
635
667
  await sourceManager->backoffBeforeRetry(
636
668
  sourceState,
@@ -638,6 +670,7 @@ let retryBehindHead = async (
638
670
  ~isRealtime,
639
671
  ~backoffMillis,
640
672
  ~minBackoffMillis=minRecoverableBackoffMillis,
673
+ ~failoverAfterRetries=behindHeadFailoverRetries,
641
674
  ~excludedSources?,
642
675
  )
643
676
  }
@@ -861,8 +894,7 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
861
894
  // Re-armed because distrusting a stream is a one-shot — the next height it
862
895
  // delivers takes it back at its word — and the silence that earned it can
863
896
  // come straight back.
864
- let rec armPokeTimeout = () =>
865
- pokeTimeoutId := Some(setTimeout(() => {
897
+ let rec armPokeTimeout = () => pokeTimeoutId := Some(setTimeout(() => {
866
898
  pokeTimeoutId := None
867
899
  if !settled.contents {
868
900
  watched->Array.forEach(subscription => subscription.distrustStream())
@@ -879,8 +911,7 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
879
911
  // operator about when they hear about a quiet chain, and spreading that
880
912
  // would report it early. Fires once — the warning is worth saying once per
881
913
  // wait, and a fallback stays recruited.
882
- let armStallTimeout = () =>
883
- stallTimeoutId := Some(setTimeout(() => {
914
+ let armStallTimeout = () => stallTimeoutId := Some(setTimeout(() => {
884
915
  stallTimeoutId := None
885
916
  if !settled.contents {
886
917
  stalled := true
@@ -917,9 +948,7 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
917
948
  }
918
949
 
919
950
  // Recruited to poll, not to stream: see `watch`.
920
- fallbackSources->Array.forEach(
921
- sourceState => sourceState->watch(~withStream=false),
922
- )
951
+ fallbackSources->Array.forEach(sourceState => sourceState->watch(~withStream=false))
923
952
 
924
953
  // A fallback recruited here can still be holding a stream from an
925
954
  // earlier wait that made it a primary, and a live stream is not
@@ -940,7 +969,6 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
940
969
  })
941
970
  }
942
971
 
943
-
944
972
  let executeQuery = async (
945
973
  sourceManager: t,
946
974
  ~query: FetchState.query,
@@ -949,11 +977,12 @@ let executeQuery = async (
949
977
  ) => {
950
978
  // Read when the manager actually runs out of sources, not on the way in: the
951
979
  // reason is recorded by the failure that disabled the last one.
952
- let noSourcesError = () => switch sourceManager.disableReason {
953
- | Some(reason) =>
954
- `The indexer doesn't have data-sources which can continue fetching. The last one was disabled because ${reason}`
955
- | None => "The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team."
956
- }
980
+ let noSourcesError = () =>
981
+ switch sourceManager.disableReason {
982
+ | Some(reason) =>
983
+ `The indexer doesn't have data-sources which can continue fetching. The last one was disabled because ${reason}`
984
+ | None => "The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team."
985
+ }
957
986
 
958
987
  // Sources where the query is impossible - lazily allocated, excluded for the duration of this query
959
988
  let excludedSourcesRef = ref(None)
@@ -1030,7 +1059,7 @@ let executeQuery = async (
1030
1059
  await sourceManager->waitForRateLimitReset(~resetMs, ~retry, ~logger)
1031
1060
  retryRef := retryRef.contents + 1
1032
1061
 
1033
- | Source.SourceBehindHead({blockNumber, requestStats}) as err =>
1062
+ | Source.SourceBehindHead({blockNumber, requestStats}) =>
1034
1063
  sourceState->recordRequestStats(requestStats)
1035
1064
  await sourceManager->retryBehindHead(
1036
1065
  sourceState,
@@ -1038,8 +1067,6 @@ let executeQuery = async (
1038
1067
  ~isRealtime,
1039
1068
  ~logger,
1040
1069
  ~blockNumber,
1041
- ~method="getItems",
1042
- ~err,
1043
1070
  ~excludedSources=?excludedSourcesRef.contents,
1044
1071
  )
1045
1072
  retryRef := retryRef.contents + 1
@@ -1135,7 +1162,11 @@ let executeQuery = async (
1135
1162
  })
1136
1163
  retryRef := 0
1137
1164
 
1138
- | FailedGettingItems({?exn, attemptedToBlock, retry: WithBackoff({message, backoffMillis})}) =>
1165
+ | FailedGettingItems({
1166
+ ?exn,
1167
+ attemptedToBlock,
1168
+ retry: WithBackoff({message, backoffMillis}),
1169
+ }) =>
1139
1170
  await sourceManager->retryFailedPage(
1140
1171
  sourceState,
1141
1172
  ~retry,
@@ -1200,22 +1231,14 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array<int>, ~isReal
1200
1231
  | Error(exn) => throw(exn)
1201
1232
  }
1202
1233
  } catch {
1203
- | Source.RateLimited({resetMs, requestStats}) =>
1204
- sourceState->recordRequestStats(requestStats)
1234
+ // A failure arrives as `res.result`, whose `res.requestStats` is already
1235
+ // recorded above, so the stats the error carries are not recorded again.
1236
+ | Source.RateLimited({resetMs}) =>
1205
1237
  await sourceManager->waitForRateLimitReset(~resetMs, ~retry, ~logger)
1206
1238
  retryRef := retryRef.contents + 1
1207
1239
 
1208
- | Source.SourceBehindHead({blockNumber, requestStats}) as err =>
1209
- sourceState->recordRequestStats(requestStats)
1210
- await sourceManager->retryBehindHead(
1211
- sourceState,
1212
- ~retry,
1213
- ~isRealtime,
1214
- ~logger,
1215
- ~blockNumber,
1216
- ~method="getBlockHashes",
1217
- ~err,
1218
- )
1240
+ | Source.SourceBehindHead({blockNumber}) =>
1241
+ await sourceManager->retryBehindHead(sourceState, ~retry, ~isRealtime, ~logger, ~blockNumber)
1219
1242
  retryRef := retryRef.contents + 1
1220
1243
 
1221
1244
  | Source.InconsistentResponse(_) as err =>
@@ -470,10 +470,11 @@ function getNextSource(sourceManager, isRealtime, excludedSources) {
470
470
  }
471
471
  }
472
472
 
473
- async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillisOpt, excludedSources) {
473
+ async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillisOpt, failoverAfterRetriesOpt, excludedSources) {
474
474
  let minBackoffMillis = minBackoffMillisOpt !== undefined ? minBackoffMillisOpt : 0;
475
+ let failoverAfterRetries = failoverAfterRetriesOpt !== undefined ? failoverAfterRetriesOpt : 2;
475
476
  let switchedToWorkingSource;
476
- if (retry >= 2 && retry % 2 === 0) {
477
+ if (retry >= failoverAfterRetries && (retry - failoverAfterRetries | 0) % 2 === 0) {
477
478
  let now = Date.now();
478
479
  sourceState.lastFailedAt = now;
479
480
  let next = getNextSource(sourceManager, isRealtime, excludedSources);
@@ -497,17 +498,31 @@ async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime,
497
498
  }
498
499
  }
499
500
 
500
- async function retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger, blockNumber, method, err, excludedSources) {
501
- let backoffMillis = Utils.expBackoff(100, retry, 60000);
502
- let log = retry >= 4 ? Logging.childWarn : Logging.childTrace;
503
- log(logger, {
504
- msg: `Block #` + blockNumber.toString() + ` is not available on the ` + sourceState.source.name + ` source yet. Instances of a load-balanced backend drift slightly around the head, so this is expected - indexing continues after an automatic retry.`,
505
- method: method,
501
+ function behindHeadBackoffMillis(retry) {
502
+ if (retry < 12) {
503
+ return Utils.expBackoff(100, retry, 1000);
504
+ } else {
505
+ return Utils.expBackoff(1000, retry - 12 | 0, 60000);
506
+ }
507
+ }
508
+
509
+ async function retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger, blockNumber, excludedSources) {
510
+ let backoffMillis = behindHeadBackoffMillis(retry);
511
+ let source = sourceState.source.name;
512
+ let block = blockNumber.toString();
513
+ let match = retry < 12 ? [
514
+ Logging.childTrace,
515
+ source + ` hasn't reached block ` + block + ` on all of its servers yet. This is normal near the latest block and resolves by itself - retrying shortly.`
516
+ ] : [
517
+ Logging.childWarn,
518
+ source + ` is still catching up to block ` + block + `. This is a delay on the provider's side; indexing will continue automatically, using another data source if one is configured.`
519
+ ];
520
+ match[0](logger, {
521
+ msg: match[1],
506
522
  retry: retry,
507
- backOffMilliseconds: backoffMillis,
508
- err: Utils.prettifyExn(err)
523
+ backOffMilliseconds: backoffMillis
509
524
  });
510
- return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, excludedSources);
525
+ return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, 12, excludedSources);
511
526
  }
512
527
 
513
528
  function stalledLog(retry, warnAfter, msg, stalled) {
@@ -536,7 +551,7 @@ async function retryInconsistentResponse(sourceManager, sourceState, retry, isRe
536
551
  err: Utils.prettifyExn(err)
537
552
  });
538
553
  Stdlib_Option.forEach(sourceState.source.onReorg, cb => cb());
539
- return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, excludedSources);
554
+ return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, undefined, excludedSources);
540
555
  }
541
556
 
542
557
  async function retryFailedPage(sourceManager, sourceState, retry, isRealtime, logger, msg, attemptedToBlock, backoffMillis, minBackoffMillisOpt, err, excludedSources) {
@@ -549,7 +564,7 @@ async function retryFailedPage(sourceManager, sourceState, retry, isRealtime, lo
549
564
  retry: retry,
550
565
  err: Stdlib_Option.map(err, Utils.prettifyExn)
551
566
  });
552
- return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillis, excludedSources);
567
+ return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillis, undefined, excludedSources);
553
568
  }
554
569
 
555
570
  function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling) {
@@ -736,7 +751,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
736
751
  retryRef = retryRef + 1 | 0;
737
752
  } else if (err.RE_EXN_ID === Source.SourceBehindHead) {
738
753
  recordStatsInto(sourceState.requestStats, err.requestStats);
739
- await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$2, err.blockNumber, "getItems", err, excludedSourcesRef);
754
+ await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$2, err.blockNumber, excludedSourcesRef);
740
755
  retryRef = retryRef + 1 | 0;
741
756
  } else if (err.RE_EXN_ID === Source.InconsistentResponse) {
742
757
  await retryInconsistentResponse(sourceManager, sourceState, retry, isRealtime, logger$2, "getItems", err, excludedSourcesRef);
@@ -866,13 +881,10 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
866
881
  } catch (raw_err) {
867
882
  let err = Primitive_exceptions.internalToException(raw_err);
868
883
  if (err.RE_EXN_ID === Source.RateLimited) {
869
- let match = err._1;
870
- recordStatsInto(sourceState.requestStats, match.requestStats);
871
- await waitForRateLimitReset(sourceManager, match.resetMs, retry, logger$1);
884
+ await waitForRateLimitReset(sourceManager, err._1.resetMs, retry, logger$1);
872
885
  retryRef = retryRef + 1 | 0;
873
886
  } else if (err.RE_EXN_ID === Source.SourceBehindHead) {
874
- recordStatsInto(sourceState.requestStats, err.requestStats);
875
- await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$1, err.blockNumber, "getBlockHashes", err, undefined);
887
+ await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$1, err.blockNumber, undefined);
876
888
  retryRef = retryRef + 1 | 0;
877
889
  } else if (err.RE_EXN_ID === Source.InconsistentResponse) {
878
890
  await retryInconsistentResponse(sourceManager, sourceState, retry, isRealtime, logger$1, "getBlockHashes", err, undefined);
@@ -886,7 +898,7 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
886
898
  backOffMilliseconds: backoffMillis,
887
899
  err: Utils.prettifyExn(err)
888
900
  });
889
- await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, undefined);
901
+ await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, undefined, undefined);
890
902
  retryRef = retryRef + 1 | 0;
891
903
  }
892
904
  }
@@ -1,14 +0,0 @@
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
- });
@@ -1,68 +0,0 @@
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));