envio 2.32.1 → 3.0.0-alpha-main-clickhouse-sink

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 (45) hide show
  1. package/index.d.ts +1 -0
  2. package/package.json +6 -5
  3. package/src/Batch.res +4 -4
  4. package/src/Change.res +9 -0
  5. package/src/Change.res.js +2 -0
  6. package/src/Config.res +5 -5
  7. package/src/Config.res.js +3 -1
  8. package/src/Envio.gen.ts +3 -3
  9. package/src/Envio.res +14 -3
  10. package/src/EventRegister.res +3 -11
  11. package/src/EventRegister.res.js +4 -8
  12. package/src/EventRegister.resi +1 -1
  13. package/src/InMemoryStore.gen.ts +6 -0
  14. package/src/InMemoryStore.res +149 -0
  15. package/src/InMemoryStore.res.js +161 -0
  16. package/src/InMemoryTable.res +50 -35
  17. package/src/InMemoryTable.res.js +52 -84
  18. package/src/Internal.gen.ts +0 -2
  19. package/src/Internal.res +20 -38
  20. package/src/Internal.res.js +2 -16
  21. package/src/LoadManager.res +23 -16
  22. package/src/LoadManager.res.js +17 -15
  23. package/src/Persistence.res +190 -38
  24. package/src/Persistence.res.js +92 -39
  25. package/src/PgStorage.res +700 -14
  26. package/src/PgStorage.res.js +431 -19
  27. package/src/Platform.res +141 -0
  28. package/src/Platform.res.js +170 -0
  29. package/src/Prometheus.res +41 -0
  30. package/src/Prometheus.res.js +45 -0
  31. package/src/SafeCheckpointTracking.res +5 -4
  32. package/src/Sink.res +47 -0
  33. package/src/Sink.res.js +36 -0
  34. package/src/Utils.res +2 -0
  35. package/src/Utils.res.js +3 -0
  36. package/src/bindings/ClickHouse.res +387 -0
  37. package/src/bindings/ClickHouse.res.js +274 -0
  38. package/src/bindings/Postgres.res +15 -0
  39. package/src/bindings/Promise.res +3 -0
  40. package/src/db/EntityHistory.res +33 -156
  41. package/src/db/EntityHistory.res.js +40 -115
  42. package/src/db/InternalTable.res +56 -55
  43. package/src/db/InternalTable.res.js +49 -52
  44. package/src/db/Table.res +86 -22
  45. package/src/db/Table.res.js +77 -10
@@ -29,13 +29,24 @@ type initialState = {
29
29
  cleanRun: bool,
30
30
  cache: dict<effectCacheRecord>,
31
31
  chains: array<initialChainState>,
32
- checkpointId: int,
32
+ checkpointId: Internal.checkpointId,
33
33
  // Needed to keep reorg detection logic between restarts
34
34
  reorgCheckpoints: array<Internal.reorgCheckpoint>,
35
35
  }
36
36
 
37
37
  type operator = [#">" | #"=" | #"<"]
38
38
 
39
+ type updatedEffectCache = {
40
+ effect: Internal.effect,
41
+ items: array<Internal.effectCacheItem>,
42
+ shouldInitialize: bool,
43
+ }
44
+
45
+ type updatedEntity = {
46
+ entityConfig: Internal.entityConfig,
47
+ updates: array<Internal.inMemoryStoreEntityUpdate<Internal.entity>>,
48
+ }
49
+
39
50
  type storage = {
40
51
  // Should return true if we already have persisted data
41
52
  // and we can skip initialization
@@ -45,7 +56,7 @@ type storage = {
45
56
  initialize: (
46
57
  ~chainConfigs: array<Config.chain>=?,
47
58
  ~entities: array<Internal.entityConfig>=?,
48
- ~enums: array<Internal.enumConfig<Internal.enum>>=?,
59
+ ~enums: array<Table.enumConfig<Table.enum>>=?,
49
60
  ) => promise<initialState>,
50
61
  resumeInitialState: unit => promise<initialState>,
51
62
  @raises("StorageError")
@@ -77,10 +88,53 @@ type storage = {
77
88
  ) => promise<unit>,
78
89
  // This is to download cache from the database to .envio/cache
79
90
  dumpEffectCache: unit => promise<unit>,
91
+ // Execute raw SQL query
92
+ executeUnsafe: string => promise<unknown>,
93
+ // Check if entity history has rows
94
+ hasEntityHistoryRows: unit => promise<bool>,
95
+ // Update chain metadata
96
+ setChainMeta: dict<InternalTable.Chains.metaFields> => promise<unknown>,
97
+ // Prune old checkpoints
98
+ pruneStaleCheckpoints: (~safeCheckpointId: Internal.checkpointId) => promise<unit>,
99
+ // Prune stale entity history
100
+ pruneStaleEntityHistory: (
101
+ ~entityName: string,
102
+ ~entityIndex: int,
103
+ ~safeCheckpointId: Internal.checkpointId,
104
+ ) => promise<unit>,
105
+ // Get rollback target checkpoint
106
+ getRollbackTargetCheckpoint: (
107
+ ~reorgChainId: int,
108
+ ~lastKnownValidBlockNumber: int,
109
+ ) => promise<array<{"id": Internal.checkpointId}>>,
110
+ // Get rollback progress diff
111
+ getRollbackProgressDiff: (
112
+ ~rollbackTargetCheckpointId: Internal.checkpointId,
113
+ ) => promise<
114
+ array<{
115
+ "chain_id": int,
116
+ "events_processed_diff": string,
117
+ "new_progress_block_number": int,
118
+ }>,
119
+ >,
120
+ // Get rollback data for entity
121
+ getRollbackData: (
122
+ ~entityConfig: Internal.entityConfig,
123
+ ~rollbackTargetCheckpointId: Internal.checkpointId,
124
+ ) => promise<(array<{"id": string}>, array<unknown>)>,
125
+ // Write batch to storage
126
+ writeBatch: (
127
+ ~batch: Batch.t,
128
+ ~rawEvents: array<InternalTable.RawEvents.t>,
129
+ ~rollbackTargetCheckpointId: option<Internal.checkpointId>,
130
+ ~isInReorgThreshold: bool,
131
+ ~config: Config.t,
132
+ ~allEntities: array<Internal.entityConfig>,
133
+ ~updatedEffectsCache: array<updatedEffectCache>,
134
+ ~updatedEntities: array<updatedEntity>,
135
+ ) => promise<unit>,
80
136
  }
81
137
 
82
- exception StorageError({message: string, reason: exn})
83
-
84
138
  type storageStatus =
85
139
  | Unknown
86
140
  | Initializing(promise<unit>)
@@ -89,38 +143,28 @@ type storageStatus =
89
143
  type t = {
90
144
  userEntities: array<Internal.entityConfig>,
91
145
  allEntities: array<Internal.entityConfig>,
92
- allEnums: array<Internal.enumConfig<Internal.enum>>,
146
+ allEnums: array<Table.enumConfig<Table.enum>>,
93
147
  mutable storageStatus: storageStatus,
94
148
  mutable storage: storage,
95
- // FIXME: This is temporary to move it library
96
- // Should be a part of the storage interface and db agnostic
97
- mutable sql: Postgres.sql,
98
149
  }
99
150
 
100
- let entityHistoryActionEnumConfig: Internal.enumConfig<EntityHistory.RowAction.t> = {
101
- name: EntityHistory.RowAction.name,
102
- variants: EntityHistory.RowAction.variants,
103
- schema: EntityHistory.RowAction.schema,
104
- default: SET,
105
- }
151
+ exception StorageError({message: string, reason: exn})
106
152
 
107
153
  let make = (
108
154
  ~userEntities,
109
155
  // TODO: Should only pass userEnums and create internal config in runtime
110
156
  ~allEnums,
111
157
  ~storage,
112
- ~sql,
113
158
  ) => {
114
159
  let allEntities = userEntities->Js.Array2.concat([InternalTable.DynamicContractRegistry.config])
115
160
  let allEnums =
116
- allEnums->Js.Array2.concat([entityHistoryActionEnumConfig->Internal.fromGenericEnumConfig])
161
+ allEnums->Js.Array2.concat([EntityHistory.RowAction.config->Table.fromGenericEnumConfig])
117
162
  {
118
163
  userEntities,
119
164
  allEntities,
120
165
  allEnums,
121
166
  storageStatus: Unknown,
122
167
  storage,
123
- sql,
124
168
  }
125
169
  }
126
170
 
@@ -197,32 +241,140 @@ let getInitializedState = persistence => {
197
241
  }
198
242
  }
199
243
 
200
- let setEffectCacheOrThrow = async (
244
+ let writeBatch = (
201
245
  persistence,
202
- ~effect: Internal.effect,
203
- ~items,
204
- ~invalidationsCount,
205
- ) => {
246
+ ~batch,
247
+ ~config,
248
+ ~inMemoryStore: InMemoryStore.t,
249
+ ~isInReorgThreshold,
250
+ ) =>
206
251
  switch persistence.storageStatus {
207
252
  | Unknown
208
253
  | Initializing(_) =>
209
254
  Js.Exn.raiseError(`Failed to access the indexer storage. The Persistence layer is not initialized.`)
210
- | Ready({cache}) => {
211
- let storage = persistence.storage
212
- let effectName = effect.name
213
- let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(effectName) {
214
- | Some(c) => c
215
- | None => {
216
- let c = {effectName, count: 0}
217
- cache->Js.Dict.set(effectName, c)
218
- c
219
- }
255
+ | Ready({cache}) =>
256
+ let updatedEntities = persistence.allEntities->Belt.Array.keepMapU(entityConfig => {
257
+ let updates =
258
+ inMemoryStore
259
+ ->InMemoryStore.getInMemTable(~entityConfig)
260
+ ->InMemoryTable.Entity.updates
261
+ if updates->Utils.Array.isEmpty {
262
+ None
263
+ } else {
264
+ Some({entityConfig, updates})
220
265
  }
221
- let initialize = effectCacheRecord.count === 0
222
- await storage.setEffectCacheOrThrow(~effect, ~items, ~initialize)
223
- effectCacheRecord.count =
224
- effectCacheRecord.count + items->Js.Array2.length - invalidationsCount
225
- Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
226
- }
266
+ })
267
+ persistence.storage.writeBatch(
268
+ ~batch,
269
+ ~rawEvents=inMemoryStore.rawEvents->InMemoryTable.values,
270
+ ~rollbackTargetCheckpointId=inMemoryStore.rollbackTargetCheckpointId,
271
+ ~isInReorgThreshold,
272
+ ~config,
273
+ ~allEntities=persistence.allEntities,
274
+ ~updatedEntities,
275
+ ~updatedEffectsCache={
276
+ inMemoryStore.effects
277
+ ->Js.Dict.keys
278
+ ->Belt.Array.keepMapU(effectName => {
279
+ let inMemTable = inMemoryStore.effects->Js.Dict.unsafeGet(effectName)
280
+ let {idsToStore, dict, effect, invalidationsCount} = inMemTable
281
+ switch idsToStore {
282
+ | [] => None
283
+ | ids => {
284
+ let items = Belt.Array.makeUninitializedUnsafe(ids->Belt.Array.length)
285
+ ids->Belt.Array.forEachWithIndex((index, id) => {
286
+ items->Js.Array2.unsafe_set(
287
+ index,
288
+ (
289
+ {
290
+ id,
291
+ output: dict->Js.Dict.unsafeGet(id),
292
+ }: Internal.effectCacheItem
293
+ ),
294
+ )
295
+ })
296
+ Some({
297
+ let effectName = effect.name
298
+ let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(
299
+ effectName,
300
+ ) {
301
+ | Some(c) => c
302
+ | None => {
303
+ let c = {effectName, count: 0}
304
+ cache->Js.Dict.set(effectName, c)
305
+ c
306
+ }
307
+ }
308
+ let shouldInitialize = effectCacheRecord.count === 0
309
+ effectCacheRecord.count =
310
+ effectCacheRecord.count + items->Js.Array2.length - invalidationsCount
311
+ Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
312
+ {effect, items, shouldInitialize}
313
+ })
314
+ }
315
+ }
316
+ })
317
+ },
318
+ )
319
+ }
320
+
321
+ let prepareRollbackDiff = async (
322
+ persistence: t,
323
+ ~rollbackTargetCheckpointId,
324
+ ~rollbackDiffCheckpointId,
325
+ ) => {
326
+ let inMemStore = InMemoryStore.make(
327
+ ~entities=persistence.allEntities,
328
+ ~rollbackTargetCheckpointId,
329
+ )
330
+
331
+ let deletedEntities = Js.Dict.empty()
332
+ let setEntities = Js.Dict.empty()
333
+
334
+ let _ =
335
+ await persistence.allEntities
336
+ ->Belt.Array.map(async entityConfig => {
337
+ let entityTable = inMemStore->InMemoryStore.getInMemTable(~entityConfig)
338
+
339
+ let (removedIdsResult, restoredEntitiesResult) = await persistence.storage.getRollbackData(
340
+ ~entityConfig,
341
+ ~rollbackTargetCheckpointId,
342
+ )
343
+
344
+ // Process removed IDs
345
+ removedIdsResult->Js.Array2.forEach(data => {
346
+ deletedEntities->Utils.Dict.push(entityConfig.name, data["id"])
347
+ entityTable->InMemoryTable.Entity.set(
348
+ Delete({
349
+ entityId: data["id"],
350
+ checkpointId: rollbackDiffCheckpointId,
351
+ }),
352
+ ~shouldSaveHistory=false,
353
+ ~containsRollbackDiffChange=true,
354
+ )
355
+ })
356
+
357
+ let restoredEntities = restoredEntitiesResult->S.parseOrThrow(entityConfig.rowsSchema)
358
+
359
+ // Process restored entities
360
+ restoredEntities->Belt.Array.forEach((entity: Internal.entity) => {
361
+ setEntities->Utils.Dict.push(entityConfig.name, entity.id)
362
+ entityTable->InMemoryTable.Entity.set(
363
+ Set({
364
+ entityId: entity.id,
365
+ checkpointId: rollbackDiffCheckpointId,
366
+ entity,
367
+ }),
368
+ ~shouldSaveHistory=false,
369
+ ~containsRollbackDiffChange=true,
370
+ )
371
+ })
372
+ })
373
+ ->Promise.all
374
+
375
+ {
376
+ "inMemStore": inMemStore,
377
+ "deletedEntities": deletedEntities,
378
+ "setEntities": setEntities,
227
379
  }
228
380
  }
@@ -1,40 +1,31 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
  'use strict';
3
3
 
4
+ var Utils = require("./Utils.res.js");
4
5
  var Js_exn = require("rescript/lib/js/js_exn.js");
5
6
  var Logging = require("./Logging.res.js");
7
+ var Belt_Array = require("rescript/lib/js/belt_Array.js");
6
8
  var Prometheus = require("./Prometheus.res.js");
7
9
  var EntityHistory = require("./db/EntityHistory.res.js");
8
10
  var ErrorHandling = require("./ErrorHandling.res.js");
11
+ var InMemoryStore = require("./InMemoryStore.res.js");
12
+ var InMemoryTable = require("./InMemoryTable.res.js");
9
13
  var InternalTable = require("./db/InternalTable.res.js");
10
14
  var Caml_exceptions = require("rescript/lib/js/caml_exceptions.js");
15
+ var S$RescriptSchema = require("rescript-schema/src/S.res.js");
11
16
  var Caml_js_exceptions = require("rescript/lib/js/caml_js_exceptions.js");
12
17
 
13
18
  var StorageError = /* @__PURE__ */Caml_exceptions.create("Persistence.StorageError");
14
19
 
15
- var entityHistoryActionEnumConfig_name = EntityHistory.RowAction.name;
16
-
17
- var entityHistoryActionEnumConfig_variants = EntityHistory.RowAction.variants;
18
-
19
- var entityHistoryActionEnumConfig_schema = EntityHistory.RowAction.schema;
20
-
21
- var entityHistoryActionEnumConfig = {
22
- name: entityHistoryActionEnumConfig_name,
23
- variants: entityHistoryActionEnumConfig_variants,
24
- schema: entityHistoryActionEnumConfig_schema,
25
- default: "SET"
26
- };
27
-
28
- function make(userEntities, allEnums, storage, sql) {
20
+ function make(userEntities, allEnums, storage) {
29
21
  var allEntities = userEntities.concat([InternalTable.DynamicContractRegistry.config]);
30
- var allEnums$1 = allEnums.concat([entityHistoryActionEnumConfig]);
22
+ var allEnums$1 = allEnums.concat([EntityHistory.RowAction.config]);
31
23
  return {
32
24
  userEntities: userEntities,
33
25
  allEntities: allEntities,
34
26
  allEnums: allEnums$1,
35
27
  storageStatus: "Unknown",
36
- storage: storage,
37
- sql: sql
28
+ storage: storage
38
29
  };
39
30
  }
40
31
 
@@ -120,7 +111,7 @@ function getInitializedState(persistence) {
120
111
  }
121
112
  }
122
113
 
123
- async function setEffectCacheOrThrow(persistence, effect, items, invalidationsCount) {
114
+ function writeBatch(persistence, batch, config, inMemoryStore, isInReorgThreshold) {
124
115
  var match = persistence.storageStatus;
125
116
  if (typeof match !== "object") {
126
117
  return Js_exn.raiseError("Failed to access the indexer storage. The Persistence layer is not initialized.");
@@ -129,31 +120,93 @@ async function setEffectCacheOrThrow(persistence, effect, items, invalidationsCo
129
120
  return Js_exn.raiseError("Failed to access the indexer storage. The Persistence layer is not initialized.");
130
121
  }
131
122
  var cache = match._0.cache;
132
- var storage = persistence.storage;
133
- var effectName = effect.name;
134
- var c = cache[effectName];
135
- var effectCacheRecord;
136
- if (c !== undefined) {
137
- effectCacheRecord = c;
138
- } else {
139
- var c$1 = {
140
- effectName: effectName,
141
- count: 0
142
- };
143
- cache[effectName] = c$1;
144
- effectCacheRecord = c$1;
145
- }
146
- var initialize = effectCacheRecord.count === 0;
147
- await storage.setEffectCacheOrThrow(effect, items, initialize);
148
- effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
149
- return Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
123
+ var updatedEntities = Belt_Array.keepMapU(persistence.allEntities, (function (entityConfig) {
124
+ var updates = InMemoryTable.Entity.updates(InMemoryStore.getInMemTable(inMemoryStore, entityConfig));
125
+ if (Utils.$$Array.isEmpty(updates)) {
126
+ return ;
127
+ } else {
128
+ return {
129
+ entityConfig: entityConfig,
130
+ updates: updates
131
+ };
132
+ }
133
+ }));
134
+ return persistence.storage.writeBatch(batch, InMemoryTable.values(inMemoryStore.rawEvents), inMemoryStore.rollbackTargetCheckpointId, isInReorgThreshold, config, persistence.allEntities, Belt_Array.keepMapU(Object.keys(inMemoryStore.effects), (function (effectName) {
135
+ var inMemTable = inMemoryStore.effects[effectName];
136
+ var idsToStore = inMemTable.idsToStore;
137
+ var invalidationsCount = inMemTable.invalidationsCount;
138
+ var effect = inMemTable.effect;
139
+ var dict = inMemTable.dict;
140
+ if (idsToStore.length === 0) {
141
+ return ;
142
+ }
143
+ var items = new Array(idsToStore.length);
144
+ Belt_Array.forEachWithIndex(idsToStore, (function (index, id) {
145
+ items[index] = {
146
+ id: id,
147
+ output: dict[id]
148
+ };
149
+ }));
150
+ var effectName$1 = effect.name;
151
+ var c = cache[effectName$1];
152
+ var effectCacheRecord;
153
+ if (c !== undefined) {
154
+ effectCacheRecord = c;
155
+ } else {
156
+ var c$1 = {
157
+ effectName: effectName$1,
158
+ count: 0
159
+ };
160
+ cache[effectName$1] = c$1;
161
+ effectCacheRecord = c$1;
162
+ }
163
+ var shouldInitialize = effectCacheRecord.count === 0;
164
+ return effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0, Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName$1), {
165
+ effect: effect,
166
+ items: items,
167
+ shouldInitialize: shouldInitialize
168
+ };
169
+ })), updatedEntities);
170
+ }
171
+
172
+ async function prepareRollbackDiff(persistence, rollbackTargetCheckpointId, rollbackDiffCheckpointId) {
173
+ var inMemStore = InMemoryStore.make(persistence.allEntities, rollbackTargetCheckpointId);
174
+ var deletedEntities = {};
175
+ var setEntities = {};
176
+ await Promise.all(Belt_Array.map(persistence.allEntities, (async function (entityConfig) {
177
+ var entityTable = InMemoryStore.getInMemTable(inMemStore, entityConfig);
178
+ var match = await persistence.storage.getRollbackData(entityConfig, rollbackTargetCheckpointId);
179
+ match[0].forEach(function (data) {
180
+ Utils.Dict.push(deletedEntities, entityConfig.name, data.id);
181
+ InMemoryTable.Entity.set(entityTable, {
182
+ type: "DELETE",
183
+ entityId: data.id,
184
+ checkpointId: rollbackDiffCheckpointId
185
+ }, false, true);
186
+ });
187
+ var restoredEntities = S$RescriptSchema.parseOrThrow(match[1], entityConfig.rowsSchema);
188
+ return Belt_Array.forEach(restoredEntities, (function (entity) {
189
+ Utils.Dict.push(setEntities, entityConfig.name, entity.id);
190
+ InMemoryTable.Entity.set(entityTable, {
191
+ type: "SET",
192
+ entityId: entity.id,
193
+ entity: entity,
194
+ checkpointId: rollbackDiffCheckpointId
195
+ }, false, true);
196
+ }));
197
+ })));
198
+ return {
199
+ inMemStore: inMemStore,
200
+ deletedEntities: deletedEntities,
201
+ setEntities: setEntities
202
+ };
150
203
  }
151
204
 
152
205
  exports.StorageError = StorageError;
153
- exports.entityHistoryActionEnumConfig = entityHistoryActionEnumConfig;
154
206
  exports.make = make;
155
207
  exports.init = init;
156
208
  exports.getInitializedStorageOrThrow = getInitializedStorageOrThrow;
157
209
  exports.getInitializedState = getInitializedState;
158
- exports.setEffectCacheOrThrow = setEffectCacheOrThrow;
159
- /* Logging Not a pure module */
210
+ exports.writeBatch = writeBatch;
211
+ exports.prepareRollbackDiff = prepareRollbackDiff;
212
+ /* Utils Not a pure module */