envio 3.3.0-alpha.2 → 3.3.0-alpha.3

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 (64) hide show
  1. package/index.d.ts +23 -21
  2. package/package.json +6 -6
  3. package/src/ChainFetching.res +25 -0
  4. package/src/ChainFetching.res.mjs +19 -0
  5. package/src/CrossChainState.res +15 -0
  6. package/src/CrossChainState.res.mjs +12 -0
  7. package/src/Ecosystem.res +0 -2
  8. package/src/EventConfigBuilder.res +42 -18
  9. package/src/EventConfigBuilder.res.mjs +21 -9
  10. package/src/EventProcessing.res +53 -37
  11. package/src/EventProcessing.res.mjs +41 -36
  12. package/src/FetchState.res +53 -43
  13. package/src/FetchState.res.mjs +71 -54
  14. package/src/HandlerLoader.res +10 -1
  15. package/src/HandlerLoader.res.mjs +15 -8
  16. package/src/Internal.res +3 -3
  17. package/src/Internal.res.mjs +1 -1
  18. package/src/LoadLayer.res +5 -5
  19. package/src/LoadLayer.res.mjs +6 -6
  20. package/src/PgStorage.res +4 -4
  21. package/src/PgStorage.res.mjs +5 -5
  22. package/src/Prometheus.res +10 -10
  23. package/src/Prometheus.res.mjs +9 -9
  24. package/src/PruneStaleHistory.res +2 -2
  25. package/src/PruneStaleHistory.res.mjs +3 -3
  26. package/src/Rollback.res +2 -2
  27. package/src/Rollback.res.mjs +3 -3
  28. package/src/SimulateItems.res +77 -15
  29. package/src/SimulateItems.res.mjs +63 -20
  30. package/src/TestIndexer.res +48 -36
  31. package/src/TestIndexer.res.mjs +35 -28
  32. package/src/bindings/Performance.res +7 -0
  33. package/src/bindings/Performance.res.mjs +21 -0
  34. package/src/bindings/Performance.resi +7 -0
  35. package/src/sources/EventRouter.res +17 -21
  36. package/src/sources/EventRouter.res.mjs +6 -9
  37. package/src/sources/Evm.res +1 -35
  38. package/src/sources/Evm.res.mjs +2 -37
  39. package/src/sources/Fuel.res +0 -2
  40. package/src/sources/Fuel.res.mjs +0 -6
  41. package/src/sources/HyperFuelSource.res +10 -11
  42. package/src/sources/HyperFuelSource.res.mjs +11 -11
  43. package/src/sources/HyperSync.res +3 -0
  44. package/src/sources/HyperSync.res.mjs +1 -0
  45. package/src/sources/HyperSync.resi +1 -0
  46. package/src/sources/HyperSyncClient.res +5 -1
  47. package/src/sources/HyperSyncSource.res +42 -24
  48. package/src/sources/HyperSyncSource.res.mjs +30 -26
  49. package/src/sources/RpcSource.res +7 -8
  50. package/src/sources/RpcSource.res.mjs +8 -8
  51. package/src/sources/SimulateSource.res +1 -1
  52. package/src/sources/Source.res +1 -1
  53. package/src/sources/SourceManager.res +17 -16
  54. package/src/sources/SourceManager.res.mjs +10 -15
  55. package/src/sources/Svm.res +4 -10
  56. package/src/sources/Svm.res.mjs +5 -12
  57. package/src/sources/SvmHyperSyncSource.res +24 -18
  58. package/src/sources/SvmHyperSyncSource.res.mjs +22 -15
  59. package/src/sources/TransactionStore.res +59 -42
  60. package/src/sources/TransactionStore.res.mjs +21 -1
  61. package/svm.schema.json +30 -37
  62. package/src/bindings/Hrtime.res +0 -58
  63. package/src/bindings/Hrtime.res.mjs +0 -90
  64. package/src/bindings/Hrtime.resi +0 -30
@@ -20,16 +20,10 @@ let transactionFields = [
20
20
  "tokenBalances",
21
21
  ]
22
22
 
23
- // Field name → bit index (the code shared with the Rust store), built once.
24
- let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields)
25
-
26
- let transactionFieldMask = (eventConfigs: array<Internal.eventConfig>): float =>
27
- eventConfigs->TransactionStore.mask(~codes=transactionFieldCodes)
23
+ let transactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
28
24
 
29
25
  let make = (~logger: Pino.t): Ecosystem.t => {
30
26
  name: Svm,
31
- blockFields: ["slot"],
32
- transactionFields,
33
27
  blockNumberName: "height",
34
28
  blockTimestampName: "time",
35
29
  blockHashName: "hash",
@@ -94,9 +88,9 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
94
88
  getBlockHashes: (~blockNumbers as _, ~logger as _) =>
95
89
  JsError.throwWithMessage("Svm does not support getting block hashes"),
96
90
  getHeightOrThrow: async () => {
97
- let timerRef = Hrtime.makeTimer()
91
+ let timerRef = Performance.now()
98
92
  let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
99
- let seconds = timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat
93
+ let seconds = timerRef->Performance.secondsSince
100
94
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getSlot")
101
95
  Prometheus.SourceRequestCount.addSeconds(
102
96
  ~sourceName=name,
@@ -110,7 +104,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
110
104
  ~fromBlock as _,
111
105
  ~toBlock as _,
112
106
  ~addressesByContractName as _,
113
- ~indexingAddresses as _,
107
+ ~contractNameByAddress as _,
114
108
  ~knownHeight as _,
115
109
  ~partitionId as _,
116
110
  ~selection as _,
@@ -3,9 +3,9 @@
3
3
  import * as Rpc from "./Rpc.res.mjs";
4
4
  import * as Rest from "../vendored/Rest.res.mjs";
5
5
  import * as Utils from "../Utils.res.mjs";
6
- import * as Hrtime from "../bindings/Hrtime.res.mjs";
7
6
  import * as Logging from "../Logging.res.mjs";
8
7
  import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as Performance from "../bindings/Performance.res.mjs";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
10
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
11
11
  import * as TransactionStore from "./TransactionStore.res.mjs";
@@ -30,17 +30,11 @@ let transactionFields = [
30
30
  "tokenBalances"
31
31
  ];
32
32
 
33
- let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields);
34
-
35
- function transactionFieldMask(eventConfigs) {
36
- return TransactionStore.mask(eventConfigs, transactionFieldCodes);
37
- }
33
+ let transactionFieldMask = TransactionStore.makeMaskFn(transactionFields);
38
34
 
39
35
  function make(logger) {
40
36
  return {
41
37
  name: "svm",
42
- blockFields: ["slot"],
43
- transactionFields: transactionFields,
44
38
  blockNumberName: "height",
45
39
  blockTimestampName: "time",
46
40
  blockHashName: "hash",
@@ -89,9 +83,9 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
89
83
  pollingInterval: 10000,
90
84
  getBlockHashes: (param, param$1) => Stdlib_JsError.throwWithMessage("Svm does not support getting block hashes"),
91
85
  getHeightOrThrow: async () => {
92
- let timerRef = Hrtime.makeTimer();
86
+ let timerRef = Performance.now();
93
87
  let height = await Rest.fetch(route, undefined, client);
94
- let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef));
88
+ let seconds = Performance.secondsSince(timerRef);
95
89
  Prometheus.SourceRequestCount.increment(name, chain, "getSlot");
96
90
  Prometheus.SourceRequestCount.addSeconds(name, chain, "getSlot", seconds);
97
91
  return height;
@@ -103,10 +97,9 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
103
97
  export {
104
98
  cleanUpRawEventFieldsInPlace,
105
99
  transactionFields,
106
- transactionFieldCodes,
107
100
  transactionFieldMask,
108
101
  make,
109
102
  GetFinalizedSlot,
110
103
  makeRPCSource,
111
104
  }
112
- /* transactionFieldCodes Not a pure module */
105
+ /* transactionFieldMask Not a pure module */
@@ -216,11 +216,11 @@ let probeRouter = (
216
216
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
217
217
  byteLengthsDesc: array<int>,
218
218
  ~contractAddress,
219
- ~indexingAddresses,
219
+ ~contractNameByAddress,
220
220
  ) => {
221
221
  let probe = (dN: option<string>) => {
222
222
  let tag = EventRouter.getSvmEventId(~programId, ~discriminator=dN)
223
- router->EventRouter.get(~tag, ~contractAddress, ~blockNumber=instr.slot, ~indexingAddresses)
223
+ router->EventRouter.get(~tag, ~contractAddress, ~contractNameByAddress)
224
224
  }
225
225
 
226
226
  let result = byteLengthsDesc->Array.reduce(None, (acc, len) =>
@@ -319,26 +319,32 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
319
319
  )
320
320
  fields
321
321
  }
322
- // Every selected field except `tokenBalances` (its own table) is read off a
323
- // stored transaction record, so the transaction table must be fetched to
324
- // populate the store even when only `transactionIndex` (a key column) is
325
- // selected.
326
- let txFieldCount = selectedTxFields->Utils.Set.size
327
- let needsTransactions = txFieldCount > 1 || (txFieldCount === 1 && !needsTokenBalances)
322
+ // The transaction table is fetched only when a selected field is actually read
323
+ // off a stored transaction record. `transactionIndex` materialises from the
324
+ // store key and `tokenBalances` lives in its own table, so neither requires it.
325
+ let needsTransactions =
326
+ selectedTxFields
327
+ ->Utils.Set.toArray
328
+ ->Array.some(field =>
329
+ switch field {
330
+ | Internal.TransactionIndex | Internal.TokenBalances => false
331
+ | _ => true
332
+ }
333
+ )
328
334
 
329
335
  let getItemsOrThrow = async (
330
336
  ~fromBlock,
331
337
  ~toBlock,
332
338
  ~addressesByContractName as _,
333
- ~indexingAddresses,
339
+ ~contractNameByAddress,
334
340
  ~knownHeight,
335
341
  ~partitionId as _,
336
342
  ~selection as _,
337
343
  ~retry,
338
344
  ~logger,
339
345
  ) => {
340
- let totalTimeRef = Hrtime.makeTimer()
341
- let pageFetchRef = Hrtime.makeTimer()
346
+ let totalTimeRef = Performance.now()
347
+ let pageFetchRef = Performance.now()
342
348
 
343
349
  let instructionSelections = buildInstructionSelections(eventConfigs)
344
350
  // Under the server's default merge mode, requesting a table's columns is
@@ -384,9 +390,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
384
390
  ),
385
391
  )
386
392
  }
387
- let pageFetchTime = pageFetchRef->Hrtime.timeSince->Hrtime.toSecondsFloat
393
+ let pageFetchTime = pageFetchRef->Performance.secondsSince
388
394
 
389
- let parsingRef = Hrtime.makeTimer()
395
+ let parsingRef = Performance.now()
390
396
 
391
397
  // Per-slot unix timestamp lookup from the response's `blocks` table. Slots
392
398
  // without a block row (rare; usually skipped slots) fall back to `None`.
@@ -434,7 +440,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
434
440
  instr,
435
441
  byteLengths,
436
442
  ~contractAddress,
437
- ~indexingAddresses,
443
+ ~contractNameByAddress,
438
444
  )
439
445
 
440
446
  switch maybeConfig {
@@ -492,7 +498,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
492
498
  let _ = logger
493
499
  })
494
500
 
495
- let parsingTimeElapsed = parsingRef->Hrtime.timeSince->Hrtime.toSecondsFloat
501
+ let parsingTimeElapsed = parsingRef->Performance.secondsSince
496
502
  let highestSlot = resp.nextSlot - 1
497
503
  let latestBlockTime =
498
504
  blockTimeBySlot
@@ -507,7 +513,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
507
513
  blockHash: b.blockhash,
508
514
  })
509
515
 
510
- let totalTimeElapsed = totalTimeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
516
+ let totalTimeElapsed = totalTimeRef->Performance.secondsSince
511
517
 
512
518
  {
513
519
  latestFetchedBlockTimestamp: latestBlockTime,
@@ -597,9 +603,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
597
603
  poweredByHyperSync: true,
598
604
  getBlockHashes,
599
605
  getHeightOrThrow: async () => {
600
- let timer = Hrtime.makeTimer()
606
+ let timer = Performance.now()
601
607
  let h = await client.getHeight()
602
- let seconds = timer->Hrtime.timeSince->Hrtime.toSecondsFloat
608
+ let seconds = timer->Performance.secondsSince
603
609
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getHeight")
604
610
  Prometheus.SourceRequestCount.addSeconds(
605
611
  ~sourceName=name,
@@ -1,9 +1,9 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Hrtime from "../bindings/Hrtime.res.mjs";
4
3
  import * as Source from "./Source.res.mjs";
5
4
  import * as Prometheus from "../Prometheus.res.mjs";
6
5
  import * as EventRouter from "./EventRouter.res.mjs";
6
+ import * as Performance from "../bindings/Performance.res.mjs";
7
7
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
8
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
@@ -185,10 +185,10 @@ function toSvmInstruction(instr, programName, instructionName, logs, block) {
185
185
  };
186
186
  }
187
187
 
188
- function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
188
+ function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, contractNameByAddress) {
189
189
  let probe = dN => {
190
190
  let tag = EventRouter.getSvmEventId(programId, dN);
191
- return EventRouter.get(router, tag, contractAddress, instr.slot, indexingAddresses);
191
+ return EventRouter.get(router, tag, contractAddress, contractNameByAddress);
192
192
  };
193
193
  let result = Stdlib_Array.reduce(byteLengthsDesc, undefined, (acc, len) => {
194
194
  if (acc !== undefined) {
@@ -279,11 +279,18 @@ function make(param) {
279
279
  return;
280
280
  }
281
281
  });
282
- let txFieldCount = selectedTxFields.size;
283
- let needsTransactions = txFieldCount > 1 || txFieldCount === 1 && !needsTokenBalances;
284
- let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
285
- let totalTimeRef = Hrtime.makeTimer();
286
- let pageFetchRef = Hrtime.makeTimer();
282
+ let needsTransactions = Array.from(selectedTxFields).some(field => {
283
+ switch (field) {
284
+ case "transactionIndex" :
285
+ case "tokenBalances" :
286
+ return false;
287
+ default:
288
+ return true;
289
+ }
290
+ });
291
+ let getItemsOrThrow = async (fromBlock, toBlock, param, contractNameByAddress, knownHeight, param$1, param$2, retry, logger) => {
292
+ let totalTimeRef = Performance.now();
293
+ let pageFetchRef = Performance.now();
287
294
  let instructionSelections = buildInstructionSelections(eventConfigs);
288
295
  let fields_block = [
289
296
  "slot",
@@ -344,8 +351,8 @@ function make(param) {
344
351
  };
345
352
  }
346
353
  let resp = match[0];
347
- let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef));
348
- let parsingRef = Hrtime.makeTimer();
354
+ let pageFetchTime = Performance.secondsSince(pageFetchRef);
355
+ let parsingRef = Performance.now();
349
356
  let blockTimeBySlot = {};
350
357
  resp.data.blocks.forEach(b => {
351
358
  let t = b.blockTime;
@@ -377,7 +384,7 @@ function make(param) {
377
384
  let programId = instr.programId;
378
385
  let byteLengths = Stdlib_Option.getOr(orderingByProgram[instr.programId], []);
379
386
  let contractAddress = instr.programId;
380
- let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses);
387
+ let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, contractNameByAddress);
381
388
  if (maybeConfig !== undefined) {
382
389
  let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
383
390
  let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
@@ -404,14 +411,14 @@ function make(param) {
404
411
  });
405
412
  }
406
413
  });
407
- let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingRef));
414
+ let parsingTimeElapsed = Performance.secondsSince(parsingRef);
408
415
  let highestSlot = resp.nextSlot - 1 | 0;
409
416
  let latestBlockTime = Stdlib_Option.getOr(blockTimeBySlot[highestSlot.toString()], 0);
410
417
  let blockHashes = resp.data.blocks.map(b => ({
411
418
  blockHash: b.blockhash,
412
419
  blockNumber: b.slot
413
420
  }));
414
- let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef));
421
+ let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
415
422
  return {
416
423
  knownHeight: knownHeight,
417
424
  blockHashes: blockHashes,
@@ -513,9 +520,9 @@ function make(param) {
513
520
  pollingInterval: 1000,
514
521
  getBlockHashes: getBlockHashes,
515
522
  getHeightOrThrow: async () => {
516
- let timer = Hrtime.makeTimer();
523
+ let timer = Performance.now();
517
524
  let h = await client.getHeight();
518
- let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timer));
525
+ let seconds = Performance.secondsSince(timer);
519
526
  Prometheus.SourceRequestCount.increment(name, chain, "getHeight");
520
527
  Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds);
521
528
  return h;
@@ -34,6 +34,13 @@ let mask = (eventConfigs: array<Internal.eventConfig>, ~codes: dict<int>): float
34
34
  selected->Utils.Set.toArray->Array.reduce(0., (mask, code) => mask +. pow2(code))
35
35
  }
36
36
 
37
+ // Build an ecosystem's mask function from its ordered field-name array. The
38
+ // field codes are derived once and closed over.
39
+ let makeMaskFn = (fields: array<string>): (array<Internal.eventConfig> => float) => {
40
+ let codes = fieldCodes(fields)
41
+ eventConfigs => eventConfigs->mask(~codes)
42
+ }
43
+
37
44
  // Drain another store (a fetch-response page) into this one.
38
45
  @send external merge: (t, t) => unit = "merge"
39
46
 
@@ -60,52 +67,62 @@ external materialize: (
60
67
  // selected none — so `event.transaction` is never `undefined` (matching the
61
68
  // inline sources). Deduped per (blockNumber, transactionIndex).
62
69
  let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: float) => {
63
- // Store-backed items arrive in (block, logIndex) order, and a transaction's
64
- // logs are contiguous within a block, so events sharing a (blockNumber,
65
- // transactionIndex) are adjacent. Group them by extending the current run
66
- // rather than hashing a string key per item. A key recurring non-adjacently
67
- // just splits into two groups (one redundant decode) — never incorrect.
68
- let blockNumbers = []
69
- let transactionIndices = []
70
- let payloadGroups = []
70
+ if mask == 0. {
71
+ // No fields selected: there's nothing to decode and no reason to group by
72
+ // key, but each store-backed item still gets an empty transaction object so
73
+ // `event.transaction` is never undefined (matching the inline sources).
74
+ items->Array.forEach(item =>
75
+ switch item {
76
+ | Internal.Event(_) =>
77
+ let payload = (item->Internal.castUnsafeEventItem).payload
78
+ switch payload->Internal.getPayloadTransaction->Nullable.toOption {
79
+ | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
80
+ | None => payload->Internal.setPayloadTransaction(%raw(`{}`))
81
+ }
82
+ | Internal.Block(_) => ()
83
+ }
84
+ )
85
+ } else {
86
+ // Store-backed items arrive in (block, logIndex) order, and a transaction's
87
+ // logs are contiguous within a block, so events sharing a (blockNumber,
88
+ // transactionIndex) are adjacent. Group them by extending the current run
89
+ // rather than hashing a string key per item. A key recurring non-adjacently
90
+ // just splits into two groups (one redundant decode) — never incorrect.
91
+ let blockNumbers = []
92
+ let transactionIndices = []
93
+ let payloadGroups = []
71
94
 
72
- items->Array.forEach(item =>
73
- switch item {
74
- | Internal.Event(_) =>
75
- let eventItem = item->Internal.castUnsafeEventItem
76
- switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
77
- | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
78
- | None =>
79
- let {blockNumber, transactionIndex} = eventItem
80
- let last = payloadGroups->Array.length - 1
81
- if (
82
- last >= 0 &&
83
- blockNumbers->Array.getUnsafe(last) == blockNumber &&
84
- transactionIndices->Array.getUnsafe(last) == transactionIndex
85
- ) {
86
- payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
87
- } else {
88
- blockNumbers->Array.push(blockNumber)
89
- transactionIndices->Array.push(transactionIndex)
90
- payloadGroups->Array.push([eventItem.payload])
95
+ items->Array.forEach(item =>
96
+ switch item {
97
+ | Internal.Event(_) =>
98
+ let eventItem = item->Internal.castUnsafeEventItem
99
+ switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
100
+ | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
101
+ | None =>
102
+ let {blockNumber, transactionIndex} = eventItem
103
+ let last = payloadGroups->Array.length - 1
104
+ if (
105
+ last >= 0 &&
106
+ blockNumbers->Array.getUnsafe(last) == blockNumber &&
107
+ transactionIndices->Array.getUnsafe(last) == transactionIndex
108
+ ) {
109
+ payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
110
+ } else {
111
+ blockNumbers->Array.push(blockNumber)
112
+ transactionIndices->Array.push(transactionIndex)
113
+ payloadGroups->Array.push([eventItem.payload])
114
+ }
91
115
  }
116
+ | Internal.Block(_) => ()
92
117
  }
93
- | Internal.Block(_) => ()
94
- }
95
- )
118
+ )
96
119
 
97
- if payloadGroups->Utils.Array.notEmpty {
98
- // A zero mask selects no fields, so there's nothing to decode — each
99
- // store-backed item still gets an empty transaction object, so
100
- // `event.transaction` is never undefined (matching the inline sources).
101
- let txs = if mask == 0. {
102
- payloadGroups->Array.map((_): Internal.eventTransaction => %raw(`{}`))
103
- } else {
104
- await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
120
+ if payloadGroups->Utils.Array.notEmpty {
121
+ let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
122
+ payloadGroups->Array.forEachWithIndex((payloads, i) => {
123
+ let tx = txs->Array.getUnsafe(i)
124
+ payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
125
+ })
105
126
  }
106
- payloadGroups->Array.forEachWithIndex((payloads, i) => {
107
- let tx = txs->Array.getUnsafe(i)
108
- payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
109
- })
110
127
  }
111
128
  }
@@ -32,7 +32,26 @@ function mask(eventConfigs, codes) {
32
32
  return Stdlib_Array.reduce(Array.from(selected), 0, (mask, code) => mask + pow2(code));
33
33
  }
34
34
 
35
+ function makeMaskFn(fields) {
36
+ let codes = fieldCodes(fields);
37
+ return eventConfigs => mask(eventConfigs, codes);
38
+ }
39
+
35
40
  async function materializeItems(store, items, mask) {
41
+ if (mask === 0) {
42
+ items.forEach(item => {
43
+ if (item.kind !== 0) {
44
+ return;
45
+ }
46
+ let payload = item.payload;
47
+ let match = payload.transaction;
48
+ if (match == null) {
49
+ payload.transaction = {};
50
+ return;
51
+ }
52
+ });
53
+ return;
54
+ }
36
55
  let blockNumbers = [];
37
56
  let transactionIndices = [];
38
57
  let payloadGroups = [];
@@ -58,7 +77,7 @@ async function materializeItems(store, items, mask) {
58
77
  if (!Utils.$$Array.notEmpty(payloadGroups)) {
59
78
  return;
60
79
  }
61
- let txs = mask === 0 ? payloadGroups.map(param => ({})) : await store.materialize(blockNumbers, transactionIndices, mask);
80
+ let txs = await store.materialize(blockNumbers, transactionIndices, mask);
62
81
  payloadGroups.forEach((payloads, i) => {
63
82
  let tx = txs[i];
64
83
  payloads.forEach(payload => {
@@ -72,6 +91,7 @@ export {
72
91
  fieldCodes,
73
92
  pow2,
74
93
  mask,
94
+ makeMaskFn,
75
95
  materializeItems,
76
96
  }
77
97
  /* Core Not a pure module */
package/svm.schema.json CHANGED
@@ -463,53 +463,46 @@
463
463
  "type": "object",
464
464
  "properties": {
465
465
  "transaction_fields": {
466
- "description": "Include the parent transaction for each matched instruction. Use `true` to include all fields.",
467
- "anyOf": [
468
- {
469
- "$ref": "#/$defs/FieldSelectionValue"
470
- },
471
- {
472
- "type": "null"
473
- }
474
- ]
466
+ "description": "Parent-transaction fields to include on each matched instruction, as a list of field names. Omit (or pass an empty list) to include no transaction.",
467
+ "type": [
468
+ "array",
469
+ "null"
470
+ ],
471
+ "items": {
472
+ "$ref": "#/$defs/SvmTransactionField"
473
+ }
475
474
  },
476
475
  "log_fields": {
477
- "description": "Include program logs scoped to each matched instruction. Use `true` to include all fields.",
478
- "anyOf": [
479
- {
480
- "$ref": "#/$defs/FieldSelectionValue"
481
- },
482
- {
483
- "type": "null"
484
- }
476
+ "description": "Set to `true` to include program logs scoped to each matched instruction.",
477
+ "type": [
478
+ "boolean",
479
+ "null"
485
480
  ]
486
481
  },
487
482
  "token_balance_fields": {
488
- "description": "Include SPL Token / Token-2022 balance snapshots for the parent transaction. Implies transaction_fields: true. Use `true` to include all fields.",
489
- "anyOf": [
490
- {
491
- "$ref": "#/$defs/FieldSelectionValue"
492
- },
493
- {
494
- "type": "null"
495
- }
483
+ "description": "Set to `true` to include SPL Token / Token-2022 balance snapshots for the parent transaction, exposed as `transaction.tokenBalances`. Independent of `transaction_fields`.",
484
+ "type": [
485
+ "boolean",
486
+ "null"
496
487
  ]
497
488
  }
498
489
  },
499
490
  "additionalProperties": false
500
491
  },
501
- "FieldSelectionValue": {
502
- "description": "Value for a field-selection entry. `true` includes all fields;\na list of field names enables per-field selection (not yet supported).",
503
- "anyOf": [
504
- {
505
- "type": "boolean"
506
- },
507
- {
508
- "type": "array",
509
- "items": {
510
- "type": "string"
511
- }
512
- }
492
+ "SvmTransactionField": {
493
+ "description": "Selectable parent-transaction field names (camelCase), matching the\npublic `svmTransaction` shape. `tokenBalances` is selected via the\nseparate `token_balance_fields` toggle, so it isn't listed here.",
494
+ "type": "string",
495
+ "enum": [
496
+ "transactionIndex",
497
+ "signatures",
498
+ "feePayer",
499
+ "success",
500
+ "err",
501
+ "fee",
502
+ "computeUnitsConsumed",
503
+ "accountKeys",
504
+ "recentBlockhash",
505
+ "version"
513
506
  ]
514
507
  },
515
508
  "ArgDef": {
@@ -1,58 +0,0 @@
1
- type seconds = float
2
- type milliseconds = float
3
- type nanoseconds = float
4
-
5
- type timeTuple = (seconds, nanoseconds)
6
-
7
- type timeRef = timeTuple
8
-
9
- type timeElapsed = timeTuple
10
-
11
- @val @scope("process") external makeTimer: unit => timeRef = "hrtime"
12
-
13
- @val @scope("process") external timeSince: timeRef => timeElapsed = "hrtime"
14
-
15
- let nanoToMilli = (nano: nanoseconds): milliseconds => nano /. 1_000_000.
16
- let secToMilli = (sec: seconds): milliseconds => sec *. 1_000.
17
-
18
- let nanoToTimeTuple = (nano: nanoseconds): timeTuple => {
19
- let factor = 1_000_000_000.
20
- let seconds = Math.floor(nano /. factor)
21
- let nanos = Float.mod(nano, factor)
22
- (seconds, nanos)
23
- }
24
-
25
- let timeElapsedToNewRef = (elapsed: timeElapsed, ref: timeRef): timeRef => {
26
- let (elapsedSeconds, elapsedNano) = elapsed
27
- let (refSeconds, refNano) = ref
28
-
29
- let (nanoExtraSeconds, remainderNanos) = nanoToTimeTuple(elapsedNano +. refNano)
30
- (elapsedSeconds +. refSeconds +. nanoExtraSeconds, remainderNanos)
31
- }
32
-
33
- let toMillis = ((sec, nano): timeElapsed): milliseconds => {
34
- sec->secToMilli +. nano->nanoToMilli
35
- }
36
-
37
- let toSeconds = ((sec, nano): timeElapsed): seconds => {
38
- sec +. nano /. 1_000_000_000.
39
- }
40
-
41
- let toSecondsFloat = (elapsed: timeElapsed): float => {
42
- elapsed->toSeconds->(Utils.magic: seconds => float)
43
- }
44
-
45
- let toInt = float => float->Int.fromFloat
46
- let intFromMillis = toInt
47
- let intFromNanos = toInt
48
- let intFromSeconds = toInt
49
- let floatFromMillis: milliseconds => float = v => v->(Utils.magic: milliseconds => float)
50
- let floatFromSeconds: seconds => float = v => v->(Utils.magic: seconds => float)
51
-
52
- let millisBetween = (~from: timeRef, ~to: timeRef): int => {
53
- to->toMillis->intFromMillis - from->toMillis->intFromMillis
54
- }
55
-
56
- let secondsBetween = (~from: timeRef, ~to: timeRef): float => {
57
- to->toSecondsFloat -. from->toSecondsFloat
58
- }