envio 3.3.0-alpha.8 → 3.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/index.d.ts +19 -0
  2. package/licenses/CLA.md +35 -0
  3. package/licenses/EULA.md +67 -0
  4. package/licenses/LICENSE.md +45 -0
  5. package/licenses/README.md +35 -0
  6. package/package.json +9 -8
  7. package/src/Batch.res.mjs +1 -1
  8. package/src/BatchProcessing.res +0 -7
  9. package/src/BatchProcessing.res.mjs +1 -8
  10. package/src/ChainFetching.res +30 -18
  11. package/src/ChainFetching.res.mjs +23 -13
  12. package/src/ChainState.res +205 -59
  13. package/src/ChainState.res.mjs +130 -27
  14. package/src/ChainState.resi +16 -1
  15. package/src/Config.res +9 -5
  16. package/src/Config.res.mjs +2 -2
  17. package/src/Core.res +20 -0
  18. package/src/Core.res.mjs +12 -0
  19. package/src/CrossChainState.res +147 -39
  20. package/src/CrossChainState.res.mjs +42 -10
  21. package/src/EffectState.res +100 -0
  22. package/src/EffectState.res.mjs +74 -0
  23. package/src/EffectState.resi +32 -0
  24. package/src/Envio.res +25 -7
  25. package/src/Envio.res.mjs +15 -19
  26. package/src/EnvioGlobal.res +53 -0
  27. package/src/EnvioGlobal.res.mjs +31 -0
  28. package/src/EventConfigBuilder.res +30 -56
  29. package/src/EventConfigBuilder.res.mjs +24 -30
  30. package/src/EventProcessing.res +8 -5
  31. package/src/EventProcessing.res.mjs +2 -1
  32. package/src/FetchState.res +842 -474
  33. package/src/FetchState.res.mjs +545 -356
  34. package/src/HandlerLoader.res +1 -1
  35. package/src/HandlerLoader.res.mjs +1 -1
  36. package/src/HandlerRegister.res +478 -305
  37. package/src/HandlerRegister.res.mjs +277 -209
  38. package/src/HandlerRegister.resi +11 -6
  39. package/src/InMemoryStore.res +14 -26
  40. package/src/InMemoryStore.res.mjs +6 -19
  41. package/src/IndexerState.res +15 -39
  42. package/src/IndexerState.res.mjs +18 -29
  43. package/src/IndexerState.resi +2 -10
  44. package/src/Internal.res +121 -18
  45. package/src/Internal.res.mjs +96 -2
  46. package/src/LoadLayer.res +44 -24
  47. package/src/LoadLayer.res.mjs +43 -20
  48. package/src/LoadLayer.resi +1 -0
  49. package/src/LogSelection.res +92 -217
  50. package/src/LogSelection.res.mjs +95 -184
  51. package/src/Main.res +29 -143
  52. package/src/Main.res.mjs +25 -88
  53. package/src/Metrics.res +1 -1
  54. package/src/Metrics.res.mjs +1 -1
  55. package/src/Persistence.res +12 -3
  56. package/src/PgStorage.res +155 -82
  57. package/src/PgStorage.res.mjs +130 -77
  58. package/src/Prometheus.res +20 -10
  59. package/src/Prometheus.res.mjs +22 -10
  60. package/src/PruneStaleHistory.res +146 -35
  61. package/src/PruneStaleHistory.res.mjs +114 -20
  62. package/src/RawEvent.res +2 -2
  63. package/src/Rollback.res +32 -13
  64. package/src/Rollback.res.mjs +24 -11
  65. package/src/RollbackCommit.res +4 -1
  66. package/src/RollbackCommit.res.mjs +3 -2
  67. package/src/SimulateDeadInputTracker.res +1 -1
  68. package/src/SimulateItems.res +39 -7
  69. package/src/SimulateItems.res.mjs +28 -9
  70. package/src/TestIndexer.res +2 -2
  71. package/src/TestIndexer.res.mjs +2 -2
  72. package/src/TopicFilter.res +1 -25
  73. package/src/TopicFilter.res.mjs +0 -74
  74. package/src/UserContext.res +62 -23
  75. package/src/UserContext.res.mjs +26 -6
  76. package/src/Writing.res +60 -21
  77. package/src/Writing.res.mjs +27 -9
  78. package/src/bindings/NodeJs.res +5 -0
  79. package/src/bindings/Viem.res +0 -5
  80. package/src/sources/EventRouter.res +0 -21
  81. package/src/sources/EventRouter.res.mjs +0 -12
  82. package/src/sources/Evm.res +4 -1
  83. package/src/sources/Evm.res.mjs +1 -1
  84. package/src/sources/EvmChain.res +2 -27
  85. package/src/sources/EvmChain.res.mjs +2 -23
  86. package/src/sources/EvmRpcClient.res +58 -23
  87. package/src/sources/EvmRpcClient.res.mjs +11 -5
  88. package/src/sources/Fuel.res +3 -1
  89. package/src/sources/Fuel.res.mjs +1 -1
  90. package/src/sources/HyperSync.res +9 -38
  91. package/src/sources/HyperSync.res.mjs +16 -28
  92. package/src/sources/HyperSync.resi +2 -2
  93. package/src/sources/HyperSyncClient.res +88 -11
  94. package/src/sources/HyperSyncClient.res.mjs +39 -6
  95. package/src/sources/HyperSyncSource.res +18 -210
  96. package/src/sources/HyperSyncSource.res.mjs +9 -137
  97. package/src/sources/Rpc.res +0 -32
  98. package/src/sources/Rpc.res.mjs +1 -46
  99. package/src/sources/RpcSource.res +129 -533
  100. package/src/sources/RpcSource.res.mjs +161 -379
  101. package/src/sources/SimulateSource.res +37 -19
  102. package/src/sources/SimulateSource.res.mjs +27 -10
  103. package/src/sources/SourceManager.res +4 -14
  104. package/src/sources/SourceManager.res.mjs +3 -9
  105. package/src/sources/SourceManager.resi +0 -2
  106. package/src/sources/SvmHyperSyncSource.res +13 -3
  107. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
@@ -0,0 +1,32 @@
1
+ type effectRateLimitState = {
2
+ callsPerDuration: int,
3
+ durationMs: int,
4
+ mutable availableCalls: int,
5
+ mutable windowStartTime: float,
6
+ mutable queueCount: int,
7
+ mutable nextWindowPromise: option<promise<unit>>,
8
+ }
9
+
10
+ type effectCacheInMemTable = {
11
+ mutable idsToStore: array<string>,
12
+ mutable invalidationsCount: int,
13
+ mutable dict: dict<Change.t<Internal.effectOutput>>,
14
+ mutable changesCount: float,
15
+ effect: Internal.effect,
16
+ scope: Internal.chainScope,
17
+ table: Table.table,
18
+ rateLimitState: option<effectRateLimitState>,
19
+ mutable activeCallsCount: int,
20
+ mutable prevCallStartTimerRef: Performance.timeRef,
21
+ }
22
+
23
+ // Opaque store of all per-(effect, scope) runtime state.
24
+ type t
25
+
26
+ let make: unit => t
27
+ // Get, or lazily create, the in-mem table for an (effect, scope). Reuses the
28
+ // surviving rate-limit window when the table is recreated after a rollback.
29
+ let getTable: (t, ~effect: Internal.effect, ~scope: Internal.chainScope) => effectCacheInMemTable
30
+ let forEach: (t, effectCacheInMemTable => unit) => unit
31
+ // Drop the cache tables but keep the rate-limit windows across a rollback.
32
+ let resetForRollback: t => unit
package/src/Envio.res CHANGED
@@ -186,11 +186,19 @@ and effectOptions<'input, 'output> = {
186
186
  rateLimit: rateLimit,
187
187
  /** Whether the effect should be cached. */
188
188
  cache?: bool,
189
+ /** Whether the effect's cache is shared across all chains. Defaults to `true`.
190
+ Set to `false` to isolate the cache (and rate limiting) per chain and enable
191
+ `context.chain.id` inside the handler. */
192
+ crossChain?: bool,
189
193
  }
194
+ and effectChain = {id: int}
190
195
  and effectContext = {
191
196
  log: logger,
192
197
  effect: 'input 'output. (effect<'input, 'output>, 'input) => promise<'output>,
193
198
  mutable cache: bool,
199
+ /** The chain the effect was called on. Only available on effects created with
200
+ `crossChain: false`; accessing it on a cross-chain effect throws. */
201
+ chain: effectChain,
194
202
  }
195
203
  and effectArgs<'input> = {
196
204
  input: 'input,
@@ -205,10 +213,23 @@ let durationToMs = (duration: rateLimitDuration) =>
205
213
  | Milliseconds(ms) => ms
206
214
  }
207
215
 
216
+ // The name becomes both a Postgres cache-table suffix and a .envio/cache file
217
+ // path segment. Path separators are excluded from the charset and a leading dot
218
+ // is disallowed, so a name can never be "." / ".." or otherwise traverse out of
219
+ // the cache dir; dots elsewhere are fine and keep existing names like
220
+ // "token.metadata" working, since the (name, scope) <-> table <-> path mapping
221
+ // stays reversible.
222
+ let effectNameRe = /^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/
223
+
208
224
  let createEffect = (
209
225
  options: effectOptions<'input, 'output>,
210
226
  handler: effectArgs<'input> => promise<'output>,
211
227
  ) => {
228
+ if !(effectNameRe->RegExp.test(options.name)) {
229
+ JsError.throwWithMessage(
230
+ `Invalid effect name "${options.name}". Effect names may contain letters, numbers, underscores, hyphens and dots (but must not start with a dot or contain a path separator), because the name is used as the cache table name and cache file path.`,
231
+ )
232
+ }
212
233
  let outputSchema =
213
234
  S.schema(_ => options.output)->(Utils.magic: S.t<S.t<'output>> => S.t<Internal.effectOutput>)
214
235
  let itemSchema = S.schema((s): Internal.effectCacheItem => {
@@ -222,8 +243,6 @@ let createEffect = (
222
243
  Internal.effectOutput,
223
244
  >
224
245
  ),
225
- activeCallsCount: 0,
226
- prevCallStartTimerRef: %raw(`null`),
227
246
  // This is the way to make the createEffect API
228
247
  // work without the need for users to call S.schema themselves,
229
248
  // but simply pass the desired object/tuple/etc.
@@ -233,7 +252,6 @@ let createEffect = (
233
252
  ),
234
253
  output: outputSchema,
235
254
  storageMeta: {
236
- table: Internal.makeCacheTable(~effectName=options.name),
237
255
  outputSchema,
238
256
  itemSchema,
239
257
  },
@@ -241,16 +259,16 @@ let createEffect = (
241
259
  | Some(true) => true
242
260
  | _ => false
243
261
  },
262
+ crossChain: switch options.crossChain {
263
+ | Some(false) => false
264
+ | _ => true
265
+ },
244
266
  rateLimit: switch options.rateLimit {
245
267
  | Disable => None
246
268
  | Enable({calls, per}) =>
247
269
  Some({
248
270
  callsPerDuration: calls,
249
271
  durationMs: per->durationToMs,
250
- availableCalls: calls,
251
- windowStartTime: Date.now(),
252
- queueCount: 0,
253
- nextWindowPromise: None,
254
272
  })
255
273
  },
256
274
  }->(Utils.magic: Internal.effect => effect<'input, 'output>)
package/src/Envio.res.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  import * as Address from "./Address.res.mjs";
4
4
  import * as Process from "process";
5
- import * as Internal from "./Internal.res.mjs";
6
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
7
7
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
8
8
 
9
9
  function durationToMs(duration) {
@@ -18,41 +18,36 @@ function durationToMs(duration) {
18
18
  }
19
19
  }
20
20
 
21
+ let effectNameRe = /^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/;
22
+
21
23
  function createEffect(options, handler) {
24
+ if (!effectNameRe.test(options.name)) {
25
+ Stdlib_JsError.throwWithMessage(`Invalid effect name "` + options.name + `". Effect names may contain letters, numbers, underscores, hyphens and dots (but must not start with a dot or contain a path separator), because the name is used as the cache table name and cache file path.`);
26
+ }
22
27
  let outputSchema = S$RescriptSchema.schema(param => options.output);
23
28
  let itemSchema = S$RescriptSchema.schema(s => ({
24
29
  id: s.m(S$RescriptSchema.string),
25
30
  output: s.m(outputSchema)
26
31
  }));
27
32
  let match = options.cache;
28
- let match$1 = options.rateLimit;
33
+ let match$1 = options.crossChain;
34
+ let match$2 = options.rateLimit;
29
35
  let tmp;
30
- if (match$1 === false) {
31
- tmp = undefined;
32
- } else {
33
- let calls = match$1.calls;
34
- tmp = {
35
- callsPerDuration: calls,
36
- durationMs: durationToMs(match$1.per),
37
- availableCalls: calls,
38
- windowStartTime: Date.now(),
39
- queueCount: 0,
40
- nextWindowPromise: undefined
41
- };
42
- }
36
+ tmp = match$2 === false ? undefined : ({
37
+ callsPerDuration: match$2.calls,
38
+ durationMs: durationToMs(match$2.per)
39
+ });
43
40
  return {
44
41
  name: options.name,
45
42
  handler: handler,
46
43
  storageMeta: {
47
44
  itemSchema: itemSchema,
48
- outputSchema: outputSchema,
49
- table: Internal.makeCacheTable(options.name)
45
+ outputSchema: outputSchema
50
46
  },
51
47
  defaultShouldCache: match !== undefined ? match : false,
48
+ crossChain: match$1 !== undefined ? match$1 : true,
52
49
  output: outputSchema,
53
50
  input: S$RescriptSchema.schema(param => options.input),
54
- activeCallsCount: 0,
55
- prevCallStartTimerRef: null,
56
51
  rateLimit: tmp
57
52
  };
58
53
  }
@@ -102,6 +97,7 @@ let TestHelpers = {
102
97
 
103
98
  export {
104
99
  durationToMs,
100
+ effectNameRe,
105
101
  createEffect,
106
102
  isNonInteractive,
107
103
  TestHelpers,
@@ -0,0 +1,53 @@
1
+ // The single process-wide mutable state record, stashed on `globalThis` so a
2
+ // duplicate envio module instance — e.g. when the CLI's `bin.mjs` resolves
3
+ // envio from one path but the user's handlers resolve it from
4
+ // `node_modules/envio` — shares one state. Without this, each copy keeps its
5
+ // own module-level state: registration would read an empty registry and the
6
+ // `indexer.chains` getters would silently fall back to static config values.
7
+ //
8
+ // Slots are opaque (`unknown`) so this module stays at the bottom of the
9
+ // dependency graph; each owning module casts its slot to the real type once
10
+ // (`HandlerRegister` for the registration slots, `Main` for the runtime
11
+ // slots, `RollbackCommit` for its callbacks).
12
+ //
13
+ // Version-gated: the slot shapes can evolve between envio versions, so the
14
+ // guard uses strict full-version equality. On mismatch we throw with a
15
+ // deduplication hint instead of silently mixing shapes across builds.
16
+ type t = {
17
+ version: string,
18
+ eventRegistrations: dict<unknown>,
19
+ mutable activeRegistration: option<unknown>,
20
+ preRegistered: array<unknown>,
21
+ rollbackCommitCallbacks: array<unknown>,
22
+ mutable indexerState: option<unknown>,
23
+ mutable persistence: option<unknown>,
24
+ }
25
+
26
+ // Record type with `mutable` so assignment typechecks; ReScript keeps the
27
+ // field name verbatim in the generated JS so the globalThis slot is
28
+ // `__envioGlobal`.
29
+ type globalThis = {mutable __envioGlobal: Nullable.t<t>}
30
+ @val external globalThis: globalThis = "globalThis"
31
+
32
+ let value: t = {
33
+ let version = Utils.EnvioPackage.value.version
34
+ switch globalThis.__envioGlobal->Nullable.toOption {
35
+ | Some(existing) if existing.version === version => existing
36
+ | Some(existing) =>
37
+ JsError.throwWithMessage(
38
+ `Multiple incompatible envio versions loaded in the same process: ${existing.version} and ${version}. Deduplicate the 'envio' dependency in your project.`,
39
+ )
40
+ | None =>
41
+ let fresh = {
42
+ version,
43
+ eventRegistrations: Dict.make(),
44
+ activeRegistration: None,
45
+ preRegistered: [],
46
+ rollbackCommitCallbacks: [],
47
+ indexerState: None,
48
+ persistence: None,
49
+ }
50
+ globalThis.__envioGlobal = Nullable.make(fresh)
51
+ fresh
52
+ }
53
+ }
@@ -0,0 +1,31 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+
6
+ let version = Utils.EnvioPackage.value.version;
7
+
8
+ let existing = globalThis.__envioGlobal;
9
+
10
+ let value;
11
+
12
+ if (existing == null) {
13
+ let fresh = {
14
+ version: version,
15
+ eventRegistrations: {},
16
+ activeRegistration: undefined,
17
+ preRegistered: [],
18
+ rollbackCommitCallbacks: [],
19
+ indexerState: undefined,
20
+ persistence: undefined
21
+ };
22
+ globalThis.__envioGlobal = fresh;
23
+ value = fresh;
24
+ } else {
25
+ value = existing.version === version ? existing : Stdlib_JsError.throwWithMessage(`Multiple incompatible envio versions loaded in the same process: ` + existing.version + ` and ` + version + `. Deduplicate the 'envio' dependency in your project.`);
26
+ }
27
+
28
+ export {
29
+ value,
30
+ }
31
+ /* existing Not a pure module */
@@ -236,54 +236,30 @@ let buildSimulateParamsSchema = (params: array<paramMeta>): S.t<Internal.eventPa
236
236
 
237
237
  // ============== Build topic filter getters ==============
238
238
 
239
- let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) => {
240
- // Handle array/tuple types - these get keccak256'd
241
- if abiType->String.endsWith("]") || abiType->String.startsWith("(") {
242
- TopicFilter.castToHexUnsafe->(Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
243
- } else {
244
- switch abiType {
245
- | "address" =>
246
- TopicFilter.fromAddress->(
247
- Utils.magic: (Address.t => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
248
- )
249
-
250
- | "bool" =>
251
- TopicFilter.fromBool->(Utils.magic: (bool => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
252
- | "string" =>
253
- TopicFilter.fromDynamicString->(
254
- Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
255
- )
256
-
257
- | "bytes" =>
258
- TopicFilter.fromDynamicBytes->(
259
- Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
260
- )
261
-
262
- | t if t->String.startsWith("uint") =>
263
- TopicFilter.fromBigInt->(Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
264
- | t if t->String.startsWith("int") =>
265
- TopicFilter.fromSignedBigInt->(
266
- Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
267
- )
268
-
269
- | t if t->String.startsWith("bytes") =>
270
- TopicFilter.castToHexUnsafe->(
271
- Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
272
- )
273
-
274
- | other => JsError.throwWithMessage(`Unsupported topic filter ABI type: ${other}`)
275
- }
276
- }
277
- }
239
+ let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) => value =>
240
+ Core.getAddon().encodeIndexedTopic(~abiType, ~value)
278
241
 
279
242
  let buildTopicGetter = (p: paramMeta) => {
280
243
  let encoder = getTopicEncoder(p.abiType)
244
+ let isTuple = p.abiType->String.startsWith("(")
281
245
  (eventFilter: dict<JSON.t>) =>
282
246
  eventFilter
283
247
  ->Utils.Dict.dangerouslyGetNonOption(p.name)
284
- ->Option.mapOr([], topicFilters =>
285
- topicFilters->(Utils.magic: JSON.t => unknown)->normalizeOrThrow->Array.map(encoder)
286
- )
248
+ ->Option.mapOr([], topicFilters => {
249
+ let raw = topicFilters->(Utils.magic: JSON.t => unknown)
250
+ // A tuple filter value is itself an array, so a directly-passed tuple is
251
+ // indistinguishable from an OR-list by shape alone. A single tuple is
252
+ // the common case, so try it first; when the value doesn't ABI-encode as
253
+ // one tuple it must be an OR-list of tuples.
254
+ if isTuple {
255
+ switch encoder(raw) {
256
+ | encoded => [encoded]
257
+ | exception _ => raw->normalizeOrThrow->Array.map(encoder)
258
+ }
259
+ } else {
260
+ raw->normalizeOrThrow->Array.map(encoder)
261
+ }
262
+ })
287
263
  }
288
264
 
289
265
  // ============== Field selection ==============
@@ -413,31 +389,26 @@ let buildEvmEventConfig = (
413
389
  }
414
390
 
415
391
  // Enrich an EVM definition into a per-(event,chain) registration: resolve the
416
- // registered `where` (with `probeChainId`) into the lazy `getEventFiltersOrThrow`
417
- // closure + address filters, and override `startBlock` with `where.block._gte`.
392
+ // registered `where` for this chain into `resolvedWhere` + address filters,
393
+ // and override `startBlock` with `where.block._gte`.
418
394
  let buildEvmOnEventRegistration = (
419
395
  ~eventConfig: Internal.evmEventConfig,
420
396
  ~isWildcard: bool,
421
397
  ~handler: option<Internal.handler>,
422
398
  ~contractRegister: option<Internal.contractRegister>,
423
- ~eventFilters: option<JSON.t>,
424
- ~probeChainId: int,
399
+ ~where: option<JSON.t>,
400
+ ~chainId: int,
425
401
  ~onEventBlockFilterSchema: S.t<option<unknown>>,
426
402
  ~startBlock: option<int>=?,
427
403
  ): Internal.evmOnEventRegistration => {
428
404
  let indexedParams = eventConfig.paramsMetadata->Array.filter(p => p.indexed)
429
405
 
430
- let {
431
- getEventFiltersOrThrow,
432
- filterByAddresses,
433
- addressFilterParamGroups,
434
- startBlock: whereStartBlock,
435
- } = LogSelection.parseEventFiltersOrThrow(
436
- ~eventFilters,
406
+ let {resolvedWhere, filterByAddresses, addressFilterParamGroups} = LogSelection.parseWhereOrThrow(
407
+ ~where,
437
408
  ~sighash=eventConfig.sighash,
438
409
  ~params=indexedParams->Array.map(p => p.name),
439
410
  ~contractName=eventConfig.contractName,
440
- ~probeChainId,
411
+ ~chainId,
441
412
  ~onEventBlockFilterSchema,
442
413
  ~topic1=?indexedParams->Array.get(0)->Option.map(buildTopicGetter),
443
414
  ~topic2=?indexedParams->Array.get(1)->Option.map(buildTopicGetter),
@@ -447,17 +418,18 @@ let buildEvmOnEventRegistration = (
447
418
  // `where.block.number._gte` overrides the contract-level startBlock when
448
419
  // present (an explicit per-event opt-in that wins over `config.yaml`);
449
420
  // otherwise the contract/chain value passes through.
450
- let resolvedStartBlock = switch whereStartBlock {
421
+ let resolvedStartBlock = switch resolvedWhere.startBlock {
451
422
  | Some(_) as sb => sb
452
423
  | None => startBlock
453
424
  }
454
425
 
455
426
  {
427
+ index: -1,
456
428
  eventConfig: (eventConfig :> Internal.eventConfig),
457
429
  isWildcard,
458
430
  handler,
459
431
  contractRegister,
460
- getEventFiltersOrThrow,
432
+ resolvedWhere,
461
433
  filterByAddresses,
462
434
  clientAddressFilter: ?buildAddressFilter(addressFilterParamGroups, ~isWildcard),
463
435
  dependsOnAddresses: Internal.dependsOnAddresses(~isWildcard, ~filterByAddresses),
@@ -537,6 +509,7 @@ let buildSvmOnEventRegistration = (
537
509
  ~contractRegister: option<Internal.contractRegister>,
538
510
  ~startBlock: option<int>=?,
539
511
  ): Internal.svmOnEventRegistration => {
512
+ index: -1,
540
513
  eventConfig: (eventConfig :> Internal.eventConfig),
541
514
  handler,
542
515
  contractRegister,
@@ -614,6 +587,7 @@ let buildFuelOnEventRegistration = (
614
587
  ~contractRegister: option<Internal.contractRegister>,
615
588
  ~startBlock: option<int>=?,
616
589
  ): Internal.fuelOnEventRegistration => {
590
+ index: -1,
617
591
  eventConfig: (eventConfig :> Internal.eventConfig),
618
592
  handler,
619
593
  contractRegister,
@@ -2,11 +2,11 @@
2
2
 
3
3
  import * as Evm from "./sources/Evm.res.mjs";
4
4
  import * as Svm from "./sources/Svm.res.mjs";
5
+ import * as Core from "./Core.res.mjs";
5
6
  import * as Utils from "./Utils.res.mjs";
6
7
  import * as Address from "./Address.res.mjs";
7
8
  import * as FuelSDK from "./sources/FuelSDK.res.mjs";
8
9
  import * as Internal from "./Internal.res.mjs";
9
- import * as TopicFilter from "./TopicFilter.res.mjs";
10
10
  import * as LogSelection from "./LogSelection.res.mjs";
11
11
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
12
12
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
@@ -208,34 +208,24 @@ function buildSimulateParamsSchema(params) {
208
208
  }
209
209
 
210
210
  function getTopicEncoder(abiType) {
211
- if (abiType.endsWith("]") || abiType.startsWith("(")) {
212
- return TopicFilter.castToHexUnsafe;
213
- }
214
- switch (abiType) {
215
- case "address" :
216
- return TopicFilter.fromAddress;
217
- case "bool" :
218
- return TopicFilter.fromBool;
219
- case "bytes" :
220
- return TopicFilter.fromDynamicBytes;
221
- case "string" :
222
- return TopicFilter.fromDynamicString;
223
- default:
224
- if (abiType.startsWith("uint")) {
225
- return TopicFilter.fromBigInt;
226
- } else if (abiType.startsWith("int")) {
227
- return TopicFilter.fromSignedBigInt;
228
- } else if (abiType.startsWith("bytes")) {
229
- return TopicFilter.castToHexUnsafe;
230
- } else {
231
- return Stdlib_JsError.throwWithMessage(`Unsupported topic filter ABI type: ` + abiType);
232
- }
233
- }
211
+ return value => Core.getAddon().encodeIndexedTopic(abiType, value);
234
212
  }
235
213
 
236
214
  function buildTopicGetter(p) {
237
215
  let encoder = getTopicEncoder(p.abiType);
238
- return eventFilter => Stdlib_Option.mapOr(eventFilter[p.name], [], topicFilters => normalizeOrThrow(topicFilters).map(encoder));
216
+ let isTuple = p.abiType.startsWith("(");
217
+ return eventFilter => Stdlib_Option.mapOr(eventFilter[p.name], [], topicFilters => {
218
+ if (!isTuple) {
219
+ return normalizeOrThrow(topicFilters).map(encoder);
220
+ }
221
+ let encoded;
222
+ try {
223
+ encoded = encoder(topicFilters);
224
+ } catch (exn) {
225
+ return normalizeOrThrow(topicFilters).map(encoder);
226
+ }
227
+ return [encoded];
228
+ });
239
229
  }
240
230
 
241
231
  let alwaysIncludedBlockFields = [
@@ -309,13 +299,15 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, blockFiel
309
299
  };
310
300
  }
311
301
 
312
- function buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, eventFilters, probeChainId, onEventBlockFilterSchema, startBlock) {
302
+ function buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, where, chainId, onEventBlockFilterSchema, startBlock) {
313
303
  let indexedParams = eventConfig.paramsMetadata.filter(p => p.indexed);
314
- let match = LogSelection.parseEventFiltersOrThrow(eventFilters, eventConfig.sighash, indexedParams.map(p => p.name), eventConfig.contractName, probeChainId, onEventBlockFilterSchema, Stdlib_Option.map(indexedParams[0], buildTopicGetter), Stdlib_Option.map(indexedParams[1], buildTopicGetter), Stdlib_Option.map(indexedParams[2], buildTopicGetter));
315
- let whereStartBlock = match.startBlock;
304
+ let match = LogSelection.parseWhereOrThrow(where, eventConfig.sighash, indexedParams.map(p => p.name), eventConfig.contractName, chainId, onEventBlockFilterSchema, Stdlib_Option.map(indexedParams[0], buildTopicGetter), Stdlib_Option.map(indexedParams[1], buildTopicGetter), Stdlib_Option.map(indexedParams[2], buildTopicGetter));
316
305
  let filterByAddresses = match.filterByAddresses;
317
- let resolvedStartBlock = whereStartBlock !== undefined ? whereStartBlock : startBlock;
306
+ let resolvedWhere = match.resolvedWhere;
307
+ let sb = resolvedWhere.startBlock;
308
+ let resolvedStartBlock = sb !== undefined ? sb : startBlock;
318
309
  return {
310
+ index: -1,
319
311
  eventConfig: eventConfig,
320
312
  handler: handler,
321
313
  contractRegister: contractRegister,
@@ -324,7 +316,7 @@ function buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractR
324
316
  dependsOnAddresses: Internal.dependsOnAddresses(isWildcard, filterByAddresses),
325
317
  clientAddressFilter: buildAddressFilter(match.addressFilterParamGroups, isWildcard, undefined),
326
318
  startBlock: resolvedStartBlock,
327
- getEventFiltersOrThrow: match.getEventFiltersOrThrow
319
+ resolvedWhere: resolvedWhere
328
320
  };
329
321
  }
330
322
 
@@ -368,6 +360,7 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
368
360
 
369
361
  function buildSvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock) {
370
362
  return {
363
+ index: -1,
371
364
  eventConfig: eventConfig,
372
365
  handler: handler,
373
366
  contractRegister: contractRegister,
@@ -437,6 +430,7 @@ function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi) {
437
430
 
438
431
  function buildFuelOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock) {
439
432
  return {
433
+ index: -1,
440
434
  eventConfig: eventConfig,
441
435
  handler: handler,
442
436
  contractRegister: contractRegister,
@@ -73,9 +73,10 @@ let runEventHandlerOrThrow = async (
73
73
  )
74
74
  }
75
75
  let handlerDuration = timeBeforeHandler->Performance.secondsSince
76
+ let eventConfig = eventItem.onEventRegistration.eventConfig
76
77
  Prometheus.ProcessingHandler.increment(
77
- ~contract=eventItem.onEventRegistration.eventConfig.contractName,
78
- ~event=eventItem.onEventRegistration.eventConfig.name,
78
+ ~contract=eventConfig.contractName,
79
+ ~event=eventConfig.name,
79
80
  ~duration=handlerDuration,
80
81
  )
81
82
  }
@@ -121,8 +122,8 @@ let runHandlerOrThrow = async (
121
122
  }),
122
123
  )
123
124
  }
124
- | Event({onEventRegistration}) =>
125
- switch onEventRegistration.handler {
125
+ | Event(_) =>
126
+ switch (item->Internal.castUnsafeEventItem).onEventRegistration.handler {
126
127
  | Some(handler) =>
127
128
  await item->runEventHandlerOrThrow(
128
129
  ~handler,
@@ -161,7 +162,9 @@ let preloadBatchOrThrow = async (
161
162
  for idx in 0 to checkpointEventsProcessed - 1 {
162
163
  let item = batch.items->Array.getUnsafe(itemIdx.contents + idx)
163
164
  switch item {
164
- | Event({onEventRegistration: {handler, eventConfig: {contractName, name: eventName}}}) =>
165
+ | Event(_) =>
166
+ let {handler, eventConfig: {contractName, name: eventName}} =
167
+ (item->Internal.castUnsafeEventItem).onEventRegistration
165
168
  switch handler {
166
169
  | None => ()
167
170
  | Some(handler) =>
@@ -65,7 +65,8 @@ async function runEventHandlerOrThrow(item, checkpointId, handler, indexerState,
65
65
  };
66
66
  }
67
67
  let handlerDuration = Performance.secondsSince(timeBeforeHandler);
68
- return Prometheus.ProcessingHandler.increment(item.onEventRegistration.eventConfig.contractName, item.onEventRegistration.eventConfig.name, handlerDuration);
68
+ let eventConfig = item.onEventRegistration.eventConfig;
69
+ return Prometheus.ProcessingHandler.increment(eventConfig.contractName, eventConfig.name, handlerDuration);
69
70
  }
70
71
 
71
72
  async function runHandlerOrThrow(item, checkpointId, indexerState, loadManager, persistence, config, chains) {