envio 2.31.1 → 2.32.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.
- package/index.d.ts +32 -0
- package/package.json +5 -5
- package/src/Batch.res +22 -2
- package/src/Batch.res.js +15 -0
- package/src/Config.res +124 -0
- package/src/Config.res.js +84 -0
- package/src/Envio.gen.ts +21 -0
- package/src/Envio.res +79 -14
- package/src/Envio.res.js +52 -15
- package/src/EventRegister.res +2 -2
- package/src/EventRegister.resi +2 -2
- package/src/InMemoryTable.res +356 -0
- package/src/InMemoryTable.res.js +401 -0
- package/src/Indexer.res +5 -0
- package/src/Internal.res +16 -4
- package/src/Persistence.res +8 -3
- package/src/Persistence.res.js +3 -2
- package/src/PgStorage.res +1 -7
- package/src/PgStorage.res.js +1 -2
- package/src/Prometheus.res +47 -9
- package/src/Prometheus.res.js +42 -14
- package/src/TableIndices.res +110 -0
- package/src/TableIndices.res.js +143 -0
- package/src/Types.ts +5 -0
- package/src/bindings/Lodash.res +3 -0
- package/src/bindings/Lodash.res.js +11 -0
- package/src/bindings/vendored-lodash-fns.js +1441 -0
- package/src/db/InternalTable.res +2 -2
- package/src/sources/RpcSource.res +2 -2
- package/src/InternalConfig.res +0 -48
- /package/src/{InternalConfig.res.js → Indexer.res.js} +0 -0
package/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type {
|
|
|
4
4
|
effectContext as EffectContext,
|
|
5
5
|
effectArgs as EffectArgs,
|
|
6
6
|
effectOptions as EffectOptions,
|
|
7
|
+
rateLimitDuration as RateLimitDuration,
|
|
8
|
+
rateLimit as RateLimit,
|
|
7
9
|
blockEvent as BlockEvent,
|
|
8
10
|
onBlockArgs as OnBlockArgs,
|
|
9
11
|
onBlockOptions as OnBlockOptions,
|
|
@@ -13,6 +15,7 @@ export type { EffectCaller } from "./src/Types.ts";
|
|
|
13
15
|
import type {
|
|
14
16
|
effect as Effect,
|
|
15
17
|
effectArgs as EffectArgs,
|
|
18
|
+
rateLimit as RateLimit,
|
|
16
19
|
} from "./src/Envio.gen.ts";
|
|
17
20
|
|
|
18
21
|
import { schema as bigDecimalSchema } from "./src/bindings/BigDecimal.gen.ts";
|
|
@@ -73,6 +76,33 @@ type Flatten<T> = T extends object
|
|
|
73
76
|
// })
|
|
74
77
|
// The behaviour is inspired by Sury code:
|
|
75
78
|
// https://github.com/DZakh/sury/blob/551f8ee32c1af95320936d00c086e5fb337f59fa/packages/sury/src/S.d.ts#L344C1-L355C50
|
|
79
|
+
export function createEffect<
|
|
80
|
+
IS,
|
|
81
|
+
OS,
|
|
82
|
+
I = UnknownToOutput<IS>,
|
|
83
|
+
O = UnknownToOutput<OS>,
|
|
84
|
+
// A hack to enforce that the inferred return type
|
|
85
|
+
// matches the output schema type
|
|
86
|
+
R extends O = O
|
|
87
|
+
>(
|
|
88
|
+
options: {
|
|
89
|
+
/** The name of the effect. Used for logging and debugging. */
|
|
90
|
+
readonly name: string;
|
|
91
|
+
/** The input schema of the effect. */
|
|
92
|
+
readonly input: IS;
|
|
93
|
+
/** The output schema of the effect. */
|
|
94
|
+
readonly output: OS;
|
|
95
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
96
|
+
readonly rateLimit: RateLimit;
|
|
97
|
+
/** Whether the effect should be cached. */
|
|
98
|
+
readonly cache?: boolean;
|
|
99
|
+
},
|
|
100
|
+
handler: (args: EffectArgs<I>) => Promise<R>
|
|
101
|
+
): Effect<I, O>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @deprecated Use createEffect instead. The only difference is that rateLimit option becomes required. Set it to false to keep the same behaviour.
|
|
105
|
+
*/
|
|
76
106
|
export function experimental_createEffect<
|
|
77
107
|
IS,
|
|
78
108
|
OS,
|
|
@@ -89,6 +119,8 @@ export function experimental_createEffect<
|
|
|
89
119
|
readonly input: IS;
|
|
90
120
|
/** The output schema of the effect. */
|
|
91
121
|
readonly output: OS;
|
|
122
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
123
|
+
readonly rateLimit?: RateLimit;
|
|
92
124
|
/** Whether the effect should be cached. */
|
|
93
125
|
readonly cache?: boolean;
|
|
94
126
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "v2.
|
|
3
|
+
"version": "v2.32.0",
|
|
4
4
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
5
5
|
"bin": "./bin.js",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://envio.dev",
|
|
27
27
|
"optionalDependencies": {
|
|
28
|
-
"envio-linux-x64": "v2.
|
|
29
|
-
"envio-linux-arm64": "v2.
|
|
30
|
-
"envio-darwin-x64": "v2.
|
|
31
|
-
"envio-darwin-arm64": "v2.
|
|
28
|
+
"envio-linux-x64": "v2.32.0",
|
|
29
|
+
"envio-linux-arm64": "v2.32.0",
|
|
30
|
+
"envio-darwin-x64": "v2.32.0",
|
|
31
|
+
"envio-darwin-arm64": "v2.32.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@envio-dev/hypersync-client": "0.6.6",
|
package/src/Batch.res
CHANGED
|
@@ -84,7 +84,7 @@ let hasUnorderedReadyItem = (fetchStates: ChainMap.t<FetchState.t>) => {
|
|
|
84
84
|
|
|
85
85
|
let hasMultichainReadyItem = (
|
|
86
86
|
fetchStates: ChainMap.t<FetchState.t>,
|
|
87
|
-
~multichain:
|
|
87
|
+
~multichain: Config.multichain,
|
|
88
88
|
) => {
|
|
89
89
|
switch multichain {
|
|
90
90
|
| Ordered => hasOrderedReadyItem(fetchStates)
|
|
@@ -496,7 +496,7 @@ let prepareUnorderedBatch = (
|
|
|
496
496
|
let make = (
|
|
497
497
|
~checkpointIdBeforeBatch,
|
|
498
498
|
~chainsBeforeBatch: ChainMap.t<chainBeforeBatch>,
|
|
499
|
-
~multichain:
|
|
499
|
+
~multichain: Config.multichain,
|
|
500
500
|
~batchSizeTarget,
|
|
501
501
|
) => {
|
|
502
502
|
if (
|
|
@@ -528,3 +528,23 @@ let findFirstEventBlockNumber = (batch: t, ~chainId) => {
|
|
|
528
528
|
}
|
|
529
529
|
result.contents
|
|
530
530
|
}
|
|
531
|
+
|
|
532
|
+
let findLastEventItem = (batch: t, ~chainId) => {
|
|
533
|
+
let idx = ref(batch.items->Array.length - 1)
|
|
534
|
+
let result = ref(None)
|
|
535
|
+
while idx.contents >= 0 && result.contents === None {
|
|
536
|
+
let item = batch.items->Array.getUnsafe(idx.contents)
|
|
537
|
+
switch item {
|
|
538
|
+
| Internal.Event(_) as eventItem => {
|
|
539
|
+
let eventItem = eventItem->Internal.castUnsafeEventItem
|
|
540
|
+
if eventItem.chain->ChainMap.Chain.toChainId === chainId {
|
|
541
|
+
result := Some(eventItem)
|
|
542
|
+
} else {
|
|
543
|
+
idx := idx.contents - 1
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
| Internal.Block(_) => idx := idx.contents - 1
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
result.contents
|
|
550
|
+
}
|
package/src/Batch.res.js
CHANGED
|
@@ -361,6 +361,20 @@ function findFirstEventBlockNumber(batch, chainId) {
|
|
|
361
361
|
return result;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
function findLastEventItem(batch, chainId) {
|
|
365
|
+
var idx = batch.items.length - 1;
|
|
366
|
+
var result;
|
|
367
|
+
while(idx >= 0 && result === undefined) {
|
|
368
|
+
var item = batch.items[idx];
|
|
369
|
+
if (item.kind === 0 && item.chain === chainId) {
|
|
370
|
+
result = item;
|
|
371
|
+
} else {
|
|
372
|
+
idx = idx - 1;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
364
378
|
exports.getOrderedNextChain = getOrderedNextChain;
|
|
365
379
|
exports.immutableEmptyBatchSizePerChain = immutableEmptyBatchSizePerChain;
|
|
366
380
|
exports.hasOrderedReadyItem = hasOrderedReadyItem;
|
|
@@ -372,4 +386,5 @@ exports.prepareOrderedBatch = prepareOrderedBatch;
|
|
|
372
386
|
exports.prepareUnorderedBatch = prepareUnorderedBatch;
|
|
373
387
|
exports.make = make;
|
|
374
388
|
exports.findFirstEventBlockNumber = findFirstEventBlockNumber;
|
|
389
|
+
exports.findLastEventItem = findLastEventItem;
|
|
375
390
|
/* Utils Not a pure module */
|
package/src/Config.res
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
open Belt
|
|
2
|
+
|
|
3
|
+
type ecosystem = | @as("evm") Evm | @as("fuel") Fuel
|
|
4
|
+
|
|
5
|
+
type sourceSyncOptions = {
|
|
6
|
+
initialBlockInterval?: int,
|
|
7
|
+
backoffMultiplicative?: float,
|
|
8
|
+
accelerationAdditive?: int,
|
|
9
|
+
intervalCeiling?: int,
|
|
10
|
+
backoffMillis?: int,
|
|
11
|
+
queryTimeoutMillis?: int,
|
|
12
|
+
fallbackStallTimeout?: int,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type contract = {
|
|
16
|
+
name: string,
|
|
17
|
+
abi: EvmTypes.Abi.t,
|
|
18
|
+
addresses: array<Address.t>,
|
|
19
|
+
events: array<Internal.eventConfig>,
|
|
20
|
+
startBlock: option<int>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type chain = {
|
|
24
|
+
id: int,
|
|
25
|
+
startBlock: int,
|
|
26
|
+
endBlock?: int,
|
|
27
|
+
maxReorgDepth: int,
|
|
28
|
+
contracts: array<contract>,
|
|
29
|
+
sources: array<Source.t>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type sourceSync = {
|
|
33
|
+
initialBlockInterval: int,
|
|
34
|
+
backoffMultiplicative: float,
|
|
35
|
+
accelerationAdditive: int,
|
|
36
|
+
intervalCeiling: int,
|
|
37
|
+
backoffMillis: int,
|
|
38
|
+
queryTimeoutMillis: int,
|
|
39
|
+
fallbackStallTimeout: int,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type multichain = | @as("ordered") Ordered | @as("unordered") Unordered
|
|
43
|
+
|
|
44
|
+
type t = {
|
|
45
|
+
shouldRollbackOnReorg: bool,
|
|
46
|
+
shouldSaveFullHistory: bool,
|
|
47
|
+
multichain: multichain,
|
|
48
|
+
chainMap: ChainMap.t<chain>,
|
|
49
|
+
defaultChain: option<chain>,
|
|
50
|
+
ecosystem: ecosystem,
|
|
51
|
+
enableRawEvents: bool,
|
|
52
|
+
preloadHandlers: bool,
|
|
53
|
+
maxAddrInPartition: int,
|
|
54
|
+
batchSize: int,
|
|
55
|
+
lowercaseAddresses: bool,
|
|
56
|
+
addContractNameToContractNameMapping: dict<string>,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let make = (
|
|
60
|
+
~shouldRollbackOnReorg=true,
|
|
61
|
+
~shouldSaveFullHistory=false,
|
|
62
|
+
~chains: array<chain>=[],
|
|
63
|
+
~enableRawEvents=false,
|
|
64
|
+
~preloadHandlers=false,
|
|
65
|
+
~ecosystem=Evm,
|
|
66
|
+
~batchSize=5000,
|
|
67
|
+
~lowercaseAddresses=false,
|
|
68
|
+
~multichain=Unordered,
|
|
69
|
+
~shouldUseHypersyncClientDecoder=true,
|
|
70
|
+
~maxAddrInPartition=5000,
|
|
71
|
+
) => {
|
|
72
|
+
// Validate that lowercase addresses is not used with viem decoder
|
|
73
|
+
if lowercaseAddresses && !shouldUseHypersyncClientDecoder {
|
|
74
|
+
Js.Exn.raiseError(
|
|
75
|
+
"lowercase addresses is not supported when event_decoder is 'viem'. Please set event_decoder to 'hypersync-client' or change address_format to 'checksum'.",
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let chainMap =
|
|
80
|
+
chains
|
|
81
|
+
->Js.Array2.map(n => {
|
|
82
|
+
(ChainMap.Chain.makeUnsafe(~chainId=n.id), n)
|
|
83
|
+
})
|
|
84
|
+
->ChainMap.fromArrayUnsafe
|
|
85
|
+
|
|
86
|
+
// Build the contract name mapping for efficient lookup
|
|
87
|
+
let addContractNameToContractNameMapping = Js.Dict.empty()
|
|
88
|
+
chains->Array.forEach(chainConfig => {
|
|
89
|
+
chainConfig.contracts->Array.forEach(contract => {
|
|
90
|
+
let addKey = "add" ++ contract.name->Utils.String.capitalize
|
|
91
|
+
addContractNameToContractNameMapping->Js.Dict.set(addKey, contract.name)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
shouldRollbackOnReorg,
|
|
97
|
+
shouldSaveFullHistory,
|
|
98
|
+
multichain,
|
|
99
|
+
chainMap,
|
|
100
|
+
defaultChain: chains->Array.get(0),
|
|
101
|
+
enableRawEvents,
|
|
102
|
+
ecosystem,
|
|
103
|
+
maxAddrInPartition,
|
|
104
|
+
preloadHandlers,
|
|
105
|
+
batchSize,
|
|
106
|
+
lowercaseAddresses,
|
|
107
|
+
addContractNameToContractNameMapping,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let shouldSaveHistory = (config, ~isInReorgThreshold) =>
|
|
112
|
+
config.shouldSaveFullHistory || (config.shouldRollbackOnReorg && isInReorgThreshold)
|
|
113
|
+
|
|
114
|
+
let shouldPruneHistory = (config, ~isInReorgThreshold) =>
|
|
115
|
+
!config.shouldSaveFullHistory && (config.shouldRollbackOnReorg && isInReorgThreshold)
|
|
116
|
+
|
|
117
|
+
let getChain = (config, ~chainId) => {
|
|
118
|
+
let chain = ChainMap.Chain.makeUnsafe(~chainId)
|
|
119
|
+
config.chainMap->ChainMap.has(chain)
|
|
120
|
+
? chain
|
|
121
|
+
: Js.Exn.raiseError(
|
|
122
|
+
"No chain with id " ++ chain->ChainMap.Chain.toString ++ " found in config.yaml",
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var Utils = require("./Utils.res.js");
|
|
5
|
+
var Js_exn = require("rescript/lib/js/js_exn.js");
|
|
6
|
+
var ChainMap = require("./ChainMap.res.js");
|
|
7
|
+
var Belt_Array = require("rescript/lib/js/belt_Array.js");
|
|
8
|
+
|
|
9
|
+
function make(shouldRollbackOnReorgOpt, shouldSaveFullHistoryOpt, chainsOpt, enableRawEventsOpt, preloadHandlersOpt, ecosystemOpt, batchSizeOpt, lowercaseAddressesOpt, multichainOpt, shouldUseHypersyncClientDecoderOpt, maxAddrInPartitionOpt) {
|
|
10
|
+
var shouldRollbackOnReorg = shouldRollbackOnReorgOpt !== undefined ? shouldRollbackOnReorgOpt : true;
|
|
11
|
+
var shouldSaveFullHistory = shouldSaveFullHistoryOpt !== undefined ? shouldSaveFullHistoryOpt : false;
|
|
12
|
+
var chains = chainsOpt !== undefined ? chainsOpt : [];
|
|
13
|
+
var enableRawEvents = enableRawEventsOpt !== undefined ? enableRawEventsOpt : false;
|
|
14
|
+
var preloadHandlers = preloadHandlersOpt !== undefined ? preloadHandlersOpt : false;
|
|
15
|
+
var ecosystem = ecosystemOpt !== undefined ? ecosystemOpt : "evm";
|
|
16
|
+
var batchSize = batchSizeOpt !== undefined ? batchSizeOpt : 5000;
|
|
17
|
+
var lowercaseAddresses = lowercaseAddressesOpt !== undefined ? lowercaseAddressesOpt : false;
|
|
18
|
+
var multichain = multichainOpt !== undefined ? multichainOpt : "unordered";
|
|
19
|
+
var shouldUseHypersyncClientDecoder = shouldUseHypersyncClientDecoderOpt !== undefined ? shouldUseHypersyncClientDecoderOpt : true;
|
|
20
|
+
var maxAddrInPartition = maxAddrInPartitionOpt !== undefined ? maxAddrInPartitionOpt : 5000;
|
|
21
|
+
if (lowercaseAddresses && !shouldUseHypersyncClientDecoder) {
|
|
22
|
+
Js_exn.raiseError("lowercase addresses is not supported when event_decoder is 'viem'. Please set event_decoder to 'hypersync-client' or change address_format to 'checksum'.");
|
|
23
|
+
}
|
|
24
|
+
var chainMap = ChainMap.fromArrayUnsafe(chains.map(function (n) {
|
|
25
|
+
return [
|
|
26
|
+
ChainMap.Chain.makeUnsafe(n.id),
|
|
27
|
+
n
|
|
28
|
+
];
|
|
29
|
+
}));
|
|
30
|
+
var addContractNameToContractNameMapping = {};
|
|
31
|
+
Belt_Array.forEach(chains, (function (chainConfig) {
|
|
32
|
+
Belt_Array.forEach(chainConfig.contracts, (function (contract) {
|
|
33
|
+
var addKey = "add" + Utils.$$String.capitalize(contract.name);
|
|
34
|
+
addContractNameToContractNameMapping[addKey] = contract.name;
|
|
35
|
+
}));
|
|
36
|
+
}));
|
|
37
|
+
return {
|
|
38
|
+
shouldRollbackOnReorg: shouldRollbackOnReorg,
|
|
39
|
+
shouldSaveFullHistory: shouldSaveFullHistory,
|
|
40
|
+
multichain: multichain,
|
|
41
|
+
chainMap: chainMap,
|
|
42
|
+
defaultChain: Belt_Array.get(chains, 0),
|
|
43
|
+
ecosystem: ecosystem,
|
|
44
|
+
enableRawEvents: enableRawEvents,
|
|
45
|
+
preloadHandlers: preloadHandlers,
|
|
46
|
+
maxAddrInPartition: maxAddrInPartition,
|
|
47
|
+
batchSize: batchSize,
|
|
48
|
+
lowercaseAddresses: lowercaseAddresses,
|
|
49
|
+
addContractNameToContractNameMapping: addContractNameToContractNameMapping
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function shouldSaveHistory(config, isInReorgThreshold) {
|
|
54
|
+
if (config.shouldSaveFullHistory) {
|
|
55
|
+
return true;
|
|
56
|
+
} else if (config.shouldRollbackOnReorg) {
|
|
57
|
+
return isInReorgThreshold;
|
|
58
|
+
} else {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function shouldPruneHistory(config, isInReorgThreshold) {
|
|
64
|
+
if (!config.shouldSaveFullHistory && config.shouldRollbackOnReorg) {
|
|
65
|
+
return isInReorgThreshold;
|
|
66
|
+
} else {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getChain(config, chainId) {
|
|
72
|
+
var chain = ChainMap.Chain.makeUnsafe(chainId);
|
|
73
|
+
if (ChainMap.has(config.chainMap, chain)) {
|
|
74
|
+
return chain;
|
|
75
|
+
} else {
|
|
76
|
+
return Js_exn.raiseError("No chain with id " + ChainMap.Chain.toString(chain) + " found in config.yaml");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
exports.make = make;
|
|
81
|
+
exports.shouldSaveHistory = shouldSaveHistory;
|
|
82
|
+
exports.shouldPruneHistory = shouldPruneHistory;
|
|
83
|
+
exports.getChain = getChain;
|
|
84
|
+
/* Utils Not a pure module */
|
package/src/Envio.gen.ts
CHANGED
|
@@ -29,6 +29,25 @@ export type logger = $$logger;
|
|
|
29
29
|
|
|
30
30
|
export type effect<input,output> = $$effect<input,output>;
|
|
31
31
|
|
|
32
|
+
export type rateLimitDuration = "second" | "minute" | number;
|
|
33
|
+
|
|
34
|
+
export type rateLimit =
|
|
35
|
+
false
|
|
36
|
+
| { readonly calls: number; readonly per: rateLimitDuration };
|
|
37
|
+
|
|
38
|
+
export type experimental_effectOptions<input,output> = {
|
|
39
|
+
/** The name of the effect. Used for logging and debugging. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/** The input schema of the effect. */
|
|
42
|
+
readonly input: RescriptSchema_S_t<input>;
|
|
43
|
+
/** The output schema of the effect. */
|
|
44
|
+
readonly output: RescriptSchema_S_t<output>;
|
|
45
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
46
|
+
readonly rateLimit?: rateLimit;
|
|
47
|
+
/** Whether the effect should be cached. */
|
|
48
|
+
readonly cache?: boolean
|
|
49
|
+
};
|
|
50
|
+
|
|
32
51
|
export type effectOptions<input,output> = {
|
|
33
52
|
/** The name of the effect. Used for logging and debugging. */
|
|
34
53
|
readonly name: string;
|
|
@@ -36,6 +55,8 @@ export type effectOptions<input,output> = {
|
|
|
36
55
|
readonly input: RescriptSchema_S_t<input>;
|
|
37
56
|
/** The output schema of the effect. */
|
|
38
57
|
readonly output: RescriptSchema_S_t<output>;
|
|
58
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
59
|
+
readonly rateLimit: rateLimit;
|
|
39
60
|
/** Whether the effect should be cached. */
|
|
40
61
|
readonly cache?: boolean
|
|
41
62
|
};
|
package/src/Envio.res
CHANGED
|
@@ -32,6 +32,28 @@ type logger = {
|
|
|
32
32
|
@@warning("-30") // Duplicated type names (input)
|
|
33
33
|
@genType.import(("./Types.ts", "Effect"))
|
|
34
34
|
type rec effect<'input, 'output>
|
|
35
|
+
@genType @unboxed
|
|
36
|
+
and rateLimitDuration =
|
|
37
|
+
| @as("second") Second
|
|
38
|
+
| @as("minute") Minute
|
|
39
|
+
| Milliseconds(int)
|
|
40
|
+
@genType @unboxed
|
|
41
|
+
and rateLimit =
|
|
42
|
+
| @as(false) Disable
|
|
43
|
+
| Enable({calls: int, per: rateLimitDuration})
|
|
44
|
+
@genType
|
|
45
|
+
and experimental_effectOptions<'input, 'output> = {
|
|
46
|
+
/** The name of the effect. Used for logging and debugging. */
|
|
47
|
+
name: string,
|
|
48
|
+
/** The input schema of the effect. */
|
|
49
|
+
input: S.t<'input>,
|
|
50
|
+
/** The output schema of the effect. */
|
|
51
|
+
output: S.t<'output>,
|
|
52
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
53
|
+
rateLimit?: rateLimit,
|
|
54
|
+
/** Whether the effect should be cached. */
|
|
55
|
+
cache?: bool,
|
|
56
|
+
}
|
|
35
57
|
@genType
|
|
36
58
|
and effectOptions<'input, 'output> = {
|
|
37
59
|
/** The name of the effect. Used for logging and debugging. */
|
|
@@ -40,6 +62,8 @@ and effectOptions<'input, 'output> = {
|
|
|
40
62
|
input: S.t<'input>,
|
|
41
63
|
/** The output schema of the effect. */
|
|
42
64
|
output: S.t<'output>,
|
|
65
|
+
/** Rate limit for the effect. Set to false to disable or provide {calls: number, per: "second" | "minute"} to enable. */
|
|
66
|
+
rateLimit: rateLimit,
|
|
43
67
|
/** Whether the effect should be cached. */
|
|
44
68
|
cache?: bool,
|
|
45
69
|
}
|
|
@@ -47,6 +71,7 @@ and effectOptions<'input, 'output> = {
|
|
|
47
71
|
and effectContext = {
|
|
48
72
|
log: logger,
|
|
49
73
|
effect: 'input 'output. (effect<'input, 'output>, 'input) => promise<'output>,
|
|
74
|
+
mutable cache: bool,
|
|
50
75
|
}
|
|
51
76
|
@genType
|
|
52
77
|
and effectArgs<'input> = {
|
|
@@ -55,12 +80,23 @@ and effectArgs<'input> = {
|
|
|
55
80
|
}
|
|
56
81
|
@@warning("+30")
|
|
57
82
|
|
|
58
|
-
let
|
|
83
|
+
let durationToMs = (duration: rateLimitDuration) =>
|
|
84
|
+
switch duration {
|
|
85
|
+
| Second => 1000
|
|
86
|
+
| Minute => 60000
|
|
87
|
+
| Milliseconds(ms) => ms
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let createEffect = (
|
|
59
91
|
options: effectOptions<'input, 'output>,
|
|
60
92
|
handler: effectArgs<'input> => promise<'output>,
|
|
61
93
|
) => {
|
|
62
94
|
let outputSchema =
|
|
63
95
|
S.schema(_ => options.output)->(Utils.magic: S.t<S.t<'output>> => S.t<Internal.effectOutput>)
|
|
96
|
+
let itemSchema = S.schema((s): Internal.effectCacheItem => {
|
|
97
|
+
id: s.matches(S.string),
|
|
98
|
+
output: s.matches(outputSchema),
|
|
99
|
+
})
|
|
64
100
|
{
|
|
65
101
|
name: options.name,
|
|
66
102
|
handler: handler->(
|
|
@@ -68,7 +104,8 @@ let experimental_createEffect = (
|
|
|
68
104
|
Internal.effectOutput,
|
|
69
105
|
>
|
|
70
106
|
),
|
|
71
|
-
|
|
107
|
+
activeCallsCount: 0,
|
|
108
|
+
prevCallStartTimerRef: %raw(`null`),
|
|
72
109
|
// This is the way to make the createEffect API
|
|
73
110
|
// work without the need for users to call S.schema themselves,
|
|
74
111
|
// but simply pass the desired object/tuple/etc.
|
|
@@ -77,20 +114,48 @@ let experimental_createEffect = (
|
|
|
77
114
|
Utils.magic: S.t<S.t<'input>> => S.t<Internal.effectInput>
|
|
78
115
|
),
|
|
79
116
|
output: outputSchema,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
117
|
+
storageMeta: {
|
|
118
|
+
table: Internal.makeCacheTable(~effectName=options.name),
|
|
119
|
+
outputSchema,
|
|
120
|
+
itemSchema,
|
|
121
|
+
},
|
|
122
|
+
defaultShouldCache: switch options.cache {
|
|
123
|
+
| Some(true) => true
|
|
124
|
+
| _ => false
|
|
125
|
+
},
|
|
126
|
+
rateLimit: switch options.rateLimit {
|
|
127
|
+
| Disable => None
|
|
128
|
+
| Enable({calls, per}) =>
|
|
86
129
|
Some({
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
callsPerDuration: calls,
|
|
131
|
+
durationMs: per->durationToMs,
|
|
132
|
+
availableCalls: calls,
|
|
133
|
+
windowStartTime: Js.Date.now(),
|
|
134
|
+
queueCount: 0,
|
|
135
|
+
nextWindowPromise: None,
|
|
90
136
|
})
|
|
91
|
-
| None
|
|
92
|
-
| Some(false) =>
|
|
93
|
-
None
|
|
94
137
|
},
|
|
95
138
|
}->(Utils.magic: Internal.effect => effect<'input, 'output>)
|
|
96
139
|
}
|
|
140
|
+
|
|
141
|
+
@deprecated(
|
|
142
|
+
"Use createEffect instead. The only difference is that rateLimit option becomes required. Set it to false to keep the same behaviour."
|
|
143
|
+
)
|
|
144
|
+
let experimental_createEffect = (
|
|
145
|
+
options: experimental_effectOptions<'input, 'output>,
|
|
146
|
+
handler: effectArgs<'input> => promise<'output>,
|
|
147
|
+
) => {
|
|
148
|
+
createEffect(
|
|
149
|
+
{
|
|
150
|
+
name: options.name,
|
|
151
|
+
input: options.input,
|
|
152
|
+
output: options.output,
|
|
153
|
+
rateLimit: switch options.rateLimit {
|
|
154
|
+
| Some(rateLimit) => rateLimit
|
|
155
|
+
| None => Disable
|
|
156
|
+
},
|
|
157
|
+
cache: ?options.cache,
|
|
158
|
+
},
|
|
159
|
+
handler,
|
|
160
|
+
)
|
|
161
|
+
}
|
package/src/Envio.res.js
CHANGED
|
@@ -4,38 +4,75 @@
|
|
|
4
4
|
var Internal = require("./Internal.res.js");
|
|
5
5
|
var S$RescriptSchema = require("rescript-schema/src/S.res.js");
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
function durationToMs(duration) {
|
|
8
|
+
if (typeof duration !== "number") {
|
|
9
|
+
if (duration === "second") {
|
|
10
|
+
return 1000;
|
|
11
|
+
} else {
|
|
12
|
+
return 60000;
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
return duration;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createEffect(options, handler) {
|
|
8
20
|
var outputSchema = S$RescriptSchema.schema(function (param) {
|
|
9
21
|
return options.output;
|
|
10
22
|
});
|
|
23
|
+
var itemSchema = S$RescriptSchema.schema(function (s) {
|
|
24
|
+
return {
|
|
25
|
+
id: s.m(S$RescriptSchema.string),
|
|
26
|
+
output: s.m(outputSchema)
|
|
27
|
+
};
|
|
28
|
+
});
|
|
11
29
|
var match = options.cache;
|
|
30
|
+
var match$1 = options.rateLimit;
|
|
12
31
|
var tmp;
|
|
13
|
-
if (match !==
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
output: s.m(outputSchema)
|
|
18
|
-
};
|
|
19
|
-
});
|
|
32
|
+
if (typeof match$1 !== "object") {
|
|
33
|
+
tmp = undefined;
|
|
34
|
+
} else {
|
|
35
|
+
var calls = match$1.calls;
|
|
20
36
|
tmp = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
callsPerDuration: calls,
|
|
38
|
+
durationMs: durationToMs(match$1.per),
|
|
39
|
+
availableCalls: calls,
|
|
40
|
+
windowStartTime: Date.now(),
|
|
41
|
+
queueCount: 0,
|
|
42
|
+
nextWindowPromise: undefined
|
|
24
43
|
};
|
|
25
|
-
} else {
|
|
26
|
-
tmp = undefined;
|
|
27
44
|
}
|
|
28
45
|
return {
|
|
29
46
|
name: options.name,
|
|
30
47
|
handler: handler,
|
|
31
|
-
|
|
48
|
+
storageMeta: {
|
|
49
|
+
itemSchema: itemSchema,
|
|
50
|
+
outputSchema: outputSchema,
|
|
51
|
+
table: Internal.makeCacheTable(options.name)
|
|
52
|
+
},
|
|
53
|
+
defaultShouldCache: match !== undefined && match ? true : false,
|
|
32
54
|
output: outputSchema,
|
|
33
55
|
input: S$RescriptSchema.schema(function (param) {
|
|
34
56
|
return options.input;
|
|
35
57
|
}),
|
|
36
|
-
|
|
58
|
+
activeCallsCount: 0,
|
|
59
|
+
prevCallStartTimerRef: null,
|
|
60
|
+
rateLimit: tmp
|
|
37
61
|
};
|
|
38
62
|
}
|
|
39
63
|
|
|
64
|
+
function experimental_createEffect(options, handler) {
|
|
65
|
+
var rateLimit = options.rateLimit;
|
|
66
|
+
return createEffect({
|
|
67
|
+
name: options.name,
|
|
68
|
+
input: options.input,
|
|
69
|
+
output: options.output,
|
|
70
|
+
rateLimit: rateLimit !== undefined ? rateLimit : false,
|
|
71
|
+
cache: options.cache
|
|
72
|
+
}, handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exports.durationToMs = durationToMs;
|
|
76
|
+
exports.createEffect = createEffect;
|
|
40
77
|
exports.experimental_createEffect = experimental_createEffect;
|
|
41
78
|
/* Internal Not a pure module */
|
package/src/EventRegister.res
CHANGED
|
@@ -4,8 +4,8 @@ type registrations = {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
type activeRegistration = {
|
|
7
|
-
ecosystem:
|
|
8
|
-
multichain:
|
|
7
|
+
ecosystem: Config.ecosystem,
|
|
8
|
+
multichain: Config.multichain,
|
|
9
9
|
preloadHandlers: bool,
|
|
10
10
|
registrations: registrations,
|
|
11
11
|
mutable finished: bool,
|
package/src/EventRegister.resi
CHANGED
|
@@ -4,8 +4,8 @@ type registrations = {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
let startRegistration: (
|
|
7
|
-
~ecosystem:
|
|
8
|
-
~multichain:
|
|
7
|
+
~ecosystem: Config.ecosystem,
|
|
8
|
+
~multichain: Config.multichain,
|
|
9
9
|
~preloadHandlers: bool,
|
|
10
10
|
) => unit
|
|
11
11
|
let finishRegistration: unit => registrations
|