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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// A chain as its storage holds it: what its `envio_chains` row keeps of its
|
|
2
|
+
// config, and the addresses the config declared for it.
|
|
3
|
+
type storedChain = {
|
|
4
|
+
id: ChainId.t,
|
|
5
|
+
ecosystem: string,
|
|
6
|
+
startBlock: int,
|
|
7
|
+
endBlock: option<int>,
|
|
8
|
+
maxReorgDepth: int,
|
|
9
|
+
configAddresses: array<AddressRows.row>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// What a storage holds of the config it was built from. `envioInfo` is `None`
|
|
13
|
+
// for a storage an older envio built, which kept no such record.
|
|
14
|
+
type stored = {
|
|
15
|
+
envioInfo: option<JSON.t>,
|
|
16
|
+
chains: array<storedChain>,
|
|
17
|
+
contractMapping: ContractMapping.t,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type t =
|
|
21
|
+
| Resume
|
|
22
|
+
// Configured since the storage was built, and the only chain its
|
|
23
|
+
// `envio start --chain` process drives.
|
|
24
|
+
| AddChain(Config.chain)
|
|
25
|
+
| Incompatible(array<string>)
|
|
26
|
+
|
|
27
|
+
// The part of a chain's config its storage keeps, shaped so a stored chain and
|
|
28
|
+
// a configured one diff by the path config.yaml spells: `chains.137.endBlock`,
|
|
29
|
+
// `chains.137.contracts.Token.addresses.0x…`.
|
|
30
|
+
let chainEntry = (
|
|
31
|
+
~startBlock: option<int>,
|
|
32
|
+
~endBlock: option<int>,
|
|
33
|
+
~maxReorgDepth: int,
|
|
34
|
+
~addresses: array<(string, string)>,
|
|
35
|
+
) => {
|
|
36
|
+
let contracts = Dict.make()
|
|
37
|
+
addresses->Array.forEach(((contractName, address)) =>
|
|
38
|
+
contracts->Utils.Dict.push(contractName, address)
|
|
39
|
+
)
|
|
40
|
+
let entry = dict{
|
|
41
|
+
"maxReorgDepth": JSON.Number(maxReorgDepth->Int.toFloat),
|
|
42
|
+
"contracts": JSON.Object(
|
|
43
|
+
contracts->Dict.mapValues(addresses => JSON.Object(
|
|
44
|
+
dict{
|
|
45
|
+
"addresses": JSON.Object(
|
|
46
|
+
addresses->Array.map(address => (address, JSON.Boolean(true)))->Dict.fromArray,
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
)),
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
startBlock->Option.forEach(startBlock =>
|
|
53
|
+
entry->Dict.set("startBlock", JSON.Number(startBlock->Int.toFloat))
|
|
54
|
+
)
|
|
55
|
+
endBlock->Option.forEach(endBlock =>
|
|
56
|
+
entry->Dict.set("endBlock", JSON.Number(endBlock->Int.toFloat))
|
|
57
|
+
)
|
|
58
|
+
JSON.Object(entry)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let withChains = (envioInfo: JSON.t, ~chains: array<(ChainId.t, JSON.t)>) =>
|
|
62
|
+
switch envioInfo {
|
|
63
|
+
| Object(fields) =>
|
|
64
|
+
let joined = fields->Dict.copy
|
|
65
|
+
joined->Dict.set(
|
|
66
|
+
"chains",
|
|
67
|
+
JSON.Object(
|
|
68
|
+
chains->Array.map(((chainId, entry)) => (chainId->ChainId.toString, entry))->Dict.fromArray,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
JSON.Object(joined)
|
|
72
|
+
| _ => envioInfo
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// `envioInfo` is the running config's, `chainConfigs` the chains this process
|
|
76
|
+
// drives. An isolated process answers for `envioInfo` and its own chains only:
|
|
77
|
+
// the ones it leaves out are checked, and added, by the processes that drive
|
|
78
|
+
// them.
|
|
79
|
+
let make = (
|
|
80
|
+
~stored: stored,
|
|
81
|
+
~envioInfo: JSON.t,
|
|
82
|
+
~chainConfigs: array<Config.chain>,
|
|
83
|
+
~contractMapping: ContractMapping.t,
|
|
84
|
+
~lowercaseAddresses: bool,
|
|
85
|
+
~isolated: bool,
|
|
86
|
+
) =>
|
|
87
|
+
switch stored.envioInfo {
|
|
88
|
+
| None => Incompatible(["storage was initialized by an older envio version"])
|
|
89
|
+
| Some(storedEnvioInfo) =>
|
|
90
|
+
let storedChainOf = chainId => stored.chains->Array.find(chain => chain.id == chainId)
|
|
91
|
+
let added = switch (isolated, chainConfigs) {
|
|
92
|
+
| (true, [chain]) if storedChainOf(chain.id)->Option.isNone => Some(chain)
|
|
93
|
+
| _ => None
|
|
94
|
+
}
|
|
95
|
+
let storedChains = isolated
|
|
96
|
+
? stored.chains->Array.filter(storedChain =>
|
|
97
|
+
chainConfigs->Array.some(chain => chain.id == storedChain.id)
|
|
98
|
+
)
|
|
99
|
+
: stored.chains
|
|
100
|
+
let storedEntries = storedChains->Array.map(storedChain => {
|
|
101
|
+
let addresses =
|
|
102
|
+
storedChain.configAddresses
|
|
103
|
+
->AddressRows.render(~ecosystem=storedChain.ecosystem, ~shouldChecksum=!lowercaseAddresses)
|
|
104
|
+
->Array.mapWithIndex((address, idx) => (
|
|
105
|
+
stored.contractMapping->ContractMapping.nameOfOrThrow(
|
|
106
|
+
(storedChain.configAddresses->Array.getUnsafe(idx)).contractId,
|
|
107
|
+
),
|
|
108
|
+
address->Address.toString,
|
|
109
|
+
))
|
|
110
|
+
(
|
|
111
|
+
storedChain.id,
|
|
112
|
+
chainEntry(
|
|
113
|
+
~startBlock=Some(storedChain.startBlock),
|
|
114
|
+
~endBlock=storedChain.endBlock,
|
|
115
|
+
~maxReorgDepth=storedChain.maxReorgDepth,
|
|
116
|
+
~addresses,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
let currentEntries = chainConfigs->Array.filterMap(chain =>
|
|
121
|
+
switch added {
|
|
122
|
+
| Some(added) if added.id == chain.id => None
|
|
123
|
+
| _ =>
|
|
124
|
+
Some((
|
|
125
|
+
chain.id,
|
|
126
|
+
chainEntry(
|
|
127
|
+
// `latest` is whatever it resolved to when the chain was first
|
|
128
|
+
// stored, so it matches any stored block.
|
|
129
|
+
~startBlock=switch chain.startBlock {
|
|
130
|
+
| Block(startBlock) => Some(startBlock)
|
|
131
|
+
| Latest => storedChainOf(chain.id)->Option.map(storedChain => storedChain.startBlock)
|
|
132
|
+
},
|
|
133
|
+
~endBlock=chain.endBlock,
|
|
134
|
+
~maxReorgDepth=chain.maxReorgDepth,
|
|
135
|
+
~addresses=chain.contracts->Array.flatMap(contract =>
|
|
136
|
+
contract.addresses->Array.map(address => (contract.name, address->Address.toString))
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
))
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
let changedPaths = Config.diffPaths(
|
|
143
|
+
~stored=storedEnvioInfo->withChains(~chains=storedEntries),
|
|
144
|
+
~current=envioInfo->withChains(~chains=currentEntries),
|
|
145
|
+
)
|
|
146
|
+
let changedPaths =
|
|
147
|
+
stored.contractMapping->ContractMapping.isEqual(contractMapping)
|
|
148
|
+
? changedPaths
|
|
149
|
+
: changedPaths->Array.concat(["contracts"])
|
|
150
|
+
switch (changedPaths, added) {
|
|
151
|
+
| ([], None) => Resume
|
|
152
|
+
| ([], Some(chain)) => AddChain(chain)
|
|
153
|
+
| (changedPaths, _) => Incompatible(changedPaths)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let incompatibleMessage = (
|
|
158
|
+
changedPaths,
|
|
159
|
+
~envioInfo: JSON.t,
|
|
160
|
+
~resetCommand: string,
|
|
161
|
+
~runCommand: option<string>,
|
|
162
|
+
) => {
|
|
163
|
+
let hasClickhouse = switch envioInfo {
|
|
164
|
+
| Object(d) =>
|
|
165
|
+
switch d->Dict.get("storage") {
|
|
166
|
+
| Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
|
|
167
|
+
| _ => false
|
|
168
|
+
}
|
|
169
|
+
| _ => false
|
|
170
|
+
}
|
|
171
|
+
Config.incompatibleMessage(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
|
|
172
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as Config from "./Config.res.mjs";
|
|
5
|
+
import * as ChainId from "./ChainId.res.mjs";
|
|
6
|
+
import * as AddressRows from "./AddressRows.res.mjs";
|
|
7
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
8
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
9
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
|
+
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
11
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
12
|
+
|
|
13
|
+
function chainEntry(startBlock, endBlock, maxReorgDepth, addresses) {
|
|
14
|
+
let contracts = {};
|
|
15
|
+
addresses.forEach(param => Utils.Dict.push(contracts, param[0], param[1]));
|
|
16
|
+
let entry = {
|
|
17
|
+
maxReorgDepth: maxReorgDepth,
|
|
18
|
+
contracts: Stdlib_Dict.mapValues(contracts, addresses => ({
|
|
19
|
+
addresses: Object.fromEntries(addresses.map(address => [
|
|
20
|
+
address,
|
|
21
|
+
true
|
|
22
|
+
]))
|
|
23
|
+
}))
|
|
24
|
+
};
|
|
25
|
+
Stdlib_Option.forEach(startBlock, startBlock => {
|
|
26
|
+
entry["startBlock"] = startBlock;
|
|
27
|
+
});
|
|
28
|
+
Stdlib_Option.forEach(endBlock, endBlock => {
|
|
29
|
+
entry["endBlock"] = endBlock;
|
|
30
|
+
});
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function withChains(envioInfo, chains) {
|
|
35
|
+
if (typeof envioInfo !== "object" || envioInfo === null || Array.isArray(envioInfo)) {
|
|
36
|
+
return envioInfo;
|
|
37
|
+
}
|
|
38
|
+
let joined = Object.assign({}, envioInfo);
|
|
39
|
+
joined["chains"] = Object.fromEntries(chains.map(param => [
|
|
40
|
+
ChainId.toString(param[0]),
|
|
41
|
+
param[1]
|
|
42
|
+
]));
|
|
43
|
+
return joined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function make(stored, envioInfo, chainConfigs, contractMapping, lowercaseAddresses, isolated) {
|
|
47
|
+
let storedEnvioInfo = stored.envioInfo;
|
|
48
|
+
if (storedEnvioInfo === undefined) {
|
|
49
|
+
return {
|
|
50
|
+
TAG: "Incompatible",
|
|
51
|
+
_0: ["storage was initialized by an older envio version"]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let storedChainOf = chainId => stored.chains.find(chain => Primitive_object.equal(chain.id, chainId));
|
|
55
|
+
let added;
|
|
56
|
+
if (isolated && chainConfigs.length === 1) {
|
|
57
|
+
let chain = chainConfigs[0];
|
|
58
|
+
added = Stdlib_Option.isNone(storedChainOf(chain.id)) ? chain : undefined;
|
|
59
|
+
} else {
|
|
60
|
+
added = undefined;
|
|
61
|
+
}
|
|
62
|
+
let storedChains = isolated ? stored.chains.filter(storedChain => chainConfigs.some(chain => Primitive_object.equal(chain.id, storedChain.id))) : stored.chains;
|
|
63
|
+
let storedEntries = storedChains.map(storedChain => {
|
|
64
|
+
let addresses = AddressRows.render(storedChain.configAddresses, storedChain.ecosystem, !lowercaseAddresses).map((address, idx) => [
|
|
65
|
+
ContractMapping.nameOfOrThrow(stored.contractMapping, storedChain.configAddresses[idx].contractId),
|
|
66
|
+
address
|
|
67
|
+
]);
|
|
68
|
+
return [
|
|
69
|
+
storedChain.id,
|
|
70
|
+
chainEntry(storedChain.startBlock, storedChain.endBlock, storedChain.maxReorgDepth, addresses)
|
|
71
|
+
];
|
|
72
|
+
});
|
|
73
|
+
let currentEntries = Stdlib_Array.filterMap(chainConfigs, chain => {
|
|
74
|
+
if (added !== undefined && Primitive_object.equal(added.id, chain.id)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let startBlock = chain.startBlock;
|
|
78
|
+
let tmp;
|
|
79
|
+
tmp = startBlock === "latest" ? Stdlib_Option.map(storedChainOf(chain.id), storedChain => storedChain.startBlock) : startBlock;
|
|
80
|
+
return [
|
|
81
|
+
chain.id,
|
|
82
|
+
chainEntry(tmp, chain.endBlock, chain.maxReorgDepth, chain.contracts.flatMap(contract => contract.addresses.map(address => [
|
|
83
|
+
contract.name,
|
|
84
|
+
address
|
|
85
|
+
])))
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
let changedPaths = Config.diffPaths(withChains(storedEnvioInfo, storedEntries), withChains(envioInfo, currentEntries));
|
|
89
|
+
let changedPaths$1 = ContractMapping.isEqual(stored.contractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
|
|
90
|
+
if (changedPaths$1.length !== 0) {
|
|
91
|
+
return {
|
|
92
|
+
TAG: "Incompatible",
|
|
93
|
+
_0: changedPaths$1
|
|
94
|
+
};
|
|
95
|
+
} else if (added !== undefined) {
|
|
96
|
+
return {
|
|
97
|
+
TAG: "AddChain",
|
|
98
|
+
_0: added
|
|
99
|
+
};
|
|
100
|
+
} else {
|
|
101
|
+
return "Resume";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function incompatibleMessage(changedPaths, envioInfo, resetCommand, runCommand) {
|
|
106
|
+
let hasClickhouse;
|
|
107
|
+
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
108
|
+
let match = envioInfo["storage"];
|
|
109
|
+
hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
|
|
110
|
+
} else {
|
|
111
|
+
hasClickhouse = false;
|
|
112
|
+
}
|
|
113
|
+
return Config.incompatibleMessage(changedPaths, resetCommand, runCommand, hasClickhouse);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
chainEntry,
|
|
118
|
+
withChains,
|
|
119
|
+
make,
|
|
120
|
+
incompatibleMessage,
|
|
121
|
+
}
|
|
122
|
+
/* Utils Not a pure module */
|
package/src/Supervisor.res
CHANGED
|
@@ -183,7 +183,7 @@ let fork = (
|
|
|
183
183
|
)
|
|
184
184
|
if pipeOutput {
|
|
185
185
|
// The supervisor writes a worker's lines the way it writes its own, which is
|
|
186
|
-
// the only way
|
|
186
|
+
// the only way the display can print them above its frame. Each stream keeps the one
|
|
187
187
|
// it was written to, so a worker's errors stay on stderr for whoever is
|
|
188
188
|
// redirecting it.
|
|
189
189
|
[
|
|
@@ -430,12 +430,12 @@ let run = async (~config: Config.t, ~workers: array<worker>, ~reset) => {
|
|
|
430
430
|
// same initialization an unsplit run does, and the supervisor hands the
|
|
431
431
|
// connections it used to its workers.
|
|
432
432
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
~isDevelopmentMode=config.isDev,
|
|
437
|
-
~
|
|
438
|
-
)
|
|
433
|
+
// Checked here once, so the workers don't each check the same handlers.
|
|
434
|
+
let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(~config)
|
|
435
|
+
let _ = await Promise.all2((
|
|
436
|
+
persistence->Persistence.initForRun(~config, ~reset, ~isDevelopmentMode=config.isDev),
|
|
437
|
+
HandlerLoader.typeCheck(~config, ~autoLoadFiles),
|
|
438
|
+
))
|
|
439
439
|
await persistence.storage.close()
|
|
440
440
|
|
|
441
441
|
let startTime = Date.make()
|
|
@@ -508,7 +508,7 @@ let run = async (~config: Config.t, ~workers: array<worker>, ~reset) => {
|
|
|
508
508
|
)
|
|
509
509
|
|
|
510
510
|
if shouldUseTui {
|
|
511
|
-
|
|
511
|
+
Tui.start(~config, ~getMetrics=() =>
|
|
512
512
|
switch reported() {
|
|
513
513
|
| [] => {...[]->merge, chains: configuredChains(config)}
|
|
514
514
|
| snapshots => snapshots->merge
|
package/src/Supervisor.res.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import * as PgStorage from "./PgStorage.res.mjs";
|
|
|
15
15
|
import * as Performance from "./bindings/Performance.res.mjs";
|
|
16
16
|
import * as Persistence from "./Persistence.res.mjs";
|
|
17
17
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
18
|
+
import * as HandlerLoader from "./HandlerLoader.res.mjs";
|
|
18
19
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
19
20
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
20
21
|
import * as Child_process from "child_process";
|
|
@@ -367,7 +368,11 @@ function configuredChains(config) {
|
|
|
367
368
|
|
|
368
369
|
async function run(config, workers, reset) {
|
|
369
370
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
370
|
-
await
|
|
371
|
+
let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(config);
|
|
372
|
+
await Promise.all([
|
|
373
|
+
Persistence.initForRun(persistence, config, reset, config.isDev),
|
|
374
|
+
HandlerLoader.typeCheck(config, autoLoadFiles)
|
|
375
|
+
]);
|
|
371
376
|
await persistence.storage.close();
|
|
372
377
|
let startTime = new Date();
|
|
373
378
|
let startTimeRef = Performance.now();
|
package/src/TestIndexer.res
CHANGED
|
@@ -275,7 +275,6 @@ let makeInitialState = (
|
|
|
275
275
|
{
|
|
276
276
|
cleanRun: true,
|
|
277
277
|
contractMapping,
|
|
278
|
-
envioInfo: Some(JSON.Encode.object(Dict.make())),
|
|
279
278
|
cache: Dict.make(),
|
|
280
279
|
chains,
|
|
281
280
|
checkpointFrontier: Frontier.empty(),
|
|
@@ -567,7 +566,7 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
567
566
|
isInitialized: async () => true,
|
|
568
567
|
// The runner injects the config-derived initial state by setting
|
|
569
568
|
// `persistence.storageStatus = Ready(...)` directly, bypassing `Persistence.init`,
|
|
570
|
-
// so
|
|
569
|
+
// so none of these is reached.
|
|
571
570
|
initialize: async (
|
|
572
571
|
~chainConfigs as _=?,
|
|
573
572
|
~entities as _=?,
|
|
@@ -578,7 +577,15 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
578
577
|
JsError.throwWithMessage(
|
|
579
578
|
"TestIndexer: initialize should not be called; the initial state is derived from config.",
|
|
580
579
|
),
|
|
581
|
-
|
|
580
|
+
readStoredConfig: async () =>
|
|
581
|
+
JsError.throwWithMessage(
|
|
582
|
+
"TestIndexer: readStoredConfig should not be called; the initial state is derived from config.",
|
|
583
|
+
),
|
|
584
|
+
addChain: async (~chainConfig as _, ~entities as _, ~contractMapping as _) =>
|
|
585
|
+
JsError.throwWithMessage(
|
|
586
|
+
"TestIndexer: addChain should not be called; the initial state is derived from config.",
|
|
587
|
+
),
|
|
588
|
+
resumeInitialState: async (~entities as _, ~chainIds as _, ~contractMapping as _) =>
|
|
582
589
|
JsError.throwWithMessage(
|
|
583
590
|
"TestIndexer: resumeInitialState should not be called; the initial state is derived from config.",
|
|
584
591
|
),
|
|
@@ -666,7 +673,10 @@ let getRegistrations = (~config) =>
|
|
|
666
673
|
switch registrationsRef.contents {
|
|
667
674
|
| Some(promise) => promise
|
|
668
675
|
| None =>
|
|
669
|
-
let promise =
|
|
676
|
+
let promise =
|
|
677
|
+
HandlerLoader.getAutoLoadFiles(~config)->Promise.then(autoLoadFiles =>
|
|
678
|
+
HandlerLoader.registerAllHandlers(~config, ~autoLoadFiles)
|
|
679
|
+
)
|
|
670
680
|
registrationsRef := Some(promise)
|
|
671
681
|
promise
|
|
672
682
|
}
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -195,7 +195,6 @@ function makeInitialState(config, processConfigChains, addressRowsByChain, contr
|
|
|
195
195
|
return {
|
|
196
196
|
cleanRun: true,
|
|
197
197
|
contractMapping: contractMapping,
|
|
198
|
-
envioInfo: {},
|
|
199
198
|
cache: {},
|
|
200
199
|
chains: chains,
|
|
201
200
|
checkpointFrontier: Frontier.empty(),
|
|
@@ -388,6 +387,8 @@ function makeInMemoryStorage(state) {
|
|
|
388
387
|
name: "test-inmemory",
|
|
389
388
|
isInitialized: async () => true,
|
|
390
389
|
initialize: async (param, param$1, param$2, param$3, param$4) => Stdlib_JsError.throwWithMessage("TestIndexer: initialize should not be called; the initial state is derived from config."),
|
|
390
|
+
readStoredConfig: async () => Stdlib_JsError.throwWithMessage("TestIndexer: readStoredConfig should not be called; the initial state is derived from config."),
|
|
391
|
+
addChain: async (param, param$1, param$2) => Stdlib_JsError.throwWithMessage("TestIndexer: addChain should not be called; the initial state is derived from config."),
|
|
391
392
|
resumeInitialState: async (param, param$1, param$2) => Stdlib_JsError.throwWithMessage("TestIndexer: resumeInitialState should not be called; the initial state is derived from config."),
|
|
392
393
|
loadOrThrow: async (filter, table) => handleLoad(state, table.tableName, filter),
|
|
393
394
|
ensureQueryIndexes: async (param, param$1, param$2) => {},
|
|
@@ -427,7 +428,7 @@ function getRegistrations(config) {
|
|
|
427
428
|
if (promise !== undefined) {
|
|
428
429
|
return promise;
|
|
429
430
|
}
|
|
430
|
-
let promise$1 = HandlerLoader.registerAllHandlers(config);
|
|
431
|
+
let promise$1 = HandlerLoader.getAutoLoadFiles(config).then(autoLoadFiles => HandlerLoader.registerAllHandlers(config, autoLoadFiles));
|
|
431
432
|
registrationsRef.contents = promise$1;
|
|
432
433
|
return promise$1;
|
|
433
434
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import module from "node:module";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { unsupportedNodeMessage } from "./NodeVersion.mjs";
|
|
4
|
+
|
|
5
|
+
const TS_URL = /\.[cm]?tsx?$/;
|
|
6
|
+
const JSON_URL = /\.json($|\?)/;
|
|
7
|
+
const NOT_FOUND_CODES = new Set([
|
|
8
|
+
"ERR_MODULE_NOT_FOUND",
|
|
9
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
10
|
+
"ERR_UNSUPPORTED_DIR_IMPORT",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
let registered = false;
|
|
14
|
+
|
|
15
|
+
// Node's own resolver still resolves every candidate the addon offers, so
|
|
16
|
+
// package exports and conditions behave as they do without the hooks.
|
|
17
|
+
export const register = (addon) => {
|
|
18
|
+
if (registered) return;
|
|
19
|
+
registered = true;
|
|
20
|
+
|
|
21
|
+
// The CLI checks this first thing; a test indexer gets here without it.
|
|
22
|
+
const unsupported = unsupportedNodeMessage();
|
|
23
|
+
if (unsupported !== undefined) throw new Error(unsupported);
|
|
24
|
+
|
|
25
|
+
// Node ignores the source maps `loadTs` inlines unless this is on.
|
|
26
|
+
module.setSourceMapsSupport(true);
|
|
27
|
+
|
|
28
|
+
// What legacy decorators lower to; tslib's `__decorate`, `__param` and
|
|
29
|
+
// `__metadata`.
|
|
30
|
+
const helpers = (globalThis.babelHelpers ??= {});
|
|
31
|
+
// A function rather than an arrow: the argument count tells a class
|
|
32
|
+
// decorator from a member's.
|
|
33
|
+
helpers.decorate ??= function (decorators, target, key, descriptor) {
|
|
34
|
+
const argumentCount = arguments.length;
|
|
35
|
+
let result =
|
|
36
|
+
argumentCount < 3
|
|
37
|
+
? target
|
|
38
|
+
: descriptor === null
|
|
39
|
+
? (descriptor = Object.getOwnPropertyDescriptor(target, key))
|
|
40
|
+
: descriptor;
|
|
41
|
+
for (let index = decorators.length - 1; index >= 0; index--) {
|
|
42
|
+
const decorator = decorators[index];
|
|
43
|
+
if (decorator) {
|
|
44
|
+
result =
|
|
45
|
+
(argumentCount < 3
|
|
46
|
+
? decorator(result)
|
|
47
|
+
: argumentCount > 3
|
|
48
|
+
? decorator(target, key, result)
|
|
49
|
+
: decorator(target, key)) || result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (argumentCount > 3 && result) Object.defineProperty(target, key, result);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
helpers.decorateParam ??= (index, decorator) => (target, key) => decorator(target, key, index);
|
|
56
|
+
helpers.decorateMetadata ??= (key, value) =>
|
|
57
|
+
typeof Reflect === "object" && typeof Reflect.metadata === "function"
|
|
58
|
+
? Reflect.metadata(key, value)
|
|
59
|
+
: undefined;
|
|
60
|
+
|
|
61
|
+
const firstResolved = (candidates, context, nextResolve) => {
|
|
62
|
+
for (const candidate of candidates) {
|
|
63
|
+
try {
|
|
64
|
+
return nextResolve(candidate, context);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (!NOT_FOUND_CODES.has(error?.code)) throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.registerHooks({
|
|
73
|
+
resolve(specifier, context, nextResolve) {
|
|
74
|
+
// Handlers are ES modules, so `require()` only comes from dependencies,
|
|
75
|
+
// including the one Node makes to read a CommonJS module's named
|
|
76
|
+
// exports. It resolves as it would without the hooks.
|
|
77
|
+
if (specifier.startsWith("node:") || context.conditions?.includes("require")) {
|
|
78
|
+
return nextResolve(specifier, context);
|
|
79
|
+
}
|
|
80
|
+
const [path, query] = specifier.split("?");
|
|
81
|
+
let resolved = firstResolved(
|
|
82
|
+
addon.tsResolveCandidates(path, context.parentURL ?? null),
|
|
83
|
+
context,
|
|
84
|
+
nextResolve
|
|
85
|
+
);
|
|
86
|
+
if (resolved === undefined) {
|
|
87
|
+
try {
|
|
88
|
+
resolved = nextResolve(path, context);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
resolved =
|
|
91
|
+
NOT_FOUND_CODES.has(error?.code) &&
|
|
92
|
+
firstResolved(
|
|
93
|
+
addon.tsNotFoundCandidates(error.code, error.url ?? null, error.message),
|
|
94
|
+
context,
|
|
95
|
+
nextResolve
|
|
96
|
+
);
|
|
97
|
+
if (!resolved) throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const url = query ? `${resolved.url}?${query}` : resolved.url;
|
|
101
|
+
// TypeScript's `resolveJsonModule` imports JSON without the
|
|
102
|
+
// `type: "json"` attribute Node requires.
|
|
103
|
+
if (JSON_URL.test(url) && context.importAttributes?.type === undefined) {
|
|
104
|
+
return {
|
|
105
|
+
...resolved,
|
|
106
|
+
url,
|
|
107
|
+
importAttributes: { ...context.importAttributes, type: "json" },
|
|
108
|
+
shortCircuit: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return { ...resolved, url, shortCircuit: true };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
load(url, context, nextLoad) {
|
|
115
|
+
if (!url.startsWith("file:") || !TS_URL.test(new URL(url).pathname)) {
|
|
116
|
+
return nextLoad(url, context);
|
|
117
|
+
}
|
|
118
|
+
return { format: "module", source: addon.loadTs(fileURLToPath(url)), shortCircuit: true };
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
};
|
package/src/Utils.res
CHANGED
|
@@ -10,14 +10,6 @@ external toMethod: 'a => 'a = "%unsafe_to_method"
|
|
|
10
10
|
|
|
11
11
|
@val external importPath: string => promise<unknown> = "import"
|
|
12
12
|
|
|
13
|
-
@val
|
|
14
|
-
external importPathWithJson: (
|
|
15
|
-
string,
|
|
16
|
-
@as(json`{with: {type: "json"}}`) _,
|
|
17
|
-
) => promise<{
|
|
18
|
-
"default": JSON.t,
|
|
19
|
-
}> = "import"
|
|
20
|
-
|
|
21
13
|
// Half the delay fixed, half spread across it. Every indexer pointed at one
|
|
22
14
|
// provider loses its stream, or gives up on a quiet chain, in the same instant
|
|
23
15
|
// that provider blinks, and putting them all back on the same schedule is how a
|
package/src/Worker.res
CHANGED
|
@@ -82,6 +82,14 @@ let bindToSupervisor = () => {
|
|
|
82
82
|
|
|
83
83
|
%%private(let send = (message: workerMessage) => NodeJs.Process.sendToParent(message)->ignore)
|
|
84
84
|
|
|
85
|
+
// A worker done with its chains exits only once its final snapshot is on the
|
|
86
|
+
// channel: the last periodic one can be up to an interval old, and the
|
|
87
|
+
// supervisor would go on reporting those chains as it left them.
|
|
88
|
+
let exitAfterReporting = (~metrics: Metrics.t) =>
|
|
89
|
+
NodeJs.Process.sendToParentThen(Snapshot({metrics, runtime: Metrics.sampleRuntime()}), () =>
|
|
90
|
+
NodeJs.process->NodeJs.exitWithCode(Success)
|
|
91
|
+
)->ignore
|
|
92
|
+
|
|
85
93
|
// Reports this process's chains and its own runtime for as long as it runs, so
|
|
86
94
|
// the supervisor can merge every worker's into the one snapshot the run serves,
|
|
87
95
|
// and listens for the one decision the supervisor makes on the run's behalf.
|
package/src/Worker.res.mjs
CHANGED
|
@@ -51,6 +51,16 @@ function bindToSupervisor() {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function exitAfterReporting(metrics) {
|
|
55
|
+
process.send({
|
|
56
|
+
kind: "snapshot",
|
|
57
|
+
metrics: metrics,
|
|
58
|
+
runtime: Metrics.sampleRuntime()
|
|
59
|
+
}, () => {
|
|
60
|
+
Process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
function bindRun(getMetrics, onReleaseRealtime) {
|
|
55
65
|
if (isEnabled) {
|
|
56
66
|
Metrics.startRuntimeCollectors();
|
|
@@ -76,6 +86,7 @@ export {
|
|
|
76
86
|
config,
|
|
77
87
|
isEnabled,
|
|
78
88
|
bindToSupervisor,
|
|
89
|
+
exitAfterReporting,
|
|
79
90
|
bindRun,
|
|
80
91
|
}
|
|
81
92
|
/* configSchema Not a pure module */
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -69,9 +69,17 @@ module Process = {
|
|
|
69
69
|
// `process` rather than off a namespace import, which would drop the
|
|
70
70
|
// receiver Node's own implementations read.
|
|
71
71
|
@val @scope("process") external sendToParent: 'msg => bool = "send"
|
|
72
|
+
// The callback runs once the message is handed to the channel.
|
|
73
|
+
@val @scope("process") external sendToParentThen: ('msg, unit => unit) => bool = "send"
|
|
72
74
|
@val @scope("process")
|
|
73
75
|
external onMessage: (@as("message") _, 'msg => unit) => unit = "on"
|
|
74
76
|
@val @scope("process") external onSignal: (string, unit => unit) => unit = "on"
|
|
77
|
+
@val @scope("process") external offSignal: (string, unit => unit) => unit = "removeListener"
|
|
78
|
+
@val @scope("process") external listenerCount: string => int = "listenerCount"
|
|
79
|
+
@val @scope("process") external pid: int = "pid"
|
|
80
|
+
@val @scope("process") external kill: (int, string) => bool = "kill"
|
|
81
|
+
@val @scope("process")
|
|
82
|
+
external onExit: (@as("exit") _, unit => unit) => unit = "on"
|
|
75
83
|
// Present only in a process forked with an IPC channel.
|
|
76
84
|
@val @scope("process") external channel: Nullable.t<unknown> = "channel"
|
|
77
85
|
@val @scope("process")
|
|
@@ -205,17 +213,10 @@ module Url = {
|
|
|
205
213
|
module ImportMeta = {
|
|
206
214
|
type t = {url: Url.t}
|
|
207
215
|
@val external importMeta: t = "import.meta"
|
|
208
|
-
// Get import.meta.url as string for module registration
|
|
209
|
-
@val external url: string = "import.meta.url"
|
|
210
216
|
// Resolve module specifier to file:// URL
|
|
211
217
|
@val external resolve: string => string = "import.meta.resolve"
|
|
212
218
|
}
|
|
213
219
|
|
|
214
|
-
module Module = {
|
|
215
|
-
// Register ESM loader hooks (e.g., for TypeScript support via tsx)
|
|
216
|
-
@module("node:module") external register: (string, string) => unit = "register"
|
|
217
|
-
}
|
|
218
|
-
|
|
219
220
|
module Path = {
|
|
220
221
|
type t
|
|
221
222
|
|
|
@@ -41,8 +41,6 @@ let Url$1 = {};
|
|
|
41
41
|
|
|
42
42
|
let ImportMeta = {};
|
|
43
43
|
|
|
44
|
-
let Module = {};
|
|
45
|
-
|
|
46
44
|
function getDirname(importMeta) {
|
|
47
45
|
return Path.dirname(Url.fileURLToPath(importMeta.url));
|
|
48
46
|
}
|
|
@@ -68,7 +66,6 @@ export {
|
|
|
68
66
|
ChildProcess,
|
|
69
67
|
Url$1 as Url,
|
|
70
68
|
ImportMeta,
|
|
71
|
-
Module,
|
|
72
69
|
Path$1 as Path,
|
|
73
70
|
WorkerThreads,
|
|
74
71
|
Fs,
|
package/src/bindings/Pino.res
CHANGED
|
@@ -113,8 +113,8 @@ module ECS = {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Logs through `console.log` rather than `stream.write`, so that the TUI can print
|
|
117
|
+
them above its frame.
|
|
118
118
|
*/
|
|
119
119
|
module MultiStreamLogger = {
|
|
120
120
|
type stream = {write: string => unit}
|