envio 2.7.1 → 2.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "v2.7.1",
3
+ "version": "v2.7.3",
4
4
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
5
5
  "bin": "./bin.js",
6
6
  "repository": {
@@ -23,13 +23,14 @@
23
23
  },
24
24
  "homepage": "https://envio.dev",
25
25
  "optionalDependencies": {
26
- "envio-linux-x64": "v2.7.1",
27
- "envio-linux-arm64": "v2.7.1",
28
- "envio-darwin-x64": "v2.7.1",
29
- "envio-darwin-arm64": "v2.7.1"
26
+ "envio-linux-x64": "v2.7.3",
27
+ "envio-linux-arm64": "v2.7.3",
28
+ "envio-darwin-x64": "v2.7.3",
29
+ "envio-darwin-arm64": "v2.7.3"
30
30
  },
31
31
  "dependencies": {
32
- "rescript": "11.1.3"
32
+ "rescript": "11.1.3",
33
+ "rescript-schema": "8.2.0"
33
34
  },
34
35
  "files": [
35
36
  "bin.js",
package/rescript.json CHANGED
@@ -11,5 +11,7 @@
11
11
  "package-specs": {
12
12
  "module": "commonjs",
13
13
  "in-source": true
14
- }
14
+ },
15
+ "bs-dependencies": ["rescript-schema"],
16
+ "bsc-flags": ["-open RescriptSchema"]
15
17
  }
@@ -0,0 +1,55 @@
1
+ type t = {
2
+ mutable lastRunTimeMillis: float,
3
+ mutable isRunning: bool,
4
+ mutable isAwaitingInterval: bool,
5
+ mutable scheduled: option<unit => promise<unit>>,
6
+ intervalMillis: float,
7
+ logger: Pino.t,
8
+ }
9
+
10
+ let make = (~intervalMillis: int, ~logger) => {
11
+ lastRunTimeMillis: 0.,
12
+ isRunning: false,
13
+ isAwaitingInterval: false,
14
+ scheduled: None,
15
+ intervalMillis: intervalMillis->Belt.Int.toFloat,
16
+ logger,
17
+ }
18
+
19
+ let rec startInternal = async (throttler: t) => {
20
+ switch throttler {
21
+ | {scheduled: Some(fn), isRunning: false, isAwaitingInterval: false} =>
22
+ let timeSinceLastRun = Js.Date.now() -. throttler.lastRunTimeMillis
23
+
24
+ //Only execute if we are passed the interval
25
+ if timeSinceLastRun >= throttler.intervalMillis {
26
+ throttler.isRunning = true
27
+ throttler.scheduled = None
28
+ throttler.lastRunTimeMillis = Js.Date.now()
29
+
30
+ switch await fn() {
31
+ | exception exn =>
32
+ throttler.logger->Pino.errorExn(
33
+ Pino.createPinoMessageWithError("Scheduled action failed in throttler", exn),
34
+ )
35
+ | _ => ()
36
+ }
37
+ throttler.isRunning = false
38
+
39
+ await throttler->startInternal
40
+ } else {
41
+ //Store isAwaitingInterval in state so that timers don't continuously get created
42
+ throttler.isAwaitingInterval = true
43
+ let _ = Js.Global.setTimeout(() => {
44
+ throttler.isAwaitingInterval = false
45
+ throttler->startInternal->ignore
46
+ }, Belt.Int.fromFloat(throttler.intervalMillis -. timeSinceLastRun))
47
+ }
48
+ | _ => ()
49
+ }
50
+ }
51
+
52
+ let schedule = (throttler: t, fn) => {
53
+ throttler.scheduled = Some(fn)
54
+ throttler->startInternal->ignore
55
+ }
package/src/Utils.res ADDED
@@ -0,0 +1,346 @@
1
+ external magic: 'a => 'b = "%identity"
2
+
3
+ module Option = {
4
+ let mapNone = (opt: option<'a>, val: 'b): option<'b> => {
5
+ switch opt {
6
+ | None => Some(val)
7
+ | Some(_) => None
8
+ }
9
+ }
10
+
11
+ let catchToNone: (unit => 'a) => option<'a> = unsafeFunc => {
12
+ try {
13
+ unsafeFunc()->Some
14
+ } catch {
15
+ | _ => None
16
+ }
17
+ }
18
+
19
+ let flatten = opt =>
20
+ switch opt {
21
+ | None => None
22
+ | Some(opt) => opt
23
+ }
24
+
25
+ let getExn = (opt, message) => {
26
+ switch opt {
27
+ | None => Js.Exn.raiseError(message)
28
+ | Some(v) => v
29
+ }
30
+ }
31
+ }
32
+
33
+ module Tuple = {
34
+ /**Access a tuple value by its index*/
35
+ @warning("-27")
36
+ let get = (tuple: 'a, index: int): option<'b> => %raw(`tuple[index]`)
37
+ }
38
+
39
+ module Dict = {
40
+ @get_index
41
+ /**
42
+ It's the same as `Js.Dict.get` but it doesn't have runtime overhead to check if the key exists.
43
+ */
44
+ external dangerouslyGetNonOption: (dict<'a>, string) => option<'a> = ""
45
+
46
+ let merge: (dict<'a>, dict<'a>) => dict<'a> = %raw(`(dictA, dictB) => ({...dictA, ...dictB})`)
47
+
48
+ let updateImmutable: (
49
+ dict<'a>,
50
+ string,
51
+ 'a,
52
+ ) => dict<'a> = %raw(`(dict, key, value) => ({...dict, [key]: value})`)
53
+ }
54
+
55
+ module Math = {
56
+ let minOptInt = (a, b) =>
57
+ switch (a, b) {
58
+ | (Some(a), Some(b)) => Pervasives.min(a, b)->Some
59
+ | (Some(a), None) => Some(a)
60
+ | (None, Some(b)) => Some(b)
61
+ | (None, None) => None
62
+ }
63
+ }
64
+ module Array = {
65
+ @val external jsArrayCreate: int => array<'a> = "Array"
66
+
67
+ /* Given a comaprator and two sorted lists, combine them into a single sorted list */
68
+ let mergeSorted = (f: ('a, 'a) => bool, xs: array<'a>, ys: array<'a>) => {
69
+ if Array.length(xs) == 0 {
70
+ ys
71
+ } else if Array.length(ys) == 0 {
72
+ xs
73
+ } else {
74
+ let n = Array.length(xs) + Array.length(ys)
75
+ let result = jsArrayCreate(n)
76
+
77
+ let rec loop = (i, j, k) => {
78
+ if i < Array.length(xs) && j < Array.length(ys) {
79
+ if f(xs[i], ys[j]) {
80
+ result[k] = xs[i]
81
+ loop(i + 1, j, k + 1)
82
+ } else {
83
+ result[k] = ys[j]
84
+ loop(i, j + 1, k + 1)
85
+ }
86
+ } else if i < Array.length(xs) {
87
+ result[k] = xs[i]
88
+ loop(i + 1, j, k + 1)
89
+ } else if j < Array.length(ys) {
90
+ result[k] = ys[j]
91
+ loop(i, j + 1, k + 1)
92
+ }
93
+ }
94
+
95
+ loop(0, 0, 0)
96
+ result
97
+ }
98
+ }
99
+
100
+ /**
101
+ Creates a shallow copy of the array and sets the value at the given index
102
+ */
103
+ let setIndexImmutable = (arr: array<'a>, index: int, value: 'a): array<'a> => {
104
+ let shallowCopy = arr->Belt.Array.copy
105
+ shallowCopy->Js.Array2.unsafe_set(index, value)
106
+ shallowCopy
107
+ }
108
+
109
+ let transposeResults = (results: array<result<'a, 'b>>): result<array<'a>, 'b> => {
110
+ let rec loop = (index: int, output: array<'a>): result<array<'a>, 'b> => {
111
+ if index >= Array.length(results) {
112
+ Ok(output)
113
+ } else {
114
+ switch results->Js.Array2.unsafe_get(index) {
115
+ | Ok(value) => {
116
+ output[index] = value
117
+ loop(index + 1, output)
118
+ }
119
+ | Error(_) as err => err->(magic: result<'a, 'b> => result<array<'a>, 'b>)
120
+ }
121
+ }
122
+ }
123
+
124
+ loop(0, Belt.Array.makeUninitializedUnsafe(results->Js.Array2.length))
125
+ }
126
+
127
+ /**
128
+ Helper to check if a value exists in an array
129
+ */
130
+ let includes = (arr: array<'a>, val: 'a) =>
131
+ arr->Js.Array2.find(item => item == val)->Belt.Option.isSome
132
+
133
+ let isEmpty = (arr: array<_>) =>
134
+ switch arr {
135
+ | [] => true
136
+ | _ => false
137
+ }
138
+
139
+ let awaitEach = async (arr: array<'a>, fn: 'a => promise<unit>) => {
140
+ for i in 0 to arr->Array.length - 1 {
141
+ let item = arr[i]
142
+ await item->fn
143
+ }
144
+ }
145
+
146
+ /**
147
+ Creates a new array removing the item at the given index
148
+
149
+ Index > array length or < 0 results in a copy of the array
150
+ */
151
+ let removeAtIndex = (array, index) => {
152
+ if index < 0 || index >= array->Array.length {
153
+ array->Array.copy
154
+ } else {
155
+ let head = array->Js.Array2.slice(~start=0, ~end_=index)
156
+ let tail = array->Belt.Array.sliceToEnd(index + 1)
157
+ [...head, ...tail]
158
+ }
159
+ }
160
+
161
+ let last = (arr: array<'a>): option<'a> => arr->Belt.Array.get(arr->Array.length - 1)
162
+
163
+ let findReverseWithIndex = (arr: array<'a>, fn: 'a => bool): option<('a, int)> => {
164
+ let rec loop = (index: int) => {
165
+ if index < 0 {
166
+ None
167
+ } else {
168
+ let item = arr[index]
169
+ if fn(item) {
170
+ Some((item, index))
171
+ } else {
172
+ loop(index - 1)
173
+ }
174
+ }
175
+ }
176
+ loop(arr->Array.length - 1)
177
+ }
178
+
179
+ /**
180
+ Currently a bug in rescript if you ignore the return value of spliceInPlace
181
+ https://github.com/rescript-lang/rescript-compiler/issues/6991
182
+ */
183
+ @send
184
+ external spliceInPlace: (array<'a>, ~pos: int, ~remove: int) => array<'a> = "splice"
185
+ }
186
+
187
+ module String = {
188
+ let capitalize = str => {
189
+ str->Js.String2.slice(~from=0, ~to_=1)->Js.String.toUpperCase ++
190
+ str->Js.String2.sliceToEnd(~from=1)
191
+ }
192
+ }
193
+
194
+ /**
195
+ Useful when an unsafe unwrap is needed on Result type
196
+ and Error holds an exn. This is better than Result.getExn
197
+ because the excepion is not just NOT_FOUND but will rather
198
+ bet the actual underlying exn
199
+ */
200
+ let unwrapResultExn = res =>
201
+ switch res {
202
+ | Ok(v) => v
203
+ | Error(exn) => exn->raise
204
+ }
205
+
206
+ external queueMicrotask: (unit => unit) => unit = "queueMicrotask"
207
+
208
+ module Schema = {
209
+ let getNonOptionalFieldNames = schema => {
210
+ let acc = []
211
+ switch schema->S.classify {
212
+ | Object({items}) =>
213
+ items->Js.Array2.forEach(item => {
214
+ switch item.schema->S.classify {
215
+ // Check for null, since we generate S.null schema for db serializing
216
+ // In the future it should be changed to Option
217
+ | Null(_) => ()
218
+ | _ => acc->Js.Array2.push(item.location)->ignore
219
+ }
220
+ })
221
+ | _ => ()
222
+ }
223
+ acc
224
+ }
225
+
226
+ let getCapitalizedFieldNames = schema => {
227
+ switch schema->S.classify {
228
+ | Object({items}) => items->Js.Array2.map(item => item.location->String.capitalize)
229
+ | _ => []
230
+ }
231
+ }
232
+
233
+ // When trying to serialize data to Json pg type, it will fail with
234
+ // PostgresError: column "params" is of type json but expression is of type boolean
235
+ // If there's bool or null on the root level. It works fine as object field values.
236
+ let coerceToJsonPgType = schema => {
237
+ schema->S.preprocess(s => {
238
+ switch s.schema->S.classify {
239
+ | Literal(Null(_))
240
+ | // This is a workaround for Fuel Bytes type
241
+ Unknown => {serializer: _ => %raw(`"null"`)}
242
+ | Null(_)
243
+ | Bool => {
244
+ serializer: unknown => {
245
+ if unknown === %raw(`null`) {
246
+ %raw(`"null"`)
247
+ } else if unknown === %raw(`false`) {
248
+ %raw(`"false"`)
249
+ } else if unknown === %raw(`true`) {
250
+ %raw(`"true"`)
251
+ } else {
252
+ unknown
253
+ }
254
+ },
255
+ }
256
+ | _ => {}
257
+ }
258
+ })
259
+ }
260
+ }
261
+
262
+ module Set = {
263
+ type t<'value>
264
+
265
+ /*
266
+ * Constructor
267
+ */
268
+ @ocaml.doc("Creates a new `Set` object.") @new
269
+ external make: unit => t<'value> = "Set"
270
+
271
+ @ocaml.doc("Creates a new `Set` object.") @new
272
+ external fromEntries: array<'value> => t<'value> = "Set"
273
+
274
+ /*
275
+ * Instance properties
276
+ */
277
+ @ocaml.doc("Returns the number of values in the `Set` object.") @get
278
+ external size: t<'value> => int = "size"
279
+
280
+ /*
281
+ * Instance methods
282
+ */
283
+ @ocaml.doc("Appends `value` to the `Set` object. Returns the `Set` object with added value.")
284
+ @send
285
+ external add: (t<'value>, 'value) => t<'value> = "add"
286
+
287
+ @ocaml.doc("Removes all elements from the `Set` object.") @send
288
+ external clear: t<'value> => unit = "clear"
289
+
290
+ @ocaml.doc(
291
+ "Removes the element associated to the `value` and returns a boolean asserting whether an element was successfully removed or not. `Set.prototype.has(value)` will return `false` afterwards."
292
+ )
293
+ @send
294
+ external delete: (t<'value>, 'value) => bool = "delete"
295
+
296
+ @ocaml.doc(
297
+ "Returns a boolean asserting whether an element is present with the given value in the `Set` object or not."
298
+ )
299
+ @send
300
+ external has: (t<'value>, 'value) => bool = "has"
301
+
302
+ external toArray: t<'a> => array<'a> = "Array.from"
303
+
304
+ /*
305
+ * Iteration methods
306
+ */
307
+ /*
308
+ /// NOTE - if we need iteration we can add this back - currently it requires the `rescript-js-iterator` library.
309
+ @ocaml.doc(
310
+ "Returns a new iterator object that yields the **values** for each element in the `Set` object in insertion order."
311
+ )
312
+ @send
313
+ external values: t<'value> => Js_iterator.t<'value> = "values"
314
+
315
+ @ocaml.doc("An alias for `Set.prototype.values()`.") @send
316
+ external keys: t<'value> => Js_iterator.t<'value> = "values"
317
+
318
+ @ocaml.doc("Returns a new iterator object that contains **an array of [value, value]** for each element in the `Set` object, in insertion order.
319
+
320
+ This is similar to the [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) object, so that each entry's `key` is the same as its `value` for a `Set`.")
321
+ @send
322
+ external entries: t<'value> => Js_iterator.t<('value, 'value)> = "entries"
323
+ */
324
+ @ocaml.doc(
325
+ "Calls `callbackFn` once for each value present in the `Set` object, in insertion order."
326
+ )
327
+ @send
328
+ external forEach: (t<'value>, 'value => unit) => unit = "forEach"
329
+
330
+ @ocaml.doc(
331
+ "Calls `callbackFn` once for each value present in the `Set` object, in insertion order."
332
+ )
333
+ @send
334
+ external forEachWithSet: (t<'value>, ('value, 'value, t<'value>) => unit) => unit = "forEach"
335
+ }
336
+
337
+ module WeakMap = {
338
+ type t<'k, 'v> = Js.WeakMap.t<'k, 'v>
339
+
340
+ @new external make: unit => t<'k, 'v> = "WeakMap"
341
+
342
+ @send external get: (t<'k, 'v>, 'k) => option<'v> = "get"
343
+ @send external unsafeGet: (t<'k, 'v>, 'k) => 'v = "get"
344
+ @send external has: (t<'k, 'v>, 'k) => bool = "has"
345
+ @send external set: (t<'k, 'v>, 'k, 'v) => t<'k, 'v> = "set"
346
+ }
@@ -0,0 +1,175 @@
1
+ type logLevelBuiltin = [
2
+ | #trace
3
+ | #debug
4
+ | #info
5
+ | #warn
6
+ | #error
7
+ | #fatal
8
+ ]
9
+ @genType
10
+ type logLevelUser = [
11
+ | // NOTE: pino does better when these are all lowercase - some parts of the code lower case logs.
12
+ #udebug
13
+ | #uinfo
14
+ | #uwarn
15
+ | #uerror
16
+ ]
17
+ type logLevel = [logLevelBuiltin | logLevelUser]
18
+
19
+ type pinoMessageBlob
20
+ type pinoMessageBlobWithError
21
+ @genType
22
+ type t = {
23
+ trace: pinoMessageBlob => unit,
24
+ debug: pinoMessageBlob => unit,
25
+ info: pinoMessageBlob => unit,
26
+ warn: pinoMessageBlob => unit,
27
+ error: pinoMessageBlob => unit,
28
+ fatal: pinoMessageBlob => unit,
29
+ }
30
+ @send external errorExn: (t, pinoMessageBlobWithError) => unit = "error"
31
+
32
+ // Bind to the 'level' property getter
33
+ @get external getLevel: t => logLevel = "level"
34
+
35
+ @ocaml.doc(`Get the available logging levels`) @get
36
+ external levels: t => 'a = "levels"
37
+
38
+ // Bind to the 'level' property setter
39
+ @set external setLevel: (t, logLevel) => unit = "level"
40
+
41
+ @ocaml.doc(`Identity function to help co-erce any type to a pino log message`)
42
+ let createPinoMessage = (message): pinoMessageBlob => Utils.magic(message)
43
+ let createPinoMessageWithError = (message, err): pinoMessageBlobWithError => {
44
+ //See https://github.com/pinojs/pino-std-serializers for standard pino serializers
45
+ //for common objects. We have also defined the serializer in this format in the
46
+ // serializers type below: `type serializers = {err: Js.Json.t => Js.Json.t}`
47
+ Utils.magic({
48
+ "msg": message,
49
+ "err": err,
50
+ })
51
+ }
52
+
53
+ module Transport = {
54
+ type t
55
+ type optionsObject
56
+ let makeTransportOptions: 'a => optionsObject = Utils.magic
57
+
58
+ // NOTE: this config is pretty polymorphic - so keeping this as all optional fields.
59
+ type rec transportTarget = {
60
+ target?: string,
61
+ targets?: array<transportTarget>,
62
+ options?: optionsObject,
63
+ levels?: dict<int>,
64
+ level?: logLevel,
65
+ }
66
+ @module("pino")
67
+ external make: transportTarget => t = "transport"
68
+ }
69
+
70
+ @module external makeWithTransport: Transport.t => t = "pino"
71
+
72
+ type hooks = {logMethod: (array<string>, string, logLevel) => unit}
73
+
74
+ type formatters = {
75
+ level: (string, int) => Js.Json.t,
76
+ bindings: Js.Json.t => Js.Json.t,
77
+ log: Js.Json.t => Js.Json.t,
78
+ }
79
+
80
+ type serializers = {err: Js.Json.t => Js.Json.t}
81
+
82
+ type options = {
83
+ name?: string,
84
+ level?: logLevel,
85
+ customLevels?: dict<int>,
86
+ useOnlyCustomLevels?: bool,
87
+ depthLimit?: int,
88
+ edgeLimit?: int,
89
+ mixin?: unit => Js.Json.t,
90
+ mixinMergeStrategy?: (Js.Json.t, Js.Json.t) => Js.Json.t,
91
+ redact?: array<string>,
92
+ hooks?: hooks,
93
+ formatters?: formatters,
94
+ serializers?: serializers,
95
+ msgPrefix?: string,
96
+ base?: Js.Json.t,
97
+ enabled?: bool,
98
+ crlf?: bool,
99
+ timestamp?: bool,
100
+ messageKey?: string,
101
+ }
102
+
103
+ @module external make: options => t = "pino"
104
+ @module external makeWithOptionsAndTransport: (options, Transport.t) => t = "pino"
105
+
106
+ type childParams
107
+ let createChildParams: 'a => childParams = Utils.magic
108
+ @send external child: (t, childParams) => t = "child"
109
+
110
+ module ECS = {
111
+ @module
112
+ external make: 'a => options = "@elastic/ecs-pino-format"
113
+ }
114
+
115
+ /**
116
+ Jank solution to make logs use console log wrather than stream.write so that ink
117
+ can render the logs statically.
118
+ */
119
+ module MultiStreamLogger = {
120
+ type stream = {write: string => unit}
121
+ type multiStream = {stream: stream, level: logLevel}
122
+ type multiStreamRes
123
+ @module("pino") external multistream: array<multiStream> => multiStreamRes = "multistream"
124
+
125
+ @module external makeWithMultiStream: (options, multiStreamRes) => t = "pino"
126
+
127
+ type destinationOpts = {
128
+ dest: string, //file path
129
+ sync: bool,
130
+ mkdir: bool,
131
+ }
132
+ @module("pino") external destination: destinationOpts => stream = "destination"
133
+
134
+ type prettyFactoryOpts = {...options, customColors?: string}
135
+ @module("pino-pretty")
136
+ external prettyFactory: prettyFactoryOpts => string => string = "prettyFactory"
137
+
138
+ let makeFormatter = logLevels => {
139
+ prettyFactory({
140
+ customLevels: logLevels,
141
+ customColors: "fatal:bgRed,error:red,warn:yellow,info:green,udebug:bgBlue,uinfo:bgGreen,uwarn:bgYellow,uerror:bgRed,debug:blue,trace:gray",
142
+ })
143
+ }
144
+
145
+ let makeStreams = (~userLogLevel, ~formatter, ~logFile, ~defaultFileLogLevel) => {
146
+ let stream = {
147
+ stream: {write: v => formatter(v)->Js.log},
148
+ level: userLogLevel,
149
+ }
150
+ let maybeFileStream = logFile->Belt.Option.mapWithDefault([], dest => [
151
+ {
152
+ level: defaultFileLogLevel,
153
+ stream: destination({dest, sync: false, mkdir: true}),
154
+ },
155
+ ])
156
+ [stream]->Belt.Array.concat(maybeFileStream)
157
+ }
158
+
159
+ let make = (
160
+ ~userLogLevel: logLevel,
161
+ ~customLevels: dict<int>,
162
+ ~logFile: option<string>,
163
+ ~options: option<options>,
164
+ ~defaultFileLogLevel,
165
+ ) => {
166
+ let options = switch options {
167
+ | Some(opts) => {...opts, customLevels, level: userLogLevel}
168
+ | None => {customLevels, level: userLogLevel}
169
+ }
170
+ let formatter = makeFormatter(customLevels)
171
+ let ms = makeStreams(~userLogLevel, ~formatter, ~logFile, ~defaultFileLogLevel)->multistream
172
+
173
+ makeWithMultiStream(options, ms)
174
+ }
175
+ }