envio 2.16.1 → 2.17.1

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/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "v2.16.1",
3
+ "version": "v2.17.1",
4
4
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
5
5
  "bin": "./bin.js",
6
+ "types": "index.d.ts",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/enviodev/hyperindex.git"
@@ -23,10 +24,10 @@
23
24
  },
24
25
  "homepage": "https://envio.dev",
25
26
  "optionalDependencies": {
26
- "envio-linux-x64": "v2.16.1",
27
- "envio-linux-arm64": "v2.16.1",
28
- "envio-darwin-x64": "v2.16.1",
29
- "envio-darwin-arm64": "v2.16.1"
27
+ "envio-linux-x64": "v2.17.1",
28
+ "envio-linux-arm64": "v2.17.1",
29
+ "envio-darwin-x64": "v2.17.1",
30
+ "envio-darwin-arm64": "v2.17.1"
30
31
  },
31
32
  "dependencies": {
32
33
  "@envio-dev/hypersync-client": "0.6.3",
@@ -3,6 +3,6 @@
3
3
  /* eslint-disable */
4
4
  /* tslint:disable */
5
5
 
6
- import type {Address as $$t} from './bindings/OpaqueTypes.ts';
6
+ import type {Address as $$t} from './Types.ts';
7
7
 
8
8
  export type t = $$t;
package/src/Address.res CHANGED
@@ -1,4 +1,4 @@
1
- @genType.import(("./bindings/OpaqueTypes.ts", "Address"))
1
+ @genType.import(("./Types.ts", "Address"))
2
2
  type t
3
3
 
4
4
  let schema = S.string->S.setName("Address")->(Utils.magic: S.t<string> => S.t<t>)
@@ -0,0 +1,115 @@
1
+ type contractName = string
2
+
3
+ // Currently this mapping append only, so we don't need to worry about
4
+ // protecting static addresses from de-registration.
5
+
6
+ type mapping = {
7
+ nameByAddress: dict<contractName>,
8
+ addressesByName: dict<Belt.Set.String.t>,
9
+ }
10
+
11
+ exception AddressRegisteredForMultipleContracts({address: Address.t, names: array<contractName>})
12
+
13
+ let addAddress = (map: mapping, ~name: string, ~address: Address.t) => {
14
+ switch map.nameByAddress->Utils.Dict.dangerouslyGetNonOption(address->Address.toString) {
15
+ | Some(currentName) if currentName != name =>
16
+ let logger = Logging.createChild(
17
+ ~params={
18
+ "address": address->Address.toString,
19
+ "existingContract": currentName,
20
+ "newContract": name,
21
+ },
22
+ )
23
+ AddressRegisteredForMultipleContracts({
24
+ address,
25
+ names: [currentName, name],
26
+ })->ErrorHandling.mkLogAndRaise(~msg="Address registered for multiple contracts", ~logger)
27
+ | _ => ()
28
+ }
29
+ map.nameByAddress->Js.Dict.set(address->Address.toString, name)
30
+
31
+ let oldAddresses =
32
+ map.addressesByName
33
+ ->Utils.Dict.dangerouslyGetNonOption(name)
34
+ ->Belt.Option.getWithDefault(Belt.Set.String.empty)
35
+ let newAddresses = oldAddresses->Belt.Set.String.add(address->Address.toString)
36
+ map.addressesByName->Js.Dict.set(name, newAddresses)
37
+ }
38
+
39
+ let getAddresses = (map: mapping, name: string) => {
40
+ map.addressesByName->Utils.Dict.dangerouslyGetNonOption(name)
41
+ }
42
+
43
+ let getName = (map: mapping, address: string) => {
44
+ map.nameByAddress->Utils.Dict.dangerouslyGetNonOption(address)
45
+ }
46
+
47
+ let make = () => {
48
+ nameByAddress: Js.Dict.empty(),
49
+ addressesByName: Js.Dict.empty(),
50
+ }
51
+
52
+ let getContractNameFromAddress = (mapping, ~contractAddress: Address.t): option<contractName> => {
53
+ mapping->getName(contractAddress->Address.toString)
54
+ }
55
+
56
+ let stringsToAddresses: array<string> => array<Address.t> = Utils.magic
57
+ let keyValStringToAddress: array<(string, string)> => array<(Address.t, string)> = Utils.magic
58
+
59
+ let getAddressesFromContractName = (mapping, ~contractName) => {
60
+ switch mapping->getAddresses(contractName) {
61
+ | Some(addresses) => addresses
62
+ | None => Belt.Set.String.empty
63
+ }
64
+ ->Belt.Set.String.toArray
65
+ ->stringsToAddresses
66
+ }
67
+
68
+ let getAllAddresses = (mapping: mapping) => {
69
+ mapping.nameByAddress->Js.Dict.keys->stringsToAddresses
70
+ }
71
+
72
+ let copy = (mapping: mapping) => {
73
+ {
74
+ nameByAddress: mapping.nameByAddress->Utils.Dict.shallowCopy,
75
+ // Since Belt.Set.String.t is immutable, we can simply do shallow copy here
76
+ addressesByName: mapping.addressesByName->Utils.Dict.shallowCopy,
77
+ }
78
+ }
79
+
80
+ let mergeInPlace = (map, ~target) => {
81
+ map.nameByAddress
82
+ ->Js.Dict.keys
83
+ ->Belt.Array.forEach(addr => {
84
+ let name = map.nameByAddress->Js.Dict.unsafeGet(addr)
85
+ target->addAddress(~address=addr->Address.unsafeFromString, ~name)
86
+ })
87
+ }
88
+
89
+ let fromArray = (nameAddrTuples: array<(Address.t, string)>) => {
90
+ let m = make()
91
+ nameAddrTuples->Belt.Array.forEach(((address, name)) => m->addAddress(~name, ~address))
92
+ m
93
+ }
94
+
95
+ /**
96
+ Creates a new mapping from the previous without the addresses passed in as "addressesToRemove"
97
+ */
98
+ let removeAddresses = (mapping: mapping, ~addressesToRemove: array<Address.t>) => {
99
+ switch addressesToRemove {
100
+ | [] => mapping
101
+ | _ =>
102
+ mapping.nameByAddress
103
+ ->Js.Dict.entries
104
+ ->Belt.Array.keep(((addr, _name)) => {
105
+ let shouldRemove = addressesToRemove->Utils.Array.includes(addr->Utils.magic)
106
+ !shouldRemove
107
+ })
108
+ ->keyValStringToAddress
109
+ ->fromArray
110
+ }
111
+ }
112
+
113
+ let addressCount = (mapping: mapping) => mapping.nameByAddress->Js.Dict.keys->Belt.Array.length
114
+
115
+ let isEmpty = (mapping: mapping) => mapping->addressCount == 0
@@ -0,0 +1,8 @@
1
+ /* TypeScript file generated from Envio.res by genType. */
2
+
3
+ /* eslint-disable */
4
+ /* tslint:disable */
5
+
6
+ import type {Logger as $$logger} from './Types.ts';
7
+
8
+ export type logger = $$logger;
package/src/Envio.res ADDED
@@ -0,0 +1,12 @@
1
+ // The file with public API.
2
+ // Should be an entry point after we get rid of the generated project.
3
+ // Don't forget to keep index.d.ts in sync with this file.
4
+
5
+ @genType.import(("./Types.ts", "Logger"))
6
+ type logger = {
7
+ debug: 'params. (string, ~params: {..} as 'params=?) => unit,
8
+ info: 'params. (string, ~params: {..} as 'params=?) => unit,
9
+ warn: 'params. (string, ~params: {..} as 'params=?) => unit,
10
+ error: 'params. (string, ~params: {..} as 'params=?) => unit,
11
+ errorWithExn: (string, exn) => unit,
12
+ }
@@ -0,0 +1,61 @@
1
+ type t = {logger: Pino.t, exn: exn, msg: option<string>}
2
+
3
+ let make = (exn, ~logger=Logging.getLogger(), ~msg=?) => {
4
+ {logger, msg, exn}
5
+ }
6
+
7
+ let log = (self: t) => {
8
+ switch self {
9
+ | {exn, msg: Some(msg), logger} =>
10
+ logger->Logging.childErrorWithExn(exn->Internal.prettifyExn, msg)
11
+ | {exn, msg: None, logger} => logger->Logging.childError(exn->Internal.prettifyExn)
12
+ }
13
+ }
14
+
15
+ let raiseExn = (self: t) => {
16
+ self.exn->Internal.prettifyExn->raise
17
+ }
18
+
19
+ let mkLogAndRaise = (~logger=?, ~msg=?, exn) => {
20
+ let exn = exn->Internal.prettifyExn
21
+ exn->make(~logger?, ~msg?)->log
22
+ exn->raise
23
+ }
24
+
25
+ let unwrapLogAndRaise = (~logger=?, ~msg=?, result) => {
26
+ switch result {
27
+ | Ok(v) => v
28
+ | Error(exn) => exn->mkLogAndRaise(~logger?, ~msg?)
29
+ }
30
+ }
31
+
32
+ let logAndRaise = self => {
33
+ self->log
34
+ self->raiseExn
35
+ }
36
+
37
+ /**
38
+ An environment to manage control flow propogating results
39
+ with Error that contain ErrorHandling.t in async
40
+ contexts and avoid nested switch statements on awaited promises
41
+ Similar to rust result propogation
42
+ */
43
+ module ResultPropogateEnv = {
44
+ exception ErrorHandlingEarlyReturn(t)
45
+
46
+ type resultWithErrorHandle<'a> = result<'a, t>
47
+ type asyncBody<'a> = unit => promise<resultWithErrorHandle<'a>>
48
+
49
+ let runAsyncEnv = async (body: asyncBody<'a>) => {
50
+ switch await body() {
51
+ | exception ErrorHandlingEarlyReturn(e) => Error(e)
52
+ | endReturn => endReturn
53
+ }
54
+ }
55
+
56
+ let propogate = (res: resultWithErrorHandle<'a>) =>
57
+ switch res {
58
+ | Ok(v) => v
59
+ | Error(e) => raise(ErrorHandlingEarlyReturn(e))
60
+ }
61
+ }
@@ -0,0 +1,87 @@
1
+ type multiChainEventIndex = {
2
+ timestamp: int,
3
+ chainId: int,
4
+ blockNumber: int,
5
+ logIndex: int,
6
+ }
7
+
8
+ //Comparator used when ordering multichain events
9
+ let getEventComparator = (multiChainEventIndex: multiChainEventIndex) => {
10
+ let {timestamp, chainId, blockNumber, logIndex} = multiChainEventIndex
11
+ (timestamp, chainId, blockNumber, logIndex)
12
+ }
13
+
14
+ let getEventComparatorFromQueueItem = (
15
+ {chain, timestamp, blockNumber, logIndex}: Internal.eventItem,
16
+ ) => {
17
+ let chainId = chain->ChainMap.Chain.toChainId
18
+ (timestamp, chainId, blockNumber, logIndex)
19
+ }
20
+
21
+ //Function used to determine if one event is earlier than another
22
+ let isEarlierEvent = (event1: multiChainEventIndex, event2: multiChainEventIndex) => {
23
+ event1->getEventComparator < event2->getEventComparator
24
+ }
25
+
26
+ type eventIndex = {
27
+ blockNumber: int,
28
+ logIndex: int,
29
+ }
30
+
31
+ // takes blockNumber, logIndex and packs them into a number with
32
+ //32 bits, 16 bits and 16 bits respectively
33
+ let packEventIndex = (~blockNumber, ~logIndex) => {
34
+ let blockNumber = blockNumber->BigInt.fromInt
35
+ let logIndex = logIndex->BigInt.fromInt
36
+ let blockNumber = BigInt.Bitwise.shift_left(blockNumber, 16->BigInt.fromInt)
37
+
38
+ blockNumber->BigInt.Bitwise.logor(logIndex)
39
+ }
40
+
41
+ //Currently not used but keeping in utils
42
+ //using @live flag for dead code analyser
43
+ @live
44
+ let packMultiChainEventIndex = (~timestamp, ~chainId, ~blockNumber, ~logIndex) => {
45
+ let timestamp = timestamp->BigInt.fromInt
46
+ let chainId = chainId->BigInt.fromInt
47
+ let blockNumber = blockNumber->BigInt.fromInt
48
+ let logIndex = logIndex->BigInt.fromInt
49
+
50
+ let timestamp = BigInt.Bitwise.shift_left(timestamp, 48->BigInt.fromInt)
51
+ let chainId = BigInt.Bitwise.shift_left(chainId, 16->BigInt.fromInt)
52
+ let blockNumber = BigInt.Bitwise.shift_left(blockNumber, 16->BigInt.fromInt)
53
+
54
+ timestamp
55
+ ->BigInt.Bitwise.logor(chainId)
56
+ ->BigInt.Bitwise.logor(blockNumber)
57
+ ->BigInt.Bitwise.logor(logIndex)
58
+ }
59
+
60
+ //Currently not used but keeping in utils
61
+ //using @live flag for dead code analyser
62
+ @live
63
+ let unpackEventIndex = (packedEventIndex: bigint) => {
64
+ let blockNumber = packedEventIndex->BigInt.Bitwise.shift_right(16->BigInt.fromInt)
65
+ let logIndexMask = 65535->BigInt.fromInt
66
+ let logIndex = packedEventIndex->BigInt.Bitwise.logand(logIndexMask)
67
+ {
68
+ blockNumber: blockNumber->BigInt.toString->Belt.Int.fromString->Belt.Option.getUnsafe,
69
+ logIndex: logIndex->BigInt.toString->Belt.Int.fromString->Belt.Option.getUnsafe,
70
+ }
71
+ }
72
+
73
+ //takes an eventIndex record and returnts a packed event index
74
+ //used in TS tests
75
+ @live
76
+ let packEventIndexFromRecord = (eventIndex: eventIndex) => {
77
+ packEventIndex(~blockNumber=eventIndex.blockNumber, ~logIndex=eventIndex.logIndex)
78
+ }
79
+
80
+ //Returns unique string id for an event using its chain id combined with event id
81
+ //Used in IO for the key in the in mem rawEvents table
82
+ let getEventIdKeyString = (~chainId: int, ~eventId: string) => {
83
+ let chainIdStr = chainId->Belt.Int.toString
84
+ let key = chainIdStr ++ "_" ++ eventId
85
+
86
+ key
87
+ }
@@ -3,7 +3,7 @@
3
3
  /* eslint-disable */
4
4
  /* tslint:disable */
5
5
 
6
- import type {invalid as $$noEventFilters} from './bindings/OpaqueTypes.ts';
6
+ import type {Invalid as $$noEventFilters} from './Types.ts';
7
7
 
8
8
  import type {t as Address_t} from './Address.gen';
9
9
 
package/src/Internal.res CHANGED
@@ -134,6 +134,8 @@ type eventItem = {
134
134
  //be reprocessed after it has loaded dynamic contracts
135
135
  //This gets set to true and does not try and reload events
136
136
  hasRegisteredDynamicContracts?: bool,
137
+ // Reuse logger object for event
138
+ mutable loggerCache?: Pino.t,
137
139
  }
138
140
 
139
141
  @genType
@@ -159,5 +161,12 @@ let fuelTransferParamsSchema = S.schema(s => {
159
161
 
160
162
  type entity = private {id: string}
161
163
 
162
- @genType.import(("./bindings/OpaqueTypes.ts", "invalid"))
164
+ @genType.import(("./Types.ts", "Invalid"))
163
165
  type noEventFilters
166
+
167
+ let prettifyExn = exn => {
168
+ switch exn->Js.Exn.anyToExnInternal {
169
+ | Js.Exn.Error(e) => e->(Utils.magic: Js.Exn.t => exn)
170
+ | exn => exn
171
+ }
172
+ }
@@ -0,0 +1,174 @@
1
+ open Belt
2
+
3
+ module Call = {
4
+ type input
5
+ type output
6
+ type t = {
7
+ input: input,
8
+ resolve: output => unit,
9
+ reject: exn => unit,
10
+ mutable promise: promise<output>,
11
+ mutable isLoading: bool,
12
+ }
13
+ }
14
+
15
+ module Group = {
16
+ type t = {
17
+ // Unique calls by input as a key
18
+ calls: dict<Call.t>,
19
+ load: array<Call.input> => promise<unit>,
20
+ getUnsafeInMemory: string => Call.output,
21
+ hasInMemory: string => bool,
22
+ }
23
+ }
24
+
25
+ type t = {
26
+ // Batches of different operations by operation key
27
+ // Can be: Load by id, load by index, effect
28
+ groups: dict<Group.t>,
29
+ mutable isCollecting: bool,
30
+ }
31
+
32
+ let make = () => {
33
+ groups: Js.Dict.empty(),
34
+ isCollecting: false,
35
+ }
36
+
37
+ let schedule = async loadManager => {
38
+ // For the first schedule, wait for a microtask first
39
+ // to collect all calls before the next await
40
+ // If the loadManager is already collecting,
41
+ // then we do nothing. The call will be automatically
42
+ // handled when the promise below resolves
43
+ loadManager.isCollecting = true
44
+ await Promise.resolve()
45
+ loadManager.isCollecting = false
46
+
47
+ let groups = loadManager.groups
48
+ groups
49
+ ->Js.Dict.keys
50
+ ->Utils.Array.forEachAsync(async key => {
51
+ let group = groups->Js.Dict.unsafeGet(key)
52
+ let calls = group.calls
53
+
54
+ let inputsToLoad = []
55
+ let currentInputKeys = []
56
+ calls
57
+ ->Js.Dict.keys
58
+ ->Js.Array2.forEach(inputKey => {
59
+ let call = calls->Js.Dict.unsafeGet(inputKey)
60
+ if !call.isLoading {
61
+ call.isLoading = true
62
+ currentInputKeys->Js.Array2.push(inputKey)->ignore
63
+ if group.hasInMemory(inputKey)->not {
64
+ inputsToLoad->Js.Array2.push(call.input)->ignore
65
+ }
66
+ }
67
+ })
68
+
69
+ if inputsToLoad->Utils.Array.isEmpty->not {
70
+ try {
71
+ await group.load(inputsToLoad)
72
+ } catch {
73
+ | exn => {
74
+ let exn = exn->Internal.prettifyExn
75
+ currentInputKeys->Array.forEach(inputKey => {
76
+ let call = calls->Js.Dict.unsafeGet(inputKey)
77
+ call.reject(exn)
78
+ })
79
+ }
80
+ }
81
+ }
82
+
83
+ if currentInputKeys->Utils.Array.isEmpty->not {
84
+ currentInputKeys->Js.Array2.forEach(inputKey => {
85
+ let call = calls->Js.Dict.unsafeGet(inputKey)
86
+ calls->Utils.Dict.deleteInPlace(inputKey)
87
+ call.resolve(group.getUnsafeInMemory(inputKey))
88
+ })
89
+
90
+ // Clean up executed batch to reset
91
+ // provided load function which
92
+ // might have an outdated function context
93
+ let latestGroup = groups->Js.Dict.unsafeGet(key)
94
+ if latestGroup.calls->Js.Dict.keys->Utils.Array.isEmpty {
95
+ groups->Utils.Dict.deleteInPlace(key)
96
+ }
97
+ }
98
+ })
99
+ }
100
+
101
+ let noopHasher = input => input->(Utils.magic: 'input => string)
102
+
103
+ let call = (
104
+ loadManager,
105
+ ~input,
106
+ ~key,
107
+ ~load,
108
+ ~hasher,
109
+ ~shouldGroup,
110
+ ~hasInMemory,
111
+ ~getUnsafeInMemory,
112
+ ) => {
113
+ // This is a micro-optimization to avoid a function call
114
+ let inputKey = hasher === noopHasher ? input->(Utils.magic: 'input => string) : hasher(input)
115
+
116
+ // We group external calls by operation to:
117
+ // 1. Reduce the IO by allowing batch requests
118
+ // 2. By allowing parallel processing of events
119
+ // and make awaits run at the same time
120
+ //
121
+ // In the handlers it's not as important to group
122
+ // calls, because usually we run a single handler at a time
123
+ // So have a quick exit when an entity is already in memory
124
+ //
125
+ // But since we're going to parallelize handlers per chain,
126
+ // keep the grouping logic when the data needs to be loaded
127
+ // It has a small additional runtime cost, but might reduce IO time
128
+ if !shouldGroup && hasInMemory(inputKey) {
129
+ getUnsafeInMemory(inputKey)->Promise.resolve
130
+ } else {
131
+ let group = switch loadManager.groups->Utils.Dict.dangerouslyGetNonOption(key) {
132
+ | Some(group) => group
133
+ | None => {
134
+ let g: Group.t = {
135
+ calls: Js.Dict.empty(),
136
+ load: load->(
137
+ Utils.magic: (array<'input> => promise<unit>) => array<Call.input> => promise<unit>
138
+ ),
139
+ getUnsafeInMemory: getUnsafeInMemory->(
140
+ Utils.magic: (string => 'output) => string => Call.output
141
+ ),
142
+ hasInMemory: hasInMemory->(Utils.magic: (string => bool) => string => bool),
143
+ }
144
+ loadManager.groups->Js.Dict.set(key, g)
145
+ g
146
+ }
147
+ }
148
+
149
+ switch group.calls->Utils.Dict.dangerouslyGetNonOption(inputKey) {
150
+ | Some(c) => c.promise
151
+ | None => {
152
+ let promise = Promise.make((resolve, reject) => {
153
+ let call: Call.t = {
154
+ input: input->(Utils.magic: 'input => Call.input),
155
+ resolve,
156
+ reject,
157
+ promise: %raw(`null`),
158
+ isLoading: false,
159
+ }
160
+ group.calls->Js.Dict.set(inputKey, call)
161
+ })
162
+
163
+ // Don't use ref since it'll allocate an object to store .contents
164
+ (group.calls->Js.Dict.unsafeGet(inputKey)).promise = promise
165
+
166
+ if !loadManager.isCollecting {
167
+ let _: promise<unit> = loadManager->schedule
168
+ }
169
+
170
+ promise
171
+ }
172
+ }->(Utils.magic: promise<Call.output> => promise<'output>)
173
+ }
174
+ }