envio 3.13.0 → 3.14.0-subgraph

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/bin.mjs +10 -1
  2. package/index.d.ts +11 -0
  3. package/package.json +8 -20
  4. package/rescript.json +1 -4
  5. package/src/Config.res +78 -100
  6. package/src/Config.res.mjs +55 -64
  7. package/src/Core.res +67 -6
  8. package/src/Core.res.mjs +58 -4
  9. package/src/EventConfigBuilder.res +4 -10
  10. package/src/EventConfigBuilder.res.mjs +1 -3
  11. package/src/EventProcessing.res +6 -6
  12. package/src/EventProcessing.res.mjs +8 -6
  13. package/src/ExitOnCaughtUp.res +5 -1
  14. package/src/ExitOnCaughtUp.res.mjs +8 -3
  15. package/src/HandlerLoader.res +98 -36
  16. package/src/HandlerLoader.res.mjs +89 -24
  17. package/src/HandlerTypeCheck.mjs +14 -0
  18. package/src/HandlerTypeCheckWorker.mjs +68 -0
  19. package/src/Internal.res +1 -1
  20. package/src/LoadLayer.res +39 -7
  21. package/src/LoadLayer.res.mjs +65 -38
  22. package/src/LoadLayer.resi +28 -0
  23. package/src/Main.res +17 -12
  24. package/src/Main.res.mjs +9 -3
  25. package/src/NodeVersion.mjs +13 -0
  26. package/src/Persistence.res +79 -38
  27. package/src/Persistence.res.mjs +41 -10
  28. package/src/PgStorage.res +86 -39
  29. package/src/PgStorage.res.mjs +83 -32
  30. package/src/ResumePlan.res +172 -0
  31. package/src/ResumePlan.res.mjs +122 -0
  32. package/src/Supervisor.res +8 -8
  33. package/src/Supervisor.res.mjs +6 -1
  34. package/src/SyncWrites.res +86 -0
  35. package/src/SyncWrites.res.mjs +85 -0
  36. package/src/TestIndexer.res +14 -4
  37. package/src/TestIndexer.res.mjs +3 -2
  38. package/src/TsModuleHooks.mjs +121 -0
  39. package/src/UserContext.res +377 -62
  40. package/src/UserContext.res.mjs +297 -39
  41. package/src/Utils.res +0 -8
  42. package/src/Worker.res +8 -0
  43. package/src/Worker.res.mjs +11 -0
  44. package/src/bindings/NodeJs.res +8 -7
  45. package/src/bindings/NodeJs.res.mjs +0 -3
  46. package/src/bindings/Pino.res +2 -2
  47. package/src/db/InternalTable.res +52 -2
  48. package/src/db/InternalTable.res.mjs +32 -3
  49. package/src/db/Table.res +10 -0
  50. package/src/db/Table.res.mjs +22 -11
  51. package/src/sources/FuelHyperSync.res +2 -0
  52. package/src/sources/FuelHyperSync.res.mjs +1 -0
  53. package/src/sources/FuelHyperSync.resi +1 -0
  54. package/src/sources/FuelHyperSyncClient.res +25 -8
  55. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  56. package/src/sources/FuelHyperSyncSource.res +24 -22
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  58. package/src/subgraph/abi-types.ts +63 -0
  59. package/src/subgraph/assemblyscript.ts +85 -0
  60. package/src/subgraph/blocks.ts +183 -0
  61. package/src/subgraph/calls.ts +149 -0
  62. package/src/subgraph/conformance.ts +83 -0
  63. package/src/subgraph/errors.ts +96 -0
  64. package/src/subgraph/graph-cli-worker.mjs +26 -0
  65. package/src/subgraph/graph-cli.ts +190 -0
  66. package/src/subgraph/graph-ts-enums.ts +28 -0
  67. package/src/subgraph/graph-ts-types/VERSION +2 -0
  68. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  69. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  70. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  71. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  72. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  73. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  74. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  75. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  76. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  77. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  78. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  79. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  80. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  81. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  82. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  83. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  84. package/src/subgraph/graph-ts.ts +2285 -0
  85. package/src/subgraph/hosts.ts +158 -0
  86. package/src/subgraph/rpc.ts +65 -0
  87. package/src/subgraph/runtime.ts +595 -0
  88. package/src/subgraph/scope.ts +68 -0
  89. package/src/tui/InitApi.res +85 -0
  90. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  91. package/src/tui/Tui.res +142 -249
  92. package/src/tui/Tui.res.mjs +123 -332
  93. package/src/Js.shim.ts +0 -11
  94. package/src/bindings/DateFns.res +0 -74
  95. package/src/bindings/DateFns.res.mjs +0 -22
  96. package/src/sources/FuelSDK.res +0 -38
  97. package/src/sources/FuelSDK.res.mjs +0 -27
  98. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  99. package/src/tui/bindings/Ink.res +0 -369
  100. package/src/tui/bindings/Ink.res.mjs +0 -66
  101. package/src/tui/bindings/Style.res +0 -123
  102. package/src/tui/bindings/Style.res.mjs +0 -2
  103. package/src/tui/components/BufferedProgressBar.res +0 -40
  104. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  105. package/src/tui/components/CustomHooks.res +0 -122
  106. package/src/tui/components/Messages.res +0 -40
  107. package/src/tui/components/Messages.res.mjs +0 -71
  108. package/src/tui/components/SyncETA.res +0 -179
  109. package/src/tui/components/SyncETA.res.mjs +0 -249
  110. package/src/tui/components/TuiData.res +0 -98
  111. package/src/tui/components/TuiData.res.mjs +0 -73
@@ -32,6 +32,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
32
32
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
33
33
  import * as ContractMapping from "./ContractMapping.res.mjs";
34
34
  import * as IndexDefinition from "./db/IndexDefinition.res.mjs";
35
+ import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
35
36
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
36
37
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
37
38
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -244,12 +245,16 @@ function getEntityHistory(entityConfig) {
244
245
  return cache$1;
245
246
  }
246
247
 
248
+ function makeCreatePartitionQuery(entityConfig, pgSchema, chainId) {
249
+ return `CREATE TABLE IF NOT EXISTS "` + pgSchema + `"."` + partitionTableName(entityConfig, chainId) + `" PARTITION OF "` + pgSchema + `"."` + entityConfig.table.tableName + `" FOR VALUES IN (` + ChainId.toString(chainId) + `);`;
250
+ }
251
+
247
252
  function makeCreateEntityTableQueries(entityConfig, pgSchema, isNumericArrayAsText, chainIdModeOpt, chainIds) {
248
253
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
249
254
  let createTable = (table, partitionByColumn) => makeCreateTableQuery(table, pgSchema, isNumericArrayAsText, chainIdMode, partitionByColumn);
250
255
  let chainIdField = Table.getChainIdField(entityConfig.table);
251
256
  return (
252
- chainIdField !== undefined ? [createTable(entityConfig.table, Table.getPgDbFieldName(chainIdField))].concat(chainIds.map(chainId => `CREATE TABLE IF NOT EXISTS "` + pgSchema + `"."` + partitionTableName(entityConfig, chainId) + `" PARTITION OF "` + pgSchema + `"."` + entityConfig.table.tableName + `" FOR VALUES IN (` + ChainId.toString(chainId) + `);`)) : [createTable(entityConfig.table, undefined)]
257
+ chainIdField !== undefined ? [createTable(entityConfig.table, Table.getPgDbFieldName(chainIdField))].concat(chainIds.map(chainId => makeCreatePartitionQuery(entityConfig, pgSchema, chainId))) : [createTable(entityConfig.table, undefined)]
253
258
  ).concat([createTable(getEntityHistory(entityConfig).table, undefined)]);
254
259
  }
255
260
 
@@ -444,6 +449,7 @@ SELECT * FROM unnest(` + match.arrayFieldTypes.map((arrayFieldType, idx) => `$`
444
449
  function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chainIdModeOpt) {
445
450
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
446
451
  let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
452
+ let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
447
453
  let quotedNonPrimaryFieldNames = match.quotedNonPrimaryFieldNames;
448
454
  let quotedFieldNames = match.quotedFieldNames;
449
455
  let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table);
@@ -459,6 +465,9 @@ function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chain
459
465
  placeholders = placeholders + ",";
460
466
  }
461
467
  placeholders = placeholders + (`$` + ((fieldIdx * itemsCount | 0) + idx | 0).toString());
468
+ if (booleanArrayColumnIndexes.includes(fieldIdx)) {
469
+ placeholders = placeholders + "::integer[]::boolean[]";
470
+ }
462
471
  }
463
472
  placeholders = placeholders + ")";
464
473
  }
@@ -473,6 +482,7 @@ VALUES` + placeholders + (
473
482
  function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
474
483
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
475
484
  let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
485
+ let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
476
486
  let byteaColumnIndexes = match.byteaColumnIndexes;
477
487
  let isRawEvents = table.tableName === InternalTable.RawEvents.table.tableName;
478
488
  let isHistoryUpdate = table.tableName.startsWith(EntityHistory.historyTablePrefix);
@@ -493,7 +503,18 @@ function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
493
503
  return {
494
504
  query: makeInsertValuesSetQuery(pgSchema, table, itemSchema, 500, chainIdMode),
495
505
  convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.preprocess(S$RescriptSchema.unnest(itemSchema), param => ({
496
- s: prim => prim.flat(1)
506
+ s: columns => {
507
+ booleanArrayColumnIndexes.forEach(index => {
508
+ columns[index] = columns[index].map(value => Stdlib_Nullable.map(value, flags => flags.map(flag => {
509
+ if (flag) {
510
+ return 1;
511
+ } else {
512
+ return 0;
513
+ }
514
+ })));
515
+ });
516
+ return columns.flat(1);
517
+ }
497
518
  })), "Output", "Input", "Sync", false),
498
519
  isInsertValues: true
499
520
  };
@@ -1234,7 +1255,6 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1234
1255
  return {
1235
1256
  cleanRun: true,
1236
1257
  contractMapping: contractMapping,
1237
- envioInfo: envioInfo,
1238
1258
  cache: cache,
1239
1259
  chains: chainConfigs.map((chainConfig, idx) => ({
1240
1260
  id: chainConfig.id,
@@ -1500,7 +1520,58 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1500
1520
  return Logging.errorWithExn(Utils.prettifyExn(exn$1), `Failed to dump cache.`);
1501
1521
  }
1502
1522
  };
1503
- let resumeInitialState = async (entities, chainIds, throwIfIncompatible) => {
1523
+ let readStoredConfig = async () => {
1524
+ let match = await Promise.all([
1525
+ InternalTable.EnvioInfo.read(sql, pgSchema),
1526
+ InternalTable.EnvioContracts.read(sql, pgSchema)
1527
+ ]);
1528
+ let contractNames = match[1];
1529
+ let envioInfo = match[0];
1530
+ if (envioInfo !== undefined) {
1531
+ if (contractNames !== undefined) {
1532
+ return {
1533
+ envioInfo: envioInfo,
1534
+ chains: await InternalTable.Chains.readStoredChains(sql, pgSchema),
1535
+ contractMapping: ContractMapping.fromStoredNames(contractNames)
1536
+ };
1537
+ } else {
1538
+ return {
1539
+ envioInfo: undefined,
1540
+ chains: [],
1541
+ contractMapping: ContractMapping.empty
1542
+ };
1543
+ }
1544
+ } else {
1545
+ return {
1546
+ envioInfo: undefined,
1547
+ chains: [],
1548
+ contractMapping: ContractMapping.empty
1549
+ };
1550
+ }
1551
+ };
1552
+ let addChain = async (chainConfig, entities, contractMapping) => {
1553
+ let partitionQueries = entities.filter(entityConfig => {
1554
+ if (entityConfig.storage.postgres) {
1555
+ return Stdlib_Option.isSome(Table.getChainIdField(entityConfig.table));
1556
+ } else {
1557
+ return false;
1558
+ }
1559
+ }).map(entityConfig => makeCreatePartitionQuery(entityConfig, pgSchema, chainConfig.id));
1560
+ let addressRows = ChainState.configStorageRows(chainConfig, ecosystem, contractMapping);
1561
+ await sql.begin(async sql => {
1562
+ for (let idx = 0, idx_finish = partitionQueries.length; idx < idx_finish; ++idx) {
1563
+ await sql.unsafe(partitionQueries[idx]);
1564
+ }
1565
+ if (Utils.$$Array.notEmpty(addressRows)) {
1566
+ await InternalTable.EnvioAddresses.insert(sql, pgSchema, addressRows, chainIdMode);
1567
+ }
1568
+ let query = InternalTable.Chains.makeInitialValuesQuery(pgSchema, [chainConfig]);
1569
+ if (query !== undefined) {
1570
+ return await sql.unsafe(query);
1571
+ }
1572
+ });
1573
+ };
1574
+ let resumeInitialState = async (entities, chainIds, contractMapping) => {
1504
1575
  let match = await Promise.all([
1505
1576
  restoreEffectCache(false),
1506
1577
  InternalTable.Chains.getInitialState(sql, pgSchema).then(rawInitialStates => {
@@ -1525,33 +1596,11 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1525
1596
  ]))
1526
1597
  ];
1527
1598
  }),
1528
- sql.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(pgSchema)),
1529
- Promise.all([
1530
- InternalTable.EnvioInfo.read(sql, pgSchema),
1531
- InternalTable.EnvioContracts.read(sql, pgSchema)
1532
- ]).then(param => {
1533
- let names = param[1];
1534
- let info = param[0];
1535
- if (info !== undefined && names !== undefined) {
1536
- return [
1537
- info,
1538
- ContractMapping.fromStoredNames(names)
1539
- ];
1540
- } else {
1541
- return [
1542
- undefined,
1543
- ContractMapping.empty
1544
- ];
1545
- }
1546
- })
1599
+ sql.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(pgSchema))
1547
1600
  ]);
1548
- let match$1 = match[3];
1549
- let storedContractMapping = match$1[1];
1550
- let storedEnvioInfo = match$1[0];
1551
- let match$2 = match[1];
1552
- let checkpointFrontier = match$2[1];
1553
- let chains = match$2[0];
1554
- throwIfIncompatible(storedEnvioInfo, storedContractMapping);
1601
+ let match$1 = match[1];
1602
+ let checkpointFrontier = match$1[1];
1603
+ let chains = match$1[0];
1555
1604
  await reloadIndexCatalog();
1556
1605
  let reorgCheckpoints = match[2].map(raw => ({
1557
1606
  id: BigInt(raw.id),
@@ -1564,8 +1613,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1564
1613
  }
1565
1614
  return {
1566
1615
  cleanRun: false,
1567
- contractMapping: storedContractMapping,
1568
- envioInfo: storedEnvioInfo,
1616
+ contractMapping: contractMapping,
1569
1617
  cache: match[0],
1570
1618
  chains: chains,
1571
1619
  checkpointFrontier: checkpointFrontier,
@@ -1647,6 +1695,8 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
1647
1695
  name: storageName,
1648
1696
  isInitialized: isInitialized,
1649
1697
  initialize: initialize,
1698
+ readStoredConfig: readStoredConfig,
1699
+ addChain: addChain,
1650
1700
  resumeInitialState: resumeInitialState,
1651
1701
  loadOrThrow: loadOrThrow,
1652
1702
  ensureQueryIndexes: ensureQueryIndexes,
@@ -1720,6 +1770,7 @@ export {
1720
1770
  getRowSchema,
1721
1771
  entityHistoryCache,
1722
1772
  getEntityHistory,
1773
+ makeCreatePartitionQuery,
1723
1774
  makeCreateEntityTableQueries,
1724
1775
  makeInitializeTransaction,
1725
1776
  makeLoadQuery,
@@ -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 */
@@ -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 ink can keep them out of its frame. Each stream keeps the one
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
- await persistence->Persistence.initForRun(
434
- ~config,
435
- ~reset,
436
- ~isDevelopmentMode=config.isDev,
437
- ~requireInitialized=false,
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
- let _rerender = Tui.start(~config, ~getMetrics=() =>
511
+ Tui.start(~config, ~getMetrics=() =>
512
512
  switch reported() {
513
513
  | [] => {...[]->merge, chains: configuredChains(config)}
514
514
  | snapshots => snapshots->merge
@@ -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 Persistence.initForRun(persistence, config, reset, config.isDev, false);
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();
@@ -0,0 +1,86 @@
1
+ // The writes one round of a synchronous handler has made. A round that
2
+ // suspends is replayed from the top, so its writes are held here rather than
3
+ // applied: the replay has to see the store exactly as the first round did, or a
4
+ // read-modify-write lands once per round.
5
+
6
+ type table = {
7
+ entityConfig: Internal.entityConfig,
8
+ scope: Internal.chainScope,
9
+ changes: dict<Change.t<Internal.entity>>,
10
+ }
11
+
12
+ // A handler runs on one chain, so an entity's scope is fixed for the whole
13
+ // round and the entity name is enough to key its table.
14
+ type t = dict<table>
15
+
16
+ let make = (): t => Dict.make()
17
+
18
+ let record = (writes: t, ~entityConfig: Internal.entityConfig, ~scope, change) => {
19
+ let table = switch writes->Utils.Dict.dangerouslyGetNonOption(entityConfig.name) {
20
+ | Some(table) => table
21
+ | None =>
22
+ let table = {entityConfig, scope, changes: Dict.make()}
23
+ writes->Dict.set(entityConfig.name, table)
24
+ table
25
+ }
26
+ table.changes->Dict.set(change->Change.getEntityId->EntityId.toKey, change)
27
+ }
28
+
29
+ let changesOf = (writes: t, ~entityConfig: Internal.entityConfig) =>
30
+ switch writes->Utils.Dict.dangerouslyGetNonOption(entityConfig.name) {
31
+ | Some({changes}) => Some(changes)
32
+ | None => None
33
+ }
34
+
35
+ // `Some(None)` is a delete this round made: the entity is known not to exist.
36
+ let getById = (writes: t, ~entityConfig, ~entityId: string) =>
37
+ switch writes->changesOf(~entityConfig) {
38
+ | Some(changes) =>
39
+ changes
40
+ ->Utils.Dict.dangerouslyGetNonOption(entityId)
41
+ ->Option.map(InMemoryTable.Entity.mapChangeToEntity)
42
+ | None => None
43
+ }
44
+
45
+ // What the store answered, as it reads once this round's writes land: a row
46
+ // the round rewrote is re-tested against the filter, and one it created joins.
47
+ let overlayFilter = (
48
+ writes: t,
49
+ entities: array<Internal.entity>,
50
+ ~entityConfig: Internal.entityConfig,
51
+ ~filter: EntityFilter.t,
52
+ ) =>
53
+ switch writes->changesOf(~entityConfig) {
54
+ | None => entities
55
+ | Some(changes) =>
56
+ let matches = filter->EntityFilter.makeMatcher(~table=entityConfig.table)
57
+ let untouched =
58
+ entities->Array.filter(entity =>
59
+ !(changes->Dict.has(entity->InMemoryTable.Entity.getEntityIdUnsafe))
60
+ )
61
+ let written =
62
+ changes
63
+ ->Dict.valuesToArray
64
+ ->Array.filterMap(change =>
65
+ switch change->InMemoryTable.Entity.mapChangeToEntity {
66
+ | Some(entity) if matches(entity) => Some(entity)
67
+ | _ => None
68
+ }
69
+ )
70
+ untouched->Array.concat(written)
71
+ }
72
+
73
+ // How a change lands in the in-memory store, whether written directly or
74
+ // committed at the end of a round.
75
+ let write = (indexerState, ~entityConfig, ~scope, change) =>
76
+ indexerState
77
+ ->InMemoryStore.getInMemTable(~entityConfig, ~scope)
78
+ ->InMemoryTable.Entity.set(
79
+ ~committedCheckpointId=indexerState->IndexerState.committedCheckpointIdFor(~scope),
80
+ change,
81
+ )
82
+
83
+ let commit = (writes: t, ~indexerState) =>
84
+ writes->Utils.Dict.forEach(({entityConfig, scope, changes}) =>
85
+ changes->Utils.Dict.forEach(change => indexerState->write(~entityConfig, ~scope, change))
86
+ )