envio 3.3.0-alpha.7 → 3.3.0-alpha.9

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 (95) hide show
  1. package/index.d.ts +23 -5
  2. package/package.json +6 -6
  3. package/src/Api.res +1 -1
  4. package/src/Api.res.mjs +1 -1
  5. package/src/ChainFetching.res +21 -10
  6. package/src/ChainFetching.res.mjs +11 -10
  7. package/src/ChainState.res +401 -171
  8. package/src/ChainState.res.mjs +270 -104
  9. package/src/ChainState.resi +19 -7
  10. package/src/Config.res +11 -27
  11. package/src/Config.res.mjs +8 -7
  12. package/src/Core.res +7 -0
  13. package/src/CrossChainState.res +82 -82
  14. package/src/CrossChainState.res.mjs +52 -59
  15. package/src/CrossChainState.resi +1 -1
  16. package/src/Ecosystem.res +3 -3
  17. package/src/Ecosystem.res.mjs +3 -3
  18. package/src/Envio.res +14 -9
  19. package/src/EnvioGlobal.res +53 -0
  20. package/src/EnvioGlobal.res.mjs +31 -0
  21. package/src/EventConfigBuilder.res +117 -69
  22. package/src/EventConfigBuilder.res.mjs +71 -34
  23. package/src/EventProcessing.res +19 -15
  24. package/src/EventProcessing.res.mjs +13 -12
  25. package/src/FetchState.res +410 -185
  26. package/src/FetchState.res.mjs +268 -253
  27. package/src/HandlerLoader.res +8 -113
  28. package/src/HandlerLoader.res.mjs +2 -88
  29. package/src/HandlerRegister.res +518 -144
  30. package/src/HandlerRegister.res.mjs +285 -133
  31. package/src/HandlerRegister.resi +24 -8
  32. package/src/IndexerState.res +6 -3
  33. package/src/IndexerState.res.mjs +3 -3
  34. package/src/IndexerState.resi +1 -1
  35. package/src/IndexingAddresses.res +10 -7
  36. package/src/IndexingAddresses.res.mjs +8 -7
  37. package/src/IndexingAddresses.resi +3 -1
  38. package/src/Internal.res +104 -39
  39. package/src/Internal.res.mjs +11 -1
  40. package/src/LogSelection.res +102 -165
  41. package/src/LogSelection.res.mjs +101 -116
  42. package/src/Main.res +49 -164
  43. package/src/Main.res.mjs +39 -104
  44. package/src/Metrics.res +43 -2
  45. package/src/Metrics.res.mjs +39 -3
  46. package/src/Prometheus.res +0 -35
  47. package/src/Prometheus.res.mjs +33 -68
  48. package/src/RawEvent.res +7 -2
  49. package/src/RawEvent.res.mjs +4 -4
  50. package/src/RollbackCommit.res +4 -1
  51. package/src/RollbackCommit.res.mjs +3 -2
  52. package/src/SimulateItems.res +20 -7
  53. package/src/SimulateItems.res.mjs +7 -11
  54. package/src/TestIndexer.res +1 -1
  55. package/src/TestIndexer.res.mjs +1 -1
  56. package/src/sources/BlockStore.res +46 -0
  57. package/src/sources/BlockStore.res.mjs +24 -0
  58. package/src/sources/EventRouter.res +19 -12
  59. package/src/sources/EventRouter.res.mjs +7 -5
  60. package/src/sources/Evm.res +58 -5
  61. package/src/sources/Evm.res.mjs +44 -5
  62. package/src/sources/EvmChain.res +14 -18
  63. package/src/sources/EvmChain.res.mjs +12 -13
  64. package/src/sources/FieldMask.res +39 -0
  65. package/src/sources/FieldMask.res.mjs +42 -0
  66. package/src/sources/Fuel.res +8 -3
  67. package/src/sources/Fuel.res.mjs +5 -4
  68. package/src/sources/HyperFuelSource.res +31 -40
  69. package/src/sources/HyperFuelSource.res.mjs +52 -44
  70. package/src/sources/HyperSync.res +31 -9
  71. package/src/sources/HyperSync.res.mjs +47 -31
  72. package/src/sources/HyperSync.resi +10 -3
  73. package/src/sources/HyperSyncClient.res +15 -4
  74. package/src/sources/HyperSyncHeightStream.res +1 -8
  75. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  76. package/src/sources/HyperSyncSource.res +58 -90
  77. package/src/sources/HyperSyncSource.res.mjs +57 -66
  78. package/src/sources/RpcSource.res +118 -120
  79. package/src/sources/RpcSource.res.mjs +92 -74
  80. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  81. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  82. package/src/sources/SimulateSource.res +5 -3
  83. package/src/sources/SimulateSource.res.mjs +12 -4
  84. package/src/sources/Source.res +19 -5
  85. package/src/sources/SourceManager.res +60 -10
  86. package/src/sources/SourceManager.res.mjs +54 -8
  87. package/src/sources/SourceManager.resi +10 -0
  88. package/src/sources/Svm.res +14 -10
  89. package/src/sources/Svm.res.mjs +23 -6
  90. package/src/sources/SvmHyperSyncClient.res +5 -6
  91. package/src/sources/SvmHyperSyncSource.res +82 -41
  92. package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
  93. package/src/sources/TransactionStore.res +6 -107
  94. package/src/sources/TransactionStore.res.mjs +5 -86
  95. package/svm.schema.json +19 -0
@@ -8,14 +8,15 @@ function deriveEffectiveStartBlock(registrationBlock, contractStartBlock) {
8
8
  return Primitive_int.max(Primitive_int.max(registrationBlock, 0), Stdlib_Option.getOr(contractStartBlock, 0));
9
9
  }
10
10
 
11
- function makeContractConfigs(eventConfigs) {
11
+ function makeContractConfigs(onEventRegistrations) {
12
12
  let contractConfigs = {};
13
- eventConfigs.forEach(ec => {
14
- let match = contractConfigs[ec.contractName];
13
+ onEventRegistrations.forEach(reg => {
14
+ let contractName = reg.eventConfig.contractName;
15
+ let match = contractConfigs[contractName];
15
16
  if (match !== undefined) {
16
17
  let startBlock = match.startBlock;
17
- let match$1 = ec.startBlock;
18
- contractConfigs[ec.contractName] = {
18
+ let match$1 = reg.startBlock;
19
+ contractConfigs[contractName] = {
19
20
  startBlock: startBlock !== undefined ? (
20
21
  match$1 !== undefined ? Primitive_int.min(startBlock, match$1) : startBlock
21
22
  ) : (
@@ -24,8 +25,8 @@ function makeContractConfigs(eventConfigs) {
24
25
  };
25
26
  return;
26
27
  }
27
- contractConfigs[ec.contractName] = {
28
- startBlock: ec.startBlock
28
+ contractConfigs[contractName] = {
29
+ startBlock: reg.startBlock
29
30
  };
30
31
  });
31
32
  return contractConfigs;
@@ -6,7 +6,9 @@ type t
6
6
 
7
7
  let deriveEffectiveStartBlock: (~registrationBlock: int, ~contractStartBlock: option<int>) => int
8
8
 
9
- let makeContractConfigs: (~eventConfigs: array<Internal.eventConfig>) => dict<contractConfig>
9
+ let makeContractConfigs: (
10
+ ~onEventRegistrations: array<Internal.onEventRegistration>,
11
+ ) => dict<contractConfig>
10
12
 
11
13
  let makeIndexingAddress: (
12
14
  ~contract: Internal.indexingAddress,
package/src/Internal.res CHANGED
@@ -166,6 +166,19 @@ let allSvmTransactionFields: array<svmTransactionField> = [
166
166
  ]
167
167
  let svmTransactionFieldSchema = S.enum(allSvmTransactionFields)
168
168
 
169
+ // All SVM block fields. `slot`/`time`/`hash` are always included; the rest are
170
+ // selectable via `field_selection.block_fields` (see `allSvmBlockFields`).
171
+ type svmBlockField =
172
+ | @as("slot") Slot
173
+ | @as("time") Time
174
+ | @as("hash") Hash
175
+ | @as("height") Height
176
+ | @as("parentSlot") ParentSlot
177
+ | @as("parentHash") ParentHash
178
+
179
+ let allSvmBlockFields: array<svmBlockField> = [Height, ParentSlot, ParentHash]
180
+ let svmBlockFieldSchema = S.enum(allSvmBlockFields)
181
+
169
182
  // Static sets of nullable field names — used by RpcSource and HyperSyncSource to wrap schemas with S.nullable
170
183
  let evmNullableBlockFields = Utils.Set.fromArray(
171
184
  (
@@ -295,8 +308,8 @@ type genericEvent<'params, 'block, 'transaction> = {
295
308
  block: 'block,
296
309
  }
297
310
 
298
- // Opaque internally — block-level values needed by the runtime
299
- // (blockNumber, timestamp, blockHash) live on the item instead.
311
+ // Opaque internally — the block number needed by the runtime lives on the
312
+ // item instead.
300
313
  type event
301
314
 
302
315
  // Opaque payload an item carries. A source builds an ecosystem-specific
@@ -312,6 +325,11 @@ type eventPayload
312
325
  @get external getPayloadTransaction: eventPayload => Nullable.t<eventTransaction> = "transaction"
313
326
  @set external setPayloadTransaction: (eventPayload, eventTransaction) => unit = "transaction"
314
327
 
328
+ // Generic access to the payload's `block`: written/enriched at batch prep for
329
+ // store-backed ecosystems (EVM/SVM HyperSync) and present inline otherwise.
330
+ @get external getPayloadBlock: eventPayload => Nullable.t<eventBlock> = "block"
331
+ @set external setPayloadBlock: (eventPayload, eventBlock) => unit = "block"
332
+
315
333
  type genericLoaderArgs<'event, 'context> = {
316
334
  event: 'event,
317
335
  context: 'context,
@@ -390,31 +408,17 @@ type indexingContract = {
390
408
  effectiveStartBlock: int,
391
409
  }
392
410
 
393
- // This is private so it's not manually constructed internally
394
- // The idea is that it can only be coerced from fuel/evmEventConfig
395
- // and it can include their fields. We prevent manual creation,
396
- // so the fields are not overwritten and we can safely cast the type back to fuel/evmEventConfig
411
+ // Definition of an event/instruction we know how to decode: identity + decode
412
+ // schemas + chain-independent field selection. A pure function of the ABI +
413
+ // config, shared across chains. `private` so it can only be coerced from an
414
+ // ecosystem variant (fields never overwritten), which lets sources cast the
415
+ // base back down to evm/fuel/svm safely.
397
416
  type eventConfig = private {
398
417
  id: string,
399
418
  name: string,
400
419
  contractName: string,
401
- isWildcard: bool,
402
- // Whether the event has an event filter which uses addresses
403
- filterByAddresses: bool,
404
- // Usually always false for wildcard events
405
- // But might be true for wildcard event with dynamic event filter by addresses
406
- dependsOnAddresses: bool,
407
- // Precompiled predicate (EVM only) for events that filter an indexed address
408
- // param by registered addresses. Given the decoded event and the log's block
409
- // number, drops an event whose param-address isn't registered at/before that
410
- // block — the param-level analogue of EventRouter's srcAddress
411
- // `effectiveStartBlock` check. Absent otherwise.
412
- clientAddressFilter?: (eventPayload, int, dict<indexingContract>) => bool,
413
- handler: option<handler>,
414
- contractRegister: option<contractRegister>,
415
420
  paramsRawEventSchema: S.schema<eventParams>,
416
421
  simulateParamsSchema: S.schema<eventParams>,
417
- startBlock: option<int>,
418
422
  // Field names selected for the chain's transaction-store materialisation
419
423
  // (camelCase, matching the ecosystem's `transactionFields`). Stored as a
420
424
  // string set so the shared mask logic is ecosystem-agnostic; sources recover
@@ -425,6 +429,12 @@ type eventConfig = private {
425
429
  // so each transaction decodes only the fields its event selected. `0.` when
426
430
  // nothing is selected or the ecosystem carries the transaction inline (Fuel).
427
431
  transactionFieldMask: float,
432
+ // Selected block fields precompiled to the block-store selection bitmask (bit
433
+ // per ecosystem field code). `0.` for ecosystems that carry the block fully
434
+ // inline (RPC/Fuel). The EVM selection always includes number/timestamp/hash,
435
+ // so an EVM mask always has their bits set; SVM stamps slot/time/hash inline
436
+ // from the response and its mask is the user's selection alone.
437
+ blockFieldMask: float,
428
438
  }
429
439
 
430
440
  type fuelEventKind =
@@ -449,29 +459,46 @@ type topicSelection = {
449
459
  topic3: array<EvmTypes.Hex.t>,
450
460
  }
451
461
 
462
+ // A single topic position of a resolved `where`: either static pre-encoded
463
+ // values, or a marker for "the currently registered addresses of this
464
+ // contract", expanded to topic values when a source query is built.
465
+ type topicFilter =
466
+ | Values(array<EvmTypes.Hex.t>)
467
+ | ContractAddresses({contractName: string})
468
+
469
+ type resolvedTopicSelection = {
470
+ topic0: array<EvmTypes.Hex.t>,
471
+ topic1: topicFilter,
472
+ topic2: topicFilter,
473
+ topic3: topicFilter,
474
+ }
475
+
476
+ // The registered `where` fully resolved at registration time for one chain.
477
+ // `topicSelections` is in disjunctive normal form (outer array is OR);
478
+ // an empty array means the `where` returned `false` for this chain.
479
+ type resolvedWhere = {
480
+ topicSelections: array<resolvedTopicSelection>,
481
+ startBlock: option<int>,
482
+ }
483
+
452
484
  // Per-event, per-invocation arguments passed to a `where` callback. The
453
485
  // concrete `chain` shape (which contract key it exposes) is generated per
454
486
  // event in user-project codegen — here it's an open record so codegen'd
455
487
  // types subtype-coerce into it cleanly.
456
488
  type onEventWhereArgs<'chain> = {chain: 'chain}
457
489
 
458
- type eventFilters =
459
- Static(array<topicSelection>) | Dynamic(array<Address.t> => array<topicSelection>)
460
-
461
490
  type evmEventConfig = {
462
491
  ...eventConfig,
463
- getEventFiltersOrThrow: ChainMap.Chain.t => eventFilters,
464
492
  selectedBlockFields: Utils.Set.t<evmBlockField>,
465
493
  sighash: string,
466
494
  topicCount: int,
467
495
  paramsMetadata: array<paramMeta>,
468
496
  }
469
497
 
470
- // Shared formula for `eventConfig.dependsOnAddresses`. Kept here so
471
- // `EventConfigBuilder.build{Evm,Fuel}EventConfig` and
472
- // `HandlerLoader.applyRegistrations` stay in sync when handler state flips
473
- // the value. Fuel events always have `filterByAddresses=false`, so callers
474
- // there simply pass it through as `false`.
498
+ // Shared formula for a registration's `dependsOnAddresses`. Kept here so the
499
+ // `EventConfigBuilder.build*OnEventRegistration` builders stay in sync. Fuel
500
+ // and SVM events always have `filterByAddresses=false`, so callers there pass
501
+ // it through as `false`.
475
502
  let dependsOnAddresses = (~isWildcard, ~filterByAddresses) => !isWildcard || filterByAddresses
476
503
 
477
504
  type evmContractConfig = {
@@ -490,6 +517,10 @@ type svmAccountFilterGroup = array<svmAccountFilter>
490
517
 
491
518
  type svmInstructionEventConfig = {
492
519
  ...eventConfig,
520
+ /** Block fields selected via `field_selection.block_fields` (`slot` is always
521
+ included and excluded from this set). Drives the block query columns;
522
+ precompiled to `blockFieldMask` for store materialisation. */
523
+ selectedBlockFields: Utils.Set.t<svmBlockField>,
493
524
  /** Base58 Solana program id this instruction belongs to. */
494
525
  programId: SvmTypes.Pubkey.t,
495
526
  /** Hex-encoded discriminator. `None` matches every instruction in the program. */
@@ -516,6 +547,44 @@ type svmInstructionEventConfig = {
516
547
  definedTypes: JSON.t,
517
548
  }
518
549
 
550
+ // Per-(event, chain) registration produced when user handler code registers an
551
+ // event (`onEvent`) or a dynamic contract registers. References its definition
552
+ // by value as `.eventConfig` and adds the handler binding plus the
553
+ // registration/`where`-derived fetch state. Not `private`: Fuel/SVM
554
+ // registrations add no ecosystem-specific fields (so they're bare aliases that
555
+ // must stay directly constructable), and the evm→base cast in sources is sound
556
+ // by ecosystem homogeneity — an EVM chain only ever holds `evmOnEventRegistration`s.
557
+ type onEventRegistration = {
558
+ eventConfig: eventConfig,
559
+ handler: option<handler>,
560
+ contractRegister: option<contractRegister>,
561
+ isWildcard: bool,
562
+ // Whether the event has an event filter which uses addresses.
563
+ filterByAddresses: bool,
564
+ // Usually always false for wildcard events, but might be true for a wildcard
565
+ // event with a dynamic event filter by addresses.
566
+ dependsOnAddresses: bool,
567
+ // Precompiled predicate for events that filter an indexed address param by
568
+ // registered addresses (see `EventConfigBuilder.buildAddressFilter`); drops a
569
+ // decoded event whose param-address isn't registered at/before the log's
570
+ // block. Absent otherwise.
571
+ clientAddressFilter?: (eventPayload, int, dict<indexingContract>) => bool,
572
+ // Final start block: the contract/chain config value, overridden by a
573
+ // `where.block.number._gte` when the registered `where` supplies one.
574
+ startBlock: option<int>,
575
+ }
576
+
577
+ type evmOnEventRegistration = {
578
+ ...onEventRegistration,
579
+ resolvedWhere: resolvedWhere,
580
+ }
581
+
582
+ // Fuel and SVM registrations add no ecosystem-specific fetch state (their
583
+ // filters are config-derived and live on the definition), so they're bare
584
+ // aliases of the base registration.
585
+ type fuelOnEventRegistration = onEventRegistration
586
+ type svmOnEventRegistration = onEventRegistration
587
+
519
588
  type svmProgramConfig = {
520
589
  name: string,
521
590
  programId: SvmTypes.Pubkey.t,
@@ -536,11 +605,9 @@ type dcs = array<indexingAddress>
536
605
  // to make item properly unboxed
537
606
  type eventItem = private {
538
607
  kind: [#0],
539
- eventConfig: eventConfig,
540
- timestamp: int,
608
+ onEventRegistration: onEventRegistration,
541
609
  chain: ChainMap.Chain.t,
542
610
  blockNumber: int,
543
- blockHash: string,
544
611
  logIndex: int,
545
612
  // Within-block transaction index — the key into the per-chain transaction
546
613
  // store. Unused (0) for ecosystems that carry the transaction inline (Fuel).
@@ -575,7 +642,7 @@ type onBlockArgs = {
575
642
  context: handlerContext,
576
643
  }
577
644
 
578
- type onBlockConfig = {
645
+ type onBlockRegistration = {
579
646
  // When there are multiple onBlock handlers per chain,
580
647
  // we want to use the order they are defined for sorting
581
648
  index: int,
@@ -591,16 +658,14 @@ type onBlockConfig = {
591
658
  type item =
592
659
  | @as(0)
593
660
  Event({
594
- eventConfig: eventConfig,
595
- timestamp: int,
661
+ onEventRegistration: onEventRegistration,
596
662
  chain: ChainMap.Chain.t,
597
663
  blockNumber: int,
598
- blockHash: string,
599
664
  logIndex: int,
600
665
  transactionIndex: int,
601
666
  payload: eventPayload,
602
667
  })
603
- | @as(1) Block({onBlockConfig: onBlockConfig, blockNumber: int, logIndex: int})
668
+ | @as(1) Block({onBlockRegistration: onBlockRegistration, blockNumber: int, logIndex: int})
604
669
 
605
670
  external castUnsafeEventItem: item => eventItem = "%identity"
606
671
 
@@ -612,7 +677,7 @@ external getItemLogIndex: item => int = "logIndex"
612
677
  let getItemChainId = item =>
613
678
  switch item {
614
679
  | Event({chain}) => chain->ChainMap.Chain.toChainId
615
- | Block({onBlockConfig: {chainId}}) => chainId
680
+ | Block({onBlockRegistration: {chainId}}) => chainId
616
681
  }
617
682
 
618
683
  @get
@@ -90,6 +90,14 @@ let allSvmTransactionFields = [
90
90
 
91
91
  let svmTransactionFieldSchema = S$RescriptSchema.$$enum(allSvmTransactionFields);
92
92
 
93
+ let allSvmBlockFields = [
94
+ "height",
95
+ "parentSlot",
96
+ "parentHash"
97
+ ];
98
+
99
+ let svmBlockFieldSchema = S$RescriptSchema.$$enum(allSvmBlockFields);
100
+
93
101
  let evmNullableBlockFields = new Set([
94
102
  "nonce",
95
103
  "difficulty",
@@ -141,7 +149,7 @@ function getItemChainId(item) {
141
149
  if (item.kind === 0) {
142
150
  return item.chain;
143
151
  } else {
144
- return item.onBlockConfig.chainId;
152
+ return item.onBlockRegistration.chainId;
145
153
  }
146
154
  }
147
155
 
@@ -178,6 +186,8 @@ export {
178
186
  evmTransactionFieldSchema,
179
187
  allSvmTransactionFields,
180
188
  svmTransactionFieldSchema,
189
+ allSvmBlockFields,
190
+ svmBlockFieldSchema,
181
191
  evmNullableBlockFields,
182
192
  evmNullableTransactionFields,
183
193
  dependsOnAddresses,