envio 3.2.1 → 3.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/package.json +6 -7
  2. package/src/Batch.res +12 -15
  3. package/src/Batch.res.mjs +4 -5
  4. package/src/BatchProcessing.res +199 -0
  5. package/src/BatchProcessing.res.mjs +125 -0
  6. package/src/ChainFetching.res +373 -0
  7. package/src/ChainFetching.res.mjs +206 -0
  8. package/src/ChainMetadata.res +27 -0
  9. package/src/ChainMetadata.res.mjs +27 -0
  10. package/src/ChainState.res +643 -0
  11. package/src/ChainState.res.mjs +476 -0
  12. package/src/ChainState.resi +87 -0
  13. package/src/Config.res +13 -4
  14. package/src/Config.res.mjs +8 -3
  15. package/src/ContractRegisterContext.res +106 -0
  16. package/src/ContractRegisterContext.res.mjs +76 -0
  17. package/src/Core.res +3 -0
  18. package/src/CrossChainState.res +294 -0
  19. package/src/CrossChainState.res.mjs +221 -0
  20. package/src/CrossChainState.resi +39 -0
  21. package/src/Ecosystem.res +58 -0
  22. package/src/Ecosystem.res.mjs +43 -1
  23. package/src/Env.res +0 -2
  24. package/src/Env.res.mjs +0 -3
  25. package/src/EventConfigBuilder.res +2 -2
  26. package/src/EventProcessing.res +60 -46
  27. package/src/EventProcessing.res.mjs +33 -32
  28. package/src/EventUtils.res +0 -4
  29. package/src/EventUtils.res.mjs +0 -4
  30. package/src/ExitOnCaughtUp.res +10 -0
  31. package/src/ExitOnCaughtUp.res.mjs +22 -0
  32. package/src/FetchState.res +205 -73
  33. package/src/FetchState.res.mjs +242 -103
  34. package/src/InMemoryStore.res +36 -451
  35. package/src/InMemoryStore.res.mjs +20 -369
  36. package/src/IndexerLoop.res +43 -0
  37. package/src/IndexerLoop.res.mjs +46 -0
  38. package/src/IndexerState.res +536 -0
  39. package/src/IndexerState.res.mjs +543 -0
  40. package/src/IndexerState.resi +128 -0
  41. package/src/Internal.res +29 -8
  42. package/src/LoadLayer.res +20 -18
  43. package/src/LoadLayer.res.mjs +14 -12
  44. package/src/LoadLayer.resi +6 -3
  45. package/src/Logging.res +11 -44
  46. package/src/Logging.res.mjs +10 -42
  47. package/src/Main.res +54 -79
  48. package/src/Main.res.mjs +44 -56
  49. package/src/PgStorage.res +8 -71
  50. package/src/PgStorage.res.mjs +3 -47
  51. package/src/Prometheus.res +1 -13
  52. package/src/Prometheus.res.mjs +84 -96
  53. package/src/PruneStaleHistory.res +45 -0
  54. package/src/PruneStaleHistory.res.mjs +46 -0
  55. package/src/RawEvent.res +73 -0
  56. package/src/RawEvent.res.mjs +51 -0
  57. package/src/Rollback.res +204 -0
  58. package/src/Rollback.res.mjs +118 -0
  59. package/src/SimulateItems.res +12 -10
  60. package/src/SimulateItems.res.mjs +1 -1
  61. package/src/UserContext.res +26 -114
  62. package/src/UserContext.res.mjs +15 -78
  63. package/src/Writing.res +242 -0
  64. package/src/Writing.res.mjs +215 -0
  65. package/src/db/InternalTable.res +14 -27
  66. package/src/sources/Evm.res +40 -1
  67. package/src/sources/Evm.res.mjs +94 -84
  68. package/src/sources/Fuel.res +40 -1
  69. package/src/sources/Fuel.res.mjs +36 -26
  70. package/src/sources/HyperFuel.res +41 -120
  71. package/src/sources/HyperFuel.res.mjs +42 -80
  72. package/src/sources/HyperFuel.resi +1 -24
  73. package/src/sources/HyperFuelClient.res +16 -297
  74. package/src/sources/HyperFuelClient.res.mjs +5 -4
  75. package/src/sources/HyperFuelSource.res +33 -8
  76. package/src/sources/HyperFuelSource.res.mjs +56 -10
  77. package/src/sources/HyperSyncSource.res +5 -3
  78. package/src/sources/HyperSyncSource.res.mjs +1 -1
  79. package/src/sources/RpcSource.res +2 -2
  80. package/src/sources/RpcSource.res.mjs +1 -1
  81. package/src/sources/SourceManager.res +14 -23
  82. package/src/sources/SourceManager.res.mjs +27 -29
  83. package/src/sources/SourceManager.resi +4 -2
  84. package/src/sources/Svm.res +18 -1
  85. package/src/sources/Svm.res.mjs +30 -22
  86. package/src/sources/SvmHyperSyncSource.res +1 -1
  87. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  88. package/src/tui/Tui.res +38 -36
  89. package/src/tui/Tui.res.mjs +51 -51
  90. package/src/ChainFetcher.res +0 -519
  91. package/src/ChainFetcher.res.mjs +0 -314
  92. package/src/ChainManager.res +0 -358
  93. package/src/ChainManager.res.mjs +0 -291
  94. package/src/Ctx.res +0 -6
  95. package/src/Ctx.res.mjs +0 -2
  96. package/src/GlobalState.res +0 -1056
  97. package/src/GlobalState.res.mjs +0 -1086
  98. package/src/GlobalStateManager.res +0 -57
  99. package/src/GlobalStateManager.res.mjs +0 -68
  100. package/src/GlobalStateManager.resi +0 -7
  101. package/src/Ports.res +0 -5
  102. package/src/Ports.res.mjs +0 -9
  103. package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
  104. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
  105. package/src/sources/EnvioApiClient.res +0 -15
  106. package/src/sources/EnvioApiClient.res.mjs +0 -24
@@ -1,136 +1,32 @@
1
- module EntityTables = {
2
- type t = dict<InMemoryTable.Entity.t>
3
- exception UndefinedEntity({entityName: string})
4
- let make = (entities: array<Internal.entityConfig>): t => {
5
- let init = Dict.make()
6
- entities->Array.forEach(entityConfig => {
7
- init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
8
- })
9
- init
10
- }
11
-
12
- let get = (self: t, ~entityName: string) => {
13
- switch self->Utils.Dict.dangerouslyGetNonOption(entityName) {
14
- | Some(table) => table
15
- | None =>
16
- UndefinedEntity({entityName: entityName})->ErrorHandling.mkLogAndRaise(
17
- ~msg="Unexpected, entity InMemoryTable is undefined",
18
- )
19
- }
20
- }
21
- }
22
-
23
- type effectCacheInMemTable = {
24
- // Cache keys whose handler output is persisted on the next write. Drained
25
- // each write; eviction is driven by the per-entry checkpointId instead.
26
- mutable idsToStore: array<string>,
27
- mutable invalidationsCount: int,
28
- // Each entry is stamped with the checkpoint that referenced it (or
29
- // loadedFromDbCheckpointId for db reads), so committed entries can be
30
- // dropped once persisted/re-derivable, mirroring entity changes.
31
- mutable dict: dict<Change.t<Internal.effectOutput>>,
32
- mutable changesCount: float,
33
- effect: Internal.effect,
34
- }
35
-
36
- type t = {
37
- allEntities: array<Internal.entityConfig>,
38
- mutable entities: dict<InMemoryTable.Entity.t>,
39
- mutable effects: dict<effectCacheInMemTable>,
40
- mutable rollback: option<Persistence.rollback>,
41
- // Last checkpoint persisted to the db.
42
- mutable committedCheckpointId: Internal.checkpointId,
43
- // Processing frontier; runs ahead of committedCheckpointId while writes lag.
44
- mutable processedCheckpointId: Internal.checkpointId,
45
- // Processed but unwritten. The cycle drains them, splitting each write at a
46
- // change in isInReorgThreshold so it never mixes history-saving modes.
47
- mutable processedBatches: array<Batch.t>,
48
- // True while a batch is being processed; guards ProcessEventBatch re-entry.
49
- mutable isProcessing: bool,
50
- // Count of processed batches; version-independent progress counter.
51
- mutable processedBatchesCount: int,
52
- // The single in-flight write loop, None when idle.
53
- mutable writeFiber: option<promise<unit>>,
54
- // Set once a write throws, to stop the loop. The error itself goes to onError.
55
- mutable hasFailedWrite: bool,
56
- // Called once on a write failure; the caller decides what to do (exit).
57
- onError: exn => unit,
58
- // Resolved after every commit so capacity/flush waiters can re-evaluate.
59
- mutable commitWaiters: array<unit => unit>,
60
- persistence: Persistence.t,
61
- config: Config.t,
62
- // Latest metadata staged per chain; used to skip unchanged restages.
63
- mutable chainMeta: dict<InternalTable.Chains.metaFields>,
64
- // Set on a real change. Folded into a batch write, else flushed on the throttle.
65
- mutable chainMetaDirty: bool,
66
- // Throttles metadata-only writes when no batches flow.
67
- chainMetaThrottler: Throttler.t,
68
- }
69
-
70
- let make = (
71
- ~entities: array<Internal.entityConfig>,
72
- ~committedCheckpointId=Internal.initialCheckpointId,
73
- ~persistence: Persistence.t,
74
- ~config: Config.t,
75
- ~onError: exn => unit,
76
- ): t => {
77
- let chainMetaThrottler = {
78
- let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis
79
- Throttler.make(
80
- ~intervalMillis,
81
- ~logger=Logging.createChild(
82
- ~params={
83
- "context": "Throttler for chain metadata writes",
84
- "intervalMillis": intervalMillis,
85
- },
86
- ),
87
- )
88
- }
1
+ // Entity and effect table primitives over IndexerState's in-memory store. State
2
+ // mutations route through IndexerState's domain operations; the write loop and
3
+ // capacity/flush coordination live in Writing.
89
4
 
90
- {
91
- allEntities: entities,
92
- entities: EntityTables.make(entities),
93
- effects: Dict.make(),
94
- rollback: None,
95
- committedCheckpointId,
96
- processedCheckpointId: committedCheckpointId,
97
- processedBatches: [],
98
- isProcessing: false,
99
- processedBatchesCount: 0,
100
- writeFiber: None,
101
- hasFailedWrite: false,
102
- onError,
103
- commitWaiters: [],
104
- persistence,
105
- config,
106
- chainMeta: Dict.make(),
107
- chainMetaDirty: false,
108
- chainMetaThrottler,
109
- }
110
- }
111
-
112
- // Max uncommitted entity/effect changes plus unwritten batch items before
113
- // processing must wait for the cycle to free capacity.
114
- let keepLatestChangesLimit = Env.inMemoryObjectsTarget
5
+ let getInMemTable = (
6
+ state: IndexerState.t,
7
+ ~entityConfig: Internal.entityConfig,
8
+ ): InMemoryTable.Entity.t =>
9
+ state->IndexerState.entities->IndexerState.EntityTables.get(~entityName=entityConfig.name)
115
10
 
116
- let getEffectInMemTable = (inMemoryStore: t, ~effect: Internal.effect) => {
11
+ let getEffectInMemTable = (state: IndexerState.t, ~effect: Internal.effect) => {
117
12
  let key = effect.name
118
- switch inMemoryStore.effects->Utils.Dict.dangerouslyGetNonOption(key) {
13
+ let effects = state->IndexerState.effects
14
+ switch effects->Utils.Dict.dangerouslyGetNonOption(key) {
119
15
  | Some(table) => table
120
16
  | None =>
121
- let table = {
17
+ let table: IndexerState.effectCacheInMemTable = {
122
18
  idsToStore: [],
123
19
  dict: Dict.make(),
124
20
  changesCount: 0.,
125
21
  invalidationsCount: 0,
126
22
  effect,
127
23
  }
128
- inMemoryStore.effects->Dict.set(key, table)
24
+ effects->Dict.set(key, table)
129
25
  table
130
26
  }
131
27
  }
132
28
 
133
- let hasEffectOutput = (inMemTable: effectCacheInMemTable, key) =>
29
+ let hasEffectOutput = (inMemTable: IndexerState.effectCacheInMemTable, key) =>
134
30
  switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
135
31
  | Some(Set(_)) => true
136
32
  | Some(Delete(_)) | None => false
@@ -139,7 +35,10 @@ let hasEffectOutput = (inMemTable: effectCacheInMemTable, key) =>
139
35
  // Returns the raw output. The output is itself an option for effects with an
140
36
  // optional output, so it must never be wrapped in another option here: Some(None)
141
37
  // is encoded as the nested-option sentinel and would leak to the handler.
142
- let getEffectOutputUnsafe = (inMemTable: effectCacheInMemTable, key): Internal.effectOutput =>
38
+ let getEffectOutputUnsafe = (
39
+ inMemTable: IndexerState.effectCacheInMemTable,
40
+ key,
41
+ ): Internal.effectOutput =>
143
42
  switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
144
43
  | Some(Set({entity: output})) => output
145
44
  | Some(Delete(_)) | None => %raw(`undefined`)
@@ -148,7 +47,7 @@ let getEffectOutputUnsafe = (inMemTable: effectCacheInMemTable, key): Internal.e
148
47
  // Records a handler output. Persisted on the next write only when shouldCache;
149
48
  // otherwise kept in memory (re-run on a later miss) but never written to the db.
150
49
  let setEffectOutput = (
151
- inMemTable: effectCacheInMemTable,
50
+ inMemTable: IndexerState.effectCacheInMemTable,
152
51
  ~checkpointId,
153
52
  ~cacheKey,
154
53
  ~output,
@@ -166,7 +65,7 @@ let setEffectOutput = (
166
65
 
167
66
  // Seeds an entry from a db read. Stamped with loadedFromDbCheckpointId so it's
168
67
  // always droppable (re-readable from the db) and never re-persisted.
169
- let initEffectOutputFromDb = (inMemTable: effectCacheInMemTable, ~cacheKey, ~output) =>
68
+ let initEffectOutputFromDb = (inMemTable: IndexerState.effectCacheInMemTable, ~cacheKey, ~output) =>
170
69
  if inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(cacheKey)->Option.isNone {
171
70
  inMemTable.changesCount = inMemTable.changesCount +. 1.
172
71
  inMemTable.dict->Dict.set(
@@ -179,7 +78,7 @@ let initEffectOutputFromDb = (inMemTable: effectCacheInMemTable, ~cacheKey, ~out
179
78
  // cache:false). Uncommitted entries stay warm. With keepLoadedFromDb, entries
180
79
  // seeded from a db read are spared. Mirrors entity dropCommittedChanges.
181
80
  let dropCommittedEffects = (
182
- inMemTable: effectCacheInMemTable,
81
+ inMemTable: IndexerState.effectCacheInMemTable,
183
82
  ~committedCheckpointId,
184
83
  ~keepLoadedFromDb,
185
84
  ) => {
@@ -197,340 +96,26 @@ let dropCommittedEffects = (
197
96
  inMemTable.changesCount = inMemTable.changesCount -. keysToDelete->Array.length->Int.toFloat
198
97
  }
199
98
 
200
- let getInMemTable = (
201
- inMemoryStore: t,
202
- ~entityConfig: Internal.entityConfig,
203
- ): InMemoryTable.Entity.t => {
204
- inMemoryStore.entities->EntityTables.get(~entityName=entityConfig.name)
205
- }
206
-
207
- let isRollingBack = (inMemoryStore: t) => inMemoryStore.rollback !== None
208
-
209
- let getChangesCount = (inMemoryStore: t) => {
210
- let total = ref(0.)
211
- inMemoryStore.allEntities->Array.forEach(entityConfig => {
212
- total := total.contents +. (inMemoryStore->getInMemTable(~entityConfig)).changesCount
213
- })
214
- inMemoryStore.effects->Utils.Dict.forEach(inMemTable => {
215
- total := total.contents +. inMemTable.changesCount
216
- })
217
- inMemoryStore.processedBatches->Array.forEach(batch => {
218
- total := total.contents +. batch.totalBatchSize->Int.toFloat
219
- })
220
- total.contents
221
- }
222
-
223
- let wakeCommitWaiters = (inMemoryStore: t) => {
224
- let waiters = inMemoryStore.commitWaiters
225
- inMemoryStore.commitWaiters = []
226
- waiters->Array.forEach(resolve => resolve())
227
- }
228
-
229
- let waitForCommit = (inMemoryStore: t): promise<unit> =>
230
- Promise.make((resolve, _) => {
231
- inMemoryStore.commitWaiters->Array.push(resolve)->ignore
232
- })
233
-
234
- // Merges the leading run of batches sharing isInReorgThreshold into one batch;
235
- // the rest stay queued for the next write. Caller guarantees processedBatches
236
- // is non-empty.
237
- let drainBatchRun = (inMemoryStore: t): Batch.t => {
238
- let all = inMemoryStore.processedBatches
239
- let isInReorgThreshold = (all->Array.getUnsafe(0)).isInReorgThreshold
240
-
241
- let rest = []
242
- let progressedChainsById = Dict.make()
243
- let totalBatchSize = ref(0)
244
- let items = []
245
- let checkpointIds = []
246
- let checkpointChainIds = []
247
- let checkpointBlockNumbers = []
248
- let checkpointBlockHashes = []
249
- let checkpointEventsProcessed = []
250
- all->Array.forEach(batch => {
251
- // Once one batch lands in rest, all later ones follow it, preserving order.
252
- if rest->Utils.Array.isEmpty && batch.isInReorgThreshold == isInReorgThreshold {
253
- batch.progressedChainsById->Utils.Dict.forEachWithKey((chainAfterBatch, key) =>
254
- progressedChainsById->Dict.set(key, chainAfterBatch)
255
- )
256
- totalBatchSize := totalBatchSize.contents + batch.totalBatchSize
257
- items->Array.pushMany(batch.items)
258
- checkpointIds->Array.pushMany(batch.checkpointIds)
259
- checkpointChainIds->Array.pushMany(batch.checkpointChainIds)
260
- checkpointBlockNumbers->Array.pushMany(batch.checkpointBlockNumbers)
261
- checkpointBlockHashes->Array.pushMany(batch.checkpointBlockHashes)
262
- checkpointEventsProcessed->Array.pushMany(batch.checkpointEventsProcessed)
263
- } else {
264
- rest->Array.push(batch)
265
- }
266
- })
267
- inMemoryStore.processedBatches = rest
268
-
269
- {
270
- totalBatchSize: totalBatchSize.contents,
271
- items,
272
- progressedChainsById,
273
- isInReorgThreshold,
274
- checkpointIds,
275
- checkpointChainIds,
276
- checkpointBlockNumbers,
277
- checkpointBlockHashes,
278
- checkpointEventsProcessed,
279
- }
280
- }
281
-
282
- // Captures the cache:true outputs to persist. The dict is left intact — entries
283
- // stay warm and are reclaimed later by dropCommittedEffects once committed.
284
- let snapshotEffects = (inMemoryStore: t, ~cache): array<Persistence.updatedEffectCache> => {
285
- let acc = []
286
- inMemoryStore.effects->Utils.Dict.forEach(inMemTable => {
287
- let {idsToStore, dict, effect, invalidationsCount} = inMemTable
288
- switch idsToStore {
289
- | [] => ()
290
- | ids =>
291
- let items = ids->Array.filterMap((id): option<Internal.effectCacheItem> =>
292
- switch dict->Dict.getUnsafe(id) {
293
- | Set({entity: output}) => Some({id, output})
294
- | Delete(_) => None
295
- }
296
- )
297
- let effectName = effect.name
298
- let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(effectName) {
299
- | Some(c) => c
300
- | None =>
301
- let c: Persistence.effectCacheRecord = {effectName, count: 0}
302
- cache->Dict.set(effectName, c)
303
- c
304
- }
305
- let shouldInitialize = effectCacheRecord.count === 0
306
- effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
307
- Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
308
- acc->Array.push(({effect, items, shouldInitialize}: Persistence.updatedEffectCache))->ignore
309
- }
310
- inMemTable.idsToStore = []
311
- inMemTable.invalidationsCount = 0
312
- })
313
- acc
314
- }
315
-
316
- let runOneWrite = async (inMemoryStore: t, ~persistence: Persistence.t, ~config) => {
317
- let cache = switch persistence.storageStatus {
318
- | Unknown
319
- | Initializing(_) =>
320
- JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`)
321
- | Ready({cache}) => cache
322
- }
323
-
324
- // Copy before the await: the batch write reads this later, in-transaction. A
325
- // restage during the write re-dirties the flag and is rewritten next iteration.
326
- let chainMetaData = if inMemoryStore.chainMetaDirty {
327
- inMemoryStore.chainMetaDirty = false
328
- Some(inMemoryStore.chainMeta->Utils.Dict.shallowCopy)
329
- } else {
330
- None
331
- }
332
-
333
- switch inMemoryStore.processedBatches {
334
- | [] =>
335
- // Metadata only: a cheap upsert, still serialized by the single write loop.
336
- switch chainMetaData {
337
- | Some(chainsData) =>
338
- await persistence.storage.setChainMeta(chainsData)->Utils.Promise.ignoreValue
339
- | None => ()
340
- }
341
- | _ =>
342
- let committedCheckpointId = inMemoryStore.committedCheckpointId
343
- let batch = inMemoryStore->drainBatchRun
344
- // The run's last checkpoint; entity changes above it stay queued for the next write.
345
- let upToCheckpointId = switch batch.checkpointIds->Utils.Array.last {
346
- | Some(checkpointId) => checkpointId
347
- | None => committedCheckpointId
348
- }
349
-
350
- let rollback = inMemoryStore.rollback
351
- inMemoryStore.rollback = None
352
-
353
- let updatedEntities = persistence.allEntities->Array.filterMap(entityConfig => {
354
- let table = inMemoryStore->getInMemTable(~entityConfig)
355
- let changes =
356
- table->InMemoryTable.Entity.snapshotChanges(~committedCheckpointId, ~upToCheckpointId)
357
- if changes->Utils.Array.isEmpty {
358
- None
359
- } else {
360
- Some(({entityConfig, changes}: Persistence.updatedEntity))
361
- }
362
- })
363
- let updatedEffectsCache = snapshotEffects(inMemoryStore, ~cache)
364
-
365
- await persistence.storage.writeBatch(
366
- ~batch,
367
- ~rollback,
368
- ~isInReorgThreshold=batch.isInReorgThreshold,
369
- ~config,
370
- ~allEntities=persistence.allEntities,
371
- ~updatedEntities,
372
- ~updatedEffectsCache,
373
- ~chainMetaData,
374
- )
375
-
376
- inMemoryStore.committedCheckpointId = upToCheckpointId
377
-
378
- switch rollback {
379
- | Some({progressBlockNumberByChainId}) if RollbackCommit.callbacks->Utils.Array.notEmpty =>
380
- await RollbackCommit.fire(~progressBlockNumberByChainId)
381
- | _ => ()
382
- }
383
- }
384
- }
385
-
386
- let hasPendingWrite = (inMemoryStore: t) =>
387
- inMemoryStore.processedBatches->Utils.Array.notEmpty || inMemoryStore.chainMetaDirty
388
-
389
- let runWriteLoop = async (inMemoryStore: t) => {
390
- while inMemoryStore->hasPendingWrite && !inMemoryStore.hasFailedWrite {
391
- try {
392
- await runOneWrite(
393
- inMemoryStore,
394
- ~persistence=inMemoryStore.persistence,
395
- ~config=inMemoryStore.config,
396
- )
397
- inMemoryStore->wakeCommitWaiters
398
- } catch {
399
- | exn =>
400
- inMemoryStore.hasFailedWrite = true
401
- inMemoryStore.onError(exn)
402
- }
403
- }
404
- inMemoryStore.writeFiber = None
405
- inMemoryStore->wakeCommitWaiters
406
- }
407
-
408
- let kick = (inMemoryStore: t) =>
409
- if (
410
- inMemoryStore.writeFiber->Option.isNone &&
411
- !inMemoryStore.hasFailedWrite &&
412
- inMemoryStore->hasPendingWrite
413
- ) {
414
- inMemoryStore.writeFiber = Some(runWriteLoop(inMemoryStore))
415
- }
416
-
417
- let metaFieldsEqual = (a: InternalTable.Chains.metaFields, b: InternalTable.Chains.metaFields) =>
418
- a.firstEventBlockNumber == b.firstEventBlockNumber &&
419
- a.latestFetchedBlockNumber == b.latestFetchedBlockNumber &&
420
- a.isHyperSync == b.isHyperSync &&
421
- // Date is boxed; compare epoch ms.
422
- a.timestampCaughtUpToHeadOrEndblock->Null.toOption->Option.map(Date.getTime) ==
423
- b.timestampCaughtUpToHeadOrEndblock->Null.toOption->Option.map(Date.getTime)
424
-
425
- // Stages chain metadata, dirtying only on a real change so restages are no-ops.
426
- let setChainMeta = (inMemoryStore: t, chainsData: dict<InternalTable.Chains.metaFields>) => {
427
- chainsData->Utils.Dict.forEachWithKey((meta, chainId) => {
428
- let changed = switch inMemoryStore.chainMeta->Utils.Dict.dangerouslyGetNonOption(chainId) {
429
- | Some(prev) => !metaFieldsEqual(meta, prev)
430
- | None => true
431
- }
432
- if changed {
433
- inMemoryStore.chainMeta->Dict.set(chainId, meta)
434
- inMemoryStore.chainMetaDirty = true
435
- }
436
- })
437
- if inMemoryStore.chainMetaDirty {
438
- inMemoryStore.chainMetaThrottler->Throttler.schedule(() => {
439
- inMemoryStore->kick
440
- Promise.resolve()
441
- })
442
- }
443
- }
444
-
445
- // Queues a processed batch and kicks the cycle. Returns immediately; the write
446
- // happens off the processing path.
447
- let commitBatch = (inMemoryStore: t, ~batch: Batch.t) => {
448
- inMemoryStore.processedBatches->Array.push(batch)->ignore
449
- switch batch.checkpointIds->Utils.Array.last {
450
- | Some(checkpointId) => inMemoryStore.processedCheckpointId = checkpointId
451
- | None => ()
452
- }
453
- inMemoryStore->kick
454
- }
455
-
456
- // Drops committed entity and effect entries across all tables. With
457
- // keepLoadedFromDb, entries seeded from a db read are spared.
458
- let dropCommitted = (inMemoryStore: t, ~keepLoadedFromDb) => {
459
- let committedCheckpointId = inMemoryStore.committedCheckpointId
460
- inMemoryStore.allEntities->Array.forEach(entityConfig =>
461
- inMemoryStore
462
- ->getInMemTable(~entityConfig)
463
- ->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
464
- )
465
- inMemoryStore.effects->Utils.Dict.forEach(inMemTable =>
466
- inMemTable->dropCommittedEffects(~committedCheckpointId, ~keepLoadedFromDb)
467
- )
468
- }
469
-
470
- // Blocks until the store holds fewer than keepLatestChangesLimit changes,
471
- // freeing committed changes first and awaiting commits as a last resort.
472
- let rec awaitCapacity = async (inMemoryStore: t) => {
473
- // After a failed write nothing will free capacity, so bail instead of waiting
474
- // on a commit that won't come (the error already went to onError).
475
- if !inMemoryStore.hasFailedWrite && inMemoryStore->getChangesCount >= keepLatestChangesLimit {
476
- // Drop committed writes first, sparing db-loaded entries (explicitly
477
- // requested, so likelier to be read again).
478
- inMemoryStore->dropCommitted(~keepLoadedFromDb=true)
479
-
480
- // Still over: drop the db-loaded entries too.
481
- if inMemoryStore->getChangesCount >= keepLatestChangesLimit {
482
- inMemoryStore->dropCommitted(~keepLoadedFromDb=false)
483
- }
484
-
485
- // Still over: what's left is uncommitted. Only wait if a queued batch can
486
- // free it; otherwise (e.g. a large rollback diff with no batch) waiting
487
- // would deadlock, so let processing proceed.
488
- if (
489
- inMemoryStore->getChangesCount >= keepLatestChangesLimit &&
490
- inMemoryStore.processedBatches->Utils.Array.notEmpty
491
- ) {
492
- inMemoryStore->kick
493
- await inMemoryStore->waitForCommit
494
- await inMemoryStore->awaitCapacity
495
- }
496
- }
497
- }
498
-
499
- // Awaits until everything processed is persisted. On a failed write we stop
500
- // draining (onError already surfaced it) rather than throw.
501
- let rec flush = async (inMemoryStore: t) => {
502
- if !inMemoryStore.hasFailedWrite {
503
- inMemoryStore->kick
504
- switch inMemoryStore.writeFiber {
505
- | Some(fiber) =>
506
- await fiber
507
- await inMemoryStore->flush
508
- | None => ()
509
- }
510
- }
511
- }
512
-
513
99
  let prepareRollbackDiff = async (
514
- inMemoryStore: t,
515
- ~persistence: Persistence.t,
100
+ state: IndexerState.t,
516
101
  ~rollbackTargetCheckpointId,
517
102
  ~rollbackDiffCheckpointId,
518
103
  ~progressBlockNumberByChainId,
519
104
  ) => {
520
- inMemoryStore.entities = EntityTables.make(inMemoryStore.allEntities)
521
- inMemoryStore.effects = Dict.make()
522
- inMemoryStore.rollback = Some({
523
- targetCheckpointId: rollbackTargetCheckpointId,
524
- diffCheckpointId: rollbackDiffCheckpointId,
525
- progressBlockNumberByChainId,
526
- })
105
+ state->IndexerState.beginRollbackDiff(
106
+ ~targetCheckpointId=rollbackTargetCheckpointId,
107
+ ~diffCheckpointId=rollbackDiffCheckpointId,
108
+ ~progressBlockNumberByChainId,
109
+ )
110
+ let persistence = state->IndexerState.persistence
111
+ let committedCheckpointId = state->IndexerState.committedCheckpointId
527
112
 
528
113
  let deletedEntities = Dict.make()
529
114
  let setEntities = Dict.make()
530
115
 
531
116
  let _ = await persistence.allEntities
532
117
  ->Array.map(async entityConfig => {
533
- let entityTable = inMemoryStore->getInMemTable(~entityConfig)
118
+ let entityTable = state->getInMemTable(~entityConfig)
534
119
 
535
120
  let (removedIdsResult, restoredEntitiesResult) = await persistence.storage.getRollbackData(
536
121
  ~entityConfig,
@@ -540,7 +125,7 @@ let prepareRollbackDiff = async (
540
125
  removedIdsResult->Array.forEach(data => {
541
126
  deletedEntities->Utils.Dict.push(entityConfig.name, data["id"])
542
127
  entityTable->InMemoryTable.Entity.set(
543
- ~committedCheckpointId=inMemoryStore.committedCheckpointId,
128
+ ~committedCheckpointId,
544
129
  Delete({
545
130
  entityId: data["id"],
546
131
  checkpointId: rollbackDiffCheckpointId,
@@ -556,7 +141,7 @@ let prepareRollbackDiff = async (
556
141
  restoredEntities->Array.forEach((entity: Internal.entity) => {
557
142
  setEntities->Utils.Dict.push(entityConfig.name, entity.id)
558
143
  entityTable->InMemoryTable.Entity.set(
559
- ~committedCheckpointId=inMemoryStore.committedCheckpointId,
144
+ ~committedCheckpointId,
560
145
  Set({
561
146
  entityId: entity.id,
562
147
  checkpointId: rollbackDiffCheckpointId,
@@ -573,9 +158,9 @@ let prepareRollbackDiff = async (
573
158
  }
574
159
  }
575
160
 
576
- let setBatchDcs = (inMemoryStore: t, ~batch: Batch.t) => {
577
- let inMemTable =
578
- inMemoryStore->getInMemTable(~entityConfig=InternalTable.EnvioAddresses.entityConfig)
161
+ let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
162
+ let inMemTable = state->getInMemTable(~entityConfig=InternalTable.EnvioAddresses.entityConfig)
163
+ let committedCheckpointId = state->IndexerState.committedCheckpointId
579
164
 
580
165
  let itemIdx = ref(0)
581
166
 
@@ -602,7 +187,7 @@ let setBatchDcs = (inMemoryStore: t, ~batch: Batch.t) => {
602
187
  }
603
188
 
604
189
  inMemTable->InMemoryTable.Entity.set(
605
- ~committedCheckpointId=inMemoryStore.committedCheckpointId,
190
+ ~committedCheckpointId,
606
191
  Set({
607
192
  entityId: entity.id,
608
193
  checkpointId,