envio 3.5.0 → 3.5.1-svm-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/evm.schema.json +7 -0
- package/fuel.schema.json +7 -0
- package/index.d.ts +33 -10
- package/package.json +6 -6
- package/src/ChainFetching.res +11 -21
- package/src/ChainFetching.res.mjs +12 -20
- package/src/ChainState.res +41 -4
- package/src/ChainState.res.mjs +39 -5
- package/src/ChainState.resi +10 -1
- package/src/Config.res +62 -3
- package/src/Config.res.mjs +49 -13
- package/src/EffectState.res +2 -2
- package/src/EffectState.res.mjs +2 -2
- package/src/EntityTables.res +32 -0
- package/src/EntityTables.res.mjs +44 -0
- package/src/Envio.res +7 -7
- package/src/Envio.res.mjs +5 -6
- package/src/FetchState.res +38 -75
- package/src/FetchState.res.mjs +20 -62
- package/src/Hasura.res +37 -3
- package/src/Hasura.res.mjs +23 -5
- package/src/InMemoryStore.res +95 -43
- package/src/InMemoryStore.res.mjs +69 -38
- package/src/IndexerState.res +30 -24
- package/src/IndexerState.res.mjs +20 -35
- package/src/IndexerState.resi +8 -6
- package/src/Internal.res +29 -18
- package/src/Internal.res.mjs +27 -10
- package/src/LoadLayer.res +39 -8
- package/src/LoadLayer.res.mjs +36 -9
- package/src/LoadLayer.resi +2 -0
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +183 -37
- package/src/PgStorage.res.mjs +149 -34
- package/src/PruneStaleHistory.res +1 -0
- package/src/PruneStaleHistory.res.mjs +2 -1
- package/src/Sink.res +3 -3
- package/src/Sink.res.mjs +2 -2
- package/src/TestIndexer.res +128 -17
- package/src/TestIndexer.res.mjs +75 -9
- package/src/UserContext.res +19 -4
- package/src/UserContext.res.mjs +15 -9
- package/src/Writing.res +8 -16
- package/src/Writing.res.mjs +10 -10
- package/src/bindings/ClickHouse.res +40 -4
- package/src/bindings/ClickHouse.res.mjs +37 -5
- package/src/bindings/Vitest.res +0 -3
- package/src/bindings/Vitest.res.mjs +2 -12
- package/src/db/EntityHistory.res +64 -21
- package/src/db/EntityHistory.res.mjs +43 -22
- package/src/db/InternalTable.res.mjs +31 -31
- package/src/db/Table.res +24 -1
- package/src/db/Table.res.mjs +26 -4
- package/src/sources/AddressStore.res +77 -26
- package/src/sources/AddressStore.res.mjs +21 -10
- package/src/sources/SvmHyperSyncClient.res +8 -7
- package/src/sources/SvmHyperSyncSource.res +38 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +24 -0
- package/svm.schema.json +17 -4
package/src/FetchState.res
CHANGED
|
@@ -690,13 +690,19 @@ module OptimizedPartitions = {
|
|
|
690
690
|
// Update density for every response, independently from whether this range
|
|
691
691
|
// is valid evidence of source capacity. A cap hit is still useful density
|
|
692
692
|
// evidence because it reports items returned across the scanned range.
|
|
693
|
-
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
693
|
+
// A response that didn't advance covers no range at all — skip it, or the
|
|
694
|
+
// 0/0 division would latch the moving average at NaN permanently.
|
|
695
|
+
let updatedEventDensity = if blockRange <= 0 {
|
|
696
|
+
p.eventDensity
|
|
697
|
+
} else {
|
|
698
|
+
let observedEventDensity = itemsCount->Int.toFloat /. blockRange->Int.toFloat
|
|
699
|
+
// Seed from the first observation, then smooth every later observation
|
|
700
|
+
// with a 1:1 moving average. Keeping initialization explicit is important:
|
|
701
|
+
// zero is valid density evidence and must participate in the next blend.
|
|
702
|
+
switch p.eventDensity {
|
|
703
|
+
| None => Some(observedEventDensity)
|
|
704
|
+
| Some(eventDensity) => Some((eventDensity +. observedEventDensity) /. 2.)
|
|
705
|
+
}
|
|
700
706
|
}
|
|
701
707
|
|
|
702
708
|
// Skip updating source capacity if a later response already updated it.
|
|
@@ -1473,7 +1479,7 @@ let warnRejectedRegistration = (
|
|
|
1473
1479
|
~params={"contractName": contractName},
|
|
1474
1480
|
`Skipping contract registration: Not a valid address for this chain's ecosystem.`,
|
|
1475
1481
|
)
|
|
1476
|
-
| Added(_)
|
|
1482
|
+
| Added(_) => ()
|
|
1477
1483
|
}
|
|
1478
1484
|
|
|
1479
1485
|
/**
|
|
@@ -1678,9 +1684,9 @@ let registerDynamicContracts = (
|
|
|
1678
1684
|
// height in here. Defaults to what the fetch state knows, which is all
|
|
1679
1685
|
// there is to go on when no response is being applied.
|
|
1680
1686
|
~claimCeiling=fetchState.knownHeight,
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
1683
|
-
|
|
1687
|
+
// Registrations collected from contractRegister calls, in the order the
|
|
1688
|
+
// handlers made them. May contain duplicates, which the store filters out.
|
|
1689
|
+
registrations: array<AddressStore.registration>,
|
|
1684
1690
|
) => {
|
|
1685
1691
|
if fetchState.normalSelection.onEventRegistrations->Utils.Array.isEmpty {
|
|
1686
1692
|
// Can the normalSelection be empty?
|
|
@@ -1689,91 +1695,42 @@ let registerDynamicContracts = (
|
|
|
1689
1695
|
)
|
|
1690
1696
|
}
|
|
1691
1697
|
|
|
1692
|
-
// Flatten every item's dcs into one batch, remembering where each came from
|
|
1693
|
-
// so the rejected ones can be spliced back out of their item (which is what
|
|
1694
|
-
// keeps them from being persisted twice).
|
|
1695
|
-
let registrations: array<AddressStore.registration> = []
|
|
1696
|
-
let sourceDcs: array<Internal.dcs> = []
|
|
1697
|
-
let sourceIndexes: array<int> = []
|
|
1698
|
-
for itemIdx in 0 to items->Array.length - 1 {
|
|
1699
|
-
switch items->Array.getUnsafe(itemIdx)->Internal.getItemDcs {
|
|
1700
|
-
| None => ()
|
|
1701
|
-
| Some(dcs) =>
|
|
1702
|
-
for dcIdx in 0 to dcs->Array.length - 1 {
|
|
1703
|
-
let dc = dcs->Array.getUnsafe(dcIdx)
|
|
1704
|
-
registrations
|
|
1705
|
-
->Array.push({
|
|
1706
|
-
address: dc.address,
|
|
1707
|
-
contractName: dc.contractName,
|
|
1708
|
-
registrationBlock: dc.registrationBlock,
|
|
1709
|
-
})
|
|
1710
|
-
->ignore
|
|
1711
|
-
sourceDcs->Array.push(dcs)->ignore
|
|
1712
|
-
sourceIndexes->Array.push(dcIdx)->ignore
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
1698
|
// Ids are handed out in registration order, so a cursor taken now selects
|
|
1718
1699
|
// exactly what this batch adds.
|
|
1719
1700
|
let idCursor = addressStore->AddressStore.nextId
|
|
1720
1701
|
// The store resolves each address against both what it already holds and the
|
|
1721
1702
|
// batch's own earlier entries, so two contracts claiming one address inside a
|
|
1722
|
-
// single batch conflict the same way as across batches.
|
|
1703
|
+
// single batch conflict the same way as across batches. It also decides which
|
|
1704
|
+
// additions this chain fetches for, since it's what holds the contract list.
|
|
1723
1705
|
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1724
1706
|
|
|
1725
1707
|
let registeringContractNames = []
|
|
1726
|
-
let hasNoEventsUpdatesRef = ref(false)
|
|
1727
1708
|
for idx in 0 to verdicts->Array.length - 1 {
|
|
1728
1709
|
let registration = registrations->Array.getUnsafe(idx)
|
|
1729
1710
|
let verdict = verdicts->Array.getUnsafe(idx)
|
|
1730
|
-
|
|
1731
|
-
| Added(
|
|
1711
|
+
switch verdict {
|
|
1712
|
+
| Added({fetchable: true}) =>
|
|
1732
1713
|
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1733
1714
|
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
1734
1715
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
// events for this contract can pick it up on restart, but skip partition
|
|
1740
|
-
// registration since there's nothing to fetch.
|
|
1741
|
-
warnAddressRegistration(
|
|
1742
|
-
~chainId=fetchState.chainId,
|
|
1743
|
-
~contractAddress=registration.address,
|
|
1744
|
-
~params={"contractName": registration.contractName},
|
|
1745
|
-
`Persisting contract registration without fetching: Contract doesn't have any events to fetch. It'll be picked up on restart if you add events for the contract.`,
|
|
1746
|
-
)
|
|
1747
|
-
true
|
|
1716
|
+
// Nothing on this chain is fetched by address for the contract, so there's
|
|
1717
|
+
// no partition to build. The address is still stored and persisted, so a
|
|
1718
|
+
// config that later adds address-dependent events picks it up on restart.
|
|
1719
|
+
| Added({fetchable: false}) => ()
|
|
1748
1720
|
| Conflict(_) | Duplicate(_) | Invalid =>
|
|
1749
1721
|
verdict->warnRejectedRegistration(
|
|
1750
1722
|
~chainId=fetchState.chainId,
|
|
1751
1723
|
~contractAddress=registration.address,
|
|
1752
1724
|
~contractName=registration.contractName,
|
|
1753
1725
|
)
|
|
1754
|
-
false
|
|
1755
|
-
}
|
|
1756
|
-
if !keep {
|
|
1757
|
-
// Mark for removal; the splice happens below, back to front, so earlier
|
|
1758
|
-
// indexes stay valid.
|
|
1759
|
-
sourceIndexes->Array.setUnsafe(idx, -1 - sourceIndexes->Array.getUnsafe(idx))
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
for idx in verdicts->Array.length - 1 downto 0 {
|
|
1763
|
-
let marked = sourceIndexes->Array.getUnsafe(idx)
|
|
1764
|
-
if marked < 0 {
|
|
1765
|
-
let _ =
|
|
1766
|
-
sourceDcs->Array.getUnsafe(idx)->Array.splice(~start=-1 - marked, ~remove=1, ~insert=[])
|
|
1767
1726
|
}
|
|
1768
1727
|
}
|
|
1769
1728
|
|
|
1770
|
-
switch
|
|
1771
|
-
//
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
| ([], true) => fetchState
|
|
1776
|
-
| (_, _) => {
|
|
1729
|
+
switch registeringContractNames {
|
|
1730
|
+
// Nothing to fetch: everything was rejected, or registered for contracts
|
|
1731
|
+
// this chain has no address-dependent events for.
|
|
1732
|
+
| [] => fetchState
|
|
1733
|
+
| _ => {
|
|
1777
1734
|
let newPartitions = []
|
|
1778
1735
|
let dynamicContractsRef = ref(fetchState.optimizedPartitions.dynamicContracts)
|
|
1779
1736
|
let mutExistingPartitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
@@ -1835,7 +1792,11 @@ let registerDynamicContracts = (
|
|
|
1835
1792
|
dynamicContract: Some(contractName),
|
|
1836
1793
|
addresses: splitAddresses,
|
|
1837
1794
|
mergeBlock: None,
|
|
1838
|
-
|
|
1795
|
+
// Must be a fresh array: sharing p's (empty) queue by
|
|
1796
|
+
// reference lets both split halves push and consume in
|
|
1797
|
+
// one array, so one half can consume the other's
|
|
1798
|
+
// fetched range and skip it for its own addresses.
|
|
1799
|
+
mutPendingQueries: [],
|
|
1839
1800
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
1840
1801
|
prevSourceRangeCapacity: p.prevSourceRangeCapacity,
|
|
1841
1802
|
eventDensity: p.eventDensity,
|
|
@@ -2709,7 +2670,9 @@ let make = (
|
|
|
2709
2670
|
// contract has no address-dependent events, so a later registration of the
|
|
2710
2671
|
// same address still conflicts and the address is still persisted.
|
|
2711
2672
|
addressStore
|
|
2712
|
-
|
|
2673
|
+
// These come from the config or from a resume, so they're already stored and
|
|
2674
|
+
// must never be drained back into a write.
|
|
2675
|
+
->AddressStore.seedBatch(
|
|
2713
2676
|
addresses->Array.map((contract): AddressStore.registration => {
|
|
2714
2677
|
address: contract.address,
|
|
2715
2678
|
contractName: contract.contractName,
|
package/src/FetchState.res.mjs
CHANGED
|
@@ -532,9 +532,14 @@ function handleQueryResponseForPartition(optimizedPartitions, p, query, knownHei
|
|
|
532
532
|
let pendingQuery = getPendingQueryOrThrow(p, query.fromBlock);
|
|
533
533
|
pendingQuery.fetchedBlock = latestFetchedBlock;
|
|
534
534
|
let blockRange = (latestFetchedBlock.blockNumber - query.fromBlock | 0) + 1 | 0;
|
|
535
|
-
let
|
|
536
|
-
|
|
537
|
-
|
|
535
|
+
let updatedEventDensity;
|
|
536
|
+
if (blockRange <= 0) {
|
|
537
|
+
updatedEventDensity = p.eventDensity;
|
|
538
|
+
} else {
|
|
539
|
+
let observedEventDensity = itemsCount / blockRange;
|
|
540
|
+
let eventDensity = p.eventDensity;
|
|
541
|
+
updatedEventDensity = eventDensity !== undefined ? (eventDensity + observedEventDensity) / 2 : observedEventDensity;
|
|
542
|
+
}
|
|
538
543
|
let shouldUpdateSourceRangeCapacity = false;
|
|
539
544
|
if (latestFetchedBlock.blockNumber > p.latestSourceRangeCapacityUpdateBlock) {
|
|
540
545
|
let queryToBlock = query.toBlock;
|
|
@@ -1086,7 +1091,6 @@ function warnRejectedRegistration(verdict, chainId, contractAddress, contractNam
|
|
|
1086
1091
|
}
|
|
1087
1092
|
switch (verdict.TAG) {
|
|
1088
1093
|
case "Added" :
|
|
1089
|
-
case "NoEvents" :
|
|
1090
1094
|
return;
|
|
1091
1095
|
case "Duplicate" :
|
|
1092
1096
|
let existingEffectiveStartBlock = verdict.existingEffectiveStartBlock;
|
|
@@ -1225,71 +1229,25 @@ function createPartitions(registeringSetsByContract, addressStore, dynamicContra
|
|
|
1225
1229
|
return make(allPartitions, maxAddrInPartition, nextPartitionIndexRef.contents, dynamicContracts, clientFilteredContracts);
|
|
1226
1230
|
}
|
|
1227
1231
|
|
|
1228
|
-
function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt,
|
|
1232
|
+
function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, registrations) {
|
|
1229
1233
|
let claimCeiling = claimCeilingOpt !== undefined ? claimCeilingOpt : fetchState.knownHeight;
|
|
1230
1234
|
if (Utils.$$Array.isEmpty(fetchState.normalSelection.onEventRegistrations)) {
|
|
1231
1235
|
Stdlib_JsError.throwWithMessage("Invalid configuration. No events to fetch for the dynamic contract registration.");
|
|
1232
1236
|
}
|
|
1233
|
-
let registrations = [];
|
|
1234
|
-
let sourceDcs = [];
|
|
1235
|
-
let sourceIndexes = [];
|
|
1236
|
-
for (let itemIdx = 0, itemIdx_finish = items.length; itemIdx < itemIdx_finish; ++itemIdx) {
|
|
1237
|
-
let dcs = items[itemIdx].dcs;
|
|
1238
|
-
if (dcs !== undefined) {
|
|
1239
|
-
for (let dcIdx = 0, dcIdx_finish = dcs.length; dcIdx < dcIdx_finish; ++dcIdx) {
|
|
1240
|
-
let dc = dcs[dcIdx];
|
|
1241
|
-
registrations.push({
|
|
1242
|
-
address: dc.address,
|
|
1243
|
-
contractName: dc.contractName,
|
|
1244
|
-
registrationBlock: dc.registrationBlock
|
|
1245
|
-
});
|
|
1246
|
-
sourceDcs.push(dcs);
|
|
1247
|
-
sourceIndexes.push(dcIdx);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
1237
|
let idCursor = addressStore.nextId();
|
|
1252
1238
|
let verdicts = AddressStore.registerBatch(addressStore, registrations);
|
|
1253
1239
|
let registeringContractNames = [];
|
|
1254
|
-
let hasNoEventsUpdatesRef = false;
|
|
1255
1240
|
for (let idx = 0, idx_finish = verdicts.length; idx < idx_finish; ++idx) {
|
|
1256
1241
|
let registration = registrations[idx];
|
|
1257
1242
|
let verdict = verdicts[idx];
|
|
1258
|
-
let keep;
|
|
1259
1243
|
let exit = 0;
|
|
1260
|
-
if (typeof verdict !== "object") {
|
|
1244
|
+
if (typeof verdict !== "object" || verdict.TAG !== "Added") {
|
|
1261
1245
|
exit = 1;
|
|
1262
|
-
} else {
|
|
1263
|
-
|
|
1264
|
-
case "Added" :
|
|
1265
|
-
if (!registeringContractNames.includes(registration.contractName)) {
|
|
1266
|
-
registeringContractNames.push(registration.contractName);
|
|
1267
|
-
}
|
|
1268
|
-
keep = true;
|
|
1269
|
-
break;
|
|
1270
|
-
case "NoEvents" :
|
|
1271
|
-
hasNoEventsUpdatesRef = true;
|
|
1272
|
-
warnAddressRegistration(fetchState.chainId, registration.address, {
|
|
1273
|
-
contractName: registration.contractName
|
|
1274
|
-
}, `Persisting contract registration without fetching: Contract doesn't have any events to fetch. It'll be picked up on restart if you add events for the contract.`);
|
|
1275
|
-
keep = true;
|
|
1276
|
-
break;
|
|
1277
|
-
default:
|
|
1278
|
-
exit = 1;
|
|
1279
|
-
}
|
|
1246
|
+
} else if (verdict.fetchable && !registeringContractNames.includes(registration.contractName)) {
|
|
1247
|
+
registeringContractNames.push(registration.contractName);
|
|
1280
1248
|
}
|
|
1281
1249
|
if (exit === 1) {
|
|
1282
1250
|
warnRejectedRegistration(verdict, fetchState.chainId, registration.address, registration.contractName);
|
|
1283
|
-
keep = false;
|
|
1284
|
-
}
|
|
1285
|
-
if (!keep) {
|
|
1286
|
-
sourceIndexes[idx] = -1 - sourceIndexes[idx] | 0;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
for (let idx$1 = verdicts.length - 1 | 0; idx$1 >= 0; --idx$1) {
|
|
1290
|
-
let marked = sourceIndexes[idx$1];
|
|
1291
|
-
if (marked < 0) {
|
|
1292
|
-
sourceDcs[idx$1].splice(-1 - marked | 0, 1);
|
|
1293
1251
|
}
|
|
1294
1252
|
}
|
|
1295
1253
|
if (registeringContractNames.length === 0) {
|
|
@@ -1298,12 +1256,12 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1298
1256
|
let newPartitions = [];
|
|
1299
1257
|
let dynamicContractsRef = fetchState.optimizedPartitions.dynamicContracts;
|
|
1300
1258
|
let mutExistingPartitions = Object.values(fetchState.optimizedPartitions.entities);
|
|
1301
|
-
for (let idx$
|
|
1302
|
-
let contractName = registeringContractNames[idx$
|
|
1259
|
+
for (let idx$1 = 0, idx_finish$1 = registeringContractNames.length; idx$1 < idx_finish$1; ++idx$1) {
|
|
1260
|
+
let contractName = registeringContractNames[idx$1];
|
|
1303
1261
|
if (!dynamicContractsRef.has(contractName)) {
|
|
1304
1262
|
dynamicContractsRef = Utils.$$Set.immutableAdd(dynamicContractsRef, contractName);
|
|
1305
|
-
for (let idx$
|
|
1306
|
-
let p = mutExistingPartitions[idx$
|
|
1263
|
+
for (let idx$2 = 0, idx_finish$2 = mutExistingPartitions.length; idx$2 < idx_finish$2; ++idx$2) {
|
|
1264
|
+
let p = mutExistingPartitions[idx$2];
|
|
1307
1265
|
let match = p.addresses.countFor(contractName);
|
|
1308
1266
|
if (match !== 0 && p.selection.dependsOnAddresses && p.mergeBlock === undefined) {
|
|
1309
1267
|
let allPartitionContractNames = p.addresses.contractNames();
|
|
@@ -1312,7 +1270,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1312
1270
|
if (!isFetching) {
|
|
1313
1271
|
let newPartitionId = (fetchState.optimizedPartitions.nextPartitionIndex + newPartitions.length | 0).toString();
|
|
1314
1272
|
let restNames = allPartitionContractNames.filter(name => name !== contractName);
|
|
1315
|
-
mutExistingPartitions[idx$
|
|
1273
|
+
mutExistingPartitions[idx$2] = withAddresses(p, p.addresses.filterByContracts(restNames));
|
|
1316
1274
|
let splitAddresses = p.addresses.filterByContracts([contractName]);
|
|
1317
1275
|
newPartitions.push({
|
|
1318
1276
|
id: newPartitionId,
|
|
@@ -1321,7 +1279,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1321
1279
|
addresses: splitAddresses,
|
|
1322
1280
|
mergeBlock: undefined,
|
|
1323
1281
|
dynamicContract: contractName,
|
|
1324
|
-
mutPendingQueries:
|
|
1282
|
+
mutPendingQueries: [],
|
|
1325
1283
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
1326
1284
|
prevSourceRangeCapacity: p.prevSourceRangeCapacity,
|
|
1327
1285
|
eventDensity: p.eventDensity,
|
|
@@ -1329,7 +1287,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1329
1287
|
});
|
|
1330
1288
|
}
|
|
1331
1289
|
} else {
|
|
1332
|
-
mutExistingPartitions[idx$
|
|
1290
|
+
mutExistingPartitions[idx$2] = {
|
|
1333
1291
|
id: p.id,
|
|
1334
1292
|
latestFetchedBlock: p.latestFetchedBlock,
|
|
1335
1293
|
selection: p.selection,
|
|
@@ -1786,7 +1744,7 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
|
|
|
1786
1744
|
});
|
|
1787
1745
|
}
|
|
1788
1746
|
let normalSelection = makeSelection(normalRegistrations, true, undefined);
|
|
1789
|
-
AddressStore.
|
|
1747
|
+
AddressStore.seedBatch(addressStore, addresses.map(contract => ({
|
|
1790
1748
|
address: contract.address,
|
|
1791
1749
|
contractName: contract.contractName,
|
|
1792
1750
|
registrationBlock: contract.registrationBlock
|
package/src/Hasura.res
CHANGED
|
@@ -281,6 +281,21 @@ let createSelectPermission = async (
|
|
|
281
281
|
)
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// The column_mapping references columns by their db names. A reference between
|
|
285
|
+
// two per-chain entities is only meaningful within one chain, so the chain-id
|
|
286
|
+
// column joins alongside the id — without it the relationship would resolve to
|
|
287
|
+
// another chain's row with the same id.
|
|
288
|
+
let makeColumnMapping = (~relationalKey, ~isDerivedFrom, ~chainIdColumn) => {
|
|
289
|
+
let pairs = [
|
|
290
|
+
isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}": "id"`,
|
|
291
|
+
]
|
|
292
|
+
switch chainIdColumn {
|
|
293
|
+
| Some(column) => pairs->Array.push(`"${column}": "${column}"`)->ignore
|
|
294
|
+
| None => ()
|
|
295
|
+
}
|
|
296
|
+
`{${pairs->Array.joinUnsafe(", ")}}`
|
|
297
|
+
}
|
|
298
|
+
|
|
284
299
|
let createEntityRelationship = async (
|
|
285
300
|
~endpoint,
|
|
286
301
|
~auth,
|
|
@@ -291,10 +306,9 @@ let createEntityRelationship = async (
|
|
|
291
306
|
~objectName: string,
|
|
292
307
|
~mappedEntity: string,
|
|
293
308
|
~isDerivedFrom: bool,
|
|
309
|
+
~chainIdColumn: option<string>,
|
|
294
310
|
~comment: option<string>=?,
|
|
295
311
|
) => {
|
|
296
|
-
// The column_mapping references columns by their db names
|
|
297
|
-
let derivedFromTo = isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}" : "id"`
|
|
298
312
|
|
|
299
313
|
let tableJson = {
|
|
300
314
|
"schema": pgSchema,
|
|
@@ -306,7 +320,9 @@ let createEntityRelationship = async (
|
|
|
306
320
|
"schema": pgSchema,
|
|
307
321
|
"name": mappedEntity,
|
|
308
322
|
},
|
|
309
|
-
"column_mapping": JSON.parseOrThrow(
|
|
323
|
+
"column_mapping": JSON.parseOrThrow(
|
|
324
|
+
makeColumnMapping(~relationalKey, ~isDerivedFrom, ~chainIdColumn),
|
|
325
|
+
),
|
|
310
326
|
},
|
|
311
327
|
}->(Utils.magic: {..} => JSON.t)
|
|
312
328
|
|
|
@@ -388,9 +404,25 @@ let trackDatabase = async (
|
|
|
388
404
|
)
|
|
389
405
|
}
|
|
390
406
|
|
|
407
|
+
// Both sides of a relationship must be per-chain for the chain to be part of
|
|
408
|
+
// the join; a per-chain entity referencing a cross-chain one resolves by id
|
|
409
|
+
// alone. The reverse (cross-chain referencing per-chain) is rejected at
|
|
410
|
+
// codegen, so it can't reach here.
|
|
411
|
+
let chainIdColumnOf = (entityName: string) =>
|
|
412
|
+
userEntities
|
|
413
|
+
->Array.find((e: Internal.entityConfig) => e.name === entityName)
|
|
414
|
+
->Option.flatMap(e => e.table->Table.getChainIdField)
|
|
415
|
+
->Option.map(Table.getPgDbFieldName)
|
|
416
|
+
|
|
391
417
|
for i in 0 to userEntities->Array.length - 1 {
|
|
392
418
|
let entityConfig = userEntities->Array.getUnsafe(i)
|
|
393
419
|
let {tableName} = entityConfig.table
|
|
420
|
+
let ownChainIdColumn = entityConfig.table->Table.getChainIdField->Option.map(Table.getPgDbFieldName)
|
|
421
|
+
let sharedChainIdColumn = mappedEntity =>
|
|
422
|
+
switch (ownChainIdColumn, chainIdColumnOf(mappedEntity)) {
|
|
423
|
+
| (Some(column), Some(_)) => Some(column)
|
|
424
|
+
| _ => None
|
|
425
|
+
}
|
|
394
426
|
|
|
395
427
|
//Set array relationships
|
|
396
428
|
let derivedFromFields = entityConfig.table->Table.getDerivedFromFields
|
|
@@ -410,6 +442,7 @@ let trackDatabase = async (
|
|
|
410
442
|
~objectName=derivedFromField.fieldName,
|
|
411
443
|
~relationalKey=relationalFieldName,
|
|
412
444
|
~mappedEntity=derivedFromField.derivedFromEntity,
|
|
445
|
+
~chainIdColumn=sharedChainIdColumn(derivedFromField.derivedFromEntity),
|
|
413
446
|
~comment=?derivedFromField.description,
|
|
414
447
|
)
|
|
415
448
|
}
|
|
@@ -428,6 +461,7 @@ let trackDatabase = async (
|
|
|
428
461
|
~objectName=field.fieldName,
|
|
429
462
|
~relationalKey=field->Table.getPgDbFieldName,
|
|
430
463
|
~mappedEntity=linkedEntityName,
|
|
464
|
+
~chainIdColumn=sharedChainIdColumn(linkedEntityName),
|
|
431
465
|
~comment=?field.description,
|
|
432
466
|
)
|
|
433
467
|
}
|
package/src/Hasura.res.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import * as Utils from "./Utils.res.mjs";
|
|
|
7
7
|
import * as Schema from "./db/Schema.res.mjs";
|
|
8
8
|
import * as Logging from "./Logging.res.mjs";
|
|
9
9
|
import * as InternalTable from "./db/InternalTable.res.mjs";
|
|
10
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
11
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
11
12
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
12
13
|
|
|
@@ -247,8 +248,15 @@ async function createSelectPermission(endpoint, auth, tableName, pgSchema, respo
|
|
|
247
248
|
});
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
let
|
|
251
|
+
function makeColumnMapping(relationalKey, isDerivedFrom, chainIdColumn) {
|
|
252
|
+
let pairs = [isDerivedFrom ? `"id": "` + relationalKey + `"` : `"` + relationalKey + `": "id"`];
|
|
253
|
+
if (chainIdColumn !== undefined) {
|
|
254
|
+
pairs.push(`"` + chainIdColumn + `": "` + chainIdColumn + `"`);
|
|
255
|
+
}
|
|
256
|
+
return `{` + pairs.join(", ") + `}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function createEntityRelationship(endpoint, auth, pgSchema, tableName, relationshipType, relationalKey, objectName, mappedEntity, isDerivedFrom, chainIdColumn, comment) {
|
|
252
260
|
let tableJson = {
|
|
253
261
|
schema: pgSchema,
|
|
254
262
|
name: tableName
|
|
@@ -259,7 +267,7 @@ async function createEntityRelationship(endpoint, auth, pgSchema, tableName, rel
|
|
|
259
267
|
schema: pgSchema,
|
|
260
268
|
name: mappedEntity
|
|
261
269
|
},
|
|
262
|
-
column_mapping: JSON.parse(
|
|
270
|
+
column_mapping: JSON.parse(makeColumnMapping(relationalKey, isDerivedFrom, chainIdColumn))
|
|
263
271
|
}
|
|
264
272
|
};
|
|
265
273
|
let args = {
|
|
@@ -313,21 +321,30 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
|
|
|
313
321
|
let tableName = tableNames[i];
|
|
314
322
|
await createSelectPermission(endpoint, auth, tableName, pgSchema, responseLimit, aggregateEntities);
|
|
315
323
|
}
|
|
324
|
+
let chainIdColumnOf = entityName => Stdlib_Option.map(Stdlib_Option.flatMap(userEntities.find(e => e.name === entityName), e => Table.getChainIdField(e.table)), Table.getPgDbFieldName);
|
|
316
325
|
for (let i$1 = 0, i_finish$1 = userEntities.length; i$1 < i_finish$1; ++i$1) {
|
|
317
326
|
let entityConfig = userEntities[i$1];
|
|
318
327
|
let match = entityConfig.table;
|
|
319
328
|
let tableName$1 = match.tableName;
|
|
329
|
+
let ownChainIdColumn = Stdlib_Option.map(Table.getChainIdField(entityConfig.table), Table.getPgDbFieldName);
|
|
330
|
+
let sharedChainIdColumn = mappedEntity => {
|
|
331
|
+
let match = chainIdColumnOf(mappedEntity);
|
|
332
|
+
if (ownChainIdColumn !== undefined && match !== undefined) {
|
|
333
|
+
return ownChainIdColumn;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
320
336
|
let derivedFromFields = Table.getDerivedFromFields(entityConfig.table);
|
|
321
337
|
for (let j = 0, j_finish = derivedFromFields.length; j < j_finish; ++j) {
|
|
322
338
|
let derivedFromField = derivedFromFields[j];
|
|
323
339
|
let relationalFieldName = Utils.unwrapResultExn(Schema.getDerivedFromPgFieldName(schema, derivedFromField));
|
|
324
|
-
await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "array", relationalFieldName, derivedFromField.fieldName, derivedFromField.derivedFromEntity, true, derivedFromField.description);
|
|
340
|
+
await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "array", relationalFieldName, derivedFromField.fieldName, derivedFromField.derivedFromEntity, true, sharedChainIdColumn(derivedFromField.derivedFromEntity), derivedFromField.description);
|
|
325
341
|
}
|
|
326
342
|
let linkedEntityFields = Table.getLinkedEntityFields(entityConfig.table);
|
|
327
343
|
for (let j$1 = 0, j_finish$1 = linkedEntityFields.length; j$1 < j_finish$1; ++j$1) {
|
|
328
344
|
let match$1 = linkedEntityFields[j$1];
|
|
345
|
+
let linkedEntityName = match$1[1];
|
|
329
346
|
let field = match$1[0];
|
|
330
|
-
await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", Table.getPgDbFieldName(field), field.fieldName,
|
|
347
|
+
await createEntityRelationship(endpoint, auth, pgSchema, tableName$1, "object", Table.getPgDbFieldName(field), field.fieldName, linkedEntityName, false, sharedChainIdColumn(linkedEntityName), field.description);
|
|
331
348
|
}
|
|
332
349
|
}
|
|
333
350
|
return Logging.info("Hasura configuration completed");
|
|
@@ -346,6 +363,7 @@ export {
|
|
|
346
363
|
makeColumnConfigs,
|
|
347
364
|
trackTables,
|
|
348
365
|
createSelectPermission,
|
|
366
|
+
makeColumnMapping,
|
|
349
367
|
createEntityRelationship,
|
|
350
368
|
trackDatabase,
|
|
351
369
|
}
|