envio 3.5.0-alpha.2 → 3.5.0-rc.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/package.json +6 -6
- package/src/Batch.res +8 -8
- package/src/Batch.res.mjs +4 -3
- package/src/BatchProcessing.res +24 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +20 -19
- package/src/ChainFetching.res.mjs +14 -14
- package/src/ChainId.res +58 -0
- package/src/ChainId.res.mjs +49 -0
- package/src/ChainId.resi +27 -0
- package/src/ChainMap.res +12 -26
- package/src/ChainMap.res.mjs +5 -41
- package/src/ChainMap.resi +5 -20
- package/src/ChainMetadata.res +1 -1
- package/src/ChainMetadata.res.mjs +3 -2
- package/src/ChainState.res +45 -24
- package/src/ChainState.res.mjs +40 -16
- package/src/ChainState.resi +3 -1
- package/src/Config.res +43 -26
- package/src/Config.res.mjs +19 -16
- package/src/ContractRegisterContext.res +1 -1
- package/src/ContractRegisterContext.res.mjs +1 -1
- package/src/Core.res +1 -0
- package/src/CrossChainState.res +90 -22
- package/src/CrossChainState.res.mjs +55 -16
- package/src/CrossChainState.resi +5 -1
- package/src/Env.res +0 -3
- package/src/Env.res.mjs +0 -3
- package/src/EventConfigBuilder.res +1 -1
- package/src/EventProcessing.res +4 -4
- package/src/EventProcessing.res.mjs +5 -5
- package/src/FetchState.res +489 -153
- package/src/FetchState.res.mjs +399 -129
- package/src/FinalizeBackfill.res +61 -0
- package/src/FinalizeBackfill.res.mjs +44 -0
- package/src/HandlerRegister.res +8 -8
- package/src/HandlerRegister.res.mjs +5 -4
- package/src/HandlerRegister.resi +1 -1
- package/src/InMemoryStore.res +4 -0
- package/src/InMemoryStore.res.mjs +1 -1
- package/src/InMemoryTable.res +15 -15
- package/src/InMemoryTable.res.mjs +14 -14
- package/src/IndexerLoop.res +9 -2
- package/src/IndexerLoop.res.mjs +8 -1
- package/src/IndexerState.res +52 -23
- package/src/IndexerState.res.mjs +52 -14
- package/src/IndexerState.resi +11 -6
- package/src/Internal.res +19 -17
- package/src/Internal.res.mjs +18 -9
- package/src/LoadLayer.res +10 -2
- package/src/LoadLayer.res.mjs +3 -1
- package/src/LogSelection.res +2 -2
- package/src/Main.res +7 -11
- package/src/Main.res.mjs +4 -6
- package/src/Metrics.res +6 -6
- package/src/Metrics.res.mjs +4 -3
- package/src/Persistence.res +23 -4
- package/src/PgStorage.res +447 -100
- package/src/PgStorage.res.mjs +285 -99
- package/src/RawEvent.res +1 -2
- package/src/RawEvent.res.mjs +1 -1
- package/src/Rollback.res +18 -20
- package/src/Rollback.res.mjs +9 -9
- package/src/RollbackCommit.res +2 -2
- package/src/RollbackCommit.res.mjs +3 -3
- package/src/SafeCheckpointTracking.res +2 -2
- package/src/SimulateDeadInputTracker.res +17 -21
- package/src/SimulateDeadInputTracker.res.mjs +9 -8
- package/src/SimulateItems.res +8 -10
- package/src/SimulateItems.res.mjs +9 -10
- package/src/Sink.res +2 -2
- package/src/Sink.res.mjs +3 -2
- package/src/TestIndexer.res +14 -14
- package/src/TestIndexer.res.mjs +16 -10
- package/src/UserContext.res +2 -2
- package/src/UserContext.res.mjs +2 -1
- package/src/Utils.res +0 -4
- package/src/Utils.res.mjs +1 -6
- package/src/bindings/ClickHouse.res +20 -2
- package/src/bindings/ClickHouse.res.mjs +25 -14
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +1 -1
- package/src/db/IndexCatalog.res +248 -0
- package/src/db/IndexCatalog.res.mjs +222 -0
- package/src/db/IndexDefinition.res +131 -0
- package/src/db/IndexDefinition.res.mjs +138 -0
- package/src/db/IndexManager.res +145 -0
- package/src/db/IndexManager.res.mjs +130 -0
- package/src/db/InternalTable.res +54 -23
- package/src/db/InternalTable.res.mjs +38 -23
- package/src/db/Table.res +24 -14
- package/src/db/Table.res.mjs +21 -16
- package/src/sources/AddressSet.res +5 -4
- package/src/sources/AddressStore.res +25 -14
- package/src/sources/AddressStore.res.mjs +10 -12
- package/src/sources/Evm.res +2 -2
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/EvmChain.res +3 -3
- package/src/sources/EvmChain.res.mjs +3 -3
- package/src/sources/EvmHyperSyncSource.res +7 -7
- package/src/sources/EvmHyperSyncSource.res.mjs +6 -6
- package/src/sources/Fuel.res +2 -2
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/FuelHyperSyncClient.res +3 -0
- package/src/sources/FuelHyperSyncClient.res.mjs +1 -0
- package/src/sources/FuelHyperSyncSource.res +5 -7
- package/src/sources/FuelHyperSyncSource.res.mjs +5 -5
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +5 -0
- package/src/sources/HyperSyncClient.res.mjs +1 -0
- package/src/sources/RpcSource.res +13 -14
- package/src/sources/RpcSource.res.mjs +13 -12
- package/src/sources/SimulateSource.res +42 -15
- package/src/sources/SimulateSource.res.mjs +27 -9
- package/src/sources/Source.res +1 -5
- package/src/sources/SourceManager.res +12 -12
- package/src/sources/SourceManager.res.mjs +11 -10
- package/src/sources/SourceManager.resi +2 -2
- package/src/sources/Svm.res +4 -5
- package/src/sources/Svm.res.mjs +5 -4
- package/src/sources/SvmHyperSyncClient.res +3 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +1 -0
- package/src/sources/SvmHyperSyncSource.res +4 -4
- package/src/sources/SvmHyperSyncSource.res.mjs +3 -3
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +2 -1
- package/src/tui/components/CustomHooks.res +5 -4
- package/src/tui/components/CustomHooks.res.mjs +4 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The FinalizingIndexes phase. Reached from the processing loop when every
|
|
2
|
+
// chain has caught up: processing is already paused (the loop awaits this),
|
|
3
|
+
// pending writes are flushed, then storage builds every missing schema-defined
|
|
4
|
+
// index and, once they all verify, commits `ready_at`. A failure part-way
|
|
5
|
+
// leaves the indexes built so far in place and reaches the processing loop's
|
|
6
|
+
// error boundary; the retry only owes what's left. Either way the indexer never
|
|
7
|
+
// reports ready with an index the schema promised still missing.
|
|
8
|
+
|
|
9
|
+
let runOnce = async (state: IndexerState.t) => {
|
|
10
|
+
Logging.info(
|
|
11
|
+
"All chains are caught up. Finalizing the indexer before switching to realtime: flushing pending writes, then creating the indexes the schema promises.",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
await Writing.flush(state)
|
|
15
|
+
|
|
16
|
+
// A failed write already surfaced through onError; committing ready_at on top
|
|
17
|
+
// of an incomplete write would claim progress that isn't durable.
|
|
18
|
+
if !(state->IndexerState.hasFailedWrite) {
|
|
19
|
+
let persistence = state->IndexerState.persistence
|
|
20
|
+
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
21
|
+
let readyAt = Date.make()
|
|
22
|
+
|
|
23
|
+
await storage.finalizeBackfill(
|
|
24
|
+
~entities=persistence.allEntities,
|
|
25
|
+
~chainIds=state
|
|
26
|
+
->IndexerState.chainStates
|
|
27
|
+
->Dict.valuesToArray
|
|
28
|
+
->Array.map(cs => (cs->ChainState.chainConfig).id),
|
|
29
|
+
~readyAt,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// Only after the commit: in-memory readiness must never run ahead of the
|
|
33
|
+
// `ready_at` a restart would read back.
|
|
34
|
+
state->IndexerState.markReady(~readyAt)
|
|
35
|
+
Logging.info("The indexer is ready. Switching to realtime indexing.")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Several paths reach the phase — a processed batch, a tick that progressed
|
|
40
|
+
// nothing — and a height update can bring another one round while the first is
|
|
41
|
+
// still building. They all join the in-flight run rather than starting a second
|
|
42
|
+
// pass over the same indexes.
|
|
43
|
+
let run = (state: IndexerState.t) =>
|
|
44
|
+
switch state->IndexerState.finalizeFiber {
|
|
45
|
+
| Some(fiber) => fiber
|
|
46
|
+
| None =>
|
|
47
|
+
let fiber = runOnce(state)->Promise.finally(() => state->IndexerState.endFinalizeFiber)
|
|
48
|
+
state->IndexerState.beginFinalizeFiber(fiber)
|
|
49
|
+
fiber
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// An indexer resumed already ready never reaches `run`, so nothing above would
|
|
53
|
+
// notice an index the schema promises being dropped or invalidated while it was
|
|
54
|
+
// down — `ensureQueryIndexes` only covers what a getWhere actually asks for.
|
|
55
|
+
// Best-effort and not awaited by the loop: indexing is already live and correct
|
|
56
|
+
// without the index, just slower.
|
|
57
|
+
let repairSchemaIndexes = (state: IndexerState.t) => {
|
|
58
|
+
let persistence = state->IndexerState.persistence
|
|
59
|
+
let storage = persistence->Persistence.getInitializedStorageOrThrow
|
|
60
|
+
storage.ensureSchemaIndexes(~entities=persistence.allEntities)
|
|
61
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Logging from "./Logging.res.mjs";
|
|
4
|
+
import * as Writing from "./Writing.res.mjs";
|
|
5
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
6
|
+
import * as Persistence from "./Persistence.res.mjs";
|
|
7
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
8
|
+
|
|
9
|
+
async function runOnce(state) {
|
|
10
|
+
Logging.info("All chains are caught up. Finalizing the indexer before switching to realtime: flushing pending writes, then creating the indexes the schema promises.");
|
|
11
|
+
await Writing.flush(state);
|
|
12
|
+
if (IndexerState.hasFailedWrite(state)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let persistence = IndexerState.persistence(state);
|
|
16
|
+
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
17
|
+
let readyAt = new Date();
|
|
18
|
+
await storage.finalizeBackfill(persistence.allEntities, Object.values(IndexerState.chainStates(state)).map(cs => ChainState.chainConfig(cs).id), readyAt);
|
|
19
|
+
IndexerState.markReady(state, readyAt);
|
|
20
|
+
return Logging.info("The indexer is ready. Switching to realtime indexing.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function run(state) {
|
|
24
|
+
let fiber = IndexerState.finalizeFiber(state);
|
|
25
|
+
if (fiber !== undefined) {
|
|
26
|
+
return fiber;
|
|
27
|
+
}
|
|
28
|
+
let fiber$1 = runOnce(state).finally(() => IndexerState.endFinalizeFiber(state));
|
|
29
|
+
IndexerState.beginFinalizeFiber(state, fiber$1);
|
|
30
|
+
return fiber$1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function repairSchemaIndexes(state) {
|
|
34
|
+
let persistence = IndexerState.persistence(state);
|
|
35
|
+
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
36
|
+
return storage.ensureSchemaIndexes(persistence.allEntities);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
runOnce,
|
|
41
|
+
run,
|
|
42
|
+
repairSchemaIndexes,
|
|
43
|
+
}
|
|
44
|
+
/* Logging Not a pure module */
|
package/src/HandlerRegister.res
CHANGED
|
@@ -82,8 +82,8 @@ let startRegistration = (~config: Config.t) => {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
let getChainRegistrations = (r: activeRegistration, ~chainId:
|
|
86
|
-
let key = chainId->
|
|
85
|
+
let getChainRegistrations = (r: activeRegistration, ~chainId: ChainId.t): chainRegistrations => {
|
|
86
|
+
let key = chainId->ChainId.toString
|
|
87
87
|
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
88
88
|
| Some(existing) => existing
|
|
89
89
|
| None =>
|
|
@@ -98,7 +98,7 @@ let getChainRegistrations = (r: activeRegistration, ~chainId: int): chainRegistr
|
|
|
98
98
|
|
|
99
99
|
let buildOnEventRegistrationWith = (
|
|
100
100
|
~config: Config.t,
|
|
101
|
-
~chainId:
|
|
101
|
+
~chainId: ChainId.t,
|
|
102
102
|
~eventConfig: Internal.eventConfig,
|
|
103
103
|
~isWildcard: bool,
|
|
104
104
|
~handler: option<Internal.handler>,
|
|
@@ -342,10 +342,10 @@ let setContractRegister = (~contractName, ~eventName, contractRegister, ~eventOp
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
// Raw onEvent registrations stored for a chain (empty if the chain has none).
|
|
345
|
-
let storedOnEventRegistrations = (r: activeRegistration, ~chainId:
|
|
345
|
+
let storedOnEventRegistrations = (r: activeRegistration, ~chainId: ChainId.t): array<
|
|
346
346
|
Internal.onEventRegistration,
|
|
347
347
|
> =>
|
|
348
|
-
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->
|
|
348
|
+
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->ChainId.toString) {
|
|
349
349
|
| Some(chainRegs) => chainRegs.onEventRegistrations
|
|
350
350
|
| None => []
|
|
351
351
|
}
|
|
@@ -373,7 +373,7 @@ let isWildcard = (~contractName, ~eventName) =>
|
|
|
373
373
|
// item still produces an item to run.
|
|
374
374
|
let getSimulateOnEventRegistrations = (
|
|
375
375
|
~config: Config.t,
|
|
376
|
-
~chainId:
|
|
376
|
+
~chainId: ChainId.t,
|
|
377
377
|
~eventConfig: Internal.eventConfig,
|
|
378
378
|
): array<Internal.onEventRegistration> => {
|
|
379
379
|
let stored = switch getActiveRegistration() {
|
|
@@ -412,7 +412,7 @@ let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
|
412
412
|
->ChainMap.values
|
|
413
413
|
->Array.forEach(chainConfig => {
|
|
414
414
|
let chainId = chainConfig.id
|
|
415
|
-
let key = chainId->
|
|
415
|
+
let key = chainId->ChainId.toString
|
|
416
416
|
|
|
417
417
|
let builtRegs = mergeRegistrations(r->storedOnEventRegistrations(~chainId), ~config)
|
|
418
418
|
let registeredKeys = Utils.Set.make()
|
|
@@ -638,7 +638,7 @@ let registerOnBlock = (
|
|
|
638
638
|
->ChainMap.values
|
|
639
639
|
->Array.forEach(chainConfig => {
|
|
640
640
|
let chainId = chainConfig.id
|
|
641
|
-
let chainObj = chainsDict->Dict.getUnsafe(chainId->
|
|
641
|
+
let chainObj = chainsDict->Dict.getUnsafe(chainId->ChainId.toString)
|
|
642
642
|
|
|
643
643
|
// Predicate returns `true` → match with no filter; `false` → skip;
|
|
644
644
|
// any plain object → structured filter. `undefined`/`null` returns
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as ChainId from "./ChainId.res.mjs";
|
|
4
5
|
import * as Logging from "./Logging.res.mjs";
|
|
5
6
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
6
7
|
import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
|
|
@@ -54,7 +55,7 @@ function startRegistration(config) {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function getChainRegistrations(r, chainId) {
|
|
57
|
-
let key =
|
|
58
|
+
let key = ChainId.toString(chainId);
|
|
58
59
|
let existing = r.registrationsByChainId[key];
|
|
59
60
|
if (existing !== undefined) {
|
|
60
61
|
return existing;
|
|
@@ -220,7 +221,7 @@ function setContractRegister(contractName, eventName, contractRegister, eventOpt
|
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
function storedOnEventRegistrations(r, chainId) {
|
|
223
|
-
let chainRegs = r.registrationsByChainId[
|
|
224
|
+
let chainRegs = r.registrationsByChainId[ChainId.toString(chainId)];
|
|
224
225
|
if (chainRegs !== undefined) {
|
|
225
226
|
return chainRegs.onEventRegistrations;
|
|
226
227
|
} else {
|
|
@@ -270,7 +271,7 @@ function finishRegistration(config) {
|
|
|
270
271
|
let registrationsByChainId = {};
|
|
271
272
|
ChainMap.values(config.chainMap).forEach(chainConfig => {
|
|
272
273
|
let chainId = chainConfig.id;
|
|
273
|
-
let key =
|
|
274
|
+
let key = ChainId.toString(chainId);
|
|
274
275
|
let builtRegs = mergeRegistrations(storedOnEventRegistrations(r, chainId), config);
|
|
275
276
|
let registeredKeys = new Set();
|
|
276
277
|
builtRegs.forEach(reg => {
|
|
@@ -385,7 +386,7 @@ function registerOnBlock(name, where, handler, getChainsObject) {
|
|
|
385
386
|
};
|
|
386
387
|
ChainMap.values(config.chainMap).forEach(chainConfig => {
|
|
387
388
|
let chainId = chainConfig.id;
|
|
388
|
-
let chainObj = chainsDict[
|
|
389
|
+
let chainObj = chainsDict[ChainId.toString(chainId)];
|
|
389
390
|
let result = where$1 !== undefined ? where$1({
|
|
390
391
|
chain: chainObj
|
|
391
392
|
}) : true;
|
package/src/HandlerRegister.resi
CHANGED
|
@@ -26,7 +26,7 @@ let isWildcard: (~contractName: string, ~eventName: string) => bool
|
|
|
26
26
|
let isDroppedByWhere: (~config: Config.t, Internal.onEventRegistration) => bool
|
|
27
27
|
let getSimulateOnEventRegistrations: (
|
|
28
28
|
~config: Config.t,
|
|
29
|
-
~chainId:
|
|
29
|
+
~chainId: ChainId.t,
|
|
30
30
|
~eventConfig: Internal.eventConfig,
|
|
31
31
|
) => array<Internal.onEventRegistration>
|
|
32
32
|
|
package/src/InMemoryStore.res
CHANGED
|
@@ -108,7 +108,11 @@ let prepareRollbackDiff = async (
|
|
|
108
108
|
let deletedEntities = Dict.make()
|
|
109
109
|
let setEntities = Dict.make()
|
|
110
110
|
|
|
111
|
+
// Rollback data comes from Postgres entity history, which is kept only for
|
|
112
|
+
// Postgres-backed entities. ClickHouse-only entities have no history to
|
|
113
|
+
// restore from, so a reorg leaves them un-reverted in the sink.
|
|
111
114
|
let _ = await persistence.allEntities
|
|
115
|
+
->Array.filter(entityConfig => entityConfig.storage.postgres)
|
|
112
116
|
->Array.map(async entityConfig => {
|
|
113
117
|
let entityTable = state->getInMemTable(~entityConfig)
|
|
114
118
|
|
|
@@ -86,7 +86,7 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
|
|
|
86
86
|
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
87
87
|
let deletedEntities = {};
|
|
88
88
|
let setEntities = {};
|
|
89
|
-
await Promise.all(persistence.allEntities.map(async entityConfig => {
|
|
89
|
+
await Promise.all(persistence.allEntities.filter(entityConfig => entityConfig.storage.postgres).map(async entityConfig => {
|
|
90
90
|
let entityTable = getInMemTable(state, entityConfig);
|
|
91
91
|
let match = await persistence.storage.getRollbackData(entityConfig, rollbackTargetCheckpointId);
|
|
92
92
|
match[0].forEach(entityId => {
|
package/src/InMemoryTable.res
CHANGED
|
@@ -2,7 +2,7 @@ module Entity = {
|
|
|
2
2
|
type relatedEntityId = string
|
|
3
3
|
type filterWithRelatedIds = (EntityFilter.t, Utils.Set.t<relatedEntityId>)
|
|
4
4
|
// Keyed by EntityFilter.toString
|
|
5
|
-
type
|
|
5
|
+
type filterIndexes = dict<filterWithRelatedIds>
|
|
6
6
|
|
|
7
7
|
type entityFilters = Utils.Set.t<EntityFilter.t>
|
|
8
8
|
type t = {
|
|
@@ -14,7 +14,7 @@ module Entity = {
|
|
|
14
14
|
// previous changes persist in the background.
|
|
15
15
|
mutable prevEntityChanges: array<Change.t<Internal.entity>>,
|
|
16
16
|
mutable filtersByEntityId: dict<entityFilters>,
|
|
17
|
-
mutable
|
|
17
|
+
mutable filterIndexes: filterIndexes,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Helper to extract an entity's id as a dict key. The raw id may be a
|
|
@@ -44,7 +44,7 @@ module Entity = {
|
|
|
44
44
|
changesCount: 0.,
|
|
45
45
|
prevEntityChanges: [],
|
|
46
46
|
filtersByEntityId: Dict.make(),
|
|
47
|
-
|
|
47
|
+
filterIndexes: Dict.make(),
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Changes to persist for checkpoints in (committedCheckpointId, upToCheckpointId].
|
|
@@ -81,7 +81,7 @@ module Entity = {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// Frees committed changes: drops latest entries at or below committedCheckpointId
|
|
84
|
-
// (re-readable from the db) and clears the per-batch
|
|
84
|
+
// (re-readable from the db) and clears the per-batch indexes (rebuilt on the next
|
|
85
85
|
// getWhere). Uncommitted changes are kept. With keepLoadedFromDb, entries seeded
|
|
86
86
|
// from a db read are spared so the cheaper-to-re-derive writes are dropped first.
|
|
87
87
|
let dropCommittedChanges = (self: t, ~committedCheckpointId, ~keepLoadedFromDb) => {
|
|
@@ -98,10 +98,10 @@ module Entity = {
|
|
|
98
98
|
keysToDelete->Array.forEach(key => self.latestEntityChangeById->Utils.Dict.deleteInPlace(key))
|
|
99
99
|
self.changesCount = self.changesCount -. keysToDelete->Array.length->Int.toFloat
|
|
100
100
|
self.filtersByEntityId = Dict.make()
|
|
101
|
-
self.
|
|
101
|
+
self.filterIndexes = Dict.make()
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
let
|
|
104
|
+
let updateIndexes = (self: t, ~entity: Internal.entity) => {
|
|
105
105
|
let entityId = entity->getEntityIdUnsafe
|
|
106
106
|
let entityAsDict = entity->(Utils.magic: Internal.entity => dict<EntityFilter.FieldValue.t>)
|
|
107
107
|
|
|
@@ -116,7 +116,7 @@ module Entity = {
|
|
|
116
116
|
})
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
self.
|
|
119
|
+
self.filterIndexes->Utils.Dict.forEach(((filter, relatedEntityIds)) => {
|
|
120
120
|
if filter->EntityFilter.matches(~entity=entityAsDict) {
|
|
121
121
|
//Add entity id to the filter index and the filter to entity filters
|
|
122
122
|
relatedEntityIds->Utils.Set.add(entityId)->ignore
|
|
@@ -127,12 +127,12 @@ module Entity = {
|
|
|
127
127
|
})
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
let
|
|
130
|
+
let deleteEntityFromIndexes = (self: t, ~entityId: string) =>
|
|
131
131
|
switch self.filtersByEntityId->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
132
132
|
| None => ()
|
|
133
133
|
| Some(entityFilters) =>
|
|
134
134
|
entityFilters->Utils.Set.forEach(filter => {
|
|
135
|
-
switch self.
|
|
135
|
+
switch self.filterIndexes->Utils.Dict.dangerouslyGetNonOption(
|
|
136
136
|
filter->EntityFilter.toString,
|
|
137
137
|
) {
|
|
138
138
|
| Some((_filter, relatedEntityIds)) =>
|
|
@@ -159,8 +159,8 @@ module Entity = {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
switch change {
|
|
162
|
-
| Set({entity}) => inMemTable->
|
|
163
|
-
| Delete({entityId}) => inMemTable->
|
|
162
|
+
| Set({entity}) => inMemTable->updateIndexes(~entity)
|
|
163
|
+
| Delete({entityId}) => inMemTable->deleteEntityFromIndexes(~entityId=entityId->EntityId.toKey)
|
|
164
164
|
}
|
|
165
165
|
inMemTable.latestEntityChangeById->Dict.set(entityKey, change)
|
|
166
166
|
}
|
|
@@ -200,12 +200,12 @@ module Entity = {
|
|
|
200
200
|
|
|
201
201
|
let hasIndex = (inMemTable: t) =>
|
|
202
202
|
(filterKey: string) =>
|
|
203
|
-
inMemTable.
|
|
203
|
+
inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) !== None
|
|
204
204
|
|
|
205
205
|
let getUnsafeOnIndex = (inMemTable: t) => {
|
|
206
206
|
let getEntity = inMemTable->getUnsafe
|
|
207
207
|
(filterKey: string) => {
|
|
208
|
-
switch inMemTable.
|
|
208
|
+
switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
209
209
|
| None =>
|
|
210
210
|
JsError.throwWithMessage(`Unexpected error. Must have an index for the filter ${filterKey}`)
|
|
211
211
|
| Some((_filter, relatedEntityIds)) =>
|
|
@@ -223,7 +223,7 @@ module Entity = {
|
|
|
223
223
|
|
|
224
224
|
let addEmptyIndex = (inMemTable: t, ~filter: EntityFilter.t) => {
|
|
225
225
|
let filterKey = filter->EntityFilter.toString
|
|
226
|
-
switch inMemTable.
|
|
226
|
+
switch inMemTable.filterIndexes->Utils.Dict.dangerouslyGetNonOption(filterKey) {
|
|
227
227
|
| Some(_) => () //Should not happen, this means the index already exists
|
|
228
228
|
| None =>
|
|
229
229
|
let relatedEntityIds = Utils.Set.make()
|
|
@@ -240,7 +240,7 @@ module Entity = {
|
|
|
240
240
|
| None => ()
|
|
241
241
|
}
|
|
242
242
|
})
|
|
243
|
-
inMemTable.
|
|
243
|
+
inMemTable.filterIndexes->Dict.set(filterKey, (filter, relatedEntityIds))
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
}
|
|
@@ -40,7 +40,7 @@ function make() {
|
|
|
40
40
|
changesCount: 0,
|
|
41
41
|
prevEntityChanges: [],
|
|
42
42
|
filtersByEntityId: {},
|
|
43
|
-
|
|
43
|
+
filterIndexes: {}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -84,10 +84,10 @@ function dropCommittedChanges(self, committedCheckpointId, keepLoadedFromDb) {
|
|
|
84
84
|
keysToDelete.forEach(key => Utils.Dict.deleteInPlace(self.latestEntityChangeById, key));
|
|
85
85
|
self.changesCount = self.changesCount - keysToDelete.length;
|
|
86
86
|
self.filtersByEntityId = {};
|
|
87
|
-
self.
|
|
87
|
+
self.filterIndexes = {};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function
|
|
90
|
+
function updateIndexes(self, entity) {
|
|
91
91
|
let entityId = getEntityIdUnsafe(entity);
|
|
92
92
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
93
93
|
if (entityFilters !== undefined) {
|
|
@@ -99,7 +99,7 @@ function updateIndices(self, entity) {
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
-
Utils.Dict.forEach(self.
|
|
102
|
+
Utils.Dict.forEach(self.filterIndexes, param => {
|
|
103
103
|
let relatedEntityIds = param[1];
|
|
104
104
|
let filter = param[0];
|
|
105
105
|
if (EntityFilter.matches(filter, entity)) {
|
|
@@ -111,14 +111,14 @@ function updateIndices(self, entity) {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function
|
|
114
|
+
function deleteEntityFromIndexes(self, entityId) {
|
|
115
115
|
let entityFilters = self.filtersByEntityId[entityId];
|
|
116
116
|
if (entityFilters === undefined) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
let entityFilters$1 = Primitive_option.valFromOption(entityFilters);
|
|
120
120
|
entityFilters$1.forEach(filter => {
|
|
121
|
-
let match = self.
|
|
121
|
+
let match = self.filterIndexes[EntityFilter.toString(filter)];
|
|
122
122
|
if (match !== undefined) {
|
|
123
123
|
match[1].delete(entityId);
|
|
124
124
|
}
|
|
@@ -139,9 +139,9 @@ function set(inMemTable, committedCheckpointId, change) {
|
|
|
139
139
|
inMemTable.changesCount = inMemTable.changesCount + 1;
|
|
140
140
|
}
|
|
141
141
|
if (change.type === "SET") {
|
|
142
|
-
|
|
142
|
+
updateIndexes(inMemTable, change.entity);
|
|
143
143
|
} else {
|
|
144
|
-
|
|
144
|
+
deleteEntityFromIndexes(inMemTable, EntityId.toKey(change.entityId));
|
|
145
145
|
}
|
|
146
146
|
inMemTable.latestEntityChangeById[entityKey] = change;
|
|
147
147
|
}
|
|
@@ -174,13 +174,13 @@ function getUnsafe(inMemTable) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function hasIndex(inMemTable) {
|
|
177
|
-
return filterKey => inMemTable.
|
|
177
|
+
return filterKey => inMemTable.filterIndexes[filterKey] !== undefined;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function getUnsafeOnIndex(inMemTable) {
|
|
181
181
|
let getEntity = getUnsafe(inMemTable);
|
|
182
182
|
return filterKey => {
|
|
183
|
-
let match = inMemTable.
|
|
183
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
184
184
|
if (match !== undefined) {
|
|
185
185
|
return Stdlib_Array.filterMap(Array.from(match[1]), entityId => {
|
|
186
186
|
if (entityId in inMemTable.latestEntityChangeById) {
|
|
@@ -195,7 +195,7 @@ function getUnsafeOnIndex(inMemTable) {
|
|
|
195
195
|
|
|
196
196
|
function addEmptyIndex(inMemTable, filter) {
|
|
197
197
|
let filterKey = EntityFilter.toString(filter);
|
|
198
|
-
let match = inMemTable.
|
|
198
|
+
let match = inMemTable.filterIndexes[filterKey];
|
|
199
199
|
if (match !== undefined) {
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
@@ -212,7 +212,7 @@ function addEmptyIndex(inMemTable, filter) {
|
|
|
212
212
|
getOrCreateEntityFilters(inMemTable, entityId).add(filter);
|
|
213
213
|
relatedEntityIds.add(entityId);
|
|
214
214
|
});
|
|
215
|
-
inMemTable.
|
|
215
|
+
inMemTable.filterIndexes[filterKey] = [
|
|
216
216
|
filter,
|
|
217
217
|
relatedEntityIds
|
|
218
218
|
];
|
|
@@ -225,8 +225,8 @@ let Entity = {
|
|
|
225
225
|
make: make,
|
|
226
226
|
snapshotChanges: snapshotChanges,
|
|
227
227
|
dropCommittedChanges: dropCommittedChanges,
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
updateIndexes: updateIndexes,
|
|
229
|
+
deleteEntityFromIndexes: deleteEntityFromIndexes,
|
|
230
230
|
set: set,
|
|
231
231
|
initValue: initValue,
|
|
232
232
|
mapChangeToEntity: mapChangeToEntity,
|
package/src/IndexerLoop.res
CHANGED
|
@@ -19,10 +19,10 @@ let start = (state: IndexerState.t) => {
|
|
|
19
19
|
launch(state, () =>
|
|
20
20
|
state
|
|
21
21
|
->IndexerState.crossChainState
|
|
22
|
-
->CrossChainState.checkAndFetch(~dispatchChain=(~
|
|
22
|
+
->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) =>
|
|
23
23
|
ChainFetching.fetchChain(
|
|
24
24
|
state,
|
|
25
|
-
|
|
25
|
+
chainId,
|
|
26
26
|
~action,
|
|
27
27
|
~stateId=state->IndexerState.epoch,
|
|
28
28
|
~scheduleFetch,
|
|
@@ -38,6 +38,13 @@ let start = (state: IndexerState.t) => {
|
|
|
38
38
|
Rollback.rollback(state, ~scheduleFetch, ~scheduleProcessing, ~scheduleRollback)
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
// Resuming already ready means the FinalizingIndexes phase is behind us and
|
|
42
|
+
// won't run again, so this is the only pass that can restore an index the
|
|
43
|
+
// database lost while the indexer was down.
|
|
44
|
+
if state->IndexerState.isRealtime {
|
|
45
|
+
launch(state, () => FinalizeBackfill.repairSchemaIndexes(state))
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
scheduleFetch()
|
|
42
49
|
scheduleProcessing()
|
|
43
50
|
}
|
package/src/IndexerLoop.res.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import * as IndexerState from "./IndexerState.res.mjs";
|
|
|
5
5
|
import * as ChainFetching from "./ChainFetching.res.mjs";
|
|
6
6
|
import * as BatchProcessing from "./BatchProcessing.res.mjs";
|
|
7
7
|
import * as CrossChainState from "./CrossChainState.res.mjs";
|
|
8
|
+
import * as FinalizeBackfill from "./FinalizeBackfill.res.mjs";
|
|
8
9
|
|
|
9
10
|
function launch(state, work) {
|
|
10
11
|
if (!IndexerState.isStopped(state)) {
|
|
@@ -15,7 +16,7 @@ function launch(state, work) {
|
|
|
15
16
|
|
|
16
17
|
function start(state) {
|
|
17
18
|
let scheduleFetch = () => {
|
|
18
|
-
let work = () => CrossChainState.checkAndFetch(IndexerState.crossChainState(state), (
|
|
19
|
+
let work = () => CrossChainState.checkAndFetch(IndexerState.crossChainState(state), (chainId, action) => ChainFetching.fetchChain(state, chainId, action, IndexerState.epoch(state), scheduleFetch, scheduleProcessing, scheduleRollback));
|
|
19
20
|
if (!IndexerState.isStopped(state)) {
|
|
20
21
|
work();
|
|
21
22
|
return;
|
|
@@ -35,6 +36,12 @@ function start(state) {
|
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
};
|
|
39
|
+
if (IndexerState.isRealtime(state)) {
|
|
40
|
+
let work = () => FinalizeBackfill.repairSchemaIndexes(state);
|
|
41
|
+
if (!IndexerState.isStopped(state)) {
|
|
42
|
+
work();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
38
45
|
scheduleFetch();
|
|
39
46
|
scheduleProcessing();
|
|
40
47
|
}
|