envio 3.1.2 → 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.
Files changed (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +52 -0
  19. package/src/EventConfigBuilder.res.mjs +32 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +23 -14
  22. package/src/FetchState.res.mjs +21 -15
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +12 -1
  29. package/src/HandlerLoader.res.mjs +6 -1
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +54 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +4 -4
  46. package/src/LogSelection.res.mjs +5 -7
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +17 -57
  68. package/src/TestIndexer.res.mjs +14 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +10 -9
  114. package/src/sources/HyperSyncSource.res.mjs +4 -4
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
@@ -1,67 +1,57 @@
1
- module type State = {
2
- type t
3
- type action
4
- type task
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
- module MakeManager = (S: State) => {
13
- type t = {mutable state: S.t, onError: exn => unit}
14
-
15
- let make = (
16
- state: S.t,
17
- ~onError=e => {
18
- e->ErrorHandling.make(~msg="Indexer has failed with an unexpected error")->ErrorHandling.log
19
- NodeJs.process->NodeJs.exitWithCode(Failure)
20
- },
21
- ) => {
22
- state,
23
- onError,
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
- let rec dispatchAction = (~stateId=0, self: t, action: S.action) => {
27
- try {
28
- let reducer = if stateId == self.state->S.getId {
29
- S.actionReducer
30
- } else {
31
- S.invalidatedActionReducer
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
- and dispatchTask = (self, task: S.task) => {
41
- let stateId = self.state->S.getId
42
- NodeJs.setImmediate(() => {
43
- if stateId !== self.state->S.getId {
44
- Logging.info("Invalidated task discarded")
45
- } else {
46
- try {
47
- S.taskReducer(self.state, task, ~dispatchAction=action =>
48
- dispatchAction(~stateId, self, action)
49
- )
50
- ->Promise.catch(e => {
51
- e->self.onError
52
- Promise.resolve()
53
- })
54
- ->ignore
55
- } catch {
56
- | e => e->self.onError
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
- module Manager = MakeManager(GlobalState)
67
- include Manager
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) ? GlobalState.actionReducer : GlobalState.invalidatedActionReducer;
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(GlobalState.taskReducer(self.state, task, action => dispatchAction(stateId, self, action)), e => {
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
@@ -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
- JsError.throwWithMessage(`SVM does not support indexer.onEvent or indexer.contractRegister. Use indexer.onSlot for per-slot handlers.`)
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
- return Stdlib_JsError.throwWithMessage(`SVM does not support indexer.onEvent or indexer.contractRegister. Use indexer.onSlot for per-slot handlers.`);
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 {
@@ -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->Belt.Array.push(fn)
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->Belt.Int.toString
165
+ let key = chainId->Int.toString
166
166
  let index =
167
167
  onBlockByChainId
168
168
  ->Utils.Dict.dangerouslyGetNonOption(key)
169
- ->Belt.Option.mapWithDefault(0, configs => configs->Belt.Array.length)
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->Belt.Option.flatMap(value => value.where)
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
- ->Belt.Option.flatMap(value => value.wildcard)
198
- ->Belt.Option.getWithDefault(false)
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->Belt.Option.isSome || r.contractRegister->Belt.Option.isSome
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->Belt.Option.map(v =>
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->Belt.Option.map(v =>
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 Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
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 = String(chainId);
125
- let index = Belt_Option.mapWithDefault(onBlockByChainId[key], 0, configs => configs.length);
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 Belt_Option.flatMap(get(contractName, eventName).eventOptions, value => value.where);
147
+ return Stdlib_Option.flatMap(get(contractName, eventName).eventOptions, value => value.where);
148
148
  }
149
149
 
150
150
  function isWildcard(contractName, eventName) {
151
- return Belt_Option.getWithDefault(Belt_Option.flatMap(get(contractName, eventName).eventOptions, value => value.wildcard), false);
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 (Belt_Option.isSome(r.handler)) {
156
+ if (Stdlib_Option.isSome(r.handler)) {
157
157
  return true;
158
158
  } else {
159
- return Belt_Option.isSome(r.contractRegister);
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 = Belt_Option.map(eventOptions, v => v);
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 = Belt_Option.map(eventOptions, v => v);
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->Belt.Int.toFloat)->Belt.Float.toInt * 1000
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
- columnDescriptions: dict<string>,
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, columnDescriptions}) => {
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 = columnDescriptions->Dict.toArray
201
+ let columnConfigEntries = columnConfigs->Dict.toArray
136
202
  if columnConfigEntries->Array.length > 0 {
137
- let columnConfig = dict{}
138
- columnConfigEntries->Array.forEach(((column, comment)) =>
139
- columnConfig->Dict.set(column, {"comment": comment}->(Utils.magic: {..} => JSON.t))
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
- columnConfig->(Utils.magic: dict<JSON.t> => JSON.t),
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
- let derivedFromTo = isDerivedFrom ? `"id": "${relationalKey}"` : `"${relationalKey}_id" : "id"`
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
- columnDescriptions: dict{},
347
+ columnConfigs: dict{},
270
348
  },
271
349
  {
272
350
  tableName: InternalTable.Views.metaViewName,
273
351
  description: None,
274
- columnDescriptions: dict{},
352
+ columnConfigs: dict{},
275
353
  },
276
354
  {
277
355
  tableName: InternalTable.Views.chainMetadataViewName,
278
356
  description: None,
279
- columnDescriptions: dict{},
357
+ columnConfigs: dict{},
280
358
  },
281
359
  ]
282
360
  let userTableConfigs = userEntities->Array.map(entity => {
283
- let columnDescriptions = dict{}
284
- entity.table.fields->Array.forEach(fieldOrDerived =>
285
- switch fieldOrDerived {
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]->Belt.Array.concatMany
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.getDerivedFromFieldName(derivedFromField)->Utils.unwrapResultExn
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.fieldName,
429
+ ~relationalKey=field->Table.getPgDbFieldName,
360
430
  ~mappedEntity=linkedEntityName,
361
431
  ~comment=?field.description,
362
432
  )