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,12 +1,12 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Address from "./Address.res.mjs";
5
4
  import * as Logging from "./Logging.res.mjs";
6
- import * as ChainMap from "./ChainMap.res.mjs";
7
5
  import * as Internal from "./Internal.res.mjs";
6
+ import * as Ecosystem from "./Ecosystem.res.mjs";
8
7
  import * as LoadLayer from "./LoadLayer.res.mjs";
9
8
  import * as EntityFilter from "./db/EntityFilter.res.mjs";
9
+ import * as IndexerState from "./IndexerState.res.mjs";
10
10
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
11
11
  import * as InMemoryStore from "./InMemoryStore.res.mjs";
12
12
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
@@ -20,7 +20,8 @@ let effectContextPrototype = (Object.create(null));
20
20
 
21
21
  Object.defineProperty(effectContextPrototype, "log", {
22
22
  get: function () {
23
- return Logging.getUserLogger(paramsByThis.get(this).item);
23
+ let params = paramsByThis.get(this);
24
+ return Ecosystem.getItemUserLogger(params.item, params.config.ecosystem);
24
25
  }
25
26
  });
26
27
 
@@ -43,7 +44,7 @@ function initEffect(params) {
43
44
  cacheKey: effectArgs_cacheKey,
44
45
  checkpointId: effectArgs_checkpointId
45
46
  };
46
- return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, params.inMemoryStore, params.isPreload, params.item);
47
+ return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
47
48
  };
48
49
  return callEffect;
49
50
  }
@@ -52,9 +53,9 @@ function getWhereHandler(params, filter) {
52
53
  let entityConfig = params.entityConfig;
53
54
  let filters = EntityFilter.parseGetWhereOrThrow(filter, entityConfig.name, entityConfig.table);
54
55
  if (filters.length !== 1) {
55
- return Promise.all(filters.map(filter => LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, params.inMemoryStore, params.isPreload, params.item, filter))).then(results => results.flat());
56
+ return Promise.all(filters.map(filter => LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, params.indexerState, params.isPreload, params.item, params.config.ecosystem, filter))).then(results => results.flat());
56
57
  } else {
57
- return LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, params.inMemoryStore, params.isPreload, params.item, filters[0]);
58
+ return LoadLayer.loadByFilter(params.loadManager, params.persistence, entityConfig, params.indexerState, params.isPreload, params.item, params.config.ecosystem, filters[0]);
58
59
  }
59
60
  }
60
61
 
@@ -72,7 +73,7 @@ function throwClickHouseReadOnly(entityConfig, op) {
72
73
 
73
74
  let entityTraps_get = (params, prop) => {
74
75
  let isClickHouseOnly = !params.entityConfig.storage.postgres;
75
- let set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.inMemoryStore, params.entityConfig), params.inMemoryStore.committedCheckpointId, {
76
+ let set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig), IndexerState.committedCheckpointId(params.indexerState), {
76
77
  type: "SET",
77
78
  entityId: entity.id,
78
79
  entity: entity,
@@ -83,7 +84,7 @@ let entityTraps_get = (params, prop) => {
83
84
  if (params.isPreload) {
84
85
  return noopDeleteUnsafe;
85
86
  } else {
86
- return entityId => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.inMemoryStore, params.entityConfig), params.inMemoryStore.committedCheckpointId, {
87
+ return entityId => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig), IndexerState.committedCheckpointId(params.indexerState), {
87
88
  type: "DELETE",
88
89
  entityId: entityId,
89
90
  checkpointId: params.checkpointId
@@ -93,13 +94,13 @@ let entityTraps_get = (params, prop) => {
93
94
  if (isClickHouseOnly) {
94
95
  return _entityId => throwClickHouseReadOnly(params.entityConfig, "get");
95
96
  } else {
96
- return entityId => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.inMemoryStore, params.isPreload, params.item, entityId);
97
+ return entityId => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId);
97
98
  }
98
99
  case "getOrCreate" :
99
100
  if (isClickHouseOnly) {
100
101
  return _entity => throwClickHouseReadOnly(params.entityConfig, "getOrCreate");
101
102
  } else {
102
- return entity => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.inMemoryStore, params.isPreload, params.item, entity.id).then(storageEntity => {
103
+ return entity => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.indexerState, params.isPreload, params.item, params.config.ecosystem, entity.id).then(storageEntity => {
103
104
  if (storageEntity !== undefined) {
104
105
  return storageEntity;
105
106
  } else {
@@ -112,7 +113,7 @@ let entityTraps_get = (params, prop) => {
112
113
  if (isClickHouseOnly) {
113
114
  return (_entityId, param) => throwClickHouseReadOnly(params.entityConfig, "getOrThrow");
114
115
  } else {
115
- return (entityId, message) => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.inMemoryStore, params.isPreload, params.item, entityId).then(entity => {
116
+ return (entityId, message) => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId).then(entity => {
116
117
  if (entity !== undefined) {
117
118
  return entity;
118
119
  } else {
@@ -139,7 +140,7 @@ let entityTraps = {
139
140
 
140
141
  let handlerTraps_get = (params, prop) => {
141
142
  if (params.isResolved) {
142
- ErrorHandling.mkLogAndRaise(Logging.getItemLogger(params.item), undefined, new Error(`Impossible to access context.` + prop + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
143
+ ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access context.` + prop + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
143
144
  }
144
145
  switch (prop) {
145
146
  case "chain" :
@@ -153,7 +154,7 @@ let handlerTraps_get = (params, prop) => {
153
154
  if (params.isPreload) {
154
155
  return Logging.noopLogger;
155
156
  } else {
156
- return Logging.getUserLogger(params.item);
157
+ return Ecosystem.getItemUserLogger(params.item, params.config.ecosystem);
157
158
  }
158
159
  default:
159
160
  let entityConfig = params.config.userEntitiesByName[prop];
@@ -161,7 +162,7 @@ let handlerTraps_get = (params, prop) => {
161
162
  return new Proxy({
162
163
  item: params.item,
163
164
  checkpointId: params.checkpointId,
164
- inMemoryStore: params.inMemoryStore,
165
+ indexerState: params.indexerState,
165
166
  loadManager: params.loadManager,
166
167
  persistence: params.persistence,
167
168
  isPreload: params.isPreload,
@@ -184,65 +185,6 @@ function getHandlerContext(params) {
184
185
  return new Proxy(params, handlerTraps);
185
186
  }
186
187
 
187
- function makeAddFunction(params, contractName) {
188
- return contractAddress => {
189
- if (params.isResolved) {
190
- ErrorHandling.mkLogAndRaise(Logging.getItemLogger(params.item), undefined, new Error(`Impossible to access context.chain after the contract register is resolved. Make sure you didn't miss an await in the handler.`));
191
- }
192
- let validatedAddress = params.config.ecosystem.name === "evm" ? (
193
- params.config.lowercaseAddresses ? Address.Evm.fromAddressLowercaseOrThrow(contractAddress) : Address.Evm.fromAddressOrThrow(contractAddress)
194
- ) : contractAddress;
195
- params.onRegister(params.item, validatedAddress, contractName);
196
- };
197
- }
198
-
199
- let contractRegisterChainTraps_get = (params, prop) => {
200
- if (prop === "id") {
201
- return params.item.chain;
202
- }
203
- let isValidContract = ChainMap.values(params.config.chainMap).some(chain => chain.contracts.some(c => c.name === prop));
204
- if (isValidContract) {
205
- return {
206
- add: makeAddFunction(params, prop)
207
- };
208
- } else {
209
- return Stdlib_JsError.throwWithMessage(`Invalid contract name '` + prop + `' on context.chain. ` + EntityFilter.codegenHelpMessage);
210
- }
211
- };
212
-
213
- let contractRegisterChainTraps = {
214
- get: contractRegisterChainTraps_get
215
- };
216
-
217
- let contractRegisterTraps_get = (params, prop) => {
218
- if (params.isResolved) {
219
- ErrorHandling.mkLogAndRaise(Logging.getItemLogger(params.item), undefined, new Error(`Impossible to access context.` + prop + ` after the contract register is resolved. Make sure you didn't miss an await in the handler.`));
220
- }
221
- switch (prop) {
222
- case "chain" :
223
- return new Proxy(params, contractRegisterChainTraps);
224
- case "log" :
225
- return Logging.getUserLogger(params.item);
226
- default:
227
- return Stdlib_JsError.throwWithMessage(`Invalid context access by '` + prop + `' property. Use context.chain.ContractName.add(address) to register contracts. ` + EntityFilter.codegenHelpMessage);
228
- }
229
- };
230
-
231
- let contractRegisterTraps = {
232
- get: contractRegisterTraps_get
233
- };
234
-
235
- function getContractRegisterContext(params) {
236
- return new Proxy(params, contractRegisterTraps);
237
- }
238
-
239
- function getContractRegisterArgs(params) {
240
- return {
241
- event: params.item.event,
242
- context: new Proxy(params, contractRegisterTraps)
243
- };
244
- }
245
-
246
188
  export {
247
189
  paramsByThis,
248
190
  effectContextPrototype,
@@ -254,10 +196,5 @@ export {
254
196
  entityTraps,
255
197
  handlerTraps,
256
198
  getHandlerContext,
257
- makeAddFunction,
258
- contractRegisterChainTraps,
259
- contractRegisterTraps,
260
- getContractRegisterContext,
261
- getContractRegisterArgs,
262
199
  }
263
200
  /* paramsByThis Not a pure module */
@@ -0,0 +1,242 @@
1
+ // The write loop: drains the processed-batch queue and staged chain metadata to
2
+ // storage off the processing path. A peer to ChainFetching/BatchProcessing/
3
+ // Rollback, but data-driven — `schedule` kicks a single write fiber whenever
4
+ // there's pending work, and processing waits on it through awaitCapacity/flush.
5
+ // State mutations go through IndexerState's domain operations.
6
+
7
+ // Max uncommitted entity/effect changes plus unwritten batch items before
8
+ // processing must wait for the cycle to free capacity.
9
+ let keepLatestChangesLimit = Env.inMemoryObjectsTarget
10
+
11
+ let getChangesCount = (state: IndexerState.t) => {
12
+ let total = ref(0.)
13
+ state
14
+ ->IndexerState.allEntities
15
+ ->Array.forEach(entityConfig => {
16
+ total := total.contents +. (state->InMemoryStore.getInMemTable(~entityConfig)).changesCount
17
+ })
18
+ state
19
+ ->IndexerState.effects
20
+ ->Utils.Dict.forEach(inMemTable => {
21
+ total := total.contents +. inMemTable.changesCount
22
+ })
23
+ state
24
+ ->IndexerState.processedBatches
25
+ ->Array.forEach(batch => {
26
+ total := total.contents +. batch.totalBatchSize->Int.toFloat
27
+ })
28
+ total.contents
29
+ }
30
+
31
+ let waitForCommit = (state: IndexerState.t): promise<unit> =>
32
+ Promise.make((resolve, _) => {
33
+ state->IndexerState.addCommitWaiter(resolve)
34
+ })
35
+
36
+ // Captures the cache:true outputs to persist. The dict is left intact — entries
37
+ // stay warm and are reclaimed later by dropCommittedEffects once committed.
38
+ let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updatedEffectCache> => {
39
+ let acc = []
40
+ state
41
+ ->IndexerState.effects
42
+ ->Utils.Dict.forEach(inMemTable => {
43
+ let {idsToStore, dict, effect, invalidationsCount} = inMemTable
44
+ switch idsToStore {
45
+ | [] => ()
46
+ | ids =>
47
+ let items = ids->Array.filterMap((id): option<Internal.effectCacheItem> =>
48
+ switch dict->Dict.getUnsafe(id) {
49
+ | Set({entity: output}) => Some({id, output})
50
+ | Delete(_) => None
51
+ }
52
+ )
53
+ let effectName = effect.name
54
+ let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(effectName) {
55
+ | Some(c) => c
56
+ | None =>
57
+ let c: Persistence.effectCacheRecord = {effectName, count: 0}
58
+ cache->Dict.set(effectName, c)
59
+ c
60
+ }
61
+ let shouldInitialize = effectCacheRecord.count === 0
62
+ effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
63
+ Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
64
+ acc->Array.push(({effect, items, shouldInitialize}: Persistence.updatedEffectCache))->ignore
65
+ }
66
+ inMemTable.idsToStore = []
67
+ inMemTable.invalidationsCount = 0
68
+ })
69
+ acc
70
+ }
71
+
72
+ let runOneWrite = async (state: IndexerState.t) => {
73
+ let persistence = state->IndexerState.persistence
74
+ let config = state->IndexerState.config
75
+ let cache = switch persistence.storageStatus {
76
+ | Unknown
77
+ | Initializing(_) =>
78
+ JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`)
79
+ | Ready({cache}) => cache
80
+ }
81
+
82
+ // Snapshot before the await: the batch write reads this later, in-transaction.
83
+ let chainMetaData = state->IndexerState.takeChainMetaSnapshot
84
+
85
+ switch state->IndexerState.processedBatches {
86
+ | [] =>
87
+ // Metadata only: a cheap upsert, still serialized by the single write loop.
88
+ switch chainMetaData {
89
+ | Some(chainsData) =>
90
+ await persistence.storage.setChainMeta(chainsData)->Utils.Promise.ignoreValue
91
+ | None => ()
92
+ }
93
+ | _ =>
94
+ let committedCheckpointId = state->IndexerState.committedCheckpointId
95
+ let batch = state->IndexerState.drainBatchRun
96
+ // The run's last checkpoint; entity changes above it stay queued for the next write.
97
+ let upToCheckpointId = switch batch.checkpointIds->Utils.Array.last {
98
+ | Some(checkpointId) => checkpointId
99
+ | None => committedCheckpointId
100
+ }
101
+
102
+ let rollback = state->IndexerState.takeRollback
103
+
104
+ let updatedEntities = persistence.allEntities->Array.filterMap(entityConfig => {
105
+ let table = state->InMemoryStore.getInMemTable(~entityConfig)
106
+ let changes =
107
+ table->InMemoryTable.Entity.snapshotChanges(~committedCheckpointId, ~upToCheckpointId)
108
+ if changes->Utils.Array.isEmpty {
109
+ None
110
+ } else {
111
+ Some(({entityConfig, changes}: Persistence.updatedEntity))
112
+ }
113
+ })
114
+ let updatedEffectsCache = snapshotEffects(state, ~cache)
115
+
116
+ await persistence.storage.writeBatch(
117
+ ~batch,
118
+ ~rollback,
119
+ ~isInReorgThreshold=batch.isInReorgThreshold,
120
+ ~config,
121
+ ~allEntities=persistence.allEntities,
122
+ ~updatedEntities,
123
+ ~updatedEffectsCache,
124
+ ~chainMetaData,
125
+ )
126
+
127
+ state->IndexerState.markCommitted(~upToCheckpointId)
128
+
129
+ switch rollback {
130
+ | Some({progressBlockNumberByChainId}) if RollbackCommit.callbacks->Utils.Array.notEmpty =>
131
+ await RollbackCommit.fire(~progressBlockNumberByChainId)
132
+ | _ => ()
133
+ }
134
+ }
135
+ }
136
+
137
+ let hasPendingWrite = (state: IndexerState.t) =>
138
+ state->IndexerState.processedBatches->Utils.Array.notEmpty || state->IndexerState.chainMetaDirty
139
+
140
+ let runWriteLoop = async (state: IndexerState.t) => {
141
+ while state->hasPendingWrite && !(state->IndexerState.hasFailedWrite) {
142
+ try {
143
+ await runOneWrite(state)
144
+ state->IndexerState.wakeCommitWaiters
145
+ } catch {
146
+ | exn => state->IndexerState.recordWriteFailure(exn)
147
+ }
148
+ }
149
+ state->IndexerState.endWriteFiber
150
+ state->IndexerState.wakeCommitWaiters
151
+ }
152
+
153
+ // Kicks the single write fiber if there's pending work and one isn't running.
154
+ let schedule = (state: IndexerState.t) =>
155
+ if (
156
+ state->IndexerState.writeFiber->Option.isNone &&
157
+ !(state->IndexerState.hasFailedWrite) &&
158
+ state->hasPendingWrite
159
+ ) {
160
+ state->IndexerState.beginWriteFiber(runWriteLoop(state))
161
+ }
162
+
163
+ // Stages chain metadata and throttles a metadata-only write when no batches flow.
164
+ let setChainMeta = (state: IndexerState.t, chainsData: dict<InternalTable.Chains.metaFields>) => {
165
+ state->IndexerState.stageChainMeta(chainsData)
166
+ if state->IndexerState.chainMetaDirty {
167
+ state
168
+ ->IndexerState.chainMetaThrottler
169
+ ->Throttler.schedule(() => {
170
+ state->schedule
171
+ Promise.resolve()
172
+ })
173
+ }
174
+ }
175
+
176
+ // Queues a processed batch and kicks the cycle. Returns immediately; the write
177
+ // happens off the processing path.
178
+ let commitBatch = (state: IndexerState.t, ~batch: Batch.t) => {
179
+ state->IndexerState.queueProcessedBatch(~batch)
180
+ state->schedule
181
+ }
182
+
183
+ // Drops committed entity and effect entries across all tables. With
184
+ // keepLoadedFromDb, entries seeded from a db read are spared.
185
+ let dropCommitted = (state: IndexerState.t, ~keepLoadedFromDb) => {
186
+ let committedCheckpointId = state->IndexerState.committedCheckpointId
187
+ state
188
+ ->IndexerState.allEntities
189
+ ->Array.forEach(entityConfig =>
190
+ state
191
+ ->InMemoryStore.getInMemTable(~entityConfig)
192
+ ->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
193
+ )
194
+ state
195
+ ->IndexerState.effects
196
+ ->Utils.Dict.forEach(inMemTable =>
197
+ inMemTable->InMemoryStore.dropCommittedEffects(~committedCheckpointId, ~keepLoadedFromDb)
198
+ )
199
+ }
200
+
201
+ // Blocks until the store holds fewer than keepLatestChangesLimit changes,
202
+ // freeing committed changes first and awaiting commits as a last resort.
203
+ let rec awaitCapacity = async (state: IndexerState.t) => {
204
+ // After a failed write nothing will free capacity, so bail instead of waiting
205
+ // on a commit that won't come (the error already went to onError).
206
+ if !(state->IndexerState.hasFailedWrite) && state->getChangesCount >= keepLatestChangesLimit {
207
+ // Drop committed writes first, sparing db-loaded entries (explicitly
208
+ // requested, so likelier to be read again).
209
+ state->dropCommitted(~keepLoadedFromDb=true)
210
+
211
+ // Still over: drop the db-loaded entries too.
212
+ if state->getChangesCount >= keepLatestChangesLimit {
213
+ state->dropCommitted(~keepLoadedFromDb=false)
214
+ }
215
+
216
+ // Still over: what's left is uncommitted. Only wait if a queued batch can
217
+ // free it; otherwise (e.g. a large rollback diff with no batch) waiting
218
+ // would deadlock, so let processing proceed.
219
+ if (
220
+ state->getChangesCount >= keepLatestChangesLimit &&
221
+ state->IndexerState.processedBatches->Utils.Array.notEmpty
222
+ ) {
223
+ state->schedule
224
+ await state->waitForCommit
225
+ await state->awaitCapacity
226
+ }
227
+ }
228
+ }
229
+
230
+ // Awaits until everything processed is persisted. On a failed write we stop
231
+ // draining (onError already surfaced it) rather than throw.
232
+ let rec flush = async (state: IndexerState.t) => {
233
+ if !(state->IndexerState.hasFailedWrite) {
234
+ state->schedule
235
+ switch state->IndexerState.writeFiber {
236
+ | Some(fiber) =>
237
+ await fiber
238
+ await state->flush
239
+ | None => ()
240
+ }
241
+ }
242
+ }
@@ -0,0 +1,215 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Env from "./Env.res.mjs";
4
+ import * as Utils from "./Utils.res.mjs";
5
+ import * as Throttler from "./Throttler.res.mjs";
6
+ import * as Prometheus from "./Prometheus.res.mjs";
7
+ import * as IndexerState from "./IndexerState.res.mjs";
8
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
9
+ import * as InMemoryStore from "./InMemoryStore.res.mjs";
10
+ import * as InMemoryTable from "./InMemoryTable.res.mjs";
11
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
12
+ import * as RollbackCommit from "./RollbackCommit.res.mjs";
13
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
14
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
15
+
16
+ function getChangesCount(state) {
17
+ let total = {
18
+ contents: 0
19
+ };
20
+ IndexerState.allEntities(state).forEach(entityConfig => {
21
+ total.contents = total.contents + InMemoryStore.getInMemTable(state, entityConfig).changesCount;
22
+ });
23
+ Utils.Dict.forEach(IndexerState.effects(state), inMemTable => {
24
+ total.contents = total.contents + inMemTable.changesCount;
25
+ });
26
+ IndexerState.processedBatches(state).forEach(batch => {
27
+ total.contents = total.contents + batch.totalBatchSize;
28
+ });
29
+ return total.contents;
30
+ }
31
+
32
+ function waitForCommit(state) {
33
+ return new Promise((resolve, param) => IndexerState.addCommitWaiter(state, resolve));
34
+ }
35
+
36
+ function snapshotEffects(state, cache) {
37
+ let acc = [];
38
+ Utils.Dict.forEach(IndexerState.effects(state), inMemTable => {
39
+ let idsToStore = inMemTable.idsToStore;
40
+ let invalidationsCount = inMemTable.invalidationsCount;
41
+ let dict = inMemTable.dict;
42
+ let effect = inMemTable.effect;
43
+ if (idsToStore.length !== 0) {
44
+ let items = Stdlib_Array.filterMap(idsToStore, id => {
45
+ let match = dict[id];
46
+ if (match.type === "SET") {
47
+ return {
48
+ id: id,
49
+ output: match.entity
50
+ };
51
+ }
52
+ });
53
+ let effectName = effect.name;
54
+ let c = cache[effectName];
55
+ let effectCacheRecord;
56
+ if (c !== undefined) {
57
+ effectCacheRecord = c;
58
+ } else {
59
+ let c$1 = {
60
+ effectName: effectName,
61
+ count: 0
62
+ };
63
+ cache[effectName] = c$1;
64
+ effectCacheRecord = c$1;
65
+ }
66
+ let shouldInitialize = effectCacheRecord.count === 0;
67
+ effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
68
+ Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
69
+ acc.push({
70
+ effect: effect,
71
+ items: items,
72
+ shouldInitialize: shouldInitialize
73
+ });
74
+ }
75
+ inMemTable.idsToStore = [];
76
+ inMemTable.invalidationsCount = 0;
77
+ });
78
+ return acc;
79
+ }
80
+
81
+ async function runOneWrite(state) {
82
+ let persistence = IndexerState.persistence(state);
83
+ let config = IndexerState.config(state);
84
+ let match = persistence.storageStatus;
85
+ let cache;
86
+ cache = typeof match !== "object" || match.TAG === "Initializing" ? Stdlib_JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`) : match._0.cache;
87
+ let chainMetaData = IndexerState.takeChainMetaSnapshot(state);
88
+ let match$1 = IndexerState.processedBatches(state);
89
+ if (match$1.length === 0) {
90
+ if (chainMetaData !== undefined) {
91
+ return await persistence.storage.setChainMeta(chainMetaData);
92
+ } else {
93
+ return;
94
+ }
95
+ }
96
+ let committedCheckpointId = IndexerState.committedCheckpointId(state);
97
+ let batch = IndexerState.drainBatchRun(state);
98
+ let checkpointId = Utils.$$Array.last(batch.checkpointIds);
99
+ let upToCheckpointId = checkpointId !== undefined ? checkpointId : committedCheckpointId;
100
+ let rollback = IndexerState.takeRollback(state);
101
+ let updatedEntities = Stdlib_Array.filterMap(persistence.allEntities, entityConfig => {
102
+ let table = InMemoryStore.getInMemTable(state, entityConfig);
103
+ let changes = InMemoryTable.Entity.snapshotChanges(table, committedCheckpointId, upToCheckpointId);
104
+ if (Utils.$$Array.isEmpty(changes)) {
105
+ return;
106
+ } else {
107
+ return {
108
+ entityConfig: entityConfig,
109
+ changes: changes
110
+ };
111
+ }
112
+ });
113
+ let updatedEffectsCache = snapshotEffects(state, cache);
114
+ await persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData);
115
+ IndexerState.markCommitted(state, upToCheckpointId);
116
+ if (rollback !== undefined && Utils.$$Array.notEmpty(RollbackCommit.callbacks)) {
117
+ return await RollbackCommit.fire(rollback.progressBlockNumberByChainId);
118
+ }
119
+ }
120
+
121
+ function hasPendingWrite(state) {
122
+ if (Utils.$$Array.notEmpty(IndexerState.processedBatches(state))) {
123
+ return true;
124
+ } else {
125
+ return IndexerState.chainMetaDirty(state);
126
+ }
127
+ }
128
+
129
+ async function runWriteLoop(state) {
130
+ while (hasPendingWrite(state) && !IndexerState.hasFailedWrite(state)) {
131
+ try {
132
+ await runOneWrite(state);
133
+ IndexerState.wakeCommitWaiters(state);
134
+ } catch (raw_exn) {
135
+ let exn = Primitive_exceptions.internalToException(raw_exn);
136
+ IndexerState.recordWriteFailure(state, exn);
137
+ }
138
+ };
139
+ IndexerState.endWriteFiber(state);
140
+ return IndexerState.wakeCommitWaiters(state);
141
+ }
142
+
143
+ function schedule(state) {
144
+ if (Stdlib_Option.isNone(IndexerState.writeFiber(state)) && !IndexerState.hasFailedWrite(state) && hasPendingWrite(state)) {
145
+ return IndexerState.beginWriteFiber(state, runWriteLoop(state));
146
+ }
147
+ }
148
+
149
+ function setChainMeta(state, chainsData) {
150
+ IndexerState.stageChainMeta(state, chainsData);
151
+ if (IndexerState.chainMetaDirty(state)) {
152
+ return Throttler.schedule(IndexerState.chainMetaThrottler(state), () => {
153
+ schedule(state);
154
+ return Promise.resolve();
155
+ });
156
+ }
157
+ }
158
+
159
+ function commitBatch(state, batch) {
160
+ IndexerState.queueProcessedBatch(state, batch);
161
+ schedule(state);
162
+ }
163
+
164
+ function dropCommitted(state, keepLoadedFromDb) {
165
+ let committedCheckpointId = IndexerState.committedCheckpointId(state);
166
+ IndexerState.allEntities(state).forEach(entityConfig => InMemoryTable.Entity.dropCommittedChanges(InMemoryStore.getInMemTable(state, entityConfig), committedCheckpointId, keepLoadedFromDb));
167
+ Utils.Dict.forEach(IndexerState.effects(state), inMemTable => InMemoryStore.dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb));
168
+ }
169
+
170
+ async function awaitCapacity(state) {
171
+ if (!IndexerState.hasFailedWrite(state) && getChangesCount(state) >= Env.inMemoryObjectsTarget) {
172
+ dropCommitted(state, true);
173
+ if (getChangesCount(state) >= Env.inMemoryObjectsTarget) {
174
+ dropCommitted(state, false);
175
+ }
176
+ if (getChangesCount(state) >= Env.inMemoryObjectsTarget && Utils.$$Array.notEmpty(IndexerState.processedBatches(state))) {
177
+ schedule(state);
178
+ await new Promise((resolve, param) => IndexerState.addCommitWaiter(state, resolve));
179
+ return await awaitCapacity(state);
180
+ } else {
181
+ return;
182
+ }
183
+ }
184
+ }
185
+
186
+ async function flush(state) {
187
+ if (IndexerState.hasFailedWrite(state)) {
188
+ return;
189
+ }
190
+ schedule(state);
191
+ let fiber = IndexerState.writeFiber(state);
192
+ if (fiber !== undefined) {
193
+ await fiber;
194
+ return await flush(state);
195
+ }
196
+ }
197
+
198
+ let keepLatestChangesLimit = Env.inMemoryObjectsTarget;
199
+
200
+ export {
201
+ keepLatestChangesLimit,
202
+ getChangesCount,
203
+ waitForCommit,
204
+ snapshotEffects,
205
+ runOneWrite,
206
+ hasPendingWrite,
207
+ runWriteLoop,
208
+ schedule,
209
+ setChainMeta,
210
+ commitBatch,
211
+ dropCommitted,
212
+ awaitCapacity,
213
+ flush,
214
+ }
215
+ /* Env Not a pure module */