envio 3.0.0-alpha.2 → 3.0.0-alpha.20
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/README.md +164 -30
- package/bin.mjs +49 -0
- package/evm.schema.json +79 -169
- package/fuel.schema.json +50 -21
- package/index.d.ts +497 -1
- package/index.js +4 -0
- package/package.json +42 -31
- package/rescript.json +4 -1
- package/src/Batch.res +11 -8
- package/src/Batch.res.mjs +11 -9
- package/src/ChainFetcher.res +531 -0
- package/src/ChainFetcher.res.mjs +339 -0
- package/src/ChainManager.res +190 -0
- package/src/ChainManager.res.mjs +166 -0
- package/src/Change.res +3 -3
- package/src/Config.gen.ts +19 -0
- package/src/Config.res +737 -22
- package/src/Config.res.mjs +703 -26
- package/src/{Indexer.res → Ctx.res} +1 -1
- package/src/Ecosystem.res +9 -124
- package/src/Ecosystem.res.mjs +19 -160
- package/src/Env.res +30 -74
- package/src/Env.res.mjs +25 -87
- package/src/Envio.gen.ts +3 -1
- package/src/Envio.res +20 -9
- package/src/EventProcessing.res +469 -0
- package/src/EventProcessing.res.mjs +337 -0
- package/src/EvmTypes.gen.ts +6 -0
- package/src/EvmTypes.res +1 -0
- package/src/FetchState.res +1256 -639
- package/src/FetchState.res.mjs +1135 -612
- package/src/GlobalState.res +1190 -0
- package/src/GlobalState.res.mjs +1183 -0
- package/src/GlobalStateManager.res +68 -0
- package/src/GlobalStateManager.res.mjs +75 -0
- package/src/GlobalStateManager.resi +7 -0
- package/src/HandlerLoader.res +89 -0
- package/src/HandlerLoader.res.mjs +79 -0
- package/src/HandlerRegister.res +357 -0
- package/src/HandlerRegister.res.mjs +299 -0
- package/src/{EventRegister.resi → HandlerRegister.resi} +13 -13
- package/src/Hasura.res +111 -175
- package/src/Hasura.res.mjs +88 -150
- package/src/InMemoryStore.res +1 -1
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/InMemoryTable.res +1 -1
- package/src/InMemoryTable.res.mjs +1 -1
- package/src/Internal.gen.ts +4 -0
- package/src/Internal.res +230 -12
- package/src/Internal.res.mjs +115 -1
- package/src/LoadLayer.res +444 -0
- package/src/LoadLayer.res.mjs +296 -0
- package/src/LoadLayer.resi +32 -0
- package/src/LogSelection.res +33 -27
- package/src/LogSelection.res.mjs +6 -0
- package/src/Logging.res +21 -7
- package/src/Logging.res.mjs +16 -8
- package/src/Main.res +377 -0
- package/src/Main.res.mjs +339 -0
- package/src/Persistence.res +7 -21
- package/src/Persistence.res.mjs +3 -3
- package/src/PgStorage.gen.ts +10 -0
- package/src/PgStorage.res +116 -69
- package/src/PgStorage.res.d.mts +5 -0
- package/src/PgStorage.res.mjs +93 -50
- package/src/Prometheus.res +294 -224
- package/src/Prometheus.res.mjs +353 -340
- package/src/ReorgDetection.res +6 -10
- package/src/ReorgDetection.res.mjs +6 -6
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/Sink.res +4 -2
- package/src/Sink.res.mjs +2 -1
- package/src/TableIndices.res +0 -1
- package/src/TestIndexer.res +692 -0
- package/src/TestIndexer.res.mjs +527 -0
- package/src/TestIndexerProxyStorage.res +205 -0
- package/src/TestIndexerProxyStorage.res.mjs +151 -0
- package/src/TopicFilter.res +1 -1
- package/src/Types.ts +1 -1
- package/src/UserContext.res +424 -0
- package/src/UserContext.res.mjs +279 -0
- package/src/Utils.res +97 -26
- package/src/Utils.res.mjs +91 -44
- package/src/bindings/BigInt.res +10 -0
- package/src/bindings/BigInt.res.mjs +15 -0
- package/src/bindings/ClickHouse.res +120 -23
- package/src/bindings/ClickHouse.res.mjs +118 -28
- package/src/bindings/DateFns.res +74 -0
- package/src/bindings/DateFns.res.mjs +22 -0
- package/src/bindings/EventSource.res +8 -1
- package/src/bindings/EventSource.res.mjs +8 -1
- package/src/bindings/Express.res +1 -0
- package/src/bindings/Hrtime.res +14 -1
- package/src/bindings/Hrtime.res.mjs +22 -2
- package/src/bindings/Hrtime.resi +4 -0
- package/src/bindings/Lodash.res +0 -1
- package/src/bindings/NodeJs.res +49 -3
- package/src/bindings/NodeJs.res.mjs +11 -3
- package/src/bindings/Pino.res +24 -10
- package/src/bindings/Pino.res.mjs +14 -8
- package/src/bindings/Postgres.gen.ts +8 -0
- package/src/bindings/Postgres.res +5 -1
- package/src/bindings/Postgres.res.d.mts +5 -0
- package/src/bindings/PromClient.res +0 -10
- package/src/bindings/PromClient.res.mjs +0 -3
- package/src/bindings/Vitest.res +142 -0
- package/src/bindings/Vitest.res.mjs +9 -0
- package/src/bindings/WebSocket.res +27 -0
- package/src/bindings/WebSocket.res.mjs +2 -0
- package/src/bindings/Yargs.res +8 -0
- package/src/bindings/Yargs.res.mjs +2 -0
- package/src/db/EntityHistory.res +7 -7
- package/src/db/EntityHistory.res.mjs +9 -9
- package/src/db/InternalTable.res +59 -111
- package/src/db/InternalTable.res.mjs +73 -104
- package/src/db/Table.res +27 -8
- package/src/db/Table.res.mjs +25 -14
- package/src/sources/Evm.res +84 -0
- package/src/sources/Evm.res.mjs +105 -0
- package/src/sources/EvmChain.res +94 -0
- package/src/sources/EvmChain.res.mjs +60 -0
- package/src/sources/Fuel.res +19 -34
- package/src/sources/Fuel.res.mjs +34 -16
- package/src/sources/FuelSDK.res +38 -0
- package/src/sources/FuelSDK.res.mjs +29 -0
- package/src/sources/HyperFuel.res +2 -2
- package/src/sources/HyperFuel.resi +1 -1
- package/src/sources/HyperFuelClient.res +2 -2
- package/src/sources/HyperFuelSource.res +33 -13
- package/src/sources/HyperFuelSource.res.mjs +24 -16
- package/src/sources/HyperSync.res +36 -6
- package/src/sources/HyperSync.res.mjs +9 -7
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncClient.res +1 -1
- package/src/sources/HyperSyncHeightStream.res +47 -116
- package/src/sources/HyperSyncHeightStream.res.mjs +46 -73
- package/src/sources/HyperSyncSource.res +118 -139
- package/src/sources/HyperSyncSource.res.mjs +104 -121
- package/src/sources/Rpc.res +86 -14
- package/src/sources/Rpc.res.mjs +101 -9
- package/src/sources/RpcSource.res +621 -364
- package/src/sources/RpcSource.res.mjs +843 -410
- package/src/sources/RpcWebSocketHeightStream.res +181 -0
- package/src/sources/RpcWebSocketHeightStream.res.mjs +196 -0
- package/src/sources/Source.res +7 -5
- package/src/sources/SourceManager.res +325 -225
- package/src/sources/SourceManager.res.mjs +314 -171
- package/src/sources/SourceManager.resi +17 -6
- package/src/sources/Svm.res +81 -0
- package/src/sources/Svm.res.mjs +90 -0
- package/src/tui/Tui.res +247 -0
- package/src/tui/Tui.res.mjs +337 -0
- package/src/tui/bindings/Ink.res +371 -0
- package/src/tui/bindings/Ink.res.mjs +72 -0
- package/src/tui/bindings/Style.res +123 -0
- package/src/tui/bindings/Style.res.mjs +2 -0
- package/src/tui/components/BufferedProgressBar.res +40 -0
- package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
- package/src/tui/components/CustomHooks.res +122 -0
- package/src/tui/components/CustomHooks.res.mjs +179 -0
- package/src/tui/components/Messages.res +41 -0
- package/src/tui/components/Messages.res.mjs +75 -0
- package/src/tui/components/SyncETA.res +174 -0
- package/src/tui/components/SyncETA.res.mjs +263 -0
- package/src/tui/components/TuiData.res +47 -0
- package/src/tui/components/TuiData.res.mjs +34 -0
- package/svm.schema.json +112 -0
- package/bin.js +0 -48
- package/src/EventRegister.res +0 -241
- package/src/EventRegister.res.mjs +0 -240
- package/src/bindings/Ethers.gen.ts +0 -14
- package/src/bindings/Ethers.res +0 -204
- package/src/bindings/Ethers.res.mjs +0 -130
- /package/src/{Indexer.res.mjs → Ctx.res.mjs} +0 -0
|
@@ -46,7 +46,10 @@ let getClickHouseFieldType = (
|
|
|
46
46
|
let baseType = switch fieldType {
|
|
47
47
|
| Int32 => "Int32"
|
|
48
48
|
| Uint32 => "UInt32"
|
|
49
|
+
| UInt52 => "UInt64"
|
|
50
|
+
| UInt64 => "UInt64"
|
|
49
51
|
| Serial => "Int32"
|
|
52
|
+
| BigSerial => "Int64"
|
|
50
53
|
| BigInt({?precision}) =>
|
|
51
54
|
switch precision {
|
|
52
55
|
| None => "String" // Fallback for unbounded BigInt
|
|
@@ -98,6 +101,97 @@ let getClickHouseFieldType = (
|
|
|
98
101
|
isNullable ? `Nullable(${baseType})` : baseType
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
// Creates an entity schema from table definition, using clickHouseDate for Date fields
|
|
105
|
+
let makeClickHouseEntitySchema = (table: Table.table): S.t<Internal.entity> => {
|
|
106
|
+
S.schema(s => {
|
|
107
|
+
let dict = Js.Dict.empty()
|
|
108
|
+
table.fields->Belt.Array.forEach(field => {
|
|
109
|
+
switch field {
|
|
110
|
+
| Field(f) => {
|
|
111
|
+
let fieldName = f->Table.getDbFieldName
|
|
112
|
+
let fieldSchema = switch f.fieldType {
|
|
113
|
+
| Date => {
|
|
114
|
+
let dateSchema = Utils.Schema.clickHouseDate->S.toUnknown
|
|
115
|
+
if f.isNullable {
|
|
116
|
+
S.null(dateSchema)->S.toUnknown
|
|
117
|
+
} else if f.isArray {
|
|
118
|
+
S.array(dateSchema)->S.toUnknown
|
|
119
|
+
} else {
|
|
120
|
+
dateSchema
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ClickHouse returns UInt64 values as strings, need to parse to float
|
|
124
|
+
| UInt52 => {
|
|
125
|
+
let uint52Schema =
|
|
126
|
+
S.float
|
|
127
|
+
->S.preprocess(_ => {
|
|
128
|
+
parser: unknown =>
|
|
129
|
+
unknown->(Utils.magic: unknown => string)->Js.Float.fromString,
|
|
130
|
+
})
|
|
131
|
+
->S.toUnknown
|
|
132
|
+
if f.isNullable {
|
|
133
|
+
S.null(uint52Schema)->S.toUnknown
|
|
134
|
+
} else if f.isArray {
|
|
135
|
+
S.array(uint52Schema)->S.toUnknown
|
|
136
|
+
} else {
|
|
137
|
+
uint52Schema
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
| _ => f.fieldSchema
|
|
141
|
+
}
|
|
142
|
+
dict->Js.Dict.set(fieldName, s.matches(fieldSchema))
|
|
143
|
+
}
|
|
144
|
+
| DerivedFrom(_) => () // Skip derived fields
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
dict->(Utils.magic: Js.Dict.t<unknown> => Internal.entity)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let logger = Logging.createChild(~params={"context": "ClickHouse"})
|
|
152
|
+
|
|
153
|
+
// On transient failure, split values in half and retry each half.
|
|
154
|
+
// If only 1 row remains, retry with delay.
|
|
155
|
+
// Delay scales from 100ms to 1000ms as retries decrease.
|
|
156
|
+
let rec insertWithRetry = async (
|
|
157
|
+
client,
|
|
158
|
+
~table: string,
|
|
159
|
+
~values: array<'a>,
|
|
160
|
+
~format: string,
|
|
161
|
+
~retries=8,
|
|
162
|
+
) => {
|
|
163
|
+
try {
|
|
164
|
+
await client->insert({table, values, format})
|
|
165
|
+
} catch {
|
|
166
|
+
| exn if retries > 0 =>
|
|
167
|
+
let delayMs = Js.Math.min_int(1000, 100 + 900 * (8 - retries) / 7)
|
|
168
|
+
if Array.length(values) > 1 {
|
|
169
|
+
logger->Logging.childWarn({
|
|
170
|
+
"msg": "ClickHouse insert failed, splitting batch in half and retrying",
|
|
171
|
+
"table": table,
|
|
172
|
+
"batchSize": Array.length(values),
|
|
173
|
+
"retriesLeft": retries,
|
|
174
|
+
"err": exn->Utils.prettifyExn,
|
|
175
|
+
})
|
|
176
|
+
await Utils.delay(delayMs)
|
|
177
|
+
let mid = Array.length(values) / 2
|
|
178
|
+
let first = values->Js.Array2.slice(~start=0, ~end_=mid)
|
|
179
|
+
let second = values->Js.Array2.sliceFrom(mid)
|
|
180
|
+
await insertWithRetry(client, ~table, ~values=first, ~format, ~retries=retries - 1)
|
|
181
|
+
await insertWithRetry(client, ~table, ~values=second, ~format, ~retries=retries - 1)
|
|
182
|
+
} else {
|
|
183
|
+
logger->Logging.childWarn({
|
|
184
|
+
"msg": "ClickHouse insert failed, retrying after delay",
|
|
185
|
+
"table": table,
|
|
186
|
+
"retriesLeft": retries,
|
|
187
|
+
"err": exn->Utils.prettifyExn,
|
|
188
|
+
})
|
|
189
|
+
await Utils.delay(delayMs)
|
|
190
|
+
await insertWithRetry(client, ~table, ~values, ~format, ~retries=retries - 1)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
101
195
|
let setCheckpointsOrThrow = async (client, ~batch: Batch.t, ~database: string) => {
|
|
102
196
|
let checkpointsCount = batch.checkpointIds->Array.length
|
|
103
197
|
if checkpointsCount === 0 {
|
|
@@ -108,7 +202,7 @@ let setCheckpointsOrThrow = async (client, ~batch: Batch.t, ~database: string) =
|
|
|
108
202
|
for idx in 0 to checkpointsCount - 1 {
|
|
109
203
|
checkpointRows
|
|
110
204
|
->Js.Array2.push((
|
|
111
|
-
batch.checkpointIds->Belt.Array.getUnsafe(idx),
|
|
205
|
+
batch.checkpointIds->Belt.Array.getUnsafe(idx)->BigInt.toString,
|
|
112
206
|
batch.checkpointChainIds->Belt.Array.getUnsafe(idx),
|
|
113
207
|
batch.checkpointBlockNumbers->Belt.Array.getUnsafe(idx),
|
|
114
208
|
batch.checkpointBlockHashes->Belt.Array.getUnsafe(idx),
|
|
@@ -118,11 +212,12 @@ let setCheckpointsOrThrow = async (client, ~batch: Batch.t, ~database: string) =
|
|
|
118
212
|
}
|
|
119
213
|
|
|
120
214
|
try {
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
215
|
+
await insertWithRetry(
|
|
216
|
+
client,
|
|
217
|
+
~table=`${database}.\`${InternalTable.Checkpoints.table.tableName}\``,
|
|
218
|
+
~values=checkpointRows,
|
|
219
|
+
~format="JSONCompactEachRow",
|
|
220
|
+
)
|
|
126
221
|
} catch {
|
|
127
222
|
| exn =>
|
|
128
223
|
raise(
|
|
@@ -135,8 +230,14 @@ let setCheckpointsOrThrow = async (client, ~batch: Batch.t, ~database: string) =
|
|
|
135
230
|
}
|
|
136
231
|
}
|
|
137
232
|
|
|
233
|
+
type setUpdatesCache = {
|
|
234
|
+
tableName: string,
|
|
235
|
+
convertOrThrow: Change.t<Internal.entity> => Js.Json.t,
|
|
236
|
+
}
|
|
237
|
+
|
|
138
238
|
let setUpdatesOrThrow = async (
|
|
139
239
|
client,
|
|
240
|
+
~cache: Utils.WeakMap.t<Internal.entityConfig, setUpdatesCache>,
|
|
140
241
|
~updates: array<Internal.inMemoryStoreEntityUpdate<Internal.entity>>,
|
|
141
242
|
~entityConfig: Internal.entityConfig,
|
|
142
243
|
~database: string,
|
|
@@ -144,17 +245,17 @@ let setUpdatesOrThrow = async (
|
|
|
144
245
|
if updates->Array.length === 0 {
|
|
145
246
|
()
|
|
146
247
|
} else {
|
|
147
|
-
let {convertOrThrow, tableName} = switch entityConfig
|
|
148
|
-
| Some(
|
|
248
|
+
let {convertOrThrow, tableName} = switch cache->Utils.WeakMap.get(entityConfig) {
|
|
249
|
+
| Some(cached) => cached
|
|
149
250
|
| None =>
|
|
150
|
-
let
|
|
251
|
+
let cached: setUpdatesCache = {
|
|
151
252
|
tableName: `${database}.\`${EntityHistory.historyTableName(
|
|
152
253
|
~entityName=entityConfig.name,
|
|
153
254
|
~entityIndex=entityConfig.index,
|
|
154
255
|
)}\``,
|
|
155
256
|
convertOrThrow: S.compile(
|
|
156
257
|
S.union([
|
|
157
|
-
EntityHistory.makeSetUpdateSchema(entityConfig.
|
|
258
|
+
EntityHistory.makeSetUpdateSchema(makeClickHouseEntitySchema(entityConfig.table)),
|
|
158
259
|
S.object(s => {
|
|
159
260
|
s.tag(EntityHistory.changeFieldName, EntityHistory.RowAction.DELETE)
|
|
160
261
|
Change.Delete({
|
|
@@ -173,8 +274,8 @@ let setUpdatesOrThrow = async (
|
|
|
173
274
|
),
|
|
174
275
|
}
|
|
175
276
|
|
|
176
|
-
entityConfig
|
|
177
|
-
|
|
277
|
+
cache->Utils.WeakMap.set(entityConfig, cached)->ignore
|
|
278
|
+
cached
|
|
178
279
|
}
|
|
179
280
|
|
|
180
281
|
try {
|
|
@@ -183,11 +284,7 @@ let setUpdatesOrThrow = async (
|
|
|
183
284
|
update.latestChange->convertOrThrow
|
|
184
285
|
})
|
|
185
286
|
|
|
186
|
-
await client
|
|
187
|
-
table: tableName,
|
|
188
|
-
values,
|
|
189
|
-
format: "JSONEachRow",
|
|
190
|
-
})
|
|
287
|
+
await insertWithRetry(client, ~table=tableName, ~values, ~format="JSONEachRow")
|
|
191
288
|
} catch {
|
|
192
289
|
| exn =>
|
|
193
290
|
raise(
|
|
@@ -224,7 +321,7 @@ let makeCreateHistoryTableQuery = (~entityConfig: Internal.entityConfig, ~databa
|
|
|
224
321
|
)}\` (
|
|
225
322
|
${fieldDefinitions->Js.Array2.joinWith(",\n ")},
|
|
226
323
|
\`${EntityHistory.checkpointIdFieldName}\` ${getClickHouseFieldType(
|
|
227
|
-
~fieldType=
|
|
324
|
+
~fieldType=UInt64,
|
|
228
325
|
~isNullable=false,
|
|
229
326
|
~isArray=false,
|
|
230
327
|
)},
|
|
@@ -247,7 +344,7 @@ let makeCreateCheckpointsTableQuery = (~database: string) => {
|
|
|
247
344
|
let eventsProcessedField = (#events_processed: InternalTable.Checkpoints.field :> string)
|
|
248
345
|
|
|
249
346
|
`CREATE TABLE IF NOT EXISTS ${database}.\`${InternalTable.Checkpoints.table.tableName}\` (
|
|
250
|
-
\`${idField}\` ${getClickHouseFieldType(~fieldType=
|
|
347
|
+
\`${idField}\` ${getClickHouseFieldType(~fieldType=UInt64, ~isNullable=false, ~isArray=false)},
|
|
251
348
|
\`${chainIdField}\` ${getClickHouseFieldType(
|
|
252
349
|
~fieldType=Int32,
|
|
253
350
|
~isNullable=false,
|
|
@@ -264,7 +361,7 @@ let makeCreateCheckpointsTableQuery = (~database: string) => {
|
|
|
264
361
|
~isArray=false,
|
|
265
362
|
)},
|
|
266
363
|
\`${eventsProcessedField}\` ${getClickHouseFieldType(
|
|
267
|
-
~fieldType=
|
|
364
|
+
~fieldType=UInt64,
|
|
268
365
|
~isNullable=false,
|
|
269
366
|
~isArray=false,
|
|
270
367
|
)}
|
|
@@ -343,7 +440,7 @@ let initialize = async (
|
|
|
343
440
|
}
|
|
344
441
|
|
|
345
442
|
// Resume ClickHouse sink after reorg by deleting rows with checkpoint IDs higher than target
|
|
346
|
-
let resume = async (client, ~database: string, ~checkpointId:
|
|
443
|
+
let resume = async (client, ~database: string, ~checkpointId: Internal.checkpointId) => {
|
|
347
444
|
try {
|
|
348
445
|
// Try to use the database - will throw if it doesn't exist
|
|
349
446
|
try {
|
|
@@ -368,14 +465,14 @@ let resume = async (client, ~database: string, ~checkpointId: float) => {
|
|
|
368
465
|
tables->Belt.Array.map(table => {
|
|
369
466
|
let tableName = table["name"]
|
|
370
467
|
client->exec({
|
|
371
|
-
query: `ALTER TABLE ${database}.\`${tableName}\` DELETE WHERE \`${EntityHistory.checkpointIdFieldName}\` > ${checkpointId->
|
|
468
|
+
query: `ALTER TABLE ${database}.\`${tableName}\` DELETE WHERE \`${EntityHistory.checkpointIdFieldName}\` > ${checkpointId->BigInt.toString}`,
|
|
372
469
|
})
|
|
373
470
|
}),
|
|
374
471
|
)->Promise.ignoreValue
|
|
375
472
|
|
|
376
473
|
// Delete stale checkpoints
|
|
377
474
|
await client->exec({
|
|
378
|
-
query: `DELETE FROM ${database}.\`${InternalTable.Checkpoints.table.tableName}\` WHERE \`${Table.idFieldName}\` > ${checkpointId->
|
|
475
|
+
query: `DELETE FROM ${database}.\`${InternalTable.Checkpoints.table.tableName}\` WHERE \`${Table.idFieldName}\` > ${checkpointId->BigInt.toString}`,
|
|
379
476
|
})
|
|
380
477
|
} catch {
|
|
381
478
|
| Persistence.StorageError(_) as exn => raise(exn)
|
|
@@ -21,6 +21,10 @@ function getClickHouseFieldType(fieldType, isNullable, isArray) {
|
|
|
21
21
|
case "Uint32" :
|
|
22
22
|
baseType = "UInt32";
|
|
23
23
|
break;
|
|
24
|
+
case "UInt52" :
|
|
25
|
+
case "UInt64" :
|
|
26
|
+
baseType = "UInt64";
|
|
27
|
+
break;
|
|
24
28
|
case "Number" :
|
|
25
29
|
baseType = "Float64";
|
|
26
30
|
break;
|
|
@@ -28,6 +32,9 @@ function getClickHouseFieldType(fieldType, isNullable, isArray) {
|
|
|
28
32
|
case "Serial" :
|
|
29
33
|
baseType = "Int32";
|
|
30
34
|
break;
|
|
35
|
+
case "BigSerial" :
|
|
36
|
+
baseType = "Int64";
|
|
37
|
+
break;
|
|
31
38
|
case "String" :
|
|
32
39
|
case "Json" :
|
|
33
40
|
baseType = "String";
|
|
@@ -76,6 +83,94 @@ function getClickHouseFieldType(fieldType, isNullable, isArray) {
|
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function makeClickHouseEntitySchema(table) {
|
|
87
|
+
return S$RescriptSchema.schema(function (s) {
|
|
88
|
+
var dict = {};
|
|
89
|
+
Belt_Array.forEach(table.fields, (function (field) {
|
|
90
|
+
if (field.TAG !== "Field") {
|
|
91
|
+
return ;
|
|
92
|
+
}
|
|
93
|
+
var f = field._0;
|
|
94
|
+
var fieldName = Table.getDbFieldName(f);
|
|
95
|
+
var match = f.fieldType;
|
|
96
|
+
var fieldSchema;
|
|
97
|
+
if (typeof match !== "object") {
|
|
98
|
+
switch (match) {
|
|
99
|
+
case "UInt52" :
|
|
100
|
+
var uint52Schema = S$RescriptSchema.preprocess(S$RescriptSchema.$$float, (function (param) {
|
|
101
|
+
return {
|
|
102
|
+
p: (function (unknown) {
|
|
103
|
+
return Number(unknown);
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
}));
|
|
107
|
+
fieldSchema = f.isNullable ? S$RescriptSchema.$$null(uint52Schema) : (
|
|
108
|
+
f.isArray ? S$RescriptSchema.array(uint52Schema) : uint52Schema
|
|
109
|
+
);
|
|
110
|
+
break;
|
|
111
|
+
case "Date" :
|
|
112
|
+
var dateSchema = Utils.Schema.clickHouseDate;
|
|
113
|
+
fieldSchema = f.isNullable ? S$RescriptSchema.$$null(dateSchema) : (
|
|
114
|
+
f.isArray ? S$RescriptSchema.array(dateSchema) : dateSchema
|
|
115
|
+
);
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
fieldSchema = f.fieldSchema;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
fieldSchema = f.fieldSchema;
|
|
122
|
+
}
|
|
123
|
+
dict[fieldName] = s.m(fieldSchema);
|
|
124
|
+
}));
|
|
125
|
+
return dict;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var logger = Logging.createChild({
|
|
130
|
+
context: "ClickHouse"
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
async function insertWithRetry(client, table, values, format, retriesOpt) {
|
|
134
|
+
var retries = retriesOpt !== undefined ? retriesOpt : 8;
|
|
135
|
+
try {
|
|
136
|
+
return await client.insert({
|
|
137
|
+
table: table,
|
|
138
|
+
values: values,
|
|
139
|
+
format: format
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (raw_exn){
|
|
143
|
+
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
144
|
+
if (retries > 0) {
|
|
145
|
+
var delayMs = Math.min(1000, 100 + (Math.imul(900, 8 - retries | 0) / 7 | 0) | 0);
|
|
146
|
+
if (values.length > 1) {
|
|
147
|
+
Logging.childWarn(logger, {
|
|
148
|
+
msg: "ClickHouse insert failed, splitting batch in half and retrying",
|
|
149
|
+
table: table,
|
|
150
|
+
batchSize: values.length,
|
|
151
|
+
retriesLeft: retries,
|
|
152
|
+
err: Utils.prettifyExn(exn)
|
|
153
|
+
});
|
|
154
|
+
await Utils.delay(delayMs);
|
|
155
|
+
var mid = (values.length >> 1);
|
|
156
|
+
var first = values.slice(0, mid);
|
|
157
|
+
var second = values.slice(mid);
|
|
158
|
+
await insertWithRetry(client, table, first, format, retries - 1 | 0);
|
|
159
|
+
return await insertWithRetry(client, table, second, format, retries - 1 | 0);
|
|
160
|
+
}
|
|
161
|
+
Logging.childWarn(logger, {
|
|
162
|
+
msg: "ClickHouse insert failed, retrying after delay",
|
|
163
|
+
table: table,
|
|
164
|
+
retriesLeft: retries,
|
|
165
|
+
err: Utils.prettifyExn(exn)
|
|
166
|
+
});
|
|
167
|
+
await Utils.delay(delayMs);
|
|
168
|
+
return await insertWithRetry(client, table, values, format, retries - 1 | 0);
|
|
169
|
+
}
|
|
170
|
+
throw exn;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
79
174
|
async function setCheckpointsOrThrow(client, batch, database) {
|
|
80
175
|
var checkpointsCount = batch.checkpointIds.length;
|
|
81
176
|
if (checkpointsCount === 0) {
|
|
@@ -84,7 +179,7 @@ async function setCheckpointsOrThrow(client, batch, database) {
|
|
|
84
179
|
var checkpointRows = [];
|
|
85
180
|
for(var idx = 0; idx < checkpointsCount; ++idx){
|
|
86
181
|
checkpointRows.push([
|
|
87
|
-
batch.checkpointIds[idx],
|
|
182
|
+
batch.checkpointIds[idx].toString(),
|
|
88
183
|
batch.checkpointChainIds[idx],
|
|
89
184
|
batch.checkpointBlockNumbers[idx],
|
|
90
185
|
batch.checkpointBlockHashes[idx],
|
|
@@ -92,11 +187,7 @@ async function setCheckpointsOrThrow(client, batch, database) {
|
|
|
92
187
|
]);
|
|
93
188
|
}
|
|
94
189
|
try {
|
|
95
|
-
return await client.
|
|
96
|
-
table: database + ".\`" + InternalTable.Checkpoints.table.tableName + "\`",
|
|
97
|
-
values: checkpointRows,
|
|
98
|
-
format: "JSONCompactEachRow"
|
|
99
|
-
});
|
|
190
|
+
return await insertWithRetry(client, database + ".\`" + InternalTable.Checkpoints.table.tableName + "\`", checkpointRows, "JSONCompactEachRow", undefined);
|
|
100
191
|
}
|
|
101
192
|
catch (raw_exn){
|
|
102
193
|
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
@@ -109,18 +200,18 @@ async function setCheckpointsOrThrow(client, batch, database) {
|
|
|
109
200
|
}
|
|
110
201
|
}
|
|
111
202
|
|
|
112
|
-
async function setUpdatesOrThrow(client, updates, entityConfig, database) {
|
|
203
|
+
async function setUpdatesOrThrow(client, cache, updates, entityConfig, database) {
|
|
113
204
|
if (updates.length === 0) {
|
|
114
205
|
return ;
|
|
115
206
|
}
|
|
116
|
-
var
|
|
207
|
+
var cached = cache.get(entityConfig);
|
|
117
208
|
var match;
|
|
118
|
-
if (
|
|
119
|
-
match =
|
|
209
|
+
if (cached !== undefined) {
|
|
210
|
+
match = cached;
|
|
120
211
|
} else {
|
|
121
|
-
var
|
|
122
|
-
var
|
|
123
|
-
EntityHistory.makeSetUpdateSchema(entityConfig.
|
|
212
|
+
var cached_tableName = database + ".\`" + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + "\`";
|
|
213
|
+
var cached_convertOrThrow = S$RescriptSchema.compile(S$RescriptSchema.union([
|
|
214
|
+
EntityHistory.makeSetUpdateSchema(makeClickHouseEntitySchema(entityConfig.table)),
|
|
124
215
|
S$RescriptSchema.object(function (s) {
|
|
125
216
|
s.tag(EntityHistory.changeFieldName, "DELETE");
|
|
126
217
|
return {
|
|
@@ -130,12 +221,12 @@ async function setUpdatesOrThrow(client, updates, entityConfig, database) {
|
|
|
130
221
|
};
|
|
131
222
|
})
|
|
132
223
|
]), "Output", "Json", "Sync", false);
|
|
133
|
-
var
|
|
134
|
-
tableName:
|
|
135
|
-
convertOrThrow:
|
|
224
|
+
var cached$1 = {
|
|
225
|
+
tableName: cached_tableName,
|
|
226
|
+
convertOrThrow: cached_convertOrThrow
|
|
136
227
|
};
|
|
137
|
-
entityConfig
|
|
138
|
-
match =
|
|
228
|
+
cache.set(entityConfig, cached$1);
|
|
229
|
+
match = cached$1;
|
|
139
230
|
}
|
|
140
231
|
var convertOrThrow = match.convertOrThrow;
|
|
141
232
|
var tableName = match.tableName;
|
|
@@ -143,11 +234,7 @@ async function setUpdatesOrThrow(client, updates, entityConfig, database) {
|
|
|
143
234
|
var values = updates.map(function (update) {
|
|
144
235
|
return convertOrThrow(update.latestChange);
|
|
145
236
|
});
|
|
146
|
-
return await client
|
|
147
|
-
table: tableName,
|
|
148
|
-
values: values,
|
|
149
|
-
format: "JSONEachRow"
|
|
150
|
-
});
|
|
237
|
+
return await insertWithRetry(client, tableName, values, "JSONEachRow", undefined);
|
|
151
238
|
}
|
|
152
239
|
catch (raw_exn){
|
|
153
240
|
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
|
|
@@ -170,14 +257,14 @@ function makeCreateHistoryTableQuery(entityConfig, database) {
|
|
|
170
257
|
var clickHouseType = getClickHouseFieldType(field$1.fieldType, field$1.isNullable, field$1.isArray);
|
|
171
258
|
return "\`" + fieldName + "\` " + clickHouseType;
|
|
172
259
|
}));
|
|
173
|
-
return "CREATE TABLE IF NOT EXISTS " + database + ".\`" + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + "\` (\n " + fieldDefinitions.join(",\n ") + ",\n \`" + EntityHistory.checkpointIdFieldName + "\` " + getClickHouseFieldType("
|
|
260
|
+
return "CREATE TABLE IF NOT EXISTS " + database + ".\`" + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + "\` (\n " + fieldDefinitions.join(",\n ") + ",\n \`" + EntityHistory.checkpointIdFieldName + "\` " + getClickHouseFieldType("UInt64", false, false) + ",\n \`" + EntityHistory.changeFieldName + "\` " + getClickHouseFieldType({
|
|
174
261
|
type: "Enum",
|
|
175
262
|
config: EntityHistory.RowAction.config
|
|
176
263
|
}, false, false) + "\n)\nENGINE = MergeTree()\nORDER BY (" + Table.idFieldName + ", " + EntityHistory.checkpointIdFieldName + ")";
|
|
177
264
|
}
|
|
178
265
|
|
|
179
266
|
function makeCreateCheckpointsTableQuery(database) {
|
|
180
|
-
return "CREATE TABLE IF NOT EXISTS " + database + ".\`" + InternalTable.Checkpoints.table.tableName + "\` (\n \`" + "id" + "\` " + getClickHouseFieldType("
|
|
267
|
+
return "CREATE TABLE IF NOT EXISTS " + database + ".\`" + InternalTable.Checkpoints.table.tableName + "\` (\n \`" + "id" + "\` " + getClickHouseFieldType("UInt64", false, false) + ",\n \`" + "chain_id" + "\` " + getClickHouseFieldType("Int32", false, false) + ",\n \`" + "block_number" + "\` " + getClickHouseFieldType("Int32", false, false) + ",\n \`" + "block_hash" + "\` " + getClickHouseFieldType("String", true, false) + ",\n \`" + "events_processed" + "\` " + getClickHouseFieldType("UInt64", false, false) + "\n)\nENGINE = MergeTree()\nORDER BY (" + "id" + ")";
|
|
181
268
|
}
|
|
182
269
|
|
|
183
270
|
function makeCreateViewQuery(entityConfig, database) {
|
|
@@ -245,11 +332,11 @@ async function resume(client, database, checkpointId) {
|
|
|
245
332
|
await Promise.all(Belt_Array.map(tables, (function (table) {
|
|
246
333
|
var tableName = table.name;
|
|
247
334
|
return client.exec({
|
|
248
|
-
query: "ALTER TABLE " + database + ".\`" + tableName + "\` DELETE WHERE \`" + EntityHistory.checkpointIdFieldName + "\` > " +
|
|
335
|
+
query: "ALTER TABLE " + database + ".\`" + tableName + "\` DELETE WHERE \`" + EntityHistory.checkpointIdFieldName + "\` > " + checkpointId.toString()
|
|
249
336
|
});
|
|
250
337
|
})));
|
|
251
338
|
return await client.exec({
|
|
252
|
-
query: "DELETE FROM " + database + ".\`" + InternalTable.Checkpoints.table.tableName + "\` WHERE \`" + Table.idFieldName + "\` > " +
|
|
339
|
+
query: "DELETE FROM " + database + ".\`" + InternalTable.Checkpoints.table.tableName + "\` WHERE \`" + Table.idFieldName + "\` > " + checkpointId.toString()
|
|
253
340
|
});
|
|
254
341
|
}
|
|
255
342
|
catch (raw_exn$1){
|
|
@@ -264,6 +351,9 @@ async function resume(client, database, checkpointId) {
|
|
|
264
351
|
|
|
265
352
|
export {
|
|
266
353
|
getClickHouseFieldType ,
|
|
354
|
+
makeClickHouseEntitySchema ,
|
|
355
|
+
logger ,
|
|
356
|
+
insertWithRetry ,
|
|
267
357
|
setCheckpointsOrThrow ,
|
|
268
358
|
setUpdatesOrThrow ,
|
|
269
359
|
makeCreateHistoryTableQuery ,
|
|
@@ -272,4 +362,4 @@ export {
|
|
|
272
362
|
initialize ,
|
|
273
363
|
resume ,
|
|
274
364
|
}
|
|
275
|
-
/*
|
|
365
|
+
/* logger Not a pure module */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Formats:
|
|
3
|
+
hh:mm:ss | 00:00:00
|
|
4
|
+
do MMM ''yy | 1st Jan '21
|
|
5
|
+
ha do MMM ''yy | 8PM 1st Jan '21
|
|
6
|
+
ha | 8PM
|
|
7
|
+
iii | Tues
|
|
8
|
+
iii MMM | Tues Jan
|
|
9
|
+
MMM | Jan
|
|
10
|
+
`)
|
|
11
|
+
*/
|
|
12
|
+
type dateFormats =
|
|
13
|
+
| @as("HH:mm:ss") HoursMinSec
|
|
14
|
+
| @as("ha") Hour
|
|
15
|
+
| @as("do MMM ''yy") DayMonthYear
|
|
16
|
+
| @as("ha do MMM ''yy") HourDayMonthYear
|
|
17
|
+
| @as("h:mma do MMM ''yy") HourMinDayMonthYear
|
|
18
|
+
| @as("iii") DayName
|
|
19
|
+
| @as("iii MMM") DayNameMonth
|
|
20
|
+
| @as("do MMM") DayMonth
|
|
21
|
+
| @as("MMM") Month
|
|
22
|
+
| @as("h:mma") HourMin
|
|
23
|
+
|
|
24
|
+
@module("date-fns") external format: (Js.Date.t, dateFormats) => string = "format"
|
|
25
|
+
|
|
26
|
+
type formatDistanceToNowOptions = {includeSeconds: bool}
|
|
27
|
+
@module("date-fns")
|
|
28
|
+
external formatDistanceToNow: Js.Date.t => string = "formatDistanceToNow"
|
|
29
|
+
|
|
30
|
+
@module("date-fns")
|
|
31
|
+
external formatDistance: (Js.Date.t, Js.Date.t) => string = "formatDistance"
|
|
32
|
+
|
|
33
|
+
@module("date-fns")
|
|
34
|
+
external formatDistanceWithOptions: (Js.Date.t, Js.Date.t, formatDistanceToNowOptions) => string =
|
|
35
|
+
"formatDistance"
|
|
36
|
+
|
|
37
|
+
@module("date-fns")
|
|
38
|
+
external formatDistanceToNowWithOptions: (Js.Date.t, formatDistanceToNowOptions) => string =
|
|
39
|
+
"formatDistanceToNow"
|
|
40
|
+
|
|
41
|
+
let formatDistanceToNowWithSeconds = (date: Js.Date.t) =>
|
|
42
|
+
date->formatDistanceToNowWithOptions({includeSeconds: true})
|
|
43
|
+
|
|
44
|
+
type durationTimeFormat = {
|
|
45
|
+
years: int,
|
|
46
|
+
months: int,
|
|
47
|
+
weeks: int,
|
|
48
|
+
days: int,
|
|
49
|
+
hours: int,
|
|
50
|
+
minutes: int,
|
|
51
|
+
seconds: int,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@module("date-fns")
|
|
55
|
+
external formatRelative: (Js.Date.t, Js.Date.t) => string = "formatRelative"
|
|
56
|
+
|
|
57
|
+
type durationFormatOutput = {format: array<string>}
|
|
58
|
+
|
|
59
|
+
@module("date-fns")
|
|
60
|
+
external formatDuration: (durationTimeFormat, durationFormatOutput) => string = "formatDuration"
|
|
61
|
+
|
|
62
|
+
type interval = {start: Js_date.t, end: Js_date.t}
|
|
63
|
+
|
|
64
|
+
@module("date-fns")
|
|
65
|
+
external intervalToDuration: interval => durationTimeFormat = "intervalToDuration"
|
|
66
|
+
|
|
67
|
+
//helper to convert millis elapsed to duration object
|
|
68
|
+
let durationFromMillis = (millis: int) =>
|
|
69
|
+
intervalToDuration({
|
|
70
|
+
start: 0->(Utils.magic: int => Js_date.t),
|
|
71
|
+
end: millis->(Utils.magic: int => Js_date.t),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
@module("date-fns") external fromUnixTime: float => Js.Date.t = "fromUnixTime"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as DateFns from "date-fns";
|
|
4
|
+
|
|
5
|
+
function formatDistanceToNowWithSeconds(date) {
|
|
6
|
+
return DateFns.formatDistanceToNow(date, {
|
|
7
|
+
includeSeconds: true
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function durationFromMillis(millis) {
|
|
12
|
+
return DateFns.intervalToDuration({
|
|
13
|
+
start: 0,
|
|
14
|
+
end: millis
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
formatDistanceToNowWithSeconds ,
|
|
20
|
+
durationFromMillis ,
|
|
21
|
+
}
|
|
22
|
+
/* date-fns Not a pure module */
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
type t
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module Fetch = {
|
|
4
|
+
type args = {body?: unknown, headers?: dict<string>, method?: string, path?: string}
|
|
5
|
+
type t = (string, ~args: args) => promise<unknown>
|
|
6
|
+
// NOTE: don't try make the type t. Rescript 11 will curry the args which breaks
|
|
7
|
+
// keet the type inline. This is a workaround for now.
|
|
8
|
+
external fetch: (string, ~args: args) => promise<unknown> = "fetch"
|
|
9
|
+
}
|
|
10
|
+
type options = {fetch?: Fetch.t}
|
|
4
11
|
|
|
5
12
|
@module("eventsource") @new
|
|
6
13
|
external create: (~url: string, ~options: options=?) => t = "EventSource"
|
package/src/bindings/Express.res
CHANGED
|
@@ -28,6 +28,7 @@ type middleware = (req, res, unit => unit) => unit
|
|
|
28
28
|
type server
|
|
29
29
|
|
|
30
30
|
@send external listen: (app, int) => server = "listen"
|
|
31
|
+
@send external onError: (server, @as("error") _, Js.Exn.t => unit) => unit = "on"
|
|
31
32
|
|
|
32
33
|
// res methods
|
|
33
34
|
@send external sendStatus: (res, int) => unit = "sendStatus"
|
package/src/bindings/Hrtime.res
CHANGED
|
@@ -34,12 +34,25 @@ let toMillis = ((sec, nano): timeElapsed): milliseconds => {
|
|
|
34
34
|
sec->secToMilli +. nano->nanoToMilli
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
let toSeconds = ((sec, nano): timeElapsed): seconds => {
|
|
38
|
+
sec +. nano /. 1_000_000_000.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let toSecondsFloat = (elapsed: timeElapsed): float => {
|
|
42
|
+
elapsed->toSeconds->(Utils.magic: seconds => float)
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
let toInt = float => float->Belt.Int.fromFloat
|
|
38
46
|
let intFromMillis = toInt
|
|
39
47
|
let intFromNanos = toInt
|
|
40
48
|
let intFromSeconds = toInt
|
|
41
|
-
let floatFromMillis = Utils.magic
|
|
49
|
+
let floatFromMillis: milliseconds => float = v => v->(Utils.magic: milliseconds => float)
|
|
50
|
+
let floatFromSeconds: seconds => float = v => v->(Utils.magic: seconds => float)
|
|
42
51
|
|
|
43
52
|
let millisBetween = (~from: timeRef, ~to: timeRef): int => {
|
|
44
53
|
to->toMillis->intFromMillis - from->toMillis->intFromMillis
|
|
45
54
|
}
|
|
55
|
+
|
|
56
|
+
let secondsBetween = (~from: timeRef, ~to: timeRef): float => {
|
|
57
|
+
to->toSecondsFloat -. from->toSecondsFloat
|
|
58
|
+
}
|