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.
- package/index.d.ts +19 -0
- package/licenses/CLA.md +35 -0
- package/licenses/EULA.md +67 -0
- package/licenses/LICENSE.md +45 -0
- package/licenses/README.md +35 -0
- package/package.json +9 -8
- package/src/Batch.res.mjs +1 -1
- package/src/BatchProcessing.res +0 -7
- package/src/BatchProcessing.res.mjs +1 -8
- package/src/ChainFetching.res +30 -18
- package/src/ChainFetching.res.mjs +23 -13
- package/src/ChainState.res +205 -59
- package/src/ChainState.res.mjs +130 -27
- package/src/ChainState.resi +16 -1
- package/src/Config.res +9 -5
- package/src/Config.res.mjs +2 -2
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +147 -39
- package/src/CrossChainState.res.mjs +42 -10
- package/src/EffectState.res +100 -0
- package/src/EffectState.res.mjs +74 -0
- package/src/EffectState.resi +32 -0
- package/src/Envio.res +25 -7
- package/src/Envio.res.mjs +15 -19
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +30 -56
- package/src/EventConfigBuilder.res.mjs +24 -30
- package/src/EventProcessing.res +8 -5
- package/src/EventProcessing.res.mjs +2 -1
- package/src/FetchState.res +842 -474
- package/src/FetchState.res.mjs +545 -356
- package/src/HandlerLoader.res +1 -1
- package/src/HandlerLoader.res.mjs +1 -1
- package/src/HandlerRegister.res +478 -305
- package/src/HandlerRegister.res.mjs +277 -209
- package/src/HandlerRegister.resi +11 -6
- package/src/InMemoryStore.res +14 -26
- package/src/InMemoryStore.res.mjs +6 -19
- package/src/IndexerState.res +15 -39
- package/src/IndexerState.res.mjs +18 -29
- package/src/IndexerState.resi +2 -10
- package/src/Internal.res +121 -18
- package/src/Internal.res.mjs +96 -2
- package/src/LoadLayer.res +44 -24
- package/src/LoadLayer.res.mjs +43 -20
- package/src/LoadLayer.resi +1 -0
- package/src/LogSelection.res +92 -217
- package/src/LogSelection.res.mjs +95 -184
- package/src/Main.res +29 -143
- package/src/Main.res.mjs +25 -88
- package/src/Metrics.res +1 -1
- package/src/Metrics.res.mjs +1 -1
- package/src/Persistence.res +12 -3
- package/src/PgStorage.res +155 -82
- package/src/PgStorage.res.mjs +130 -77
- package/src/Prometheus.res +20 -10
- package/src/Prometheus.res.mjs +22 -10
- package/src/PruneStaleHistory.res +146 -35
- package/src/PruneStaleHistory.res.mjs +114 -20
- package/src/RawEvent.res +2 -2
- package/src/Rollback.res +32 -13
- package/src/Rollback.res.mjs +24 -11
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +1 -1
- package/src/SimulateItems.res +39 -7
- package/src/SimulateItems.res.mjs +28 -9
- package/src/TestIndexer.res +2 -2
- package/src/TestIndexer.res.mjs +2 -2
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/UserContext.res +62 -23
- package/src/UserContext.res.mjs +26 -6
- package/src/Writing.res +60 -21
- package/src/Writing.res.mjs +27 -9
- package/src/bindings/NodeJs.res +5 -0
- package/src/bindings/Viem.res +0 -5
- package/src/sources/EventRouter.res +0 -21
- package/src/sources/EventRouter.res.mjs +0 -12
- package/src/sources/Evm.res +4 -1
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/EvmChain.res +2 -27
- package/src/sources/EvmChain.res.mjs +2 -23
- package/src/sources/EvmRpcClient.res +58 -23
- package/src/sources/EvmRpcClient.res.mjs +11 -5
- package/src/sources/Fuel.res +3 -1
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/HyperSync.res +9 -38
- package/src/sources/HyperSync.res.mjs +16 -28
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +88 -11
- package/src/sources/HyperSyncClient.res.mjs +39 -6
- package/src/sources/HyperSyncSource.res +18 -210
- package/src/sources/HyperSyncSource.res.mjs +9 -137
- package/src/sources/Rpc.res +0 -32
- package/src/sources/Rpc.res.mjs +1 -46
- package/src/sources/RpcSource.res +129 -533
- package/src/sources/RpcSource.res.mjs +161 -379
- package/src/sources/SimulateSource.res +37 -19
- package/src/sources/SimulateSource.res.mjs +27 -10
- package/src/sources/SourceManager.res +4 -14
- package/src/sources/SourceManager.res.mjs +3 -9
- package/src/sources/SourceManager.resi +0 -2
- package/src/sources/SvmHyperSyncSource.res +13 -3
- 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.
|
|
33
|
+
let match$1 = options.crossChain;
|
|
34
|
+
let match$2 = options.rateLimit;
|
|
29
35
|
let tmp;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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)
|
|
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`
|
|
417
|
-
//
|
|
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
|
-
~
|
|
424
|
-
~
|
|
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
|
-
|
|
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
|
-
~
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
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,
|
package/src/EventProcessing.res
CHANGED
|
@@ -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=
|
|
78
|
-
~event=
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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) {
|