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/bin.mjs
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { unsupportedNodeMessage } from "./src/NodeVersion.mjs";
|
|
3
|
+
|
|
4
|
+
// Checked before the rest loads, which an older Node may not even parse.
|
|
5
|
+
const unsupported = unsupportedNodeMessage();
|
|
6
|
+
if (unsupported !== undefined) {
|
|
7
|
+
console.error(unsupported);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { run } = await import("./src/Bin.res.mjs");
|
|
3
12
|
await run(process.argv.slice(2));
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,17 @@ declare const bigintSchema: Sury.Schema<bigint>;
|
|
|
12
12
|
/** Ethereum address — a 20-byte hex string prefixed with `0x`. */
|
|
13
13
|
export type Address = `0x${string}`;
|
|
14
14
|
|
|
15
|
+
/** A value a `Json` entity field can hold. A `bigint` isn't JSON — store its
|
|
16
|
+
* decimal string with `.toString()`. Shadows the global `JSON` interface only
|
|
17
|
+
* where imported; the `JSON` object itself is unaffected. */
|
|
18
|
+
export type JSON =
|
|
19
|
+
| string
|
|
20
|
+
| number
|
|
21
|
+
| boolean
|
|
22
|
+
| null
|
|
23
|
+
| readonly JSON[]
|
|
24
|
+
| { readonly [key: string]: JSON };
|
|
25
|
+
|
|
15
26
|
/** Structured logger bound to an event or handler context. Messages are
|
|
16
27
|
* displayed in the console and the Envio Hosted Service. */
|
|
17
28
|
export type Logger = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dapp"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=22.
|
|
30
|
+
"node": ">=22.15.0"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"bin.mjs",
|
|
@@ -42,11 +42,6 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@elastic/ecs-pino-format": "1.4.0",
|
|
45
|
-
"@fuel-ts/crypto": "0.96.1",
|
|
46
|
-
"@fuel-ts/errors": "0.96.1",
|
|
47
|
-
"@fuel-ts/hasher": "0.96.1",
|
|
48
|
-
"@fuel-ts/math": "0.96.1",
|
|
49
|
-
"@fuel-ts/utils": "0.96.1",
|
|
50
45
|
"bignumber.js": "9.3.1",
|
|
51
46
|
"eventsource": "4.1.0",
|
|
52
47
|
"express": "4.19.2",
|
|
@@ -56,20 +51,13 @@
|
|
|
56
51
|
"rescript-schema": "9.5.1",
|
|
57
52
|
"viem": "2.54.0",
|
|
58
53
|
"dotenv": "16.4.5",
|
|
59
|
-
"
|
|
60
|
-
"@rescript/react": "0.14.1",
|
|
61
|
-
"react": "19.2.5",
|
|
62
|
-
"ink": "6.8.0",
|
|
63
|
-
"ink-big-text": "2.0.0",
|
|
64
|
-
"ink-spinner": "5.0.0",
|
|
65
|
-
"postgres": "3.4.8",
|
|
66
|
-
"tsx": "4.21.0"
|
|
54
|
+
"postgres": "3.4.8"
|
|
67
55
|
},
|
|
68
56
|
"optionalDependencies": {
|
|
69
|
-
"envio-linux-x64": "3.
|
|
70
|
-
"envio-linux-x64-musl": "3.
|
|
71
|
-
"envio-linux-arm64": "3.
|
|
72
|
-
"envio-darwin-x64": "3.
|
|
73
|
-
"envio-darwin-arm64": "3.
|
|
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"
|
|
74
62
|
}
|
|
75
63
|
}
|
package/rescript.json
CHANGED
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
"module": "esmodule",
|
|
13
13
|
"in-source": true
|
|
14
14
|
},
|
|
15
|
-
"
|
|
16
|
-
"version": 4
|
|
17
|
-
},
|
|
18
|
-
"dependencies": ["rescript-schema", "@rescript/react"],
|
|
15
|
+
"dependencies": ["rescript-schema"],
|
|
19
16
|
"compiler-flags": ["-open RescriptSchema"],
|
|
20
17
|
"warnings": {
|
|
21
18
|
"error": "+a"
|
package/src/Config.res
CHANGED
|
@@ -136,6 +136,9 @@ type t = {
|
|
|
136
136
|
reorgThresholdReadyTolerance: int,
|
|
137
137
|
lowercaseAddresses: bool,
|
|
138
138
|
isDev: bool,
|
|
139
|
+
// The operator's `-d` / `--config` flags, repeated in every command printed
|
|
140
|
+
// for them to run next. Empty when they gave neither.
|
|
141
|
+
projectFlags: string,
|
|
139
142
|
// An `envio start --chain` process: drives a subset of the schema's chains
|
|
140
143
|
// while sibling processes drive the rest, so it only touches what its own
|
|
141
144
|
// chains own — their partitions' indexes, their `ready_at`, their resume.
|
|
@@ -147,6 +150,7 @@ type t = {
|
|
|
147
150
|
// the schema alone: a cross-chain entity has rows any chain's reorg can
|
|
148
151
|
// reach, so its checkpoints have to be comparable across chains.
|
|
149
152
|
checkpointSequence: CheckpointSequence.t,
|
|
153
|
+
envioInfo: JSON.t,
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
|
|
@@ -595,6 +599,7 @@ let publicConfigSchema = S.schema(s =>
|
|
|
595
599
|
"handlers": s.matches(S.option(S.string)),
|
|
596
600
|
"isDev": s.matches(S.option(S.bool)),
|
|
597
601
|
"isolatedChains": s.matches(S.option(S.array(ChainId.schema))),
|
|
602
|
+
"projectFlags": s.matches(S.option(S.string)),
|
|
598
603
|
"fullBatchSize": s.matches(S.option(S.int)),
|
|
599
604
|
"rollbackOnReorg": s.matches(S.option(S.bool)),
|
|
600
605
|
"saveFullHistory": s.matches(S.option(S.bool)),
|
|
@@ -666,6 +671,41 @@ let isolate = (config: t, ~chainIds: array<ChainId.t>) => {
|
|
|
666
671
|
}
|
|
667
672
|
}
|
|
668
673
|
|
|
674
|
+
// What the command decided rather than the project's files: `envio dev` vs
|
|
675
|
+
// `envio start`, which chains this process drives, and how it found the
|
|
676
|
+
// project.
|
|
677
|
+
let commandFields = ["isDev", "isolatedChains", "projectFlags"]
|
|
678
|
+
|
|
679
|
+
let withProjectFlags = (command, ~config: t) =>
|
|
680
|
+
switch config.projectFlags {
|
|
681
|
+
| "" => command
|
|
682
|
+
| flags => `${command} ${flags}`
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
let ecosystemFields = ["evm", "fuel", "svm"]
|
|
686
|
+
|
|
687
|
+
// What `envio_info` records: the public config without the command's fields
|
|
688
|
+
// or its chains. A chain is recorded by its own `envio_chains` row and its
|
|
689
|
+
// config's `envio_addresses` rows instead, so adding one never rewrites this
|
|
690
|
+
// record. The rest of a chain's config — its sources, its block lag, a
|
|
691
|
+
// contract's start block — is read from config.yaml on every start.
|
|
692
|
+
let toEnvioInfo = (publicConfigJson: JSON.t): JSON.t => {
|
|
693
|
+
let envioInfo = publicConfigJson->JSON.stringify->JSON.parseOrThrow
|
|
694
|
+
switch envioInfo {
|
|
695
|
+
| Object(obj) => {
|
|
696
|
+
commandFields->Array.forEach(field => obj->Utils.Dict.deleteInPlace(field))
|
|
697
|
+
ecosystemFields->Array.forEach(ecosystem =>
|
|
698
|
+
switch obj->Dict.get(ecosystem) {
|
|
699
|
+
| Some(Object(ecosystemDict)) => ecosystemDict->Utils.Dict.deleteInPlace("chains")
|
|
700
|
+
| _ => ()
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
| _ => ()
|
|
705
|
+
}
|
|
706
|
+
envioInfo
|
|
707
|
+
}
|
|
708
|
+
|
|
669
709
|
let fromPublic = (publicConfigJson: JSON.t) => {
|
|
670
710
|
let maxAddrInPartition = Env.maxAddrInPartition
|
|
671
711
|
// Parse public config
|
|
@@ -1136,11 +1176,13 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
1136
1176
|
reorgThresholdReadyTolerance: 100,
|
|
1137
1177
|
lowercaseAddresses,
|
|
1138
1178
|
isDev: publicConfig["isDev"]->Option.getOr(false),
|
|
1179
|
+
projectFlags: publicConfig["projectFlags"]->Option.getOr(""),
|
|
1139
1180
|
isolated: false,
|
|
1140
1181
|
userEntitiesByName,
|
|
1141
1182
|
userEntities,
|
|
1142
1183
|
allEnums,
|
|
1143
1184
|
checkpointSequence: CheckpointSequence.fromEntities(userEntities),
|
|
1185
|
+
envioInfo: publicConfigJson->toEnvioInfo,
|
|
1144
1186
|
}
|
|
1145
1187
|
|
|
1146
1188
|
switch publicConfig["isolatedChains"] {
|
|
@@ -1255,49 +1297,6 @@ let getPublicConfigJson = () =>
|
|
|
1255
1297
|
| None => Core.getConfigJson()->JSON.parseOrThrow
|
|
1256
1298
|
}
|
|
1257
1299
|
|
|
1258
|
-
// Drops source URLs from each chain so RPC/hypersync edits don't trigger
|
|
1259
|
-
// the resume-time compat check (and don't end up in `envio_info`). Also
|
|
1260
|
-
// drops `isDev`, which toggles between `envio dev` and `envio start` and
|
|
1261
|
-
// has no bearing on schema/indexing compatibility.
|
|
1262
|
-
let stripSensitiveData = (json: JSON.t): JSON.t => {
|
|
1263
|
-
let cloned = json->JSON.stringify->JSON.parseOrThrow
|
|
1264
|
-
let stripChains = (ecosystem: option<JSON.t>) =>
|
|
1265
|
-
switch ecosystem {
|
|
1266
|
-
| Some(Object(ecosystemDict)) =>
|
|
1267
|
-
switch ecosystemDict->Dict.get("chains") {
|
|
1268
|
-
| Some(Object(chains)) =>
|
|
1269
|
-
chains
|
|
1270
|
-
->Dict.valuesToArray
|
|
1271
|
-
->Array.forEach(chainJson =>
|
|
1272
|
-
switch chainJson {
|
|
1273
|
-
| Object(chain) => {
|
|
1274
|
-
chain->Utils.Dict.deleteInPlace("rpcs")
|
|
1275
|
-
chain->Utils.Dict.deleteInPlace("hypersync")
|
|
1276
|
-
}
|
|
1277
|
-
| _ => ()
|
|
1278
|
-
}
|
|
1279
|
-
)
|
|
1280
|
-
| _ => ()
|
|
1281
|
-
}
|
|
1282
|
-
| _ => ()
|
|
1283
|
-
}
|
|
1284
|
-
switch cloned {
|
|
1285
|
-
| Object(obj) => {
|
|
1286
|
-
obj->Utils.Dict.deleteInPlace("isDev")
|
|
1287
|
-
obj->Utils.Dict.deleteInPlace("isolatedChains")
|
|
1288
|
-
stripChains(obj->Dict.get("evm"))
|
|
1289
|
-
stripChains(obj->Dict.get("fuel"))
|
|
1290
|
-
stripChains(obj->Dict.get("svm"))
|
|
1291
|
-
}
|
|
1292
|
-
| _ => ()
|
|
1293
|
-
}
|
|
1294
|
-
cloned
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// What the storage layer records as the config this schema was built from,
|
|
1298
|
-
// and checks a resuming run against.
|
|
1299
|
-
let envioInfo = () => getPublicConfigJson()->stripSensitiveData
|
|
1300
|
-
|
|
1301
1300
|
// Postgres jsonb doesn't preserve key order, so canonicalize with sorted
|
|
1302
1301
|
// keys before string-comparing.
|
|
1303
1302
|
let rec canonicalJson = (json: JSON.t): JSON.t =>
|
|
@@ -1317,9 +1316,10 @@ let rec canonicalJson = (json: JSON.t): JSON.t =>
|
|
|
1317
1316
|
// Returns dotted leaf paths (`a.b[i].c`) where `stored` differs from
|
|
1318
1317
|
// `current`, restricted to the highest-priority top-level tier with any
|
|
1319
1318
|
// diff. Tiers in order: version → name → storage → ecosystem
|
|
1320
|
-
// (evm/fuel/svm
|
|
1321
|
-
// containing a diff is the only one
|
|
1322
|
-
// so a single noisy section doesn't bury
|
|
1319
|
+
// (evm/fuel/svm, and the chains a resume puts beside them) → entities →
|
|
1320
|
+
// other top-level keys. The first tier containing a diff is the only one
|
|
1321
|
+
// rendered; lower tiers are silenced so a single noisy section doesn't bury
|
|
1322
|
+
// the actionable change.
|
|
1323
1323
|
let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
1324
1324
|
let canonEq = (a: JSON.t, b: JSON.t) =>
|
|
1325
1325
|
JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b))
|
|
@@ -1387,7 +1387,7 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
|
1387
1387
|
["chainIdMode"],
|
|
1388
1388
|
["name"],
|
|
1389
1389
|
["storage"],
|
|
1390
|
-
["evm", "fuel", "svm"],
|
|
1390
|
+
["evm", "fuel", "svm", "chains"],
|
|
1391
1391
|
["entities"],
|
|
1392
1392
|
]
|
|
1393
1393
|
let firstHit = tiers->Array.reduce(None, (acc, tier) =>
|
|
@@ -1417,65 +1417,34 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
|
1417
1417
|
acc
|
|
1418
1418
|
}
|
|
1419
1419
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
//
|
|
1423
|
-
//
|
|
1424
|
-
//
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
let
|
|
1420
|
+
// The `incompatible config` error listing each path in `changedPaths`, plus
|
|
1421
|
+
// the remediation options. `~resetCommand` is rendered as-is for option 2
|
|
1422
|
+
// (the wipe-and-redo). `~runCommand` controls option 3 (parallel indexer
|
|
1423
|
+
// recipe): when `None`, option 3 is omitted — the migrate flow uses this
|
|
1424
|
+
// because running a second indexer doesn't apply. `~hasClickhouse` adds the
|
|
1425
|
+
// extra env line so users running both Postgres and Clickhouse get a complete
|
|
1426
|
+
// override.
|
|
1427
|
+
let incompatibleMessage = (
|
|
1428
1428
|
changedPaths: array<string>,
|
|
1429
1429
|
~resetCommand: string,
|
|
1430
1430
|
~runCommand: option<string>,
|
|
1431
1431
|
~hasClickhouse: bool,
|
|
1432
1432
|
) => {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
`\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
|
|
1443
|
-
}
|
|
1444
|
-
JsError.throwWithMessage(
|
|
1445
|
-
`The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
|
|
1446
|
-
col,
|
|
1447
|
-
)}# resume indexing where it left off\n 2. ${resetCommand->padTo(
|
|
1448
|
-
col,
|
|
1449
|
-
)}# delete all indexed data and start over${option3}`,
|
|
1450
|
-
)
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
let throwIfResumeIncompatible = (
|
|
1455
|
-
~storedEnvioInfo: option<JSON.t>,
|
|
1456
|
-
~storedContractMapping: ContractMapping.t,
|
|
1457
|
-
~envioInfo: JSON.t,
|
|
1458
|
-
~contractMapping: ContractMapping.t,
|
|
1459
|
-
~resetCommand: string,
|
|
1460
|
-
~runCommand: option<string>,
|
|
1461
|
-
) => {
|
|
1462
|
-
let changedPaths = switch storedEnvioInfo {
|
|
1463
|
-
| None => ["storage was initialized by an older envio version"]
|
|
1464
|
-
| Some(stored) => diffPaths(~stored, ~current=envioInfo)
|
|
1465
|
-
}
|
|
1466
|
-
let changedPaths =
|
|
1467
|
-
storedContractMapping->ContractMapping.isEqual(contractMapping)
|
|
1468
|
-
? changedPaths
|
|
1469
|
-
: changedPaths->Array.concat(["contracts"])
|
|
1470
|
-
let hasClickhouse = switch envioInfo {
|
|
1471
|
-
| Object(d) =>
|
|
1472
|
-
switch d->Dict.get("storage") {
|
|
1473
|
-
| Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
|
|
1474
|
-
| _ => false
|
|
1475
|
-
}
|
|
1476
|
-
| _ => false
|
|
1433
|
+
let bullets = changedPaths->Array.map(p => ` - ${p}`)->Array.joinUnsafe("\n")
|
|
1434
|
+
let option1 = "Revert the changes above"
|
|
1435
|
+
let padTo = (s, col) => s ++ " "->String.repeat(Math.Int.max(col - String.length(s), 1))
|
|
1436
|
+
let col = Math.Int.max(String.length(option1), String.length(resetCommand)) + 2
|
|
1437
|
+
let option3 = switch runCommand {
|
|
1438
|
+
| None => ""
|
|
1439
|
+
| Some(cmd) =>
|
|
1440
|
+
let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : ""
|
|
1441
|
+
`\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
|
|
1477
1442
|
}
|
|
1478
|
-
|
|
1443
|
+
`The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
|
|
1444
|
+
col,
|
|
1445
|
+
)}# resume indexing where it left off\n 2. ${resetCommand->padTo(
|
|
1446
|
+
col,
|
|
1447
|
+
)}# delete all indexed data and start over${option3}`
|
|
1479
1448
|
}
|
|
1480
1449
|
|
|
1481
1450
|
// The returned value is a pure function of the JSON: it holds only event
|
package/src/Config.res.mjs
CHANGED
|
@@ -18,7 +18,6 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
18
18
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
19
19
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
20
20
|
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
21
|
-
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
22
21
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
23
22
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
24
23
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
@@ -439,6 +438,7 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
|
|
|
439
438
|
handlers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
440
439
|
isDev: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
441
440
|
isolatedChains: s.m(S$RescriptSchema.option(S$RescriptSchema.array(ChainId.schema))),
|
|
441
|
+
projectFlags: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
442
442
|
fullBatchSize: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
|
|
443
443
|
rollbackOnReorg: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
444
444
|
saveFullHistory: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
@@ -495,6 +495,41 @@ function isolate(config, chainIds) {
|
|
|
495
495
|
return newrecord;
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
let commandFields = [
|
|
499
|
+
"isDev",
|
|
500
|
+
"isolatedChains",
|
|
501
|
+
"projectFlags"
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
function withProjectFlags(command, config) {
|
|
505
|
+
let flags = config.projectFlags;
|
|
506
|
+
if (flags === "") {
|
|
507
|
+
return command;
|
|
508
|
+
} else {
|
|
509
|
+
return command + ` ` + flags;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let ecosystemFields = [
|
|
514
|
+
"evm",
|
|
515
|
+
"fuel",
|
|
516
|
+
"svm"
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
function toEnvioInfo(publicConfigJson) {
|
|
520
|
+
let envioInfo = JSON.parse(JSON.stringify(publicConfigJson));
|
|
521
|
+
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
522
|
+
commandFields.forEach(field => Utils.Dict.deleteInPlace(envioInfo, field));
|
|
523
|
+
ecosystemFields.forEach(ecosystem => {
|
|
524
|
+
let match = envioInfo[ecosystem];
|
|
525
|
+
if (typeof match === "object" && match !== null && !Array.isArray(match)) {
|
|
526
|
+
return Utils.Dict.deleteInPlace(match, "chains");
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return envioInfo;
|
|
531
|
+
}
|
|
532
|
+
|
|
498
533
|
function fromPublic(publicConfigJson) {
|
|
499
534
|
let publicConfig;
|
|
500
535
|
try {
|
|
@@ -801,7 +836,9 @@ function fromPublic(publicConfigJson) {
|
|
|
801
836
|
let config_enableRawEvents = Stdlib_Option.getOr(publicConfig.rawEvents, false);
|
|
802
837
|
let config_batchSize = Stdlib_Option.getOr(publicConfig.fullBatchSize, 5000);
|
|
803
838
|
let config_isDev = Stdlib_Option.getOr(publicConfig.isDev, false);
|
|
839
|
+
let config_projectFlags = Stdlib_Option.getOr(publicConfig.projectFlags, "");
|
|
804
840
|
let config_checkpointSequence = CheckpointSequence.fromEntities(userEntities);
|
|
841
|
+
let config_envioInfo = toEnvioInfo(publicConfigJson);
|
|
805
842
|
let config = {
|
|
806
843
|
name: config_name,
|
|
807
844
|
description: config_description,
|
|
@@ -823,11 +860,13 @@ function fromPublic(publicConfigJson) {
|
|
|
823
860
|
reorgThresholdReadyTolerance: 100,
|
|
824
861
|
lowercaseAddresses: lowercaseAddresses,
|
|
825
862
|
isDev: config_isDev,
|
|
863
|
+
projectFlags: config_projectFlags,
|
|
826
864
|
isolated: false,
|
|
827
865
|
userEntitiesByName: userEntitiesByName,
|
|
828
866
|
userEntities: userEntities,
|
|
829
867
|
allEnums: allEnums,
|
|
830
|
-
checkpointSequence: config_checkpointSequence
|
|
868
|
+
checkpointSequence: config_checkpointSequence,
|
|
869
|
+
envioInfo: config_envioInfo
|
|
831
870
|
};
|
|
832
871
|
let chainIds = publicConfig.isolatedChains;
|
|
833
872
|
if (chainIds !== undefined) {
|
|
@@ -933,44 +972,6 @@ function getPublicConfigJson() {
|
|
|
933
972
|
}
|
|
934
973
|
}
|
|
935
974
|
|
|
936
|
-
function stripSensitiveData(json) {
|
|
937
|
-
let cloned = JSON.parse(JSON.stringify(json));
|
|
938
|
-
let stripChains = ecosystem => {
|
|
939
|
-
if (ecosystem === undefined) {
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
if (typeof ecosystem !== "object" || ecosystem === null || Array.isArray(ecosystem)) {
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
let match = ecosystem["chains"];
|
|
946
|
-
if (match === undefined) {
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
if (typeof match !== "object" || match === null || Array.isArray(match)) {
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
Object.values(match).forEach(chainJson => {
|
|
953
|
-
if (typeof chainJson !== "object" || chainJson === null || Array.isArray(chainJson)) {
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
Utils.Dict.deleteInPlace(chainJson, "rpcs");
|
|
957
|
-
Utils.Dict.deleteInPlace(chainJson, "hypersync");
|
|
958
|
-
});
|
|
959
|
-
};
|
|
960
|
-
if (typeof cloned === "object" && cloned !== null && !Array.isArray(cloned)) {
|
|
961
|
-
Utils.Dict.deleteInPlace(cloned, "isDev");
|
|
962
|
-
Utils.Dict.deleteInPlace(cloned, "isolatedChains");
|
|
963
|
-
stripChains(cloned["evm"]);
|
|
964
|
-
stripChains(cloned["fuel"]);
|
|
965
|
-
stripChains(cloned["svm"]);
|
|
966
|
-
}
|
|
967
|
-
return cloned;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
function envioInfo() {
|
|
971
|
-
return stripSensitiveData(getPublicConfigJson());
|
|
972
|
-
}
|
|
973
|
-
|
|
974
975
|
function canonicalJson(json) {
|
|
975
976
|
if (Array.isArray(json)) {
|
|
976
977
|
return json.map(canonicalJson);
|
|
@@ -1088,7 +1089,8 @@ function diffPaths(stored, current) {
|
|
|
1088
1089
|
[
|
|
1089
1090
|
"evm",
|
|
1090
1091
|
"fuel",
|
|
1091
|
-
"svm"
|
|
1092
|
+
"svm",
|
|
1093
|
+
"chains"
|
|
1092
1094
|
],
|
|
1093
1095
|
["entities"]
|
|
1094
1096
|
];
|
|
@@ -1113,10 +1115,7 @@ function diffPaths(stored, current) {
|
|
|
1113
1115
|
return acc;
|
|
1114
1116
|
}
|
|
1115
1117
|
|
|
1116
|
-
function
|
|
1117
|
-
if (changedPaths.length === 0) {
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1118
|
+
function incompatibleMessage(changedPaths, resetCommand, runCommand, hasClickhouse) {
|
|
1120
1119
|
let bullets = changedPaths.map(p => ` - ` + p).join("\n");
|
|
1121
1120
|
let option1 = "Revert the changes above";
|
|
1122
1121
|
let padTo = (s, col) => s + " ".repeat(Math.max(col - s.length | 0, 1));
|
|
@@ -1128,20 +1127,7 @@ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhou
|
|
|
1128
1127
|
} else {
|
|
1129
1128
|
option3 = "";
|
|
1130
1129
|
}
|
|
1131
|
-
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
function throwIfResumeIncompatible(storedEnvioInfo, storedContractMapping, envioInfo, contractMapping, resetCommand, runCommand) {
|
|
1135
|
-
let changedPaths = storedEnvioInfo !== undefined ? diffPaths(storedEnvioInfo, envioInfo) : ["storage was initialized by an older envio version"];
|
|
1136
|
-
let changedPaths$1 = ContractMapping.isEqual(storedContractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
|
|
1137
|
-
let hasClickhouse;
|
|
1138
|
-
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
1139
|
-
let match = envioInfo["storage"];
|
|
1140
|
-
hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
|
|
1141
|
-
} else {
|
|
1142
|
-
hasClickhouse = false;
|
|
1143
|
-
}
|
|
1144
|
-
throwIfIncompatible(changedPaths$1, resetCommand, runCommand, hasClickhouse);
|
|
1130
|
+
return `The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3;
|
|
1145
1131
|
}
|
|
1146
1132
|
|
|
1147
1133
|
function load() {
|
|
@@ -1195,6 +1181,10 @@ export {
|
|
|
1195
1181
|
getChain,
|
|
1196
1182
|
isPerChain,
|
|
1197
1183
|
isolate,
|
|
1184
|
+
commandFields,
|
|
1185
|
+
withProjectFlags,
|
|
1186
|
+
ecosystemFields,
|
|
1187
|
+
toEnvioInfo,
|
|
1198
1188
|
fromPublic,
|
|
1199
1189
|
normalizeUserAddress,
|
|
1200
1190
|
normalizeSimulateAddress,
|
|
@@ -1202,12 +1192,9 @@ export {
|
|
|
1202
1192
|
prime,
|
|
1203
1193
|
withCommandFields,
|
|
1204
1194
|
getPublicConfigJson,
|
|
1205
|
-
stripSensitiveData,
|
|
1206
|
-
envioInfo,
|
|
1207
1195
|
canonicalJson,
|
|
1208
1196
|
diffPaths,
|
|
1209
|
-
|
|
1210
|
-
throwIfResumeIncompatible,
|
|
1197
|
+
incompatibleMessage,
|
|
1211
1198
|
load,
|
|
1212
1199
|
getPgUserEntities,
|
|
1213
1200
|
}
|
package/src/Core.res
CHANGED
|
@@ -12,6 +12,7 @@ type transactionStoreCtor
|
|
|
12
12
|
type blockStoreCtor
|
|
13
13
|
type clickHouseSinkCtor
|
|
14
14
|
type addressStoreCtor
|
|
15
|
+
type tuiCtor
|
|
15
16
|
// Test-only: a local HyperSync server, bound by MockHyperSyncServer in envio-tests.
|
|
16
17
|
type mockHyperSyncServerCtor
|
|
17
18
|
type fromUserApiOptions = {
|
|
@@ -33,6 +34,10 @@ type addon = {
|
|
|
33
34
|
isSvmPubkey: (~value: string) => bool,
|
|
34
35
|
fromUserApi: (string, fromUserApiOptions) => fromUserApiResult,
|
|
35
36
|
runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
|
|
37
|
+
loadTs: string => string,
|
|
38
|
+
tsCheckHandlerFormat: string => unit,
|
|
39
|
+
tsResolveCandidates: (string, Null.t<string>) => array<string>,
|
|
40
|
+
tsNotFoundCandidates: (string, Null.t<string>, string) => array<string>,
|
|
36
41
|
@as("EvmHyperSyncClient")
|
|
37
42
|
evmHyperSyncClient: evmHyperSyncClientCtor,
|
|
38
43
|
@as("EvmRpcClient")
|
|
@@ -49,6 +54,8 @@ type addon = {
|
|
|
49
54
|
addressStore: addressStoreCtor,
|
|
50
55
|
@as("ClickHouseSink")
|
|
51
56
|
clickHouseSink: clickHouseSinkCtor,
|
|
57
|
+
@as("Tui")
|
|
58
|
+
tui: tuiCtor,
|
|
52
59
|
@as("MockHyperSyncServer")
|
|
53
60
|
mockHyperSyncServer: mockHyperSyncServerCtor,
|
|
54
61
|
encodeAddresses: (~ecosystem: string, ~addresses: array<Address.t>) => array<NodeJs.Buffer.t>,
|
|
@@ -99,7 +106,7 @@ let callRequire: ({..}, string) => addon = %raw(`(req, id) => req(id)`)
|
|
|
99
106
|
let envioPackageDir = pathDirname(pathDirname(fileURLToPath(importMetaUrl)))
|
|
100
107
|
|
|
101
108
|
// Runs `cargo build` on every invocation (like `cargo run`).
|
|
102
|
-
let loadDevAddon: ({..}, string) => addon = %raw(`function(req, envioDir) {
|
|
109
|
+
let loadDevAddon: ({..}, string) => Null.t<addon> = %raw(`function(req, envioDir) {
|
|
103
110
|
var cp = Nodechild_process;
|
|
104
111
|
var path = Nodepath;
|
|
105
112
|
var fs = Nodefs;
|
|
@@ -177,6 +184,22 @@ let loadDevAddon: ({..}, string) => addon = %raw(`function(req, envioDir) {
|
|
|
177
184
|
// `code`, and any other fields a diagnostic might rely on.
|
|
178
185
|
let rethrow: JsExn.t => 'a = %raw(`function(e) { throw e }`)
|
|
179
186
|
|
|
187
|
+
type packageJson = {version: string}
|
|
188
|
+
let requirePackageJson: ({..}, string) => packageJson = %raw(`(req, p) => req(p)`)
|
|
189
|
+
let devVersion = "0.0.1-dev"
|
|
190
|
+
|
|
191
|
+
let isGlibc: unit => bool = %raw(`() => Boolean(process.report?.getReport().header.glibcVersionRuntime)`)
|
|
192
|
+
|
|
193
|
+
@val external npmUserAgent: option<string> = "process.env.npm_config_user_agent"
|
|
194
|
+
|
|
195
|
+
let addCommand = () =>
|
|
196
|
+
switch npmUserAgent {
|
|
197
|
+
| Some(ua) if ua->String.startsWith("pnpm/") => "pnpm add"
|
|
198
|
+
| Some(ua) if ua->String.startsWith("yarn/") => "yarn add"
|
|
199
|
+
| Some(ua) if ua->String.startsWith("bun/") => "bun add"
|
|
200
|
+
| _ => "npm install"
|
|
201
|
+
}
|
|
202
|
+
|
|
180
203
|
let loadAddon = () => {
|
|
181
204
|
let req = createRequire(importMetaUrl)
|
|
182
205
|
|
|
@@ -212,15 +235,32 @@ let loadAddon = () => {
|
|
|
212
235
|
switch tryRequire(0) {
|
|
213
236
|
| Some(addon) => addon
|
|
214
237
|
| None =>
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
let version = requirePackageJson(req, "../package.json").version
|
|
239
|
+
// Publishing stamps the real version, so only a monorepo checkout (or a
|
|
240
|
+
// `file:` link to one) can have a dev build to fall back to. Skipping it
|
|
241
|
+
// spares published installs a `pnpm list` spawn before the error below.
|
|
242
|
+
let devAddon = if version === devVersion {
|
|
243
|
+
loadDevAddon(req, envioPackageDir)->Null.toOption
|
|
244
|
+
} else {
|
|
245
|
+
None
|
|
246
|
+
}
|
|
247
|
+
switch devAddon {
|
|
217
248
|
| Some(addon) => addon
|
|
218
249
|
| None =>
|
|
219
250
|
let host = `${processPlatform}-${processArch}`
|
|
220
|
-
let msg =
|
|
251
|
+
let msg = switch candidates {
|
|
252
|
+
| []
|
|
253
|
+
if processPlatform === "win32" => `envio doesn't run natively on Windows. Use WSL 2 instead: https://learn.microsoft.com/windows/wsl/install`
|
|
254
|
+
| [] =>
|
|
221
255
|
`envio doesn't support ${host}. Supported: linux-x64 (glibc/musl), linux-arm64, darwin-x64, darwin-arm64.`
|
|
222
|
-
|
|
223
|
-
|
|
256
|
+
| _ =>
|
|
257
|
+
let pkg = if processPlatform === "linux" && processArch === "x64" && !isGlibc() {
|
|
258
|
+
`envio-linux-x64-musl`
|
|
259
|
+
} else {
|
|
260
|
+
candidates->Array.getUnsafe(0)
|
|
261
|
+
}
|
|
262
|
+
`envio's native binary for ${host} isn't installed (package "${pkg}"). This happens when optional dependencies are skipped (--omit=optional, --no-optional) or the lockfile was generated on another platform.
|
|
263
|
+
Reinstall dependencies, or add the package explicitly: ${addCommand()} ${pkg}@${version}`
|
|
224
264
|
}
|
|
225
265
|
JsError.throwWithMessage(msg)
|
|
226
266
|
}
|