envio 3.1.2 → 3.2.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 (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +87 -0
  19. package/src/EventConfigBuilder.res.mjs +53 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +63 -67
  22. package/src/FetchState.res.mjs +44 -42
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +18 -2
  29. package/src/HandlerLoader.res.mjs +16 -34
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +70 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +90 -21
  46. package/src/LogSelection.res.mjs +72 -21
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +35 -68
  68. package/src/TestIndexer.res.mjs +17 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +12 -11
  114. package/src/sources/HyperSyncSource.res.mjs +6 -6
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
@@ -1,14 +1,14 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Table from "./db/Table.res.mjs";
3
4
  import * as Utils from "./Utils.res.mjs";
4
5
  import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
6
  import * as Logging from "./Logging.res.mjs";
6
- import * as Internal from "./Internal.res.mjs";
7
- import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
8
7
  import * as Prometheus from "./Prometheus.res.mjs";
9
8
  import * as LoadManager from "./LoadManager.res.mjs";
10
9
  import * as Persistence from "./Persistence.res.mjs";
11
- import * as TableIndices from "./TableIndices.res.mjs";
10
+ import * as EntityFilter from "./db/EntityFilter.res.mjs";
11
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
12
12
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
13
13
  import * as InMemoryStore from "./InMemoryStore.res.mjs";
14
14
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
@@ -23,7 +23,11 @@ function loadById(loadManager, persistence, entityConfig, inMemoryStore, shouldG
23
23
  let timerRef = Prometheus.StorageLoad.startOperation(storage.name, key);
24
24
  let dbEntities;
25
25
  try {
26
- dbEntities = await storage.loadByIdsOrThrow(idsToLoad, entityConfig.table, entityConfig.rowsSchema);
26
+ dbEntities = await storage.loadOrThrow({
27
+ operator: "in",
28
+ fieldName: Table.idFieldName,
29
+ fieldValue: idsToLoad
30
+ }, entityConfig.table);
27
31
  } catch (raw_exn) {
28
32
  let exn = Primitive_exceptions.internalToException(raw_exn);
29
33
  if (exn.RE_EXN_ID === Persistence.StorageError) {
@@ -79,8 +83,8 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
79
83
  state.nextWindowPromise = undefined;
80
84
  }
81
85
  let immediateCount = Math.min(state.availableCalls, effectArgs.length);
82
- let immediateArgs = Belt_Array.slice(effectArgs, 0, immediateCount);
83
- let queuedArgs = Belt_Array.sliceToEnd(effectArgs, immediateCount);
86
+ let immediateArgs = effectArgs.slice(0, immediateCount);
87
+ let queuedArgs = effectArgs.slice(immediateCount);
84
88
  state.availableCalls = state.availableCalls - immediateCount | 0;
85
89
  for (let idx = 0, idx_finish = immediateArgs.length; idx < idx_finish; ++idx) {
86
90
  promises.push(callEffect(effect, immediateArgs[idx], inMemTable, timerRef, onError));
@@ -139,7 +143,11 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
139
143
  let outputSchema = match$1.outputSchema;
140
144
  let dbEntities;
141
145
  try {
142
- dbEntities = await storage.loadByIdsOrThrow(idsToLoad, match$1.table, Internal.effectCacheItemRowsSchema);
146
+ dbEntities = await storage.loadOrThrow({
147
+ operator: "in",
148
+ fieldName: Table.idFieldName,
149
+ fieldValue: idsToLoad
150
+ }, match$1.table);
143
151
  } catch (raw_exn) {
144
152
  let exn = Primitive_exceptions.internalToException(raw_exn);
145
153
  Logging.childWarn(Logging.getItemLogger(item), {
@@ -189,48 +197,20 @@ function loadEffect(loadManager, persistence, effect, effectArgs, inMemoryStore,
189
197
  return LoadManager.call(loadManager, effectArgs, key, load, args => args.cacheKey, shouldGroup, hash => InMemoryStore.hasEffectOutput(inMemTable, hash), hash => InMemoryStore.getEffectOutputUnsafe(inMemTable, hash));
190
198
  }
191
199
 
192
- function loadByField(loadManager, persistence, operator, entityConfig, inMemoryStore, fieldName, fieldValueSchema, shouldGroup, item, fieldValue) {
193
- let operatorCallName;
194
- switch (operator) {
195
- case "Eq" :
196
- operatorCallName = "eq";
197
- break;
198
- case "Gt" :
199
- operatorCallName = "gt";
200
- break;
201
- case "Lt" :
202
- operatorCallName = "lt";
203
- break;
204
- }
205
- let key = entityConfig.name + `.getWhere.` + fieldName + `.` + operatorCallName;
200
+ function loadByFilter(loadManager, persistence, entityConfig, inMemoryStore, shouldGroup, item, filter) {
201
+ let key = EntityFilter.toOperationKey(filter, entityConfig.name);
206
202
  let inMemTable = InMemoryStore.getInMemTable(inMemoryStore, entityConfig);
207
- let load = async (fieldValues, param) => {
203
+ let load = async (filters, param) => {
208
204
  let storage = Persistence.getInitializedStorageOrThrow(persistence);
209
205
  let timerRef = Prometheus.StorageLoad.startOperation(storage.name, key);
210
206
  let size = {
211
207
  contents: 0
212
208
  };
213
- let indiciesToLoad = fieldValues.map(fieldValue => ({
214
- fieldName: fieldName,
215
- fieldValue: fieldValue,
216
- operator: operator
217
- }));
218
- await Promise.all(indiciesToLoad.map(async index => {
219
- InMemoryTable.Entity.addEmptyIndex(inMemTable, index);
209
+ filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter));
210
+ let queries = EntityFilter.merge(filters);
211
+ await Promise.all(queries.map(async filter => {
220
212
  try {
221
- let tmp;
222
- switch (index.operator) {
223
- case "Eq" :
224
- tmp = "=";
225
- break;
226
- case "Gt" :
227
- tmp = ">";
228
- break;
229
- case "Lt" :
230
- tmp = "<";
231
- break;
232
- }
233
- let entities = await storage.loadByFieldOrThrow(TableIndices.Index.getFieldName(index), fieldValueSchema, index.fieldValue, tmp, entityConfig.table, entityConfig.rowsSchema);
213
+ let entities = await storage.loadOrThrow(filter, entityConfig.table);
234
214
  entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, inMemoryStore.committedCheckpointId, entity.id, entity));
235
215
  size.contents = size.contents + entities.length | 0;
236
216
  return;
@@ -238,23 +218,21 @@ function loadByField(loadManager, persistence, operator, entityConfig, inMemoryS
238
218
  let exn = Primitive_exceptions.internalToException(raw_exn);
239
219
  if (exn.RE_EXN_ID === Persistence.StorageError) {
240
220
  return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Logging.getItemLogger(item), {
241
- operator: operatorCallName,
242
- tableName: entityConfig.table.tableName,
243
- fieldName: fieldName,
244
- fieldValue: fieldValue
221
+ operation: key,
222
+ params: EntityFilter.getParams(filter)
245
223
  }), exn.message, exn.reason);
246
224
  }
247
225
  throw exn;
248
226
  }
249
227
  }));
250
- return Prometheus.StorageLoad.endOperation(timerRef, storage.name, key, fieldValues.length, size.contents);
228
+ return Prometheus.StorageLoad.endOperation(timerRef, storage.name, key, Stdlib_Array.reduce(queries, 0, (acc, query) => acc + EntityFilter.valuesCount(query) | 0), size.contents);
251
229
  };
252
- return LoadManager.call(loadManager, fieldValue, key, load, fieldValue => TableIndices.FieldValue.toString(fieldValue), shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable, fieldName, operator), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable, fieldName, operator));
230
+ return LoadManager.call(loadManager, filter, key, load, EntityFilter.toString, shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable));
253
231
  }
254
232
 
255
233
  export {
256
234
  loadById,
257
- loadByField,
235
+ loadByFilter,
258
236
  loadEffect,
259
237
  }
260
- /* Utils Not a pure module */
238
+ /* Table Not a pure module */
@@ -8,17 +8,14 @@ let loadById: (
8
8
  ~entityId: string,
9
9
  ) => promise<option<Internal.entity>>
10
10
 
11
- let loadByField: (
11
+ let loadByFilter: (
12
12
  ~loadManager: LoadManager.t,
13
13
  ~persistence: Persistence.t,
14
- ~operator: TableIndices.Operator.t,
15
14
  ~entityConfig: Internal.entityConfig,
16
15
  ~inMemoryStore: InMemoryStore.t,
17
- ~fieldName: string,
18
- ~fieldValueSchema: RescriptSchema.S.t<'fieldValue>,
19
16
  ~shouldGroup: bool,
20
17
  ~item: Internal.item,
21
- ~fieldValue: 'fieldValue,
18
+ ~filter: EntityFilter.t,
22
19
  ) => promise<array<Internal.entity>>
23
20
 
24
21
  let loadEffect: (
@@ -12,7 +12,7 @@ let makeTopicSelection = (~topic0, ~topic1=[], ~topic2=[], ~topic3=[]) =>
12
12
  }
13
13
 
14
14
  let hasFilters = ({topic1, topic2, topic3}: Internal.topicSelection) => {
15
- [topic1, topic2, topic3]->Array.find(topic => !Utils.Array.isEmpty(topic))->Belt.Option.isSome
15
+ [topic1, topic2, topic3]->Array.find(topic => !Utils.Array.isEmpty(topic))->Option.isSome
16
16
  }
17
17
 
18
18
  /**
@@ -24,11 +24,11 @@ let compressTopicSelections = (topicSelections: array<Internal.topicSelection>)
24
24
 
25
25
  let selectionsWithFilters = []
26
26
 
27
- topicSelections->Belt.Array.forEach(selection => {
27
+ topicSelections->Array.forEach(selection => {
28
28
  if selection->hasFilters {
29
29
  selectionsWithFilters->Array.push(selection)->ignore
30
30
  } else {
31
- selection.topic0->Belt.Array.forEach(topic0 => {
31
+ selection.topic0->Array.forEach(topic0 => {
32
32
  topic0sOfSelectionsWithoutFilters->Array.push(topic0)->ignore
33
33
  })
34
34
  }
@@ -43,7 +43,7 @@ let compressTopicSelections = (topicSelections: array<Internal.topicSelection>)
43
43
  topic2: [],
44
44
  topic3: [],
45
45
  }
46
- Belt.Array.concat([selectionWithoutFilters], selectionsWithFilters)
46
+ Array.concat([selectionWithoutFilters], selectionsWithFilters)
47
47
  }
48
48
  }
49
49
 
@@ -60,6 +60,10 @@ let make = (~addresses, ~topicSelections) => {
60
60
  type parsedEventFilters = {
61
61
  getEventFiltersOrThrow: ChainMap.Chain.t => Internal.eventFilters,
62
62
  filterByAddresses: bool,
63
+ // Indexed params filtered by `chain.<Contract>.addresses`, in disjunctive
64
+ // normal form (outer array OR of AND-groups). Empty unless `filterByAddresses`.
65
+ // Consumed by the codegen of the event's `clientAddressFilter`.
66
+ addressFilterParamGroups: array<array<string>>,
63
67
  // `_gte` from the top-level `block` filter of the user's `where`,
64
68
  // resolved at build time (per-chain via the `probeChainId`). The
65
69
  // caller uses this to override the per-event `startBlock` — a
@@ -151,6 +155,70 @@ let makeDetectionChainArg = (
151
155
  chainObj
152
156
  }
153
157
 
158
+ // Sentinel returned by `chain.<Contract>.addresses` during detection. A Proxy
159
+ // whose traps throw on any access, so the only non-throwing use is passing it
160
+ // straight through as a param filter value — which `extractAddressFilterGroups`
161
+ // then finds by identity. Misuse (spread/map/index/...) fails loud at the site.
162
+ let makeAddressesProbe: (
163
+ ~contractName: string,
164
+ ) => array<Address.t> = %raw(`function (contractName) {
165
+ var trap = function () {
166
+ throw new Error(
167
+ 'Invalid where configuration for "' + contractName +
168
+ '": chain.' + contractName + '.addresses must be passed directly as an indexed-param ' +
169
+ 'filter value (e.g. { params: { to: chain.' + contractName + '.addresses } }). ' +
170
+ 'It cannot be spread, mapped, indexed, or otherwise transformed.'
171
+ );
172
+ };
173
+ return new Proxy([], {get: trap});
174
+ }`)
175
+
176
+ // Find which indexed params the probed `where` result assigned the Proxy to
177
+ // (DNF: object => one AND-group, array => OR of groups), neutralizing each match
178
+ // to `[]` so later passes can't touch the throwing Proxy. Throws when the
179
+ // callback read the addresses but didn't use them as a param filter value, since
180
+ // the caller only invokes this once it knows the addresses were read.
181
+ let extractAddressFilterGroupsOrThrow = (
182
+ result: JSON.t,
183
+ ~probe: array<Address.t>,
184
+ ~contractName: string,
185
+ ): array<array<string>> => {
186
+ let groups = []
187
+ let scanGroup = (paramsObj: dict<JSON.t>) => {
188
+ let names = []
189
+ paramsObj->Utils.Dict.forEachWithKey((value, key) => {
190
+ if value === probe->(Utils.magic: array<Address.t> => JSON.t) {
191
+ names->Array.push(key)->ignore
192
+ paramsObj->Dict.set(key, []->(Utils.magic: array<unknown> => JSON.t))
193
+ }
194
+ })
195
+ if names->Utils.Array.isEmpty->not {
196
+ groups->Array.push(names)->ignore
197
+ }
198
+ }
199
+ switch result {
200
+ | Object(obj) =>
201
+ switch obj->Dict.get("params") {
202
+ | Some(Object(p)) => scanGroup(p)
203
+ | Some(Array(arr)) =>
204
+ arr->Array.forEach(item =>
205
+ switch item {
206
+ | Object(p) => scanGroup(p)
207
+ | _ => ()
208
+ }
209
+ )
210
+ | _ => ()
211
+ }
212
+ | _ => ()
213
+ }
214
+ if groups->Utils.Array.isEmpty {
215
+ JsError.throwWithMessage(
216
+ `Invalid where configuration for ${contractName}. The callback reads \`chain.${contractName}.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.${contractName}.addresses } }.`,
217
+ )
218
+ }
219
+ groups
220
+ }
221
+
154
222
  let parseEventFiltersOrThrow = {
155
223
  let emptyTopics = []
156
224
  let noopGetter = _ => emptyTopics
@@ -167,6 +235,7 @@ let parseEventFiltersOrThrow = {
167
235
  ~topic3=noopGetter,
168
236
  ): parsedEventFilters => {
169
237
  let filterByAddresses = ref(false)
238
+ let addressFilterParamGroups = ref([])
170
239
  let startBlock = ref(None)
171
240
  let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
172
241
  let default = {
@@ -286,24 +355,23 @@ let parseEventFiltersOrThrow = {
286
355
  // `startBlock` (from `where.block`) for this chain — a second
287
356
  // invocation would risk observing different state for callbacks
288
357
  // that close over mutable references.
289
- let probedResult = try {
290
- let chain = makeDetectionChainArg(
291
- ~contractName,
292
- ~chainId=probeChainId,
293
- ~getAddresses=() => {
294
- filterByAddresses := true
295
- []
296
- },
297
- )
298
- Some(fn({chain: chain->Obj.magic}))
299
- } catch {
300
- | _ => None
301
- }
302
- switch probedResult {
303
- | Some(result) =>
304
- startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, result)
305
- | None => ()
358
+ // A misused Proxy (or any throw from the callback) propagates as-is —
359
+ // the Proxy's guidance message surfaces without wrapping.
360
+ let addressesProbe = makeAddressesProbe(~contractName)
361
+ let chain = makeDetectionChainArg(
362
+ ~contractName,
363
+ ~chainId=probeChainId,
364
+ ~getAddresses=() => {
365
+ filterByAddresses := true
366
+ addressesProbe
367
+ },
368
+ )
369
+ let probedResult = fn({chain: chain->Obj.magic})
370
+ if filterByAddresses.contents {
371
+ addressFilterParamGroups :=
372
+ extractAddressFilterGroupsOrThrow(probedResult, ~probe=addressesProbe, ~contractName)
306
373
  }
374
+ startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, probedResult)
307
375
  if filterByAddresses.contents {
308
376
  chain => Internal.Dynamic(
309
377
  addresses => {
@@ -343,6 +411,7 @@ let parseEventFiltersOrThrow = {
343
411
  {
344
412
  getEventFiltersOrThrow,
345
413
  filterByAddresses: filterByAddresses.contents,
414
+ addressFilterParamGroups: addressFilterParamGroups.contents,
346
415
  startBlock: startBlock.contents,
347
416
  }
348
417
  }
@@ -1,8 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
5
- import * as Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
5
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
7
6
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
7
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -35,7 +34,7 @@ function makeTopicSelection(topic0, topic1Opt, topic2Opt, topic3Opt) {
35
34
  }
36
35
 
37
36
  function hasFilters(param) {
38
- return Belt_Option.isSome([
37
+ return Stdlib_Option.isSome([
39
38
  param.topic1,
40
39
  param.topic2,
41
40
  param.topic3
@@ -45,12 +44,11 @@ function hasFilters(param) {
45
44
  function compressTopicSelections(topicSelections) {
46
45
  let topic0sOfSelectionsWithoutFilters = [];
47
46
  let selectionsWithFilters = [];
48
- Belt_Array.forEach(topicSelections, selection => {
47
+ topicSelections.forEach(selection => {
49
48
  if (hasFilters(selection)) {
50
49
  selectionsWithFilters.push(selection);
51
- return;
52
50
  } else {
53
- return Belt_Array.forEach(selection.topic0, topic0 => {
51
+ selection.topic0.forEach(topic0 => {
54
52
  topic0sOfSelectionsWithoutFilters.push(topic0);
55
53
  });
56
54
  }
@@ -67,7 +65,7 @@ function compressTopicSelections(topicSelections) {
67
65
  topic2: selectionWithoutFilters_topic2,
68
66
  topic3: selectionWithoutFilters_topic3
69
67
  };
70
- return Belt_Array.concat([selectionWithoutFilters], selectionsWithFilters);
68
+ return [selectionWithoutFilters].concat(selectionsWithFilters);
71
69
  }
72
70
 
73
71
  function make(addresses, topicSelections) {
@@ -123,6 +121,58 @@ function makeDetectionChainArg(contractName, chainId, getAddresses) {
123
121
  return chainObj;
124
122
  }
125
123
 
124
+ let makeAddressesProbe = (function (contractName) {
125
+ var trap = function () {
126
+ throw new Error(
127
+ 'Invalid where configuration for "' + contractName +
128
+ '": chain.' + contractName + '.addresses must be passed directly as an indexed-param ' +
129
+ 'filter value (e.g. { params: { to: chain.' + contractName + '.addresses } }). ' +
130
+ 'It cannot be spread, mapped, indexed, or otherwise transformed.'
131
+ );
132
+ };
133
+ return new Proxy([], {get: trap});
134
+ });
135
+
136
+ function extractAddressFilterGroupsOrThrow(result, probe, contractName) {
137
+ let groups = [];
138
+ let scanGroup = paramsObj => {
139
+ let names = [];
140
+ Utils.Dict.forEachWithKey(paramsObj, (value, key) => {
141
+ if (value === probe) {
142
+ names.push(key);
143
+ paramsObj[key] = [];
144
+ return;
145
+ }
146
+ });
147
+ if (!Utils.$$Array.isEmpty(names)) {
148
+ groups.push(names);
149
+ return;
150
+ }
151
+ };
152
+ if (typeof result === "object" && result !== null && !Array.isArray(result)) {
153
+ let match = result["params"];
154
+ if (match !== undefined) {
155
+ if (Array.isArray(match)) {
156
+ match.forEach(item => {
157
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
158
+ return scanGroup(item);
159
+ }
160
+ });
161
+ } else {
162
+ switch (typeof match) {
163
+ case "object" :
164
+ scanGroup(match);
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ if (Utils.$$Array.isEmpty(groups)) {
171
+ Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
172
+ }
173
+ return groups;
174
+ }
175
+
126
176
  let emptyTopics = [];
127
177
 
128
178
  function noopGetter(param) {
@@ -136,6 +186,7 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
136
186
  let filterByAddresses = {
137
187
  contents: false
138
188
  };
189
+ let addressFilterParamGroups = [];
139
190
  let startBlock;
140
191
  let topic0 = [sighash];
141
192
  let $$default = {
@@ -210,21 +261,18 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
210
261
  let getEventFiltersOrThrow;
211
262
  if (eventFilters !== undefined) {
212
263
  if (typeof eventFilters === "function") {
213
- let probedResult;
214
- try {
215
- let chain = makeDetectionChainArg(contractName, probeChainId, () => {
216
- filterByAddresses.contents = true;
217
- return [];
218
- });
219
- probedResult = eventFilters({
220
- chain: chain
221
- });
222
- } catch (exn) {
223
- probedResult = undefined;
224
- }
225
- if (probedResult !== undefined) {
226
- startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
264
+ let addressesProbe = makeAddressesProbe(contractName);
265
+ let chain = makeDetectionChainArg(contractName, probeChainId, () => {
266
+ filterByAddresses.contents = true;
267
+ return addressesProbe;
268
+ });
269
+ let probedResult = eventFilters({
270
+ chain: chain
271
+ });
272
+ if (filterByAddresses.contents) {
273
+ addressFilterParamGroups = extractAddressFilterGroupsOrThrow(probedResult, addressesProbe, contractName);
227
274
  }
275
+ startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
228
276
  getEventFiltersOrThrow = filterByAddresses.contents ? chain => ({
229
277
  TAG: "Dynamic",
230
278
  _0: addresses => {
@@ -260,6 +308,7 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
260
308
  return {
261
309
  getEventFiltersOrThrow: getEventFiltersOrThrow,
262
310
  filterByAddresses: filterByAddresses.contents,
311
+ addressFilterParamGroups: addressFilterParamGroups,
263
312
  startBlock: startBlock
264
313
  };
265
314
  }
@@ -274,6 +323,8 @@ export {
274
323
  extractStartBlock,
275
324
  makeChainArg,
276
325
  makeDetectionChainArg,
326
+ makeAddressesProbe,
327
+ extractAddressFilterGroupsOrThrow,
277
328
  parseEventFiltersOrThrow,
278
329
  }
279
330
  /* eventBlockRangeSchema Not a pure module */
package/src/Logging.res CHANGED
@@ -172,7 +172,7 @@ let getItemLogger = {
172
172
  "chainId": chain->ChainMap.Chain.toChainId,
173
173
  "block": blockNumber,
174
174
  "logIndex": logIndex,
175
- "address": event.srcAddress,
175
+ "address": (event->Internal.toGenericEvent).srcAddress,
176
176
  }->createChildParams
177
177
  | Block({blockNumber, onBlockConfig}) =>
178
178
  {
package/src/Main.res CHANGED
@@ -296,6 +296,59 @@ let getGlobalIndexer = (): 'indexer => {
296
296
  )
297
297
  }
298
298
 
299
+ // SVM identity: `{program, instruction}` from TS or
300
+ // `{instruction: GADT{contract, _0}}` from ReScript. Same two-format dance
301
+ // as the EVM `parseIdentityConfig`, but reading the SVM-native field names.
302
+ let parseSvmIdentityConfig = (identityConfig: 'a) => {
303
+ let raw =
304
+ identityConfig->(
305
+ Utils.magic: 'a => {"program": unknown, "instruction": unknown, "where": option<JSON.t>}
306
+ )
307
+ let (programName, instructionName) = if typeof(raw["program"]) === #string {
308
+ (
309
+ raw["program"]->(Utils.magic: unknown => string),
310
+ raw["instruction"]->(Utils.magic: unknown => string),
311
+ )
312
+ } else {
313
+ let inst = raw["instruction"]->(Utils.magic: unknown => {"contract": string, "_0": string})
314
+ (inst["contract"], inst["_0"])
315
+ }
316
+ let where = raw["where"]
317
+ let eventOptions: option<Internal.eventOptions<_>> = switch where {
318
+ | None => None
319
+ | Some(_) =>
320
+ Some({
321
+ where: ?(where->(Utils.magic: option<JSON.t> => option<_>)),
322
+ })
323
+ }
324
+ (programName, instructionName, eventOptions)
325
+ }
326
+
327
+ // onInstruction: delegates to HandlerRegister.setHandler. The SVM analog of
328
+ // onEvent; the registration store keys on `(contractName, eventName)` which
329
+ // for SVM is `(programName, instructionName)`.
330
+ let onInstructionFn = (identityConfig: 'a, handler: 'b) => {
331
+ HandlerRegister.throwIfFinishedRegistration(~methodName="onInstruction")
332
+ let (programName, instructionName, eventOptions) = parseSvmIdentityConfig(identityConfig)
333
+ // The generic dispatch hands every handler `{event, context}`. SVM handlers
334
+ // receive the instruction under `instruction`, so remap the field here; the
335
+ // payload object itself is the `svmInstruction` built in SvmHyperSyncSource.
336
+ let userHandler =
337
+ handler->(
338
+ Utils.magic: 'b => Envio.svmOnInstructionArgs<Internal.handlerContext> => promise<unit>
339
+ )
340
+ HandlerRegister.setHandler(
341
+ ~contractName=programName,
342
+ ~eventName=instructionName,
343
+ (args: Internal.genericHandlerArgs<Internal.event, Internal.handlerContext>) =>
344
+ userHandler({
345
+ instruction: args.event->(Utils.magic: Internal.event => Envio.svmInstruction),
346
+ context: args.context,
347
+ }),
348
+ ~eventOptions,
349
+ )
350
+ }
351
+
299
352
  // contractRegister: delegates to HandlerRegister.setContractRegister
300
353
  let contractRegisterFn = (identityConfig: 'a, handler: 'b) => {
301
354
  HandlerRegister.throwIfFinishedRegistration(~methodName="contractRegister")
@@ -467,6 +520,7 @@ let getGlobalIndexer = (): 'indexer => {
467
520
  "description",
468
521
  "chainIds",
469
522
  "chains",
523
+ "onInstruction",
470
524
  "onSlot",
471
525
  "~internalAndWillBeRemovedSoon_onRollbackCommit",
472
526
  ]
@@ -490,6 +544,7 @@ let getGlobalIndexer = (): 'indexer => {
490
544
  chains->(Utils.magic: {..} => unknown)
491
545
  }
492
546
  | "onEvent" => onEventFn->Utils.magic
547
+ | "onInstruction" => onInstructionFn->Utils.magic
493
548
  | "contractRegister" => contractRegisterFn->Utils.magic
494
549
  | "onBlock" | "onSlot" => onBlockFn->Utils.magic
495
550
  | "~internalAndWillBeRemovedSoon_onRollbackCommit" => onRollbackCommitFn->Utils.magic
@@ -791,8 +846,12 @@ let start = async (
791
846
  ~onError,
792
847
  )
793
848
  let gsManager =
794
- globalState->GlobalStateManager.make(~onError=exn =>
795
- onError(exn->ErrorHandling.make(~msg="Indexer has failed with an unexpected error"))
849
+ globalState->GlobalStateManager.make(
850
+ ~reducers=GlobalState.makeReducers(
851
+ ~markBatchProcessed=MarkBatchProcessedAdapter.make(~inMemoryStore=ctx.inMemoryStore),
852
+ ),
853
+ ~onError=exn =>
854
+ onError(exn->ErrorHandling.make(~msg="Indexer has failed with an unexpected error")),
796
855
  )
797
856
  if shouldUseTui {
798
857
  let _rerender = Tui.start(~getState=() => gsManager->GlobalStateManager.getState)
package/src/Main.res.mjs CHANGED
@@ -32,6 +32,7 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
32
32
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
33
33
  import * as GlobalStateManager from "./GlobalStateManager.res.mjs";
34
34
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
35
+ import * as MarkBatchProcessedAdapter from "./adapters/MarkBatchProcessedAdapter.res.mjs";
35
36
 
36
37
  let chainDataSchema = S$RescriptSchema.schema(s => ({
37
38
  chainId: s.m(S$RescriptSchema.float),
@@ -251,6 +252,38 @@ function getGlobalIndexer() {
251
252
  let match = parseIdentityConfig(identityConfig);
252
253
  HandlerRegister.setHandler(match[0], match[1], handler, match[2], undefined);
253
254
  };
255
+ let parseSvmIdentityConfig = identityConfig => {
256
+ let match;
257
+ if (typeof identityConfig.program === "string") {
258
+ match = [
259
+ identityConfig.program,
260
+ identityConfig.instruction
261
+ ];
262
+ } else {
263
+ let inst = identityConfig.instruction;
264
+ match = [
265
+ inst.contract,
266
+ inst._0
267
+ ];
268
+ }
269
+ let where = identityConfig.where;
270
+ let eventOptions = where !== undefined ? ({
271
+ where: where
272
+ }) : undefined;
273
+ return [
274
+ match[0],
275
+ match[1],
276
+ eventOptions
277
+ ];
278
+ };
279
+ let onInstructionFn = (identityConfig, handler) => {
280
+ HandlerRegister.throwIfFinishedRegistration("onInstruction");
281
+ let match = parseSvmIdentityConfig(identityConfig);
282
+ HandlerRegister.setHandler(match[0], match[1], args => handler({
283
+ instruction: args.event,
284
+ context: args.context
285
+ }), match[2], undefined);
286
+ };
254
287
  let contractRegisterFn = (identityConfig, handler) => {
255
288
  HandlerRegister.throwIfFinishedRegistration("contractRegister");
256
289
  let match = parseIdentityConfig(identityConfig);
@@ -346,6 +379,7 @@ function getGlobalIndexer() {
346
379
  "description",
347
380
  "chainIds",
348
381
  "chains",
382
+ "onInstruction",
349
383
  "onSlot",
350
384
  "~internalAndWillBeRemovedSoon_onRollbackCommit"
351
385
  ];
@@ -380,6 +414,8 @@ function getGlobalIndexer() {
380
414
  return Config.loadWithoutRegistrations().name;
381
415
  case "onEvent" :
382
416
  return onEventFn;
417
+ case "onInstruction" :
418
+ return onInstructionFn;
383
419
  case "onBlock" :
384
420
  case "onSlot" :
385
421
  return onBlockFn;
@@ -587,7 +623,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
587
623
  }
588
624
  let chainManager = ChainManager.makeFromDbState(Persistence.getInitializedState(persistence$1), config$2, registrations, undefined);
589
625
  let globalState = GlobalState.make(ctx, chainManager, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError);
590
- let gsManager = GlobalStateManager.make(globalState, exn => onError(ErrorHandling.make(exn, undefined, "Indexer has failed with an unexpected error")));
626
+ let gsManager = GlobalStateManager.make(globalState, GlobalState.makeReducers(undefined, undefined, undefined, MarkBatchProcessedAdapter.make(ctx_inMemoryStore)), exn => onError(ErrorHandling.make(exn, undefined, "Indexer has failed with an unexpected error")));
591
627
  if (shouldUseTui) {
592
628
  Tui.start(() => GlobalStateManager.getState(gsManager));
593
629
  }