envio 3.12.0 → 3.13.0-alpha.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/BatchProcessing.res +9 -9
- package/src/BatchProcessing.res.mjs +5 -5
- package/src/Bin.res +24 -17
- package/src/Bin.res.mjs +5 -0
- package/src/ChainFetching.res +31 -13
- package/src/ChainFetching.res.mjs +9 -4
- package/src/ChainState.res +55 -3
- package/src/ChainState.res.mjs +46 -3
- package/src/ChainState.resi +3 -1
- package/src/Config.res +35 -0
- package/src/Config.res.mjs +39 -0
- package/src/Core.res +4 -0
- package/src/Core.res.mjs +4 -0
- package/src/CrossChainState.res +61 -4
- package/src/CrossChainState.res.mjs +39 -5
- package/src/CrossChainState.resi +10 -1
- package/src/Env.res +4 -0
- package/src/FetchState.res +51 -46
- package/src/FetchState.res.mjs +54 -36
- package/src/InMemoryTable.res +155 -67
- package/src/InMemoryTable.res.mjs +151 -60
- package/src/IndexerLoop.res +2 -0
- package/src/IndexerLoop.res.mjs +1 -0
- package/src/IndexerState.res +41 -1
- package/src/IndexerState.res.mjs +43 -4
- package/src/IndexerState.resi +10 -0
- package/src/LoadLayer.res +57 -34
- package/src/LoadLayer.res.mjs +45 -62
- package/src/LoadLayer.resi +1 -1
- package/src/Logging.res +38 -6
- package/src/Logging.res.mjs +32 -5
- package/src/Main.res +131 -268
- package/src/Main.res.mjs +28 -152
- package/src/Metrics.res +263 -102
- package/src/Metrics.res.mjs +227 -48
- package/src/Persistence.res +27 -2
- package/src/Persistence.res.mjs +9 -2
- package/src/PgStorage.res +109 -71
- package/src/PgStorage.res.mjs +88 -58
- package/src/Server.res +181 -0
- package/src/Server.res.mjs +143 -0
- package/src/Supervisor.res +415 -0
- package/src/Supervisor.res.mjs +325 -0
- package/src/TestIndexer.res +9 -25
- package/src/TestIndexer.res.mjs +5 -4
- package/src/UserContext.res +13 -32
- package/src/UserContext.res.mjs +1 -7
- package/src/Utils.res +1 -4
- package/src/Utils.res.mjs +7 -16
- package/src/Worker.res +95 -0
- package/src/Worker.res.mjs +80 -0
- package/src/bindings/NodeJs.res +41 -0
- package/src/db/EntityFilter.res +487 -275
- package/src/db/EntityFilter.res.mjs +557 -309
- package/src/db/InternalTable.res +8 -1
- package/src/db/InternalTable.res.mjs +5 -1
- package/src/db/Table.res +21 -6
- package/src/db/Table.res.mjs +13 -4
- package/src/sources/BlockStore.res +7 -2
- package/src/sources/EvmHyperSyncSource.res +2 -0
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -2
- package/src/sources/FuelHyperSyncSource.res +1 -0
- package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
- package/src/sources/HyperSync.res +4 -0
- package/src/sources/HyperSync.res.mjs +4 -2
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/HyperSyncSSE.res +1 -1
- package/src/sources/HyperSyncSSE.res.mjs +4 -10
- package/src/sources/RpcSource.res +1 -0
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SimulateSource.res +1 -0
- package/src/sources/SimulateSource.res.mjs +1 -1
- package/src/sources/Source.res +7 -0
- package/src/sources/SourceManager.res +4 -3
- package/src/sources/SourceManager.res.mjs +2 -2
- package/src/sources/SvmHyperSyncClient.res +5 -0
- package/src/sources/SvmHyperSyncSource.res +3 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
- package/src/tui/Tui.res +24 -0
- package/src/tui/Tui.res.mjs +18 -0
- package/src/tui/components/SyncETA.res +12 -6
- package/src/tui/components/SyncETA.res.mjs +12 -8
package/src/IndexerState.res
CHANGED
|
@@ -115,6 +115,10 @@ type t = {
|
|
|
115
115
|
// waitForNewBlock waiter is bound to the old, pre-realtime source). A fetch
|
|
116
116
|
// response or waiter carrying an older epoch than this is discarded.
|
|
117
117
|
mutable epoch: int,
|
|
118
|
+
// The loop's one door in from outside it: IndexerLoop owns scheduling and
|
|
119
|
+
// wires this when it starts, so an event the loop can't see for itself can
|
|
120
|
+
// still make it re-evaluate. A no-op before then.
|
|
121
|
+
mutable scheduleProcessing: unit => unit,
|
|
118
122
|
// None off the simulate path.
|
|
119
123
|
simulateDeadInputTracker: option<SimulateDeadInputTracker.t>,
|
|
120
124
|
// --- Metric counters, rendered by Metrics at scrape time. ---
|
|
@@ -141,6 +145,7 @@ let make = (
|
|
|
141
145
|
~chainStates: dict<ChainState.t>,
|
|
142
146
|
~isRealtime: bool,
|
|
143
147
|
~targetBufferSize=CrossChainState.calculateTargetBufferSize(),
|
|
148
|
+
~holdRealtime=false,
|
|
144
149
|
~committedFrontier=Frontier.empty(),
|
|
145
150
|
~isDevelopmentMode=false,
|
|
146
151
|
~shouldUseTui=false,
|
|
@@ -180,11 +185,17 @@ let make = (
|
|
|
180
185
|
chainMetaDirty: false,
|
|
181
186
|
chainMetaThrottler,
|
|
182
187
|
isProcessing: false,
|
|
183
|
-
crossChainState: CrossChainState.make(
|
|
188
|
+
crossChainState: CrossChainState.make(
|
|
189
|
+
~chainStates,
|
|
190
|
+
~isRealtime,
|
|
191
|
+
~targetBufferSize,
|
|
192
|
+
~holdRealtime,
|
|
193
|
+
),
|
|
184
194
|
indexerStartTime: Date.make(),
|
|
185
195
|
indexerStartTimeRef: Performance.now(),
|
|
186
196
|
rollbackState: NoRollback,
|
|
187
197
|
lastPrunedAtMillis: Dict.make(),
|
|
198
|
+
scheduleProcessing: () => (),
|
|
188
199
|
loadManager: LoadManager.make(),
|
|
189
200
|
keepProcessAlive: isDevelopmentMode || shouldUseTui,
|
|
190
201
|
exitAfterFirstEventBlock,
|
|
@@ -227,6 +238,9 @@ let makeFromDbState = (
|
|
|
227
238
|
~exitAfterFirstEventBlock=false,
|
|
228
239
|
~reducedPollingInterval=?,
|
|
229
240
|
~targetBufferSize=CrossChainState.calculateTargetBufferSize(),
|
|
241
|
+
// A process driving part of a split run waits for its supervisor before
|
|
242
|
+
// entering the reorg threshold or switching to realtime.
|
|
243
|
+
~holdRealtime=false,
|
|
230
244
|
~onError,
|
|
231
245
|
~onExit=?,
|
|
232
246
|
) => {
|
|
@@ -274,6 +288,7 @@ let makeFromDbState = (
|
|
|
274
288
|
~chainStates,
|
|
275
289
|
~isRealtime,
|
|
276
290
|
~targetBufferSize,
|
|
291
|
+
~holdRealtime,
|
|
277
292
|
~committedFrontier=initialState.checkpointFrontier,
|
|
278
293
|
~isDevelopmentMode,
|
|
279
294
|
~shouldUseTui,
|
|
@@ -500,9 +515,33 @@ let isFinalizingIndexes = (state: t) =>
|
|
|
500
515
|
state.crossChainState->CrossChainState.isCaughtUp &&
|
|
501
516
|
!(state.crossChainState->CrossChainState.isRealtime)
|
|
502
517
|
|
|
518
|
+
// The FinalizingIndexes phase is the transition a held process waits on: it
|
|
519
|
+
// ends with `ready_at` committed and the indexer realtime.
|
|
520
|
+
let shouldFinalizeIndexes = (state: t) =>
|
|
521
|
+
state->isFinalizingIndexes && !(state.crossChainState->CrossChainState.isHoldingRealtime)
|
|
522
|
+
|
|
503
523
|
let markCaughtUpIfSettled = (state: t) =>
|
|
504
524
|
state.crossChainState->CrossChainState.markCaughtUpIfSettled
|
|
505
525
|
|
|
526
|
+
let isReadyToEnterReorgThreshold = (state: t, ~batch) =>
|
|
527
|
+
state.crossChainState->CrossChainState.isReadyToEnterReorgThreshold(~batch)
|
|
528
|
+
|
|
529
|
+
let bindScheduleProcessing = (state: t, scheduleProcessing) =>
|
|
530
|
+
state.scheduleProcessing = scheduleProcessing
|
|
531
|
+
|
|
532
|
+
// A process still waiting on its supervisor owes the schema the indexes its
|
|
533
|
+
// chains deferred, so reaching every end block doesn't make it done.
|
|
534
|
+
let isHoldingRealtime = (state: t) => state.crossChainState->CrossChainState.isHoldingRealtime
|
|
535
|
+
|
|
536
|
+
let hasArrivedAtHead = (state: t) => state.crossChainState->CrossChainState.hasArrivedAtHead
|
|
537
|
+
|
|
538
|
+
let releaseRealtime = (state: t) => {
|
|
539
|
+
state.crossChainState->CrossChainState.releaseRealtime
|
|
540
|
+
// Every chain is parked at the head with no batch coming, so nothing would
|
|
541
|
+
// notice the hold is gone without a pass through processing.
|
|
542
|
+
state.scheduleProcessing()
|
|
543
|
+
}
|
|
544
|
+
|
|
506
545
|
let markReady = (state: t, ~readyAt) => state.crossChainState->CrossChainState.markReady(~readyAt)
|
|
507
546
|
|
|
508
547
|
let rollbackState = (state: t) => state.rollbackState
|
|
@@ -572,6 +611,7 @@ let toMetrics = (state: t): Metrics.t => {
|
|
|
572
611
|
elapsedSeconds: state.indexerStartTimeRef->Performance.secondsSince,
|
|
573
612
|
targetBufferSize: state.crossChainState->CrossChainState.targetBufferSize,
|
|
574
613
|
isInReorgThreshold: state.crossChainState->CrossChainState.isInReorgThreshold,
|
|
614
|
+
hasArrivedAtHead: state.crossChainState->CrossChainState.hasArrivedAtHead,
|
|
575
615
|
rollbackEnabled: state.config.shouldRollbackOnReorg,
|
|
576
616
|
maxBatchSize: state.config.batchSize,
|
|
577
617
|
preloadSeconds: state.preloadSeconds,
|
package/src/IndexerState.res.mjs
CHANGED
|
@@ -24,8 +24,9 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
24
24
|
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
25
25
|
import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
|
|
26
26
|
|
|
27
|
-
function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
27
|
+
function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
28
28
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
29
|
+
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
29
30
|
let committedFrontier = committedFrontierOpt !== undefined ? committedFrontierOpt : Frontier.empty();
|
|
30
31
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
31
32
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
@@ -54,7 +55,7 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
54
55
|
chainMetaDirty: false,
|
|
55
56
|
chainMetaThrottler: chainMetaThrottler,
|
|
56
57
|
isProcessing: false,
|
|
57
|
-
crossChainState: CrossChainState.make(chainStates, isRealtime, targetBufferSize),
|
|
58
|
+
crossChainState: CrossChainState.make(chainStates, isRealtime, targetBufferSize, holdRealtime),
|
|
58
59
|
rollbackState: "NoRollback",
|
|
59
60
|
indexerStartTime: new Date(),
|
|
60
61
|
indexerStartTimeRef: Performance.now(),
|
|
@@ -66,6 +67,7 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
66
67
|
onExit: onExit,
|
|
67
68
|
isStopped: false,
|
|
68
69
|
epoch: 0,
|
|
70
|
+
scheduleProcessing: () => {},
|
|
69
71
|
simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
|
|
70
72
|
preloadSeconds: 0,
|
|
71
73
|
processingSeconds: 0,
|
|
@@ -82,11 +84,12 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, onError, onExit) {
|
|
87
|
+
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit) {
|
|
86
88
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
87
89
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
88
90
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
89
91
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
92
|
+
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
90
93
|
let isInReorgThreshold = initialState.cleanRun ? false : initialState.chains.some(resumedChainState => {
|
|
91
94
|
let progressBlockNumber = resumedChainState.progressBlockNumber;
|
|
92
95
|
let sourceBlockNumber = resumedChainState.sourceBlockNumber;
|
|
@@ -104,7 +107,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
|
|
|
104
107
|
let chainConfig = ChainMap.get(config.chainMap, chainId);
|
|
105
108
|
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, reducedPollingInterval);
|
|
106
109
|
});
|
|
107
|
-
let state = make(config, persistence, chainStates, isRealtime, targetBufferSize, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
110
|
+
let state = make(config, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
108
111
|
CrossChainState.markCaughtUpOnResume(state.crossChainState);
|
|
109
112
|
Utils.Dict.forEach(initialState.cache, param => {
|
|
110
113
|
let count = param.count;
|
|
@@ -350,10 +353,39 @@ function isFinalizingIndexes(state) {
|
|
|
350
353
|
}
|
|
351
354
|
}
|
|
352
355
|
|
|
356
|
+
function shouldFinalizeIndexes(state) {
|
|
357
|
+
if (isFinalizingIndexes(state)) {
|
|
358
|
+
return !CrossChainState.isHoldingRealtime(state.crossChainState);
|
|
359
|
+
} else {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
353
364
|
function markCaughtUpIfSettled(state) {
|
|
354
365
|
CrossChainState.markCaughtUpIfSettled(state.crossChainState);
|
|
355
366
|
}
|
|
356
367
|
|
|
368
|
+
function isReadyToEnterReorgThreshold(state, batch) {
|
|
369
|
+
return CrossChainState.isReadyToEnterReorgThreshold(state.crossChainState, batch);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function bindScheduleProcessing(state, scheduleProcessing) {
|
|
373
|
+
state.scheduleProcessing = scheduleProcessing;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isHoldingRealtime(state) {
|
|
377
|
+
return CrossChainState.isHoldingRealtime(state.crossChainState);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function hasArrivedAtHead(state) {
|
|
381
|
+
return CrossChainState.hasArrivedAtHead(state.crossChainState);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function releaseRealtime(state) {
|
|
385
|
+
CrossChainState.releaseRealtime(state.crossChainState);
|
|
386
|
+
state.scheduleProcessing();
|
|
387
|
+
}
|
|
388
|
+
|
|
357
389
|
function markReady(state, readyAt) {
|
|
358
390
|
CrossChainState.markReady(state.crossChainState, readyAt);
|
|
359
391
|
}
|
|
@@ -447,6 +479,7 @@ function toMetrics(state) {
|
|
|
447
479
|
elapsedSeconds: Performance.secondsSince(state.indexerStartTimeRef),
|
|
448
480
|
targetBufferSize: CrossChainState.targetBufferSize(state.crossChainState),
|
|
449
481
|
isInReorgThreshold: CrossChainState.isInReorgThreshold(state.crossChainState),
|
|
482
|
+
hasArrivedAtHead: CrossChainState.hasArrivedAtHead(state.crossChainState),
|
|
450
483
|
rollbackEnabled: state.config.shouldRollbackOnReorg,
|
|
451
484
|
maxBatchSize: state.config.batchSize,
|
|
452
485
|
preloadSeconds: state.preloadSeconds,
|
|
@@ -844,6 +877,12 @@ export {
|
|
|
844
877
|
isRealtime,
|
|
845
878
|
isFinalizingIndexes,
|
|
846
879
|
markCaughtUpIfSettled,
|
|
880
|
+
isReadyToEnterReorgThreshold,
|
|
881
|
+
shouldFinalizeIndexes,
|
|
882
|
+
bindScheduleProcessing,
|
|
883
|
+
isHoldingRealtime,
|
|
884
|
+
hasArrivedAtHead,
|
|
885
|
+
releaseRealtime,
|
|
847
886
|
markReady,
|
|
848
887
|
rollbackState,
|
|
849
888
|
indexerStartTime,
|
package/src/IndexerState.resi
CHANGED
|
@@ -16,6 +16,7 @@ let make: (
|
|
|
16
16
|
~chainStates: dict<ChainState.t>,
|
|
17
17
|
~isRealtime: bool,
|
|
18
18
|
~targetBufferSize: int=?,
|
|
19
|
+
~holdRealtime: bool=?,
|
|
19
20
|
~committedFrontier: Frontier.t=?,
|
|
20
21
|
~isDevelopmentMode: bool=?,
|
|
21
22
|
~shouldUseTui: bool=?,
|
|
@@ -34,6 +35,7 @@ let makeFromDbState: (
|
|
|
34
35
|
~exitAfterFirstEventBlock: bool=?,
|
|
35
36
|
~reducedPollingInterval: int=?,
|
|
36
37
|
~targetBufferSize: int=?,
|
|
38
|
+
~holdRealtime: bool=?,
|
|
37
39
|
~onError: ErrorHandling.t => unit,
|
|
38
40
|
~onExit: unit => unit=?,
|
|
39
41
|
) => t
|
|
@@ -93,6 +95,14 @@ let shouldSaveHistory: t => dict<bool>
|
|
|
93
95
|
let isRealtime: t => bool
|
|
94
96
|
let isFinalizingIndexes: t => bool
|
|
95
97
|
let markCaughtUpIfSettled: t => unit
|
|
98
|
+
let isReadyToEnterReorgThreshold: (t, ~batch: Batch.t) => bool
|
|
99
|
+
let shouldFinalizeIndexes: t => bool
|
|
100
|
+
// Wires the loop's way back in. IndexerLoop calls this as it starts.
|
|
101
|
+
let bindScheduleProcessing: (t, unit => unit) => unit
|
|
102
|
+
let isHoldingRealtime: t => bool
|
|
103
|
+
let hasArrivedAtHead: t => bool
|
|
104
|
+
// The supervisor's go-ahead for a process driving part of a split run.
|
|
105
|
+
let releaseRealtime: t => unit
|
|
96
106
|
let markReady: (t, ~readyAt: Date.t) => unit
|
|
97
107
|
let rollbackState: t => rollbackState
|
|
98
108
|
let indexerStartTime: t => Date.t
|
package/src/LoadLayer.res
CHANGED
|
@@ -6,23 +6,6 @@ let scopeKeySuffix = (scope: Internal.chainScope) =>
|
|
|
6
6
|
| Chain(chainId) => `.${chainId->ChainId.toString}`
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
// Narrows a query to the scope's chain. Cross-chain entities have no chain-id
|
|
10
|
-
// column, so their filter is left untouched.
|
|
11
|
-
let scopeFilter = (filter: EntityFilter.t, ~table: Table.table, ~scope: Internal.chainScope) =>
|
|
12
|
-
switch (scope, table->Table.getChainIdField) {
|
|
13
|
-
| (Chain(chainId), Some(field)) =>
|
|
14
|
-
EntityFilter.And({
|
|
15
|
-
filters: [
|
|
16
|
-
filter,
|
|
17
|
-
Eq({
|
|
18
|
-
fieldName: field.fieldName,
|
|
19
|
-
fieldValue: chainId->(Utils.magic: ChainId.t => unknown),
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
})
|
|
23
|
-
| _ => filter
|
|
24
|
-
}
|
|
25
|
-
|
|
26
9
|
let loadById = (
|
|
27
10
|
~loadManager,
|
|
28
11
|
~persistence: Persistence.t,
|
|
@@ -48,10 +31,10 @@ let loadById = (
|
|
|
48
31
|
(
|
|
49
32
|
await storage.loadOrThrow(
|
|
50
33
|
~table=entityConfig.table,
|
|
51
|
-
~filter=EntityFilter.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
~filter=EntityFilter.byIds(idsToLoad)->EntityFilter.scoped(
|
|
35
|
+
~table=entityConfig.table,
|
|
36
|
+
~scope,
|
|
37
|
+
),
|
|
55
38
|
)
|
|
56
39
|
)->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
57
40
|
} catch {
|
|
@@ -273,15 +256,9 @@ let loadEffect = (
|
|
|
273
256
|
let {outputSchema} = effect.storageMeta
|
|
274
257
|
|
|
275
258
|
let dbEntities = try {
|
|
276
|
-
(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
~filter=EntityFilter.In({
|
|
280
|
-
fieldName: Table.idFieldName,
|
|
281
|
-
fieldValue: idsToLoad->(Utils.magic: array<string> => array<unknown>),
|
|
282
|
-
}),
|
|
283
|
-
)
|
|
284
|
-
)->(Utils.magic: array<unknown> => array<Internal.effectCacheItem>)
|
|
259
|
+
(await storage.loadOrThrow(~table, ~filter=EntityFilter.byIds(idsToLoad)))->(
|
|
260
|
+
Utils.magic: array<unknown> => array<Internal.effectCacheItem>
|
|
261
|
+
)
|
|
285
262
|
} catch {
|
|
286
263
|
| exn =>
|
|
287
264
|
Ecosystem.getItemLogger(item, ~ecosystem)->Logging.childWarn({
|
|
@@ -355,7 +332,7 @@ let loadEffect = (
|
|
|
355
332
|
)
|
|
356
333
|
}
|
|
357
334
|
|
|
358
|
-
let
|
|
335
|
+
let loadByParsedFilter = (
|
|
359
336
|
~loadManager,
|
|
360
337
|
~persistence: Persistence.t,
|
|
361
338
|
~entityConfig: Internal.entityConfig,
|
|
@@ -378,7 +355,9 @@ let loadByFilter = (
|
|
|
378
355
|
|
|
379
356
|
let size = ref(0)
|
|
380
357
|
|
|
381
|
-
filters->Array.forEach(filter =>
|
|
358
|
+
filters->Array.forEach(filter =>
|
|
359
|
+
inMemTable->InMemoryTable.Entity.addEmptyIndex(~filter, ~table=entityConfig.table)
|
|
360
|
+
)
|
|
382
361
|
|
|
383
362
|
// Any non-derived field can be filtered on, so the columns this query reads
|
|
384
363
|
// are indexed on demand before it runs rather than promised by the schema.
|
|
@@ -399,7 +378,7 @@ let loadByFilter = (
|
|
|
399
378
|
(
|
|
400
379
|
await storage.loadOrThrow(
|
|
401
380
|
~table=entityConfig.table,
|
|
402
|
-
~filter=filter->
|
|
381
|
+
~filter=filter->EntityFilter.scoped(~table=entityConfig.table, ~scope),
|
|
403
382
|
)
|
|
404
383
|
)->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
405
384
|
|
|
@@ -412,6 +391,10 @@ let loadByFilter = (
|
|
|
412
391
|
)
|
|
413
392
|
})
|
|
414
393
|
|
|
394
|
+
// Every row this filter's values could match is now in the table, so a
|
|
395
|
+
// later getWhere naming any of them needs no round trip of its own.
|
|
396
|
+
inMemTable->InMemoryTable.Entity.recordLoadedValues(~filter, ~table=entityConfig.table)
|
|
397
|
+
|
|
415
398
|
size := size.contents + entities->Array.length
|
|
416
399
|
} catch {
|
|
417
400
|
| Persistence.StorageError({message, reason}) =>
|
|
@@ -442,13 +425,53 @@ let loadByFilter = (
|
|
|
442
425
|
)
|
|
443
426
|
}
|
|
444
427
|
|
|
428
|
+
// Keying an _in walks every value, so it's computed once here and handed to
|
|
429
|
+
// the load manager rather than recomputed by the hasher.
|
|
430
|
+
let filterKey = filter->EntityFilter.toString(~table=entityConfig.table)
|
|
431
|
+
|
|
432
|
+
if !(inMemTable->InMemoryTable.Entity.hasIndex)(filterKey) {
|
|
433
|
+
inMemTable->InMemoryTable.Entity.tryIndexFromLoadedValues(~filter, ~table=entityConfig.table)
|
|
434
|
+
}
|
|
435
|
+
|
|
445
436
|
loadManager->LoadManager.call(
|
|
446
437
|
~key,
|
|
447
438
|
~load,
|
|
448
439
|
~input=filter,
|
|
449
440
|
~shouldGroup,
|
|
450
|
-
~hasher=
|
|
441
|
+
~hasher=_ => filterKey,
|
|
451
442
|
~getUnsafeInMemory=inMemTable->InMemoryTable.Entity.getUnsafeOnIndex,
|
|
452
443
|
~hasInMemory=inMemTable->InMemoryTable.Entity.hasIndex,
|
|
453
444
|
)
|
|
454
445
|
}
|
|
446
|
+
|
|
447
|
+
let loadByFilter = (
|
|
448
|
+
~loadManager,
|
|
449
|
+
~persistence,
|
|
450
|
+
~entityConfig: Internal.entityConfig,
|
|
451
|
+
~scope,
|
|
452
|
+
~indexerState,
|
|
453
|
+
~shouldGroup,
|
|
454
|
+
~item,
|
|
455
|
+
~ecosystem,
|
|
456
|
+
~filter: dict<dict<unknown>>,
|
|
457
|
+
) =>
|
|
458
|
+
// Rejecting rather than throwing keeps a bad filter failing only its own
|
|
459
|
+
// call, even when the caller batches several with Promise.all.
|
|
460
|
+
try {
|
|
461
|
+
loadByParsedFilter(
|
|
462
|
+
~loadManager,
|
|
463
|
+
~persistence,
|
|
464
|
+
~entityConfig,
|
|
465
|
+
~scope,
|
|
466
|
+
~indexerState,
|
|
467
|
+
~shouldGroup,
|
|
468
|
+
~item,
|
|
469
|
+
~ecosystem,
|
|
470
|
+
~filter=filter->EntityFilter.parseOrThrow(
|
|
471
|
+
~entityName=entityConfig.name,
|
|
472
|
+
~table=entityConfig.table,
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
} catch {
|
|
476
|
+
| exn => Promise.reject(exn->Utils.prettifyExn)
|
|
477
|
+
}
|
package/src/LoadLayer.res.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Table from "./db/Table.res.mjs";
|
|
4
3
|
import * as Utils from "./Utils.res.mjs";
|
|
5
4
|
import * as ChainId from "./ChainId.res.mjs";
|
|
6
5
|
import * as Logging from "./Logging.res.mjs";
|
|
@@ -27,25 +26,6 @@ function scopeKeySuffix(scope) {
|
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
function scopeFilter(filter, table, scope) {
|
|
31
|
-
let match = Table.getChainIdField(table);
|
|
32
|
-
if (scope === "crossChain" || match === undefined) {
|
|
33
|
-
return filter;
|
|
34
|
-
} else {
|
|
35
|
-
return {
|
|
36
|
-
operator: "and",
|
|
37
|
-
filters: [
|
|
38
|
-
filter,
|
|
39
|
-
{
|
|
40
|
-
operator: "=",
|
|
41
|
-
fieldName: match.fieldName,
|
|
42
|
-
fieldValue: scope
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
29
|
function loadById(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, entityId) {
|
|
50
30
|
let key = entityConfig.name + `.get` + scopeKeySuffix(scope);
|
|
51
31
|
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
|
|
@@ -54,11 +34,7 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
|
|
|
54
34
|
let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
|
|
55
35
|
let dbEntities;
|
|
56
36
|
try {
|
|
57
|
-
dbEntities = await storage.loadOrThrow(
|
|
58
|
-
operator: "in",
|
|
59
|
-
fieldName: Table.idFieldName,
|
|
60
|
-
fieldValue: idsToLoad
|
|
61
|
-
}, entityConfig.table, scope), entityConfig.table);
|
|
37
|
+
dbEntities = await storage.loadOrThrow(EntityFilter.scoped(EntityFilter.byIds(idsToLoad), entityConfig.table, scope), entityConfig.table);
|
|
62
38
|
} catch (raw_exn) {
|
|
63
39
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
64
40
|
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
@@ -157,11 +133,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
|
|
|
157
133
|
let outputSchema = match$1.outputSchema;
|
|
158
134
|
let dbEntities;
|
|
159
135
|
try {
|
|
160
|
-
dbEntities = await storage.loadOrThrow(
|
|
161
|
-
operator: "in",
|
|
162
|
-
fieldName: Table.idFieldName,
|
|
163
|
-
fieldValue: idsToLoad
|
|
164
|
-
}, table);
|
|
136
|
+
dbEntities = await storage.loadOrThrow(EntityFilter.byIds(idsToLoad), table);
|
|
165
137
|
} catch (raw_exn) {
|
|
166
138
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
167
139
|
Logging.childWarn(Ecosystem.getItemLogger(item, ecosystem), {
|
|
@@ -211,38 +183,49 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
|
|
|
211
183
|
}
|
|
212
184
|
|
|
213
185
|
function loadByFilter(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, filter) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let
|
|
218
|
-
let
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
186
|
+
try {
|
|
187
|
+
let filter$1 = EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table);
|
|
188
|
+
let key = EntityFilter.toOperationKey(filter$1, entityConfig.name) + scopeKeySuffix(scope);
|
|
189
|
+
let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
|
|
190
|
+
let load = async (filters, param) => {
|
|
191
|
+
let storage = Persistence.getInitializedStorageOrThrow(persistence);
|
|
192
|
+
let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
|
|
193
|
+
let size = {
|
|
194
|
+
contents: 0
|
|
195
|
+
};
|
|
196
|
+
filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter, entityConfig.table));
|
|
197
|
+
await storage.ensureQueryIndexes(entityConfig, scope, filters);
|
|
198
|
+
let queries = EntityFilter.merge(filters);
|
|
199
|
+
await Promise.all(queries.map(async filter => {
|
|
200
|
+
try {
|
|
201
|
+
let entities = await storage.loadOrThrow(EntityFilter.scoped(filter, entityConfig.table, scope), entityConfig.table);
|
|
202
|
+
let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
|
|
203
|
+
entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entity.id, entity));
|
|
204
|
+
InMemoryTable.Entity.recordLoadedValues(inMemTable, filter, entityConfig.table);
|
|
205
|
+
size.contents = size.contents + entities.length | 0;
|
|
206
|
+
return;
|
|
207
|
+
} catch (raw_exn) {
|
|
208
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
209
|
+
if (exn.RE_EXN_ID === Persistence.StorageError) {
|
|
210
|
+
return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Ecosystem.getItemLogger(item, ecosystem), {
|
|
211
|
+
operation: key,
|
|
212
|
+
params: EntityFilter.getParams(filter)
|
|
213
|
+
}), exn.message, exn.reason);
|
|
214
|
+
}
|
|
215
|
+
throw exn;
|
|
239
216
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
217
|
+
}));
|
|
218
|
+
return IndexerState.endStorageLoad(indexerState, timerRef, storage.name, key, Stdlib_Array.reduce(queries, 0, (acc, query) => acc + EntityFilter.valuesCount(query) | 0), size.contents);
|
|
219
|
+
};
|
|
220
|
+
let filterKey = EntityFilter.toString(filter$1, entityConfig.table);
|
|
221
|
+
if (!InMemoryTable.Entity.hasIndex(inMemTable)(filterKey)) {
|
|
222
|
+
InMemoryTable.Entity.tryIndexFromLoadedValues(inMemTable, filter$1, entityConfig.table);
|
|
223
|
+
}
|
|
224
|
+
return LoadManager.call(loadManager, filter$1, key, load, param => filterKey, shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable));
|
|
225
|
+
} catch (raw_exn) {
|
|
226
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
227
|
+
return Promise.reject(Utils.prettifyExn(exn));
|
|
228
|
+
}
|
|
246
229
|
}
|
|
247
230
|
|
|
248
231
|
export {
|
|
@@ -250,4 +233,4 @@ export {
|
|
|
250
233
|
loadByFilter,
|
|
251
234
|
loadEffect,
|
|
252
235
|
}
|
|
253
|
-
/*
|
|
236
|
+
/* Utils Not a pure module */
|
package/src/LoadLayer.resi
CHANGED
package/src/Logging.res
CHANGED
|
@@ -26,6 +26,30 @@ let logLevels = [
|
|
|
26
26
|
|
|
27
27
|
%%private(let logger = ref(None))
|
|
28
28
|
|
|
29
|
+
// Fields every line this process logs carries. Merged into each line rather
|
|
30
|
+
// than bound to a child logger: pino writes a child's bindings and the line's
|
|
31
|
+
// own fields side by side, so a line that names the same key would carry it
|
|
32
|
+
// twice. A fresh object per line, since pino merges the line's fields into
|
|
33
|
+
// whatever this returns.
|
|
34
|
+
%%private(let context: ref<dict<JSON.t>> = ref(Dict.make()))
|
|
35
|
+
%%private(let mixin = () => JSON.Object(context.contents->Dict.copy))
|
|
36
|
+
|
|
37
|
+
// A child logger that binds a field the process already carries would have pino
|
|
38
|
+
// write it twice: a child's bindings and the context are concatenated into the
|
|
39
|
+
// line, not merged. The context is the one place a line names it, which it can
|
|
40
|
+
// be because a process only takes one when its every line is about that chain.
|
|
41
|
+
%%private(
|
|
42
|
+
let withoutContext = (params: 'a) =>
|
|
43
|
+
switch context.contents->Dict.keysToArray {
|
|
44
|
+
| [] => params
|
|
45
|
+
| keys => {
|
|
46
|
+
let narrowed = params->(Utils.magic: 'a => dict<JSON.t>)->Dict.copy
|
|
47
|
+
keys->Array.forEach(key => narrowed->Dict.delete(key))
|
|
48
|
+
narrowed->(Utils.magic: dict<JSON.t> => 'a)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
29
53
|
let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLevel) => {
|
|
30
54
|
// Currently unused - useful if using multiple transports.
|
|
31
55
|
// let pinoRaw = {"target": "pino/file", "level": Config.userLogLevel}
|
|
@@ -56,17 +80,19 @@ let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLeve
|
|
|
56
80
|
...Pino.ECS.make(),
|
|
57
81
|
customLevels: logLevels,
|
|
58
82
|
base,
|
|
83
|
+
mixin,
|
|
59
84
|
},
|
|
60
85
|
Transport.make(pinoFile),
|
|
61
86
|
)
|
|
62
87
|
| EcsConsoleMultistream =>
|
|
63
|
-
makeMultiStreamLogger(~logFile=None, ~options=Some({...Pino.ECS.make(), base}))
|
|
88
|
+
makeMultiStreamLogger(~logFile=None, ~options=Some({...Pino.ECS.make(), base, mixin}))
|
|
64
89
|
| EcsConsole =>
|
|
65
90
|
make({
|
|
66
91
|
...Pino.ECS.make(),
|
|
67
92
|
level: userLogLevel,
|
|
68
93
|
customLevels: logLevels,
|
|
69
94
|
base,
|
|
95
|
+
mixin,
|
|
70
96
|
})
|
|
71
97
|
| FileOnly =>
|
|
72
98
|
makeWithOptionsAndTransport(
|
|
@@ -74,12 +100,13 @@ let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLeve
|
|
|
74
100
|
customLevels: logLevels,
|
|
75
101
|
level: defaultFileLogLevel,
|
|
76
102
|
base,
|
|
103
|
+
mixin,
|
|
77
104
|
},
|
|
78
105
|
Transport.make(pinoFile),
|
|
79
106
|
)
|
|
80
|
-
| ConsoleRaw => makeMultiStreamLogger(~logFile=None, ~options=Some({base
|
|
81
|
-
| ConsolePretty => makeMultiStreamLogger(~logFile=None, ~options=Some({base
|
|
82
|
-
| Both => makeMultiStreamLogger(~logFile=Some(logFilePath), ~options=Some({base
|
|
107
|
+
| ConsoleRaw => makeMultiStreamLogger(~logFile=None, ~options=Some({base, mixin}))
|
|
108
|
+
| ConsolePretty => makeMultiStreamLogger(~logFile=None, ~options=Some({base, mixin}))
|
|
109
|
+
| Both => makeMultiStreamLogger(~logFile=Some(logFilePath), ~options=Some({base, mixin}))
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
112
|
|
|
@@ -149,10 +176,15 @@ let childFatal = (logger, params: 'a) => {
|
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
let createChild = (~params: 'a) => {
|
|
152
|
-
getLogger()->child(params->createChildParams)
|
|
179
|
+
getLogger()->child(params->withoutContext->createChildParams)
|
|
153
180
|
}
|
|
181
|
+
|
|
182
|
+
// What belongs on every line is the run's to decide; the logger only carries
|
|
183
|
+
// what it is handed. A line that names one of these fields itself wins.
|
|
184
|
+
let setContext = (fields: dict<JSON.t>) => context := fields
|
|
185
|
+
|
|
154
186
|
let createChildFrom = (~logger: t, ~params: 'a) => {
|
|
155
|
-
logger->child(params->createChildParams)
|
|
187
|
+
logger->child(params->withoutContext->createChildParams)
|
|
156
188
|
}
|
|
157
189
|
|
|
158
190
|
@inline
|