envio 3.15.0 → 3.16.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 (99) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +8 -8
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/ChainState.res +48 -7
  11. package/src/ChainState.res.mjs +27 -11
  12. package/src/Clock.res +57 -0
  13. package/src/Clock.res.mjs +72 -0
  14. package/src/Config.res +10 -3
  15. package/src/Config.res.mjs +4 -2
  16. package/src/Core.res +4 -40
  17. package/src/EffectState.res +10 -2
  18. package/src/EffectState.res.mjs +8 -4
  19. package/src/EffectState.resi +3 -1
  20. package/src/EntityId.res +2 -7
  21. package/src/EntityId.res.mjs +1 -8
  22. package/src/EntityTables.res +4 -1
  23. package/src/EntityTables.res.mjs +1 -1
  24. package/src/FetchState.res +157 -69
  25. package/src/FetchState.res.mjs +144 -49
  26. package/src/FinalizeBackfill.res +2 -4
  27. package/src/FinalizeBackfill.res.mjs +3 -4
  28. package/src/InMemoryTable.res +17 -16
  29. package/src/InMemoryTable.res.mjs +14 -15
  30. package/src/IndexerState.res +28 -4
  31. package/src/IndexerState.res.mjs +19 -6
  32. package/src/IndexerState.resi +4 -0
  33. package/src/Internal.res +0 -49
  34. package/src/Internal.res.mjs +1 -80
  35. package/src/LoadLayer.res +14 -8
  36. package/src/LoadLayer.res.mjs +8 -6
  37. package/src/Main.res.mjs +1 -1
  38. package/src/Persistence.res +3 -23
  39. package/src/Persistence.res.mjs +3 -3
  40. package/src/Rollback.res +0 -1
  41. package/src/Rollback.res.mjs +1 -1
  42. package/src/SafeCheckpointTracking.res +4 -4
  43. package/src/SafeCheckpointTracking.res.mjs +1 -1
  44. package/src/StagedRows.res +106 -0
  45. package/src/StagedRows.res.mjs +102 -0
  46. package/src/Staging.res +32 -14
  47. package/src/Staging.res.mjs +17 -20
  48. package/src/Storage.res +277 -948
  49. package/src/Storage.res.mjs +215 -707
  50. package/src/Supervisor.res +75 -56
  51. package/src/Supervisor.res.mjs +55 -26
  52. package/src/TestIndexer.res +22 -27
  53. package/src/TestIndexer.res.mjs +20 -17
  54. package/src/Throttler.res +15 -8
  55. package/src/Throttler.res.mjs +9 -6
  56. package/src/Throttler.resi +1 -1
  57. package/src/Utils.res +0 -6
  58. package/src/Utils.res.mjs +0 -5
  59. package/src/Writing.res +0 -1
  60. package/src/Writing.res.mjs +1 -1
  61. package/src/bindings/ClickHouse.res +0 -322
  62. package/src/bindings/ClickHouse.res.mjs +0 -338
  63. package/src/bindings/StorageClient.res +107 -174
  64. package/src/bindings/StorageClient.res.mjs +19 -8
  65. package/src/bindings/Vitest.res +0 -26
  66. package/src/bindings/Vitest.res.mjs +0 -3
  67. package/src/db/Table.res +24 -150
  68. package/src/db/Table.res.mjs +38 -272
  69. package/src/sources/ChainSources.res +2 -0
  70. package/src/sources/ChainSources.res.mjs +2 -2
  71. package/src/sources/EvmChain.res +3 -0
  72. package/src/sources/EvmChain.res.mjs +4 -2
  73. package/src/sources/EvmHyperSyncSource.res +2 -2
  74. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  75. package/src/sources/EvmRpcClient.res +13 -3
  76. package/src/sources/EvmRpcClient.res.mjs +4 -3
  77. package/src/sources/EvmRpcWs.res +4 -3
  78. package/src/sources/EvmRpcWs.res.mjs +2 -2
  79. package/src/sources/HeightFeed.res +31 -10
  80. package/src/sources/HeightFeed.res.mjs +10 -6
  81. package/src/sources/HeightStream.res +32 -17
  82. package/src/sources/HeightStream.res.mjs +10 -10
  83. package/src/sources/HyperSyncSSE.res +4 -6
  84. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  85. package/src/sources/RpcSource.res +10 -1
  86. package/src/sources/RpcSource.res.mjs +4 -3
  87. package/src/sources/Source.res +3 -0
  88. package/src/sources/SourceManager.res +84 -77
  89. package/src/sources/SourceManager.res.mjs +17 -20
  90. package/src/sources/SourceManager.resi +3 -4
  91. package/src/sources/StartBlockResolver.res +1 -0
  92. package/src/sources/StartBlockResolver.res.mjs +3 -2
  93. package/src/sources/SvmHyperSyncSource.res +2 -2
  94. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  95. package/svm.schema.json +11 -2
  96. package/src/PgWriting.res +0 -36
  97. package/src/PgWriting.res.mjs +0 -34
  98. package/src/db/IndexDefinition.res +0 -47
  99. package/src/db/IndexDefinition.res.mjs +0 -67
package/src/Utils.res CHANGED
@@ -10,12 +10,6 @@ external toMethod: 'a => 'a = "%unsafe_to_method"
10
10
 
11
11
  @val external importPath: string => promise<unknown> = "import"
12
12
 
13
- // Half the delay fixed, half spread across it. Every indexer pointed at one
14
- // provider loses its stream, or gives up on a quiet chain, in the same instant
15
- // that provider blinks, and putting them all back on the same schedule is how a
16
- // blip becomes a stampede.
17
- let jitter = delay => delay / 2 + (Math.random() *. (delay / 2)->Int.toFloat)->Float.toInt
18
-
19
13
  // Clearing a pending timer and forgetting it are one action. A ref left holding
20
14
  // a spent id reads as a timer still pending, and the next clear looks like it
21
15
  // did something.
package/src/Utils.res.mjs CHANGED
@@ -13,10 +13,6 @@ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js
13
13
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
14
14
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
15
15
 
16
- function jitter(delay) {
17
- return (delay / 2 | 0) + (Math.random() * (delay / 2 | 0) | 0) | 0;
18
- }
19
-
20
16
  function clearTimeoutRef(timeoutId) {
21
17
  let id = timeoutId.contents;
22
18
  if (id !== undefined) {
@@ -813,7 +809,6 @@ function replaceLoneSurrogateEscapes(json) {
813
809
  let maxBackoffExponent = 20;
814
810
 
815
811
  export {
816
- jitter,
817
812
  clearTimeoutRef,
818
813
  maxBackoffExponent,
819
814
  expBackoff,
package/src/Writing.res CHANGED
@@ -153,7 +153,6 @@ let runOneWrite = async (state: IndexerState.t) => {
153
153
  ~batch,
154
154
  ~rollback,
155
155
  ~config,
156
- ~allEntities=persistence.allEntities,
157
156
  ~updatedEntities,
158
157
  ~registeredAddresses,
159
158
  ~updatedEffectsCache,
@@ -121,7 +121,7 @@ async function runOneWrite(state) {
121
121
  });
122
122
  let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
123
123
  await Promise.all([
124
- persistence.storage.writeBatch(batch, rollback, config, persistence.allEntities, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
124
+ persistence.storage.writeBatch(batch, rollback, config, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
125
125
  PruneStaleHistory.runConcurrent(state, pruneTargets)
126
126
  ]);
127
127
  IndexerState.markCommitted(state, writtenFrontier);
@@ -1,325 +1,3 @@
1
- let fieldTypeName = (fieldType: Table.fieldType) =>
2
- switch fieldType {
3
- | String => "String"
4
- | Boolean => "Boolean"
5
- | Uint32 => "Uint32"
6
- | UInt52 => "UInt52"
7
- | SmallInt =>
8
- JsError.throwWithMessage("ClickHouse doesn't support the internal SmallInt column type")
9
- | Bytea => "Bytea"
10
- | UInt64 => "UInt64"
11
- | Int32 => "Int32"
12
- | ChainId => "ChainId"
13
- | Number => "Number"
14
- | Serial => "Serial"
15
- | BigSerial => "BigSerial"
16
- | Json => "Json"
17
- | Date => "Date"
18
- | BigInt(_) => "BigInt"
19
- | BigDecimal(_) => "BigDecimal"
20
- | Enum(_) => "Enum"
21
- }
22
-
23
- let column = (
24
- ~name,
25
- ~rowName=?,
26
- ~fieldName=?,
27
- ~fieldType: Table.fieldType,
28
- ~isNullable=false,
29
- ~isArray=false,
30
- ): StorageClient.clickHouseColumn => {
31
- name,
32
- ?rowName,
33
- ?fieldName,
34
- fieldType: fieldType->fieldTypeName,
35
- isNullable,
36
- isArray,
37
- precision: ?switch fieldType {
38
- | BigInt({?precision}) => precision
39
- | BigDecimal({?config}) => config->Option.map(((precision, _)) => precision)
40
- | _ => None
41
- },
42
- scale: ?switch fieldType {
43
- | BigDecimal({?config}) => config->Option.map(((_, scale)) => scale)
44
- | _ => None
45
- },
46
- enumVariants: ?switch fieldType {
47
- | Enum({config}) =>
48
- Some(config.variants->Array.map(variant => variant->(Utils.magic: Table.enum => string)))
49
- | _ => None
50
- },
51
- }
52
-
53
- let fieldColumn = (field: Table.field) =>
54
- column(
55
- ~name=field->Table.getClickHouseDbFieldName,
56
- ~rowName=field->Table.getPgDbFieldName,
57
- ~fieldName=field.fieldName,
58
- ~fieldType=field.fieldType,
59
- ~isNullable=field.isNullable,
60
- ~isArray=field.isArray,
61
- )
62
-
63
- let entityTable = (entityConfig: Internal.entityConfig): StorageClient.clickHouseTable => {
64
- let options = entityConfig.storage.clickhouseOptions
65
- {
66
- name: entityConfig.name,
67
- historyTable: EntityHistory.historyTableName(
68
- ~entityName=entityConfig.name,
69
- ~entityIndex=entityConfig.index,
70
- ),
71
- columns: entityConfig.table.fields->Array.filterMap(field =>
72
- switch field {
73
- | Table.Field(f) => Some(fieldColumn(f))
74
- | DerivedFrom(_) => None
75
- }
76
- ),
77
- chainIdColumn: ?(
78
- entityConfig.table->Table.getChainIdField->Option.map(Table.getClickHouseDbFieldName)
79
- ),
80
- partitionBy: ?(options->Option.flatMap(options => options.partitionBy)),
81
- orderBy: ?(options->Option.flatMap(options => options.orderBy)),
82
- ttl: ?(options->Option.flatMap(options => options.ttl)),
83
- skippingIndexes: ?(
84
- options->Option.flatMap(options =>
85
- options.skippingIndexes->Option.map(indexes =>
86
- indexes->Array.map(
87
- (index): StorageClient.skippingIndex => {
88
- name: index.name,
89
- expr: index.expr,
90
- indexType: index.type_,
91
- granularity: ?index.granularity,
92
- },
93
- )
94
- )
95
- )
96
- ),
97
- }
98
- }
99
-
100
- // Visits every node on the way into JSON text. A bigint would make
101
- // `JSON.stringify` throw and fail the whole batch, so it travels as its digits.
102
- %%private(
103
- let jsonSafe = (~column) =>
104
- (_, value: JSON.t) =>
105
- switch value->typeof {
106
- | #bigint =>
107
- value->(Utils.magic: JSON.t => unknown)->Staging.stringOf->(Utils.magic: string => JSON.t)
108
- // A Uint8Array inside a list column travels as its byte values, which the
109
- // addon reads back into raw bytes.
110
- | #object =>
111
- switch value->(Utils.magic: JSON.t => unknown)->Utils.Bytes.asUint8Array {
112
- | Some(bytes) =>
113
- bytes
114
- ->(Utils.magic: Uint8Array.t => Array.arrayLike<int>)
115
- ->Array.fromArrayLike
116
- ->(Utils.magic: array<int> => JSON.t)
117
- | None => value
118
- }
119
- | #number =>
120
- let _ = value->(Utils.magic: JSON.t => float)->Staging.finiteOrThrow(~column)
121
- value
122
- | _ => value
123
- }
124
- )
125
-
126
- type cell = Change.t<Internal.entity> => unknown
127
-
128
- type historyCells = {
129
- setCells: array<cell>,
130
- deleteCells: array<cell>,
131
- }
132
-
133
- type table = {
134
- handle: int,
135
- name: string,
136
- columns: array<Staging.column>,
137
- // An entity's history cells, by the scope whose chain id they stamp.
138
- historyCells: dict<historyCells>,
139
- }
140
-
141
- let makeTable = (~handle, ~name, {names, kinds, nullable}: StorageClient.clickHouseRegistered) => {
142
- handle,
143
- name,
144
- columns: names->Array.mapWithIndex((name, index): Staging.column => {
145
- name,
146
- kind: kinds->Array.getUnsafe(index)->Staging.kindOfOrdinal,
147
- isNullable: nullable->Array.getUnsafe(index),
148
- replacer: Replacer(jsonSafe(~column=name)),
149
- }),
150
- historyCells: Dict.make(),
151
- }
152
-
153
- // A Json column holds the document's text. Staging writes a string as is, so a
154
- // document that is a string has to become its own JSON text here, or it would
155
- // read back as something JSON can't parse. JSON `null` and an absent value are
156
- // both stored as `null`: the column is never Nullable.
157
- %%private(
158
- let documentText = (value: unknown) =>
159
- switch value->(Utils.magic: unknown => Nullable.t<JSON.t>) {
160
- | Value(_) => value->Sql.canonicalDocument->(Utils.magic: string => unknown)
161
- | Null | Undefined => "null"->(Utils.magic: string => unknown)
162
- }
163
- )
164
-
165
- %%private(
166
- let compileSerializer = schema =>
167
- S.compile(schema, ~input=Value, ~output=Json, ~typeValidation=false, ~mode=Sync)->(
168
- Utils.magic: (unknown => JSON.t) => unknown => unknown
169
- )
170
- )
171
-
172
- %%private(
173
- let documentsText = (f: Table.field): (unknown => unknown) =>
174
- if f.isArray {
175
- value =>
176
- switch value->(Utils.magic: unknown => Nullable.t<array<unknown>>) {
177
- | Value(documents) =>
178
- documents->Array.map(documentText)->(Utils.magic: array<unknown> => unknown)
179
- | Null | Undefined => value
180
- }
181
- } else {
182
- documentText
183
- }
184
- )
185
-
186
- %%private(
187
- let serializerOf = (f: Table.field): (unknown => unknown) => {
188
- let shaped = baseSchema =>
189
- if f.isNullable {
190
- Utils.Schema.nullTolerant(baseSchema)->S.toUnknown
191
- } else if f.isArray {
192
- S.array(baseSchema)->S.toUnknown
193
- } else {
194
- baseSchema
195
- }
196
- switch f.fieldType {
197
- | Json => documentsText(f)
198
- | Date => compileSerializer(shaped(Utils.Schema.clickHouseDate->S.toUnknown))
199
- | Bytea => compileSerializer(shaped(S.json(~validate=false)->S.toUnknown))
200
- | ChainId => compileSerializer(ChainId.schema->S.toUnknown)
201
- | _ => compileSerializer(f.fieldSchema)
202
- }
203
- }
204
- )
205
-
206
- @get external changeEntity: Change.t<Internal.entity> => dict<unknown> = "entity"
207
-
208
- %%private(let absent: unknown = %raw(`undefined`))
209
-
210
- %%private(
211
- let makeHistoryCells = (
212
- table,
213
- ~entityConfig: Internal.entityConfig,
214
- ~scope: Internal.chainScope,
215
- ): historyCells => {
216
- let sourceTable = entityConfig.table
217
- let chainIdTag = switch (
218
- sourceTable->Table.getChainIdField,
219
- scope->Internal.chainScopeChainId,
220
- ) {
221
- | (Some(field), Some(chainId)) =>
222
- Some((field->Table.getClickHouseDbFieldName, chainId->(Utils.magic: ChainId.t => unknown)))
223
- | _ => None
224
- }
225
- let fieldsByColumn = Dict.make()
226
- sourceTable.fields->Array.forEach(field =>
227
- switch field {
228
- | Table.Field(f) => fieldsByColumn->Dict.set(f->Table.getClickHouseDbFieldName, f)
229
- | DerivedFrom(_) => ()
230
- }
231
- )
232
-
233
- let setCells = []
234
- let deleteCells = []
235
- let registeredNames = table.columns->Array.map(column => column.name)->Utils.Set.fromArray
236
- table.columns->Array.forEach(({name}) => {
237
- let (set, delete) = if name === EntityHistory.checkpointIdFieldName {
238
- let cell = change => change->Change.getCheckpointId->(Utils.magic: bigint => unknown)
239
- (cell, cell)
240
- } else if name === EntityHistory.changeFieldName {
241
- (
242
- _ => (EntityHistory.RowAction.SET :> string)->(Utils.magic: string => unknown),
243
- _ => (EntityHistory.RowAction.DELETE :> string)->(Utils.magic: string => unknown),
244
- )
245
- } else {
246
- switch (chainIdTag, fieldsByColumn->Utils.Dict.dangerouslyGetNonOption(name)) {
247
- | (Some((column, chainId)), _) if column === name => (_ => chainId, _ => chainId)
248
- | (_, Some(f)) =>
249
- let serialize = serializerOf(f)
250
- let apiName = f->Table.getApiFieldName
251
- if apiName === Table.idFieldName {
252
- let cell = change =>
253
- serialize(change->Change.getEntityId->(Utils.magic: EntityId.t => unknown))
254
- (cell, cell)
255
- } else {
256
- (change => serialize(change->changeEntity->Dict.getUnsafe(apiName)), _ => absent)
257
- }
258
- | (_, None) =>
259
- JsError.throwWithMessage(
260
- `ClickHouse table "${table.name}" registered a column "${name}" that entity "${entityConfig.name}" has no value for`,
261
- )
262
- }
263
- }
264
- setCells->Array.push(set)
265
- deleteCells->Array.push(delete)
266
- })
267
-
268
- fieldsByColumn->Dict.forEachWithKey((f, column) =>
269
- if !(registeredNames->Utils.Set.has(column)) {
270
- JsError.throwWithMessage(
271
- `ClickHouse table "${table.name}" registered no column for field "${f.fieldName}" of entity "${entityConfig.name}"`,
272
- )
273
- }
274
- )
275
-
276
- {setCells, deleteCells}
277
- }
278
- )
279
-
280
- // Every change as a row of the entity's history: a deletion keeps only the
281
- // columns that say which entity it removed.
282
- let stageChangesOrThrow = (
283
- arena,
284
- table,
285
- ~entityConfig: Internal.entityConfig,
286
- ~scope: Internal.chainScope,
287
- ~changes: array<Change.t<Internal.entity>>,
288
- ) => {
289
- let stage =
290
- arena->Staging.begin(~table=table.handle, ~rows=changes->Array.length, ~columns=table.columns)
291
- try {
292
- let cacheKey = scope->Internal.chainScopeToString
293
- let cells = switch table.historyCells->Utils.Dict.dangerouslyGetNonOption(cacheKey) {
294
- | Some(cached) => cached
295
- | None =>
296
- let cells = table->makeHistoryCells(~entityConfig, ~scope)
297
- table.historyCells->Dict.set(cacheKey, cells)
298
- cells
299
- }
300
- let columns = stage->Staging.columnCount
301
- changes->Array.forEachWithIndex((change, row) => {
302
- let (cells, write) = switch change {
303
- | Change.Set(_) => (cells.setCells, Staging.writeValue)
304
- | Delete(_) => (cells.deleteCells, Staging.writeDeletedValue)
305
- }
306
- for column in 0 to columns - 1 {
307
- stage->write(~column, ~row, (cells->Array.getUnsafe(column))(change))
308
- }
309
- })
310
- stage->Staging.commit
311
- } catch {
312
- | exn =>
313
- stage->Staging.abort
314
- throw(
315
- Persistence.StorageError({
316
- message: `Failed to convert items for ClickHouse table "${table.name}"`,
317
- reason: exn->Utils.prettifyExn,
318
- }),
319
- )
320
- }
321
- }
322
-
323
1
  // Every connection setting has to be there: an unset one would otherwise reach
324
2
  // the server as an empty value and fail there, for a reason naming neither.
325
3
  let optionsFromEnvOrThrow = (): StorageClient.clickHouseOptions => {