envio 3.1.1 → 3.2.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/evm.schema.json +83 -11
- package/fuel.schema.json +83 -11
- package/index.d.ts +184 -3
- package/package.json +6 -6
- package/src/Batch.res +2 -2
- package/src/ChainFetcher.res +27 -3
- package/src/ChainFetcher.res.mjs +17 -3
- package/src/ChainManager.res +163 -0
- package/src/ChainManager.res.mjs +136 -0
- package/src/Config.res +213 -30
- package/src/Config.res.mjs +102 -41
- package/src/Core.res +16 -10
- package/src/Ecosystem.res +0 -3
- package/src/Env.res +2 -2
- package/src/Env.res.mjs +2 -2
- package/src/Envio.res +101 -2
- package/src/Envio.res.mjs +2 -3
- package/src/EventConfigBuilder.res +52 -0
- package/src/EventConfigBuilder.res.mjs +32 -0
- package/src/EventUtils.res +2 -2
- package/src/FetchState.res +126 -71
- package/src/FetchState.res.mjs +73 -51
- package/src/GlobalState.res +219 -363
- package/src/GlobalState.res.mjs +314 -491
- package/src/GlobalStateManager.res +49 -59
- package/src/GlobalStateManager.res.mjs +5 -4
- package/src/GlobalStateManager.resi +1 -1
- package/src/HandlerLoader.res +12 -1
- package/src/HandlerLoader.res.mjs +6 -1
- package/src/HandlerRegister.res +9 -9
- package/src/HandlerRegister.res.mjs +9 -9
- package/src/Hasura.res +102 -32
- package/src/Hasura.res.mjs +88 -34
- package/src/InMemoryStore.res +10 -1
- package/src/InMemoryStore.res.mjs +4 -1
- package/src/InMemoryTable.res +83 -136
- package/src/InMemoryTable.res.mjs +57 -86
- package/src/Internal.res +54 -5
- package/src/Internal.res.mjs +2 -8
- package/src/LazyLoader.res +2 -2
- package/src/LazyLoader.res.mjs +3 -3
- package/src/LoadLayer.res +47 -60
- package/src/LoadLayer.res.mjs +28 -50
- package/src/LoadLayer.resi +2 -5
- package/src/LogSelection.res +4 -4
- package/src/LogSelection.res.mjs +5 -7
- package/src/Logging.res +1 -1
- package/src/Main.res +61 -2
- package/src/Main.res.mjs +37 -1
- package/src/Persistence.res +3 -16
- package/src/PgStorage.res +125 -114
- package/src/PgStorage.res.mjs +112 -95
- package/src/Ports.res +5 -0
- package/src/Ports.res.mjs +9 -0
- package/src/Prometheus.res +3 -3
- package/src/Prometheus.res.mjs +4 -4
- package/src/ReorgDetection.res +4 -4
- package/src/ReorgDetection.res.mjs +4 -5
- package/src/SafeCheckpointTracking.res +16 -16
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/SimulateItems.res +10 -14
- package/src/SimulateItems.res.mjs +5 -2
- package/src/Sink.res +1 -1
- package/src/Sink.res.mjs +1 -2
- package/src/SvmTypes.res +9 -0
- package/src/SvmTypes.res.mjs +14 -0
- package/src/TestIndexer.res +17 -57
- package/src/TestIndexer.res.mjs +14 -48
- package/src/TestIndexerProxyStorage.res +23 -23
- package/src/TestIndexerProxyStorage.res.mjs +12 -15
- package/src/Throttler.res +2 -2
- package/src/Time.res +2 -2
- package/src/Time.res.mjs +2 -2
- package/src/UserContext.res +19 -118
- package/src/UserContext.res.mjs +10 -66
- package/src/Utils.res +15 -15
- package/src/Utils.res.mjs +7 -8
- package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
- package/src/bindings/BigDecimal.res +1 -1
- package/src/bindings/BigDecimal.res.mjs +2 -2
- package/src/bindings/ClickHouse.res +8 -6
- package/src/bindings/ClickHouse.res.mjs +5 -5
- package/src/bindings/Hrtime.res +1 -1
- package/src/bindings/Pino.res +2 -2
- package/src/bindings/Pino.res.mjs +3 -4
- package/src/db/EntityFilter.res +410 -0
- package/src/db/EntityFilter.res.mjs +424 -0
- package/src/db/EntityHistory.res +1 -1
- package/src/db/EntityHistory.res.mjs +1 -1
- package/src/db/InternalTable.res +10 -10
- package/src/db/InternalTable.res.mjs +41 -45
- package/src/db/Schema.res +2 -2
- package/src/db/Schema.res.mjs +3 -3
- package/src/db/Table.res +106 -22
- package/src/db/Table.res.mjs +84 -35
- package/src/sources/EventRouter.res +67 -2
- package/src/sources/EventRouter.res.mjs +45 -3
- package/src/sources/Evm.res +0 -7
- package/src/sources/Evm.res.mjs +0 -15
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +1 -2
- package/src/sources/EvmRpcClient.res +42 -0
- package/src/sources/EvmRpcClient.res.mjs +64 -0
- package/src/sources/Fuel.res +0 -7
- package/src/sources/Fuel.res.mjs +0 -15
- package/src/sources/HyperFuelSource.res +5 -4
- package/src/sources/HyperFuelSource.res.mjs +2 -2
- package/src/sources/HyperSyncClient.res +9 -5
- package/src/sources/HyperSyncClient.res.mjs +2 -2
- package/src/sources/HyperSyncHeightStream.res +2 -2
- package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
- package/src/sources/HyperSyncSource.res +10 -9
- package/src/sources/HyperSyncSource.res.mjs +4 -4
- package/src/sources/Rpc.res +1 -5
- package/src/sources/Rpc.res.mjs +1 -9
- package/src/sources/RpcSource.res +57 -21
- package/src/sources/RpcSource.res.mjs +47 -20
- package/src/sources/RpcWebSocketHeightStream.res +1 -1
- package/src/sources/SourceManager.res +3 -2
- package/src/sources/SourceManager.res.mjs +1 -1
- package/src/sources/Svm.res +3 -10
- package/src/sources/Svm.res.mjs +4 -18
- package/src/sources/SvmHyperSyncClient.res +265 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
- package/src/sources/SvmHyperSyncSource.res +638 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
- package/src/tui/Tui.res +9 -2
- package/src/tui/Tui.res.mjs +18 -3
- package/src/tui/components/BufferedProgressBar.res +2 -2
- package/src/tui/components/TuiData.res +3 -0
- package/svm.schema.json +523 -14
- package/src/TableIndices.res +0 -115
- package/src/TableIndices.res.mjs +0 -144
|
@@ -1,67 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let taskReducer: (t, task, ~dispatchAction: action => unit) => promise<unit>
|
|
7
|
-
let actionReducer: (t, action) => (t, array<task>)
|
|
8
|
-
let invalidatedActionReducer: (t, action) => (t, array<task>)
|
|
9
|
-
let getId: t => int
|
|
1
|
+
type t = {
|
|
2
|
+
mutable state: GlobalState.t,
|
|
3
|
+
onError: exn => unit,
|
|
4
|
+
reducers: GlobalState.reducers,
|
|
10
5
|
}
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
7
|
+
let make = (
|
|
8
|
+
state: GlobalState.t,
|
|
9
|
+
~reducers: GlobalState.reducers,
|
|
10
|
+
~onError=e => {
|
|
11
|
+
e->ErrorHandling.make(~msg="Indexer has failed with an unexpected error")->ErrorHandling.log
|
|
12
|
+
NodeJs.process->NodeJs.exitWithCode(Failure)
|
|
13
|
+
},
|
|
14
|
+
) => {
|
|
15
|
+
state,
|
|
16
|
+
onError,
|
|
17
|
+
reducers,
|
|
18
|
+
}
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
let (nextState, nextTasks) = reducer(self.state, action)
|
|
34
|
-
self.state = nextState
|
|
35
|
-
nextTasks->Array.forEach(task => dispatchTask(self, task))
|
|
36
|
-
} catch {
|
|
37
|
-
| e => e->self.onError
|
|
20
|
+
let rec dispatchAction = (~stateId=0, self: t, action: GlobalState.action) => {
|
|
21
|
+
try {
|
|
22
|
+
let reducer = if stateId == self.state->GlobalState.getId {
|
|
23
|
+
self.reducers.actionReducer
|
|
24
|
+
} else {
|
|
25
|
+
self.reducers.invalidatedActionReducer
|
|
38
26
|
}
|
|
27
|
+
let (nextState, nextTasks) = reducer(self.state, action)
|
|
28
|
+
self.state = nextState
|
|
29
|
+
nextTasks->Array.forEach(task => dispatchTask(self, task))
|
|
30
|
+
} catch {
|
|
31
|
+
| e => e->self.onError
|
|
39
32
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
33
|
+
}
|
|
34
|
+
and dispatchTask = (self, task: GlobalState.task) => {
|
|
35
|
+
let stateId = self.state->GlobalState.getId
|
|
36
|
+
NodeJs.setImmediate(() => {
|
|
37
|
+
if stateId !== self.state->GlobalState.getId {
|
|
38
|
+
Logging.info("Invalidated task discarded")
|
|
39
|
+
} else {
|
|
40
|
+
try {
|
|
41
|
+
self.reducers.taskReducer(self.state, task, ~dispatchAction=action =>
|
|
42
|
+
dispatchAction(~stateId, self, action)
|
|
43
|
+
)
|
|
44
|
+
->Promise.catch(e => {
|
|
45
|
+
e->self.onError
|
|
46
|
+
Promise.resolve()
|
|
47
|
+
})
|
|
48
|
+
->ignore
|
|
49
|
+
} catch {
|
|
50
|
+
| e => e->self.onError
|
|
58
51
|
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let getState = self => self.state
|
|
63
|
-
let setState = (self: t, state: S.t) => self.state = state
|
|
52
|
+
}
|
|
53
|
+
})
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
let getState = self => self.state
|
|
57
|
+
let setState = (self: t, state: GlobalState.t) => self.state = state
|
|
@@ -7,21 +7,22 @@ import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
|
7
7
|
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
8
8
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
9
9
|
|
|
10
|
-
function make(state, onErrorOpt) {
|
|
10
|
+
function make(state, reducers, onErrorOpt) {
|
|
11
11
|
let onError = onErrorOpt !== undefined ? onErrorOpt : e => {
|
|
12
12
|
ErrorHandling.log(ErrorHandling.make(e, undefined, "Indexer has failed with an unexpected error"));
|
|
13
13
|
Process.exit(1);
|
|
14
14
|
};
|
|
15
15
|
return {
|
|
16
16
|
state: state,
|
|
17
|
-
onError: onError
|
|
17
|
+
onError: onError,
|
|
18
|
+
reducers: reducers
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function dispatchAction(stateIdOpt, self, action) {
|
|
22
23
|
let stateId = stateIdOpt !== undefined ? stateIdOpt : 0;
|
|
23
24
|
try {
|
|
24
|
-
let reducer = stateId === GlobalState.getId(self.state) ?
|
|
25
|
+
let reducer = stateId === GlobalState.getId(self.state) ? self.reducers.actionReducer : self.reducers.invalidatedActionReducer;
|
|
25
26
|
let match = reducer(self.state, action);
|
|
26
27
|
self.state = match[0];
|
|
27
28
|
match[1].forEach(task => dispatchTask(self, task));
|
|
@@ -38,7 +39,7 @@ function dispatchTask(self, task) {
|
|
|
38
39
|
return Logging.info("Invalidated task discarded");
|
|
39
40
|
}
|
|
40
41
|
try {
|
|
41
|
-
Stdlib_Promise.$$catch(
|
|
42
|
+
Stdlib_Promise.$$catch(self.reducers.taskReducer(self.state, task, action => dispatchAction(stateId, self, action)), e => {
|
|
42
43
|
self.onError(e);
|
|
43
44
|
return Promise.resolve();
|
|
44
45
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type t
|
|
2
2
|
|
|
3
|
-
let make: (GlobalState.t, ~onError: exn => unit=?) => t
|
|
3
|
+
let make: (GlobalState.t, ~reducers: GlobalState.reducers, ~onError: exn => unit=?) => t
|
|
4
4
|
let dispatchAction: (~stateId: int=?, t, GlobalState.action) => unit
|
|
5
5
|
let dispatchTask: (t, GlobalState.task) => unit
|
|
6
6
|
let getState: t => GlobalState.t
|
package/src/HandlerLoader.res
CHANGED
|
@@ -143,7 +143,18 @@ let applyRegistrations = (~config: Config.t): Config.t => {
|
|
|
143
143
|
dependsOnAddresses: Internal.dependsOnAddresses(~isWildcard, ~filterByAddresses),
|
|
144
144
|
} :> Internal.eventConfig)
|
|
145
145
|
| Svm =>
|
|
146
|
-
|
|
146
|
+
let svmEv =
|
|
147
|
+
ev->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
148
|
+
({
|
|
149
|
+
...svmEv,
|
|
150
|
+
isWildcard,
|
|
151
|
+
handler,
|
|
152
|
+
contractRegister,
|
|
153
|
+
dependsOnAddresses: Internal.dependsOnAddresses(
|
|
154
|
+
~isWildcard,
|
|
155
|
+
~filterByAddresses=false,
|
|
156
|
+
),
|
|
157
|
+
} :> Internal.eventConfig)
|
|
147
158
|
}
|
|
148
159
|
},
|
|
149
160
|
)
|
|
@@ -110,7 +110,12 @@ function applyRegistrations(config) {
|
|
|
110
110
|
kind: ev.kind
|
|
111
111
|
};
|
|
112
112
|
case "svm" :
|
|
113
|
-
|
|
113
|
+
let newrecord = {...ev};
|
|
114
|
+
newrecord.contractRegister = contractRegister;
|
|
115
|
+
newrecord.handler = handler;
|
|
116
|
+
newrecord.dependsOnAddresses = Internal.dependsOnAddresses(isWildcard, false);
|
|
117
|
+
newrecord.isWildcard = isWildcard;
|
|
118
|
+
return newrecord;
|
|
114
119
|
}
|
|
115
120
|
});
|
|
116
121
|
return {
|
package/src/HandlerRegister.res
CHANGED
|
@@ -89,7 +89,7 @@ let preRegistered = registry.preRegistered
|
|
|
89
89
|
|
|
90
90
|
let withRegistration = (fn: activeRegistration => unit) => {
|
|
91
91
|
switch activeRegistration.contents {
|
|
92
|
-
| None => preRegistered->
|
|
92
|
+
| None => preRegistered->Array.push(fn)
|
|
93
93
|
| Some(r) =>
|
|
94
94
|
if r.finished {
|
|
95
95
|
JsError.throwWithMessage(
|
|
@@ -162,11 +162,11 @@ let registerOnBlock = (
|
|
|
162
162
|
) => {
|
|
163
163
|
withRegistration(registration => {
|
|
164
164
|
let onBlockByChainId = registration.registrations.onBlockByChainId
|
|
165
|
-
let key = chainId->
|
|
165
|
+
let key = chainId->Int.toString
|
|
166
166
|
let index =
|
|
167
167
|
onBlockByChainId
|
|
168
168
|
->Utils.Dict.dangerouslyGetNonOption(key)
|
|
169
|
-
->
|
|
169
|
+
->Option.mapOr(0, configs => configs->Array.length)
|
|
170
170
|
onBlockByChainId->Utils.Dict.push(
|
|
171
171
|
key,
|
|
172
172
|
(
|
|
@@ -190,16 +190,16 @@ let getContractRegister = (~contractName, ~eventName) =>
|
|
|
190
190
|
get(~contractName, ~eventName).contractRegister
|
|
191
191
|
|
|
192
192
|
let getOnEventWhere = (~contractName, ~eventName) =>
|
|
193
|
-
get(~contractName, ~eventName).eventOptions->
|
|
193
|
+
get(~contractName, ~eventName).eventOptions->Option.flatMap(value => value.where)
|
|
194
194
|
|
|
195
195
|
let isWildcard = (~contractName, ~eventName) =>
|
|
196
196
|
get(~contractName, ~eventName).eventOptions
|
|
197
|
-
->
|
|
198
|
-
->
|
|
197
|
+
->Option.flatMap(value => value.wildcard)
|
|
198
|
+
->Option.getOr(false)
|
|
199
199
|
|
|
200
200
|
let hasRegistration = (~contractName, ~eventName) => {
|
|
201
201
|
let r = get(~contractName, ~eventName)
|
|
202
|
-
r.handler->
|
|
202
|
+
r.handler->Option.isSome || r.contractRegister->Option.isSome
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
type eventNamespace = {contractName: string, eventName: string}
|
|
@@ -284,7 +284,7 @@ let setHandler = (
|
|
|
284
284
|
)
|
|
285
285
|
| Some(prevHandler) =>
|
|
286
286
|
let incomingEventOptions =
|
|
287
|
-
eventOptions->
|
|
287
|
+
eventOptions->Option.map(v =>
|
|
288
288
|
v->(Utils.magic: Internal.eventOptions<'where> => Internal.eventOptions<JSON.t>)
|
|
289
289
|
)
|
|
290
290
|
if eventOptionsMatch(t.eventOptions, incomingEventOptions) {
|
|
@@ -341,7 +341,7 @@ let setContractRegister = (
|
|
|
341
341
|
)
|
|
342
342
|
| Some(prevContractRegister) =>
|
|
343
343
|
let incomingEventOptions =
|
|
344
|
-
eventOptions->
|
|
344
|
+
eventOptions->Option.map(v =>
|
|
345
345
|
v->(Utils.magic: Internal.eventOptions<'where> => Internal.eventOptions<JSON.t>)
|
|
346
346
|
)
|
|
347
347
|
if eventOptionsMatch(t.eventOptions, incomingEventOptions) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
4
|
import * as Logging from "./Logging.res.mjs";
|
|
5
|
-
import * as
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
7
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
8
|
|
|
@@ -121,8 +121,8 @@ function throwIfFinishedRegistration(methodName) {
|
|
|
121
121
|
function registerOnBlock(name, chainId, interval, startBlock, endBlock, handler) {
|
|
122
122
|
withRegistration(registration => {
|
|
123
123
|
let onBlockByChainId = registration.registrations.onBlockByChainId;
|
|
124
|
-
let key =
|
|
125
|
-
let index =
|
|
124
|
+
let key = chainId.toString();
|
|
125
|
+
let index = Stdlib_Option.mapOr(onBlockByChainId[key], 0, configs => configs.length);
|
|
126
126
|
Utils.Dict.push(onBlockByChainId, key, {
|
|
127
127
|
index: index,
|
|
128
128
|
name: name,
|
|
@@ -144,19 +144,19 @@ function getContractRegister(contractName, eventName) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function getOnEventWhere(contractName, eventName) {
|
|
147
|
-
return
|
|
147
|
+
return Stdlib_Option.flatMap(get(contractName, eventName).eventOptions, value => value.where);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function isWildcard(contractName, eventName) {
|
|
151
|
-
return
|
|
151
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(get(contractName, eventName).eventOptions, value => value.wildcard), false);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
function hasRegistration(contractName, eventName) {
|
|
155
155
|
let r = get(contractName, eventName);
|
|
156
|
-
if (
|
|
156
|
+
if (Stdlib_Option.isSome(r.handler)) {
|
|
157
157
|
return true;
|
|
158
158
|
} else {
|
|
159
|
-
return
|
|
159
|
+
return Stdlib_Option.isSome(r.contractRegister);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -223,7 +223,7 @@ function setHandler(contractName, eventName, handler, eventOptions, loggerOpt) {
|
|
|
223
223
|
let t = get(contractName, eventName);
|
|
224
224
|
let prevHandler = t.handler;
|
|
225
225
|
if (prevHandler !== undefined) {
|
|
226
|
-
let incomingEventOptions =
|
|
226
|
+
let incomingEventOptions = Stdlib_Option.map(eventOptions, v => v);
|
|
227
227
|
if (!eventOptionsMatch(t.eventOptions, incomingEventOptions)) {
|
|
228
228
|
return raiseDuplicateRegistration(contractName, eventName, "Cannot register a second handler with different options. Make sure all handlers for the same event use identical options (wildcard, where)", logger);
|
|
229
229
|
}
|
|
@@ -253,7 +253,7 @@ function setContractRegister(contractName, eventName, contractRegister, eventOpt
|
|
|
253
253
|
let t = get(contractName, eventName);
|
|
254
254
|
let prevContractRegister = t.contractRegister;
|
|
255
255
|
if (prevContractRegister !== undefined) {
|
|
256
|
-
let incomingEventOptions =
|
|
256
|
+
let incomingEventOptions = Stdlib_Option.map(eventOptions, v => v);
|
|
257
257
|
if (!eventOptionsMatch(t.eventOptions, incomingEventOptions)) {
|
|
258
258
|
return raiseDuplicateRegistration(contractName, eventName, "Cannot register a second contractRegister with different options. Make sure all handlers for the same event use identical options (wildcard, where)", logger);
|
|
259
259
|
}
|
package/src/Hasura.res
CHANGED
|
@@ -40,6 +40,19 @@ let clearMetadataRoute = Rest.route(() => {
|
|
|
40
40
|
responses,
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
let reloadMetadataRoute = Rest.route(() => {
|
|
44
|
+
method: Post,
|
|
45
|
+
path: "",
|
|
46
|
+
input: s => {
|
|
47
|
+
let _ = s.field("type", S.literal("reload_metadata"))
|
|
48
|
+
{
|
|
49
|
+
"args": s.field("args", S.json(~validate=false)),
|
|
50
|
+
"auth": s->auth,
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
responses,
|
|
54
|
+
})
|
|
55
|
+
|
|
43
56
|
let trackTablesRoute = Rest.route(() => {
|
|
44
57
|
method: Post,
|
|
45
58
|
path: "",
|
|
@@ -79,7 +92,7 @@ let sendOperation = async (~endpoint, ~auth, ~operation: JSON.t) => {
|
|
|
79
92
|
} catch {
|
|
80
93
|
| exn =>
|
|
81
94
|
if attempt < maxRetries {
|
|
82
|
-
let backoffMs = Math.pow(2.0, ~exp=attempt->
|
|
95
|
+
let backoffMs = Math.pow(2.0, ~exp=attempt->Int.toFloat)->Float.toInt * 1000
|
|
83
96
|
await Time.resolvePromiseAfterDelay(~delayMilliseconds=backoffMs)
|
|
84
97
|
await retry(~attempt=attempt + 1)
|
|
85
98
|
} else {
|
|
@@ -110,10 +123,63 @@ let clearHasuraMetadata = async (~endpoint, ~auth) => {
|
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
126
|
+
let reloadHasuraMetadata = async (~endpoint, ~auth) => {
|
|
127
|
+
try {
|
|
128
|
+
let result = await reloadMetadataRoute->Rest.fetch(
|
|
129
|
+
{
|
|
130
|
+
"auth": auth,
|
|
131
|
+
"args": {
|
|
132
|
+
"reload_sources": ["default"],
|
|
133
|
+
}->(Utils.magic: 'a => JSON.t),
|
|
134
|
+
},
|
|
135
|
+
~client=Rest.client(endpoint),
|
|
136
|
+
)
|
|
137
|
+
let msg = switch result {
|
|
138
|
+
| QuerySucceeded => "Hasura metadata reloaded"
|
|
139
|
+
| AlreadyDone => "Hasura metadata reload acknowledged"
|
|
140
|
+
}
|
|
141
|
+
Logging.trace(msg)
|
|
142
|
+
} catch {
|
|
143
|
+
| exn =>
|
|
144
|
+
Logging.error({
|
|
145
|
+
"msg": `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
|
|
146
|
+
"err": exn->Utils.prettifyExn,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type columnConfig = {
|
|
152
|
+
// The GraphQL field name exposed by Hasura, when it differs from the
|
|
153
|
+
// column name in the database (eg with `column_name_format: snake_case`).
|
|
154
|
+
customName: option<string>,
|
|
155
|
+
comment: option<string>,
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
type trackTableConfig = {
|
|
114
159
|
tableName: string,
|
|
115
160
|
description: option<string>,
|
|
116
|
-
|
|
161
|
+
// Keyed by db column name
|
|
162
|
+
columnConfigs: dict<columnConfig>,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let makeColumnConfigs = (table: Table.table): dict<columnConfig> => {
|
|
166
|
+
let columnConfigs = dict{}
|
|
167
|
+
table.fields->Array.forEach(fieldOrDerived =>
|
|
168
|
+
switch fieldOrDerived {
|
|
169
|
+
| Table.Field(field) => {
|
|
170
|
+
let apiFieldName = field->Table.getApiFieldName
|
|
171
|
+
let dbFieldName = field->Table.getPgDbFieldName
|
|
172
|
+
// Expose renamed columns in GraphQL under the original field name
|
|
173
|
+
let customName = apiFieldName === dbFieldName ? None : Some(apiFieldName)
|
|
174
|
+
switch (customName, field.description) {
|
|
175
|
+
| (None, None) => ()
|
|
176
|
+
| (customName, comment) => columnConfigs->Dict.set(dbFieldName, {customName, comment})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
| Table.DerivedFrom(_) => ()
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
columnConfigs
|
|
117
183
|
}
|
|
118
184
|
|
|
119
185
|
let trackTables = async (~endpoint, ~auth, ~pgSchema, ~tableConfigs: array<trackTableConfig>) => {
|
|
@@ -124,7 +190,7 @@ let trackTables = async (~endpoint, ~auth, ~pgSchema, ~tableConfigs: array<track
|
|
|
124
190
|
"args": {
|
|
125
191
|
// If set to false, any warnings will cause the API call to fail and no new tables to be tracked. Otherwise tables that fail to track will be raised as warnings. (default: true)
|
|
126
192
|
"allow_warnings": false,
|
|
127
|
-
"tables": tableConfigs->Array.map(({tableName, description,
|
|
193
|
+
"tables": tableConfigs->Array.map(({tableName, description, columnConfigs}) => {
|
|
128
194
|
let configuration = dict{
|
|
129
195
|
"custom_name": tableName->(Utils.magic: string => JSON.t),
|
|
130
196
|
}
|
|
@@ -132,15 +198,26 @@ let trackTables = async (~endpoint, ~auth, ~pgSchema, ~tableConfigs: array<track
|
|
|
132
198
|
| Some(d) => configuration->Dict.set("comment", d->(Utils.magic: string => JSON.t))
|
|
133
199
|
| None => ()
|
|
134
200
|
}
|
|
135
|
-
let columnConfigEntries =
|
|
201
|
+
let columnConfigEntries = columnConfigs->Dict.toArray
|
|
136
202
|
if columnConfigEntries->Array.length > 0 {
|
|
137
|
-
let
|
|
138
|
-
columnConfigEntries->Array.forEach(((column,
|
|
139
|
-
|
|
140
|
-
|
|
203
|
+
let columnConfigJson = dict{}
|
|
204
|
+
columnConfigEntries->Array.forEach(((column, config)) => {
|
|
205
|
+
let entry = dict{}
|
|
206
|
+
switch config.customName {
|
|
207
|
+
| Some(customName) =>
|
|
208
|
+
entry->Dict.set("custom_name", customName->(Utils.magic: string => JSON.t))
|
|
209
|
+
| None => ()
|
|
210
|
+
}
|
|
211
|
+
switch config.comment {
|
|
212
|
+
| Some(comment) =>
|
|
213
|
+
entry->Dict.set("comment", comment->(Utils.magic: string => JSON.t))
|
|
214
|
+
| None => ()
|
|
215
|
+
}
|
|
216
|
+
columnConfigJson->Dict.set(column, entry->(Utils.magic: dict<JSON.t> => JSON.t))
|
|
217
|
+
})
|
|
141
218
|
configuration->Dict.set(
|
|
142
219
|
"column_config",
|
|
143
|
-
|
|
220
|
+
columnConfigJson->(Utils.magic: dict<JSON.t> => JSON.t),
|
|
144
221
|
)
|
|
145
222
|
}
|
|
146
223
|
{
|
|
@@ -216,7 +293,8 @@ let createEntityRelationship = async (
|
|
|
216
293
|
~isDerivedFrom: bool,
|
|
217
294
|
~comment: option<string>=?,
|
|
218
295
|
) => {
|
|
219
|
-
|
|
296
|
+
// The column_mapping references columns by their db names
|
|
297
|
+
let derivedFromTo = isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}" : "id"`
|
|
220
298
|
|
|
221
299
|
let tableJson = {
|
|
222
300
|
"schema": pgSchema,
|
|
@@ -266,44 +344,36 @@ let trackDatabase = async (
|
|
|
266
344
|
{
|
|
267
345
|
tableName: InternalTable.RawEvents.table.tableName,
|
|
268
346
|
description: None,
|
|
269
|
-
|
|
347
|
+
columnConfigs: dict{},
|
|
270
348
|
},
|
|
271
349
|
{
|
|
272
350
|
tableName: InternalTable.Views.metaViewName,
|
|
273
351
|
description: None,
|
|
274
|
-
|
|
352
|
+
columnConfigs: dict{},
|
|
275
353
|
},
|
|
276
354
|
{
|
|
277
355
|
tableName: InternalTable.Views.chainMetadataViewName,
|
|
278
356
|
description: None,
|
|
279
|
-
|
|
357
|
+
columnConfigs: dict{},
|
|
280
358
|
},
|
|
281
359
|
]
|
|
282
360
|
let userTableConfigs = userEntities->Array.map(entity => {
|
|
283
|
-
|
|
284
|
-
entity.table.
|
|
285
|
-
|
|
286
|
-
| Table.Field(field) =>
|
|
287
|
-
switch field.description {
|
|
288
|
-
| Some(d) => columnDescriptions->Dict.set(field->Table.getDbFieldName, d)
|
|
289
|
-
| None => ()
|
|
290
|
-
}
|
|
291
|
-
| Table.DerivedFrom(_) => ()
|
|
292
|
-
}
|
|
293
|
-
)
|
|
294
|
-
{
|
|
295
|
-
tableName: entity.table.tableName,
|
|
296
|
-
description: entity.table.description,
|
|
297
|
-
columnDescriptions,
|
|
298
|
-
}
|
|
361
|
+
tableName: entity.table.tableName,
|
|
362
|
+
description: entity.table.description,
|
|
363
|
+
columnConfigs: entity.table->makeColumnConfigs,
|
|
299
364
|
})
|
|
300
|
-
let tableConfigs = [exposedInternalTableConfigs, userTableConfigs]->
|
|
365
|
+
let tableConfigs = [exposedInternalTableConfigs, userTableConfigs]->Array.flat
|
|
301
366
|
let tableNames = tableConfigs->Array.map(c => c.tableName)
|
|
302
367
|
|
|
303
368
|
Logging.info("Tracking tables in Hasura")
|
|
304
369
|
|
|
305
370
|
let _ = await clearHasuraMetadata(~endpoint, ~auth)
|
|
306
371
|
|
|
372
|
+
// Force Hasura to re-introspect the source schema before tracking, otherwise
|
|
373
|
+
// freshly-created user tables may be invisible to pg_track_tables and the call
|
|
374
|
+
// returns `metadata-warnings` (HTTP 400), leaving tracking permanently broken.
|
|
375
|
+
await reloadHasuraMetadata(~endpoint, ~auth)
|
|
376
|
+
|
|
307
377
|
await trackTables(~endpoint, ~auth, ~pgSchema, ~tableConfigs)
|
|
308
378
|
|
|
309
379
|
for i in 0 to tableNames->Array.length - 1 {
|
|
@@ -328,7 +398,7 @@ let trackDatabase = async (
|
|
|
328
398
|
let derivedFromField = derivedFromFields->Array.getUnsafe(j)
|
|
329
399
|
//determines the actual name of the underlying relational field (if it's an entity mapping then suffixes _id for eg.)
|
|
330
400
|
let relationalFieldName =
|
|
331
|
-
schema->Schema.
|
|
401
|
+
schema->Schema.getDerivedFromPgFieldName(derivedFromField)->Utils.unwrapResultExn
|
|
332
402
|
|
|
333
403
|
await createEntityRelationship(
|
|
334
404
|
~endpoint,
|
|
@@ -356,7 +426,7 @@ let trackDatabase = async (
|
|
|
356
426
|
~relationshipType="object",
|
|
357
427
|
~isDerivedFrom=false,
|
|
358
428
|
~objectName=field.fieldName,
|
|
359
|
-
~relationalKey=field.
|
|
429
|
+
~relationalKey=field->Table.getPgDbFieldName,
|
|
360
430
|
~mappedEntity=linkedEntityName,
|
|
361
431
|
~comment=?field.description,
|
|
362
432
|
)
|