envio 3.15.0 → 3.16.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 (99) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +8 -8
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/ChainState.res +48 -7
  11. package/src/ChainState.res.mjs +27 -11
  12. package/src/Clock.res +57 -0
  13. package/src/Clock.res.mjs +72 -0
  14. package/src/Config.res +10 -3
  15. package/src/Config.res.mjs +4 -2
  16. package/src/Core.res +4 -40
  17. package/src/EffectState.res +10 -2
  18. package/src/EffectState.res.mjs +8 -4
  19. package/src/EffectState.resi +3 -1
  20. package/src/EntityId.res +2 -7
  21. package/src/EntityId.res.mjs +1 -8
  22. package/src/EntityTables.res +4 -1
  23. package/src/EntityTables.res.mjs +1 -1
  24. package/src/FetchState.res +157 -69
  25. package/src/FetchState.res.mjs +144 -49
  26. package/src/FinalizeBackfill.res +2 -4
  27. package/src/FinalizeBackfill.res.mjs +3 -4
  28. package/src/InMemoryTable.res +17 -16
  29. package/src/InMemoryTable.res.mjs +14 -15
  30. package/src/IndexerState.res +28 -4
  31. package/src/IndexerState.res.mjs +19 -6
  32. package/src/IndexerState.resi +4 -0
  33. package/src/Internal.res +0 -49
  34. package/src/Internal.res.mjs +1 -80
  35. package/src/LoadLayer.res +14 -8
  36. package/src/LoadLayer.res.mjs +8 -6
  37. package/src/Main.res.mjs +1 -1
  38. package/src/Persistence.res +3 -23
  39. package/src/Persistence.res.mjs +3 -3
  40. package/src/Rollback.res +0 -1
  41. package/src/Rollback.res.mjs +1 -1
  42. package/src/SafeCheckpointTracking.res +4 -4
  43. package/src/SafeCheckpointTracking.res.mjs +1 -1
  44. package/src/StagedRows.res +106 -0
  45. package/src/StagedRows.res.mjs +102 -0
  46. package/src/Staging.res +32 -14
  47. package/src/Staging.res.mjs +17 -20
  48. package/src/Storage.res +277 -948
  49. package/src/Storage.res.mjs +215 -707
  50. package/src/Supervisor.res +75 -56
  51. package/src/Supervisor.res.mjs +55 -26
  52. package/src/TestIndexer.res +22 -27
  53. package/src/TestIndexer.res.mjs +20 -17
  54. package/src/Throttler.res +15 -8
  55. package/src/Throttler.res.mjs +9 -6
  56. package/src/Throttler.resi +1 -1
  57. package/src/Utils.res +0 -6
  58. package/src/Utils.res.mjs +0 -5
  59. package/src/Writing.res +0 -1
  60. package/src/Writing.res.mjs +1 -1
  61. package/src/bindings/ClickHouse.res +0 -322
  62. package/src/bindings/ClickHouse.res.mjs +0 -338
  63. package/src/bindings/StorageClient.res +107 -174
  64. package/src/bindings/StorageClient.res.mjs +19 -8
  65. package/src/bindings/Vitest.res +0 -26
  66. package/src/bindings/Vitest.res.mjs +0 -3
  67. package/src/db/Table.res +24 -150
  68. package/src/db/Table.res.mjs +38 -272
  69. package/src/sources/ChainSources.res +2 -0
  70. package/src/sources/ChainSources.res.mjs +2 -2
  71. package/src/sources/EvmChain.res +3 -0
  72. package/src/sources/EvmChain.res.mjs +4 -2
  73. package/src/sources/EvmHyperSyncSource.res +2 -2
  74. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  75. package/src/sources/EvmRpcClient.res +13 -3
  76. package/src/sources/EvmRpcClient.res.mjs +4 -3
  77. package/src/sources/EvmRpcWs.res +4 -3
  78. package/src/sources/EvmRpcWs.res.mjs +2 -2
  79. package/src/sources/HeightFeed.res +31 -10
  80. package/src/sources/HeightFeed.res.mjs +10 -6
  81. package/src/sources/HeightStream.res +32 -17
  82. package/src/sources/HeightStream.res.mjs +10 -10
  83. package/src/sources/HyperSyncSSE.res +4 -6
  84. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  85. package/src/sources/RpcSource.res +10 -1
  86. package/src/sources/RpcSource.res.mjs +4 -3
  87. package/src/sources/Source.res +3 -0
  88. package/src/sources/SourceManager.res +84 -77
  89. package/src/sources/SourceManager.res.mjs +17 -20
  90. package/src/sources/SourceManager.resi +3 -4
  91. package/src/sources/StartBlockResolver.res +1 -0
  92. package/src/sources/StartBlockResolver.res.mjs +3 -2
  93. package/src/sources/SvmHyperSyncSource.res +2 -2
  94. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  95. package/svm.schema.json +11 -2
  96. package/src/PgWriting.res +0 -36
  97. package/src/PgWriting.res.mjs +0 -34
  98. package/src/db/IndexDefinition.res +0 -47
  99. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -0,0 +1,72 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Performance from "./bindings/Performance.res.mjs";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
6
+
7
+ let refuseReal = {
8
+ contents: false
9
+ };
10
+
11
+ function refused(what) {
12
+ return Stdlib_JsError.throwWithMessage(`Clock.real ` + what + ` on virtual time. Use the clock the code was given, so advanceTime can see and move it.`);
13
+ }
14
+
15
+ function real_now() {
16
+ if (refuseReal.contents) {
17
+ return refused("read the time");
18
+ } else {
19
+ return Performance.now();
20
+ }
21
+ }
22
+
23
+ function real_setTimeout(label, callback, millis) {
24
+ if (refuseReal.contents && millis > 0) {
25
+ return refused(`armed "` + label + `"`);
26
+ } else {
27
+ return setTimeout(callback, millis);
28
+ }
29
+ }
30
+
31
+ function real_clearTimeout(timer) {
32
+ clearTimeout(timer);
33
+ }
34
+
35
+ function real_random(prim) {
36
+ return Math.random();
37
+ }
38
+
39
+ let real = {
40
+ now: real_now,
41
+ setTimeout: real_setTimeout,
42
+ clearTimeout: real_clearTimeout,
43
+ random: real_random
44
+ };
45
+
46
+ function delay(clock, label, millis) {
47
+ return new Promise((resolve, _reject) => {
48
+ clock.setTimeout(label, () => resolve(), millis);
49
+ });
50
+ }
51
+
52
+ function jitter(clock, delay) {
53
+ return (delay / 2 | 0) + (clock.random() * (delay / 2 | 0) | 0) | 0;
54
+ }
55
+
56
+ function clearRef(clock, timer) {
57
+ let timer$1 = timer.contents;
58
+ if (timer$1 !== undefined) {
59
+ clock.clearTimeout(Primitive_option.valFromOption(timer$1));
60
+ }
61
+ timer.contents = undefined;
62
+ }
63
+
64
+ export {
65
+ refuseReal,
66
+ refused,
67
+ real,
68
+ delay,
69
+ jitter,
70
+ clearRef,
71
+ }
72
+ /* No side effect */
package/src/Config.res CHANGED
@@ -65,6 +65,7 @@ type chain = {
65
65
  endBlock?: int,
66
66
  maxReorgDepth: int,
67
67
  blockLag: int,
68
+ blockThrottle: int,
68
69
  contracts: array<contract>,
69
70
  sourceConfig: sourceConfig,
70
71
  }
@@ -217,6 +218,7 @@ let publicConfigChainSchema = S.schema(s =>
217
218
  "endBlock": s.matches(S.option(S.int)),
218
219
  "maxReorgDepth": s.matches(S.option(S.int)),
219
220
  "blockLag": s.matches(S.option(S.int)),
221
+ "blockThrottle": s.matches(S.option(S.int)),
220
222
  // EVM/Fuel source config (hypersync for EVM, hyperfuel for Fuel)
221
223
  "hypersync": s.matches(S.option(S.string)),
222
224
  "rpcs": s.matches(S.option(S.array(rpcConfigSchema))),
@@ -1069,18 +1071,23 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1069
1071
  startBlock: publicChainConfig["startBlock"],
1070
1072
  endBlock: ?publicChainConfig["endBlock"],
1071
1073
  maxReorgDepth: switch ecosystemName {
1072
- | Ecosystem.Evm => publicChainConfig["maxReorgDepth"]->Option.getOr(200)
1074
+ // One less than the 200 blocks behind the head a source checks for a
1075
+ // lagging node, so the backfill, which stops one block below the window,
1076
+ // ends exactly where those checks begin.
1077
+ | Ecosystem.Evm => publicChainConfig["maxReorgDepth"]->Option.getOr(199)
1073
1078
  // Tower BFT roots a block once 32 votes lock it in (MAX_LOCKOUT_HISTORY
1074
1079
  // is 31, plus the slot itself), which is what `finalized` waits for — so
1075
1080
  // 32 is the protocol's own bound on how far a fork can be replaced. That
1076
1081
  // bound counts *blocks* while the threshold here is measured in slot
1077
1082
  // numbers, and skipped slots make the slot distance the larger of the
1078
1083
  // two, so the default keeps a wide margin over it. Slots are ~0.4s, so
1079
- // even this spans well under two minutes of chain.
1080
- | Ecosystem.Svm => publicChainConfig["maxReorgDepth"]->Option.getOr(200)
1084
+ // even this spans well under two minutes of chain. One less than 200
1085
+ // for the same reason as EVM: the backfill stops one slot below it.
1086
+ | Ecosystem.Svm => publicChainConfig["maxReorgDepth"]->Option.getOr(199)
1081
1087
  | Ecosystem.Fuel => 0
1082
1088
  },
1083
1089
  blockLag: publicChainConfig["blockLag"]->Option.getOr(0),
1090
+ blockThrottle: publicChainConfig["blockThrottle"]->Option.getOr(0),
1084
1091
  contracts,
1085
1092
  sourceConfig,
1086
1093
  }
@@ -88,6 +88,7 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
88
88
  endBlock: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
89
89
  maxReorgDepth: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
90
90
  blockLag: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
91
+ blockThrottle: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
91
92
  hypersync: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
92
93
  rpcs: s.m(S$RescriptSchema.option(S$RescriptSchema.array(rpcConfigSchema))),
93
94
  contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(chainContractSchema)))
@@ -762,7 +763,7 @@ function fromPublic(publicConfigJson) {
762
763
  break;
763
764
  case "evm" :
764
765
  case "svm" :
765
- tmp = Stdlib_Option.getOr(publicChainConfig.maxReorgDepth, 200);
766
+ tmp = Stdlib_Option.getOr(publicChainConfig.maxReorgDepth, 199);
766
767
  break;
767
768
  }
768
769
  return {
@@ -773,6 +774,7 @@ function fromPublic(publicConfigJson) {
773
774
  endBlock: publicChainConfig.endBlock,
774
775
  maxReorgDepth: tmp,
775
776
  blockLag: Stdlib_Option.getOr(publicChainConfig.blockLag, 0),
777
+ blockThrottle: Stdlib_Option.getOr(publicChainConfig.blockThrottle, 0),
776
778
  contracts: contracts,
777
779
  sourceConfig: sourceConfig
778
780
  };
@@ -972,7 +974,7 @@ function diffPaths(stored, current) {
972
974
  }
973
975
  return;
974
976
  }
975
- } else {
977
+ } else if (s !== null) {
976
978
  switch (typeof s) {
977
979
  case "object" :
978
980
  if (typeof c === "object" && c !== null && !Array.isArray(c)) {
package/src/Core.res CHANGED
@@ -18,6 +18,8 @@ type loggerCtor
18
18
  type mockHyperSyncServerCtor
19
19
  // Test-only: the ledger behind envio-tests' MockSource.
20
20
  type mockSourceCtor
21
+ // Test-only: the virtual clock a scenario's source layer runs on.
22
+ type virtualClockCtor
21
23
  type fromUserApiOptions = {
22
24
  schema?: string,
23
25
  env?: dict<string>,
@@ -31,46 +33,6 @@ type fromUserApiResult = {
31
33
  indexerCode: Null.t<string>,
32
34
  }
33
35
 
34
- // One column of a Postgres table, flattened for the boundary: napi carries no
35
- // tagged union, so the field type arrives as its name plus whichever of the
36
- // modifiers it takes.
37
- type pgColumnInput = {
38
- // The database column name, renames already resolved.
39
- name: string,
40
- fieldType: string,
41
- isArray: bool,
42
- isNullable: bool,
43
- isPrimaryKey: bool,
44
- defaultValue?: string,
45
- precision?: int,
46
- scale?: int,
47
- enumName?: string,
48
- }
49
-
50
- type pgTableInput = {
51
- // In the order the table declares them.
52
- columns: array<pgColumnInput>,
53
- // The columns a batch's rows carry, in the order they are laid out.
54
- writeColumns: array<string>,
55
- partitionByColumn?: string,
56
- appendOnly: bool,
57
- // An entity's history table, and the column its chain is in when it is
58
- // per-chain.
59
- historyTable?: string,
60
- chainIdColumn?: string,
61
- }
62
-
63
- type pgIndexColumnInput = {
64
- name: string,
65
- direction: string,
66
- }
67
-
68
- type pgIndexInput = {
69
- tableName: string,
70
- columns: array<pgIndexColumnInput>,
71
- method: string,
72
- }
73
-
74
36
  type addon = {
75
37
  getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
76
38
  encodeIndexedTopic: (~abiType: string, ~value: unknown) => EvmTypes.Hex.t,
@@ -107,6 +69,8 @@ type addon = {
107
69
  mockHyperSyncServer: mockHyperSyncServerCtor,
108
70
  @as("MockSource")
109
71
  mockSource: mockSourceCtor,
72
+ @as("VirtualClock")
73
+ virtualClock: virtualClockCtor,
110
74
  encodeAddresses: (~ecosystem: string, ~addresses: array<Address.t>) => array<NodeJs.Buffer.t>,
111
75
  normalizeEvmAddress: (~address: string, ~shouldChecksum: bool) => Null.t<Address.t>,
112
76
  renderAddresses: (
@@ -11,6 +11,8 @@ type effectRateLimitState = {
11
11
  mutable availableCalls: int,
12
12
  mutable windowStartTime: float,
13
13
  mutable nextWindowPromise: option<promise<unit>>,
14
+ // The run's clock: a test on virtual time opens the next window by advancing.
15
+ clock: Clock.t,
14
16
  }
15
17
 
16
18
  // Per-(effect, scope) counters rendered into the envio_effect_* metrics. Live
@@ -80,9 +82,14 @@ type t = {
80
82
  // rollback; stats and the rate-limit window survive because the table does.
81
83
  tables: dict<effectCacheInMemTable>,
82
84
  unregisteredCacheCounts: dict<unregisteredCacheCount>,
85
+ clock: Clock.t,
83
86
  }
84
87
 
85
- let make = (): t => {tables: Dict.make(), unregisteredCacheCounts: Dict.make()}
88
+ let make = (~clock=Clock.real, ()): t => {
89
+ tables: Dict.make(),
90
+ unregisteredCacheCounts: Dict.make(),
91
+ clock,
92
+ }
86
93
 
87
94
  let setUnregisteredCacheCount = (self: t, ~effectName, ~scope, ~count) => {
88
95
  let tableName = Internal.EffectCache.toTableName(~effectName, ~scope)
@@ -214,8 +221,9 @@ let getTable = (self: t, ~effect: Internal.effect, ~scope: Internal.chainScope)
214
221
  callsPerDuration,
215
222
  durationMs,
216
223
  availableCalls: callsPerDuration,
217
- windowStartTime: Date.now(),
224
+ windowStartTime: self.clock.now(),
218
225
  nextWindowPromise: None,
226
+ clock: self.clock,
219
227
  })
220
228
  }
221
229
  let inMemTable: effectCacheInMemTable = {
@@ -1,13 +1,16 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Clock from "./Clock.res.mjs";
3
4
  import * as Utils from "./Utils.res.mjs";
4
5
  import * as Internal from "./Internal.res.mjs";
5
6
  import * as Performance from "./bindings/Performance.res.mjs";
6
7
 
7
- function make() {
8
+ function make(clockOpt, param) {
9
+ let clock = clockOpt !== undefined ? clockOpt : Clock.real;
8
10
  return {
9
11
  tables: {},
10
- unregisteredCacheCounts: {}
12
+ unregisteredCacheCounts: {},
13
+ clock: clock
11
14
  };
12
15
  }
13
16
 
@@ -130,8 +133,9 @@ function getTable(self, effect, scope) {
130
133
  callsPerDuration: callsPerDuration,
131
134
  durationMs: match$1.durationMs,
132
135
  availableCalls: callsPerDuration,
133
- windowStartTime: Date.now(),
134
- nextWindowPromise: undefined
136
+ windowStartTime: self.clock.now(),
137
+ nextWindowPromise: undefined,
138
+ clock: self.clock
135
139
  };
136
140
  } else {
137
141
  rateLimitState = undefined;
@@ -4,6 +4,8 @@ type effectRateLimitState = {
4
4
  mutable availableCalls: int,
5
5
  mutable windowStartTime: float,
6
6
  mutable nextWindowPromise: option<promise<unit>>,
7
+ // The run's clock: a test on virtual time opens the next window by advancing.
8
+ clock: Clock.t,
7
9
  }
8
10
 
9
11
  // Per-(effect, scope) metric counters. Survive rollback so they stay monotonic.
@@ -32,7 +34,7 @@ type effectCacheInMemTable = {
32
34
  // Opaque store of all per-(effect, scope) runtime state.
33
35
  type t
34
36
 
35
- let make: unit => t
37
+ let make: (~clock: Clock.t=?, unit) => t
36
38
  // Get, or lazily create, the in-mem table for an (effect, scope). Reuses the
37
39
  // surviving rate-limit window when the table is recreated after a rollback.
38
40
  let getTable: (t, ~effect: Internal.effect, ~scope: Internal.chainScope) => effectCacheInMemTable
package/src/EntityId.res CHANGED
@@ -1,15 +1,10 @@
1
1
  // Opaque representation of an entity id. The generated per-entity types expose
2
- // the real scalar (string / int / bigint); the generic storage and in-memory
2
+ // the real scalar (string / int / bigint / Uint8Array); the generic storage and in-memory
3
3
  // layers work with any entity's id through this type without knowing which
4
4
  // scalar backs it. The runtime value is always the real id, never a stringified
5
- // form — `toKey` derives the string only where a JS object/dict key is needed.
5
+ // form — a table's `entityIdToKey` derives the string only where a dict or Set key is needed.
6
6
  type t
7
7
 
8
8
  external unsafeOfAny: 'a => t = "%identity"
9
9
  external unsafeToAny: t => 'a = "%identity"
10
10
  external unsafeOfString: string => t = "%identity"
11
-
12
- // Stringified id used as a JS object/dict key. `String` matches how JS coerces
13
- // a value used as an object key, so an id indexes the same whether the raw
14
- // value or its key form is used for lookup, across string/int/bigint.
15
- let toKey: t => string = %raw(`String`)
@@ -1,9 +1,2 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
-
4
- let toKey = String;
5
-
6
- export {
7
- toKey,
8
- }
9
- /* toKey Not a pure module */
2
+ /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
@@ -6,7 +6,10 @@ type t = dict<InMemoryTable.Entity.t>
6
6
  let make = (entities: array<Internal.entityConfig>): t => {
7
7
  let init = Dict.make()
8
8
  entities->Array.forEach(entityConfig => {
9
- init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
9
+ init->Dict.set(
10
+ (entityConfig.name :> string),
11
+ InMemoryTable.Entity.make(~entityIdToKey=entityConfig.table.entityIdToKey),
12
+ )
10
13
  })
11
14
  init
12
15
  }
@@ -6,7 +6,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
6
6
  function make(entities) {
7
7
  let init = {};
8
8
  entities.forEach(entityConfig => {
9
- init[entityConfig.name] = InMemoryTable.Entity.make();
9
+ init[entityConfig.name] = InMemoryTable.Entity.make(entityConfig.table.entityIdToKey);
10
10
  });
11
11
  return init;
12
12
  }