envio 3.3.0 → 3.3.1-rc.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 (54) hide show
  1. package/package.json +6 -7
  2. package/src/ChainFetching.res +4 -32
  3. package/src/ChainFetching.res.mjs +2 -12
  4. package/src/ChainState.res +73 -53
  5. package/src/ChainState.res.mjs +61 -30
  6. package/src/ChainState.resi +13 -17
  7. package/src/Config.res +5 -0
  8. package/src/Config.res.mjs +1 -0
  9. package/src/CrossChainState.res +2 -8
  10. package/src/CrossChainState.res.mjs +6 -8
  11. package/src/CrossChainState.resi +1 -0
  12. package/src/EffectState.res +183 -44
  13. package/src/EffectState.res.mjs +134 -26
  14. package/src/EffectState.resi +26 -3
  15. package/src/EventProcessing.res +14 -9
  16. package/src/EventProcessing.res.mjs +7 -7
  17. package/src/FetchState.res +18 -29
  18. package/src/FetchState.res.mjs +20 -37
  19. package/src/IndexerState.res +280 -31
  20. package/src/IndexerState.res.mjs +234 -25
  21. package/src/IndexerState.resi +22 -0
  22. package/src/LoadLayer.res +19 -65
  23. package/src/LoadLayer.res.mjs +13 -55
  24. package/src/Main.res +42 -23
  25. package/src/Main.res.mjs +32 -35
  26. package/src/Metrics.res +943 -66
  27. package/src/Metrics.res.mjs +367 -50
  28. package/src/Persistence.res +3 -0
  29. package/src/PgStorage.res +3 -8
  30. package/src/PgStorage.res.mjs +3 -4
  31. package/src/PruneStaleHistory.res +1 -1
  32. package/src/PruneStaleHistory.res.mjs +1 -2
  33. package/src/Rollback.res +4 -5
  34. package/src/Rollback.res.mjs +2 -3
  35. package/src/SimulateItems.res.mjs +3 -21
  36. package/src/TestIndexer.res.mjs +4 -23
  37. package/src/TestIndexerProxyStorage.res +1 -0
  38. package/src/TestIndexerProxyStorage.res.mjs +1 -1
  39. package/src/Writing.res +3 -5
  40. package/src/Writing.res.mjs +3 -6
  41. package/src/bindings/ClickHouse.res +101 -17
  42. package/src/bindings/ClickHouse.res.mjs +58 -19
  43. package/src/bindings/NodeJs.res +64 -0
  44. package/src/bindings/NodeJs.res.mjs +6 -0
  45. package/src/sources/HyperSyncClient.res +5 -11
  46. package/src/sources/SourceManager.res +72 -27
  47. package/src/sources/SourceManager.res.mjs +81 -11
  48. package/src/sources/SourceManager.resi +14 -0
  49. package/src/tui/Tui.res +1 -1
  50. package/src/tui/Tui.res.mjs +2 -2
  51. package/src/Prometheus.res +0 -789
  52. package/src/Prometheus.res.mjs +0 -847
  53. package/src/bindings/PromClient.res +0 -72
  54. package/src/bindings/PromClient.res.mjs +0 -38
@@ -614,31 +614,12 @@ function initTestWorker() {
614
614
  }
615
615
  let patchConfig = (config, registrationsByChainId) => {
616
616
  let config$1 = SimulateItems.patchConfig(config, processConfig, registrationsByChainId);
617
- if (exitAfterFirstEventBlock) {
618
- return {
619
- name: config$1.name,
620
- description: config$1.description,
621
- handlers: config$1.handlers,
622
- contractHandlers: config$1.contractHandlers,
623
- shouldRollbackOnReorg: config$1.shouldRollbackOnReorg,
624
- shouldSaveFullHistory: config$1.shouldSaveFullHistory,
625
- storage: config$1.storage,
626
- chainMap: config$1.chainMap,
627
- defaultChain: config$1.defaultChain,
628
- ecosystem: config$1.ecosystem,
629
- enableRawEvents: config$1.enableRawEvents,
630
- maxAddrInPartition: config$1.maxAddrInPartition,
631
- batchSize: 1,
632
- lowercaseAddresses: config$1.lowercaseAddresses,
633
- isDev: config$1.isDev,
634
- userEntitiesByName: config$1.userEntitiesByName,
635
- userEntities: config$1.userEntities,
636
- allEntities: config$1.allEntities,
637
- allEnums: config$1.allEnums
638
- };
639
- } else {
617
+ if (!exitAfterFirstEventBlock) {
640
618
  return config$1;
641
619
  }
620
+ let newrecord = {...config$1};
621
+ newrecord.batchSize = 1;
622
+ return newrecord;
642
623
  };
643
624
  Stdlib_Promise.$$catch(Main.start(persistence, undefined, true, exitAfterFirstEventBlock, patchConfig), exn => {
644
625
  let toThrow = exn.RE_EXN_ID === Main.FatalError ? exn._1 : Utils.prettifyExn(exn);
@@ -138,6 +138,7 @@ let makeStorage = (proxy: t): Persistence.storage => {
138
138
  ~updatedEffectsCache as _,
139
139
  ~updatedEntities,
140
140
  ~chainMetaData as _,
141
+ ~onWrite as _,
141
142
  ) => {
142
143
  // Encode entities to JSON for serialization across worker boundary
143
144
  let serializableEntities = updatedEntities->Array.map((
@@ -74,7 +74,7 @@ function makeStorage(proxy) {
74
74
  getRollbackTargetCheckpoint: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
75
75
  getRollbackProgressDiff: async param => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
76
76
  getRollbackData: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. Set rollbackOnReorg to false in config."),
77
- writeBatch: async (batch, param, param$1, param$2, param$3, param$4, updatedEntities, param$5) => {
77
+ writeBatch: async (batch, param, param$1, param$2, param$3, param$4, updatedEntities, param$5, param$6) => {
78
78
  let serializableEntities = updatedEntities.map(param => {
79
79
  let entityConfig = param.entityConfig;
80
80
  let encodeChange = change => {
package/src/Writing.res CHANGED
@@ -61,11 +61,7 @@ let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updated
61
61
  }
62
62
  let shouldInitialize = effectCacheRecord.count === 0
63
63
  effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
64
- Prometheus.EffectCacheCount.set(
65
- ~count=effectCacheRecord.count,
66
- ~effectName,
67
- ~scope=scope->Internal.EffectCache.scopeToString,
68
- )
64
+ inMemTable->EffectState.commitCacheCount(~count=effectCacheRecord.count)
69
65
  acc
70
66
  ->Array.push(
71
67
  (
@@ -155,6 +151,8 @@ let runOneWrite = async (state: IndexerState.t) => {
155
151
  ~updatedEntities,
156
152
  ~updatedEffectsCache,
157
153
  ~chainMetaData,
154
+ ~onWrite=(~storage, ~timeSeconds) =>
155
+ state->IndexerState.recordStorageWrite(~storage, ~timeSeconds),
158
156
  ),
159
157
  PruneStaleHistory.runConcurrent(state, ~targets=pruneTargets),
160
158
  ))
@@ -2,9 +2,7 @@
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
4
  import * as Utils from "./Utils.res.mjs";
5
- import * as Internal from "./Internal.res.mjs";
6
5
  import * as Throttler from "./Throttler.res.mjs";
7
- import * as Prometheus from "./Prometheus.res.mjs";
8
6
  import * as EffectState from "./EffectState.res.mjs";
9
7
  import * as IndexerState from "./IndexerState.res.mjs";
10
8
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -43,7 +41,6 @@ function snapshotEffects(state, cache) {
43
41
  let invalidationsCount = inMemTable.invalidationsCount;
44
42
  let dict = inMemTable.dict;
45
43
  let table = inMemTable.table;
46
- let scope = inMemTable.scope;
47
44
  let effect = inMemTable.effect;
48
45
  if (idsToStore.length !== 0) {
49
46
  let items = Stdlib_Array.filterMap(idsToStore, id => {
@@ -64,7 +61,7 @@ function snapshotEffects(state, cache) {
64
61
  } else {
65
62
  let c$1 = {
66
63
  effectName: effectName,
67
- scope: scope,
64
+ scope: inMemTable.scope,
68
65
  tableName: tableName,
69
66
  count: 0
70
67
  };
@@ -73,7 +70,7 @@ function snapshotEffects(state, cache) {
73
70
  }
74
71
  let shouldInitialize = effectCacheRecord.count === 0;
75
72
  effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
76
- Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName, Internal.EffectCache.scopeToString(scope));
73
+ EffectState.commitCacheCount(inMemTable, effectCacheRecord.count);
77
74
  acc.push({
78
75
  table: table,
79
76
  itemSchema: effect.storageMeta.itemSchema,
@@ -126,7 +123,7 @@ async function runOneWrite(state) {
126
123
  });
127
124
  let pruneTargets = PruneStaleHistory.select(state, writtenEntityNames, Stdlib_Option.isSome(rollback));
128
125
  await Promise.all([
129
- persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData),
126
+ persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData, (storage, timeSeconds) => IndexerState.recordStorageWrite(state, storage, timeSeconds)),
130
127
  PruneStaleHistory.runConcurrent(state, pruneTargets)
131
128
  ]);
132
129
  IndexerState.markCommitted(state, upToCheckpointId);
@@ -307,11 +307,34 @@ let setUpdatesOrThrow = async (
307
307
  }
308
308
  }
309
309
 
310
+ // A plain database created with ON CLUSTER doesn't turn subsequent DDL into
311
+ // cluster-wide statements; ClickHouse keeps no "this database is clustered"
312
+ // flag. Without a Replicated database engine, every CREATE must carry its own
313
+ // ON CLUSTER to reach all replicas, otherwise it runs only on the connected
314
+ // node. With a Replicated database engine the DDL propagates via the database's
315
+ // own log, and combining it with ON CLUSTER is rejected/double-applied — so
316
+ // table-level DDL must carry the clause only in the plain-database case.
317
+ // The '{cluster}' macro resolves to each node's configured cluster name.
318
+ let onClusterClause = (~onCluster: bool) => onCluster ? ` ON CLUSTER '{cluster}'` : ""
319
+
320
+ // Strip both engine arguments `(...)` and a trailing `SETTINGS ...` clause to
321
+ // get the bare engine name, e.g. `Replicated('/p','{shard}','{replica}') SETTINGS x=1`
322
+ // and `Replicated SETTINGS x=1` both yield `Replicated`.
323
+ let databaseEngineName = (engineSpec: string) =>
324
+ engineSpec
325
+ ->String.trim
326
+ ->String.split("(")
327
+ ->Array.getUnsafe(0)
328
+ ->String.split(" ")
329
+ ->Array.getUnsafe(0)
330
+ ->String.trim
331
+
310
332
  // Generate CREATE TABLE query for entity history table
311
333
  let makeCreateHistoryTableQuery = (
312
334
  ~entityConfig: Internal.entityConfig,
313
335
  ~database: string,
314
336
  ~replicated: bool=false,
337
+ ~onCluster: bool=false,
315
338
  ) => {
316
339
  let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()"
317
340
  let fieldDefinitions = entityConfig.table.fields->Array.filterMap(field => {
@@ -333,7 +356,7 @@ let makeCreateHistoryTableQuery = (
333
356
  `CREATE TABLE IF NOT EXISTS ${database}.\`${EntityHistory.historyTableName(
334
357
  ~entityName=entityConfig.name,
335
358
  ~entityIndex=entityConfig.index,
336
- )}\` (
359
+ )}\`${onClusterClause(~onCluster)} (
337
360
  ${fieldDefinitions->Array.joinUnsafe(",\n ")},
338
361
  \`${EntityHistory.checkpointIdFieldName}\` ${getClickHouseFieldType(
339
362
  ~fieldType=UInt64,
@@ -351,7 +374,11 @@ ORDER BY (${Table.idFieldName}, ${EntityHistory.checkpointIdFieldName})`
351
374
  }
352
375
 
353
376
  // Generate CREATE TABLE query for checkpoints
354
- let makeCreateCheckpointsTableQuery = (~database: string, ~replicated: bool=false) => {
377
+ let makeCreateCheckpointsTableQuery = (
378
+ ~database: string,
379
+ ~replicated: bool=false,
380
+ ~onCluster: bool=false,
381
+ ) => {
355
382
  let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()"
356
383
  let idField = (#id: InternalTable.Checkpoints.field :> string)
357
384
  let chainIdField = (#chain_id: InternalTable.Checkpoints.field :> string)
@@ -359,7 +386,9 @@ let makeCreateCheckpointsTableQuery = (~database: string, ~replicated: bool=fals
359
386
  let blockHashField = (#block_hash: InternalTable.Checkpoints.field :> string)
360
387
  let eventsProcessedField = (#events_processed: InternalTable.Checkpoints.field :> string)
361
388
 
362
- `CREATE TABLE IF NOT EXISTS ${database}.\`${InternalTable.Checkpoints.table.tableName}\` (
389
+ `CREATE TABLE IF NOT EXISTS ${database}.\`${InternalTable.Checkpoints.table.tableName}\`${onClusterClause(
390
+ ~onCluster,
391
+ )} (
363
392
  \`${idField}\` ${getClickHouseFieldType(~fieldType=UInt64, ~isNullable=false, ~isArray=false)},
364
393
  \`${chainIdField}\` ${getClickHouseFieldType(
365
394
  ~fieldType=Int32,
@@ -387,7 +416,11 @@ ORDER BY (${idField})`
387
416
  }
388
417
 
389
418
  // Generate CREATE VIEW query for entity current state
390
- let makeCreateViewQuery = (~entityConfig: Internal.entityConfig, ~database: string) => {
419
+ let makeCreateViewQuery = (
420
+ ~entityConfig: Internal.entityConfig,
421
+ ~database: string,
422
+ ~onCluster: bool=false,
423
+ ) => {
391
424
  let historyTableName = EntityHistory.historyTableName(
392
425
  ~entityName=entityConfig.name,
393
426
  ~entityIndex=entityConfig.index,
@@ -409,7 +442,7 @@ let makeCreateViewQuery = (~entityConfig: Internal.entityConfig, ~database: stri
409
442
  })
410
443
  ->Array.joinUnsafe(", ")
411
444
 
412
- `CREATE VIEW IF NOT EXISTS ${database}.\`${entityConfig.name}\` AS
445
+ `CREATE VIEW IF NOT EXISTS ${database}.\`${entityConfig.name}\`${onClusterClause(~onCluster)} AS
413
446
  SELECT ${entityFields}
414
447
  FROM (
415
448
  SELECT ${entityFields}, \`${EntityHistory.changeFieldName}\`
@@ -429,20 +462,33 @@ let initialize = async (
429
462
  ~enums as _: array<Table.enumConfig<Table.enum>>,
430
463
  ) => {
431
464
  try {
432
- let replicated = Env.ClickHouse.replicated()
433
465
  let databaseEngine = Env.ClickHouse.databaseEngine()
434
466
  let databaseEngineClause = switch databaseEngine {
435
467
  | Some(engine) => ` ENGINE = ${engine}`
436
468
  | None => ""
437
469
  }
438
- // Replicated database engine requires CREATE DATABASE to run on every
439
- // replica; ON CLUSTER fans the DDL out via Keeper. The '{cluster}' macro
440
- // resolves to each node's configured cluster name.
441
- let onClusterClause = replicated ? ` ON CLUSTER '{cluster}'` : ""
470
+ let hasReplicatedDatabaseEngine = switch databaseEngine {
471
+ | Some(engine) => engine->databaseEngineName === "Replicated"
472
+ | None => false
473
+ }
474
+ let envReplicated = Env.ClickHouse.replicated()
475
+ // A Replicated database engine only replicates data when its tables use the
476
+ // ReplicatedMergeTree engine, so it implies replicated mode even when
477
+ // ENVIO_CLICKHOUSE_REPLICATED is unset.
478
+ let replicated = envReplicated || hasReplicatedDatabaseEngine
479
+ if hasReplicatedDatabaseEngine && !envReplicated {
480
+ Logging.info(
481
+ "ENVIO_CLICKHOUSE_DATABASE_ENGINE is Replicated; enabling replicated mode so tables use the ReplicatedMergeTree engine.",
482
+ )
483
+ }
484
+ let databaseOnClusterClause = onClusterClause(~onCluster=replicated)
485
+ // DDL that a Replicated database engine propagates itself must not carry
486
+ // ON CLUSTER on top of it — the clause is only for the plain-database case.
487
+ let ddlOnCluster = replicated && !hasReplicatedDatabaseEngine
442
488
 
443
489
  switch databaseEngine {
444
490
  | Some(engineSpec) => {
445
- let expectedEngineName = engineSpec->String.split("(")->Array.getUnsafe(0)->String.trim
491
+ let expectedEngineName = engineSpec->databaseEngineName
446
492
  let existingResult = await client->query({
447
493
  query: `SELECT engine FROM system.databases WHERE name = '${database}'`,
448
494
  })
@@ -458,22 +504,60 @@ let initialize = async (
458
504
  | None => ()
459
505
  }
460
506
 
461
- await client->exec({query: `TRUNCATE DATABASE IF EXISTS ${database}${onClusterClause}`})
507
+ if hasReplicatedDatabaseEngine {
508
+ // TRUNCATE DATABASE is unsupported on Replicated databases, so a reset
509
+ // has to DROP and recreate instead (plain databases keep the TRUNCATE
510
+ // fallback below). This requires the ClickHouse user to hold the DROP
511
+ // privilege; without it the reset fails here with ACCESS_DENIED. ON
512
+ // CLUSTER removes the database from every node — the engine's own log
513
+ // can't replicate the drop of the database it lives in — and SYNC waits
514
+ // for the drop to finish before the CREATE below.
515
+ await client->exec({
516
+ query: `DROP DATABASE IF EXISTS ${database} ON CLUSTER '{cluster}' SYNC`,
517
+ })
518
+ } else {
519
+ await client->exec({
520
+ query: `TRUNCATE DATABASE IF EXISTS ${database}${onClusterClause(~onCluster=ddlOnCluster)}`,
521
+ })
522
+ }
462
523
  await client->exec({
463
- query: `CREATE DATABASE IF NOT EXISTS ${database}${onClusterClause}${databaseEngineClause}`,
524
+ query: `CREATE DATABASE IF NOT EXISTS ${database}${databaseOnClusterClause}${databaseEngineClause}`,
464
525
  })
465
- await client->exec({query: `USE ${database}`})
466
526
 
467
527
  await Promise.all(
468
528
  entities->Array.map(entityConfig =>
469
- client->exec({query: makeCreateHistoryTableQuery(~entityConfig, ~database, ~replicated)})
529
+ client->exec({
530
+ query: makeCreateHistoryTableQuery(
531
+ ~entityConfig,
532
+ ~database,
533
+ ~replicated,
534
+ ~onCluster=ddlOnCluster,
535
+ ),
536
+ })
470
537
  ),
471
538
  )->Utils.Promise.ignoreValue
472
- await client->exec({query: makeCreateCheckpointsTableQuery(~database, ~replicated)})
539
+ await client->exec({
540
+ query: makeCreateCheckpointsTableQuery(~database, ~replicated, ~onCluster=ddlOnCluster),
541
+ })
542
+
543
+ // The client pools HTTP connections, so consecutive statements may reach
544
+ // different replicas, while a Replicated database applies DDL from its
545
+ // Keeper log asynchronously. A CREATE VIEW is analyzed against the node's
546
+ // local metadata and can land on a replica that hasn't applied the table
547
+ // creates yet, failing with UNKNOWN_TABLE. Block until every replica has
548
+ // caught up before creating the views. ON CLUSTER must precede the
549
+ // database name in this command's grammar.
550
+ if hasReplicatedDatabaseEngine {
551
+ await client->exec({
552
+ query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ${database}`,
553
+ })
554
+ }
473
555
 
474
556
  await Promise.all(
475
557
  entities->Array.map(entityConfig =>
476
- client->exec({query: makeCreateViewQuery(~entityConfig, ~database)})
558
+ client->exec({
559
+ query: makeCreateViewQuery(~entityConfig, ~database, ~onCluster=ddlOnCluster),
560
+ })
477
561
  ),
478
562
  )->Utils.Promise.ignoreValue
479
563
 
@@ -235,8 +235,21 @@ async function setUpdatesOrThrow(client, cache, changes, entityConfig, database)
235
235
  }
236
236
  }
237
237
 
238
- function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt) {
238
+ function onClusterClause(onCluster) {
239
+ if (onCluster) {
240
+ return ` ON CLUSTER '{cluster}'`;
241
+ } else {
242
+ return "";
243
+ }
244
+ }
245
+
246
+ function databaseEngineName(engineSpec) {
247
+ return engineSpec.trim().split("(")[0].split(" ")[0].trim();
248
+ }
249
+
250
+ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onClusterOpt) {
239
251
  let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
252
+ let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
240
253
  let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
241
254
  let fieldDefinitions = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
242
255
  if (field.TAG !== "Field") {
@@ -247,7 +260,9 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt) {
247
260
  let clickHouseType = getClickHouseFieldType(field$1.fieldType, field$1.isNullable, field$1.isArray);
248
261
  return `\`` + fieldName + `\` ` + clickHouseType;
249
262
  });
250
- return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\` (
263
+ return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\`` + (
264
+ onCluster ? ` ON CLUSTER '{cluster}'` : ""
265
+ ) + ` (
251
266
  ` + fieldDefinitions.join(",\n ") + `,
252
267
  \`` + EntityHistory.checkpointIdFieldName + `\` ` + getClickHouseFieldType("UInt64", false, false) + `,
253
268
  \`` + EntityHistory.changeFieldName + `\` ` + getClickHouseFieldType({
@@ -259,10 +274,13 @@ ENGINE = ` + tableEngine + `
259
274
  ORDER BY (` + Table.idFieldName + `, ` + EntityHistory.checkpointIdFieldName + `)`;
260
275
  }
261
276
 
262
- function makeCreateCheckpointsTableQuery(database, replicatedOpt) {
277
+ function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt) {
263
278
  let replicated = replicatedOpt !== undefined ? replicatedOpt : false;
279
+ let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
264
280
  let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()";
265
- return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\` (
281
+ return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + InternalTable.Checkpoints.table.tableName + `\`` + (
282
+ onCluster ? ` ON CLUSTER '{cluster}'` : ""
283
+ ) + ` (
266
284
  \`` + "id" + `\` ` + getClickHouseFieldType("UInt64", false, false) + `,
267
285
  \`` + "chain_id" + `\` ` + getClickHouseFieldType("Int32", false, false) + `,
268
286
  \`` + "block_number" + `\` ` + getClickHouseFieldType("Int32", false, false) + `,
@@ -273,7 +291,8 @@ ENGINE = ` + tableEngine + `
273
291
  ORDER BY (` + "id" + `)`;
274
292
  }
275
293
 
276
- function makeCreateViewQuery(entityConfig, database) {
294
+ function makeCreateViewQuery(entityConfig, database, onClusterOpt) {
295
+ let onCluster = onClusterOpt !== undefined ? onClusterOpt : false;
277
296
  let historyTableName = EntityHistory.historyTableName(entityConfig.name, entityConfig.index);
278
297
  let checkpointsTableName = InternalTable.Checkpoints.table.tableName;
279
298
  let entityFields = Stdlib_Array.filterMap(entityConfig.table.fields, field => {
@@ -283,7 +302,9 @@ function makeCreateViewQuery(entityConfig, database) {
283
302
  let fieldName = Table.getClickHouseDbFieldName(field._0);
284
303
  return `\`` + fieldName + `\``;
285
304
  }).join(", ");
286
- return `CREATE VIEW IF NOT EXISTS ` + database + `.\`` + entityConfig.name + `\` AS
305
+ return `CREATE VIEW IF NOT EXISTS ` + database + `.\`` + entityConfig.name + `\`` + (
306
+ onCluster ? ` ON CLUSTER '{cluster}'` : ""
307
+ ) + ` AS
287
308
  SELECT ` + entityFields + `
288
309
  FROM (
289
310
  SELECT ` + entityFields + `, \`` + EntityHistory.changeFieldName + `\`
@@ -297,12 +318,18 @@ WHERE \`` + EntityHistory.changeFieldName + `\` = '` + "SET" + `'`;
297
318
 
298
319
  async function initialize(client, database, entities, param) {
299
320
  try {
300
- let replicated = Env.ClickHouse.replicated();
301
321
  let databaseEngine = Env.ClickHouse.databaseEngine();
302
322
  let databaseEngineClause = databaseEngine !== undefined ? ` ENGINE = ` + databaseEngine : "";
303
- let onClusterClause = replicated ? ` ON CLUSTER '{cluster}'` : "";
323
+ let hasReplicatedDatabaseEngine = databaseEngine !== undefined ? databaseEngineName(databaseEngine) === "Replicated" : false;
324
+ let envReplicated = Env.ClickHouse.replicated();
325
+ let replicated = envReplicated || hasReplicatedDatabaseEngine;
326
+ if (hasReplicatedDatabaseEngine && !envReplicated) {
327
+ Logging.info("ENVIO_CLICKHOUSE_DATABASE_ENGINE is Replicated; enabling replicated mode so tables use the ReplicatedMergeTree engine.");
328
+ }
329
+ let databaseOnClusterClause = replicated ? ` ON CLUSTER '{cluster}'` : "";
330
+ let ddlOnCluster = replicated && !hasReplicatedDatabaseEngine;
304
331
  if (databaseEngine !== undefined) {
305
- let expectedEngineName = databaseEngine.split("(")[0].trim();
332
+ let expectedEngineName = databaseEngineName(databaseEngine);
306
333
  let existingResult = await client.query({
307
334
  query: `SELECT engine FROM system.databases WHERE name = '` + database + `'`
308
335
  });
@@ -315,23 +342,33 @@ async function initialize(client, database, entities, param) {
315
342
  }
316
343
  }
317
344
  }
345
+ if (hasReplicatedDatabaseEngine) {
346
+ await client.exec({
347
+ query: `DROP DATABASE IF EXISTS ` + database + ` ON CLUSTER '{cluster}' SYNC`
348
+ });
349
+ } else {
350
+ await client.exec({
351
+ query: `TRUNCATE DATABASE IF EXISTS ` + database + (
352
+ ddlOnCluster ? ` ON CLUSTER '{cluster}'` : ""
353
+ )
354
+ });
355
+ }
318
356
  await client.exec({
319
- query: `TRUNCATE DATABASE IF EXISTS ` + database + onClusterClause
320
- });
321
- await client.exec({
322
- query: `CREATE DATABASE IF NOT EXISTS ` + database + onClusterClause + databaseEngineClause
323
- });
324
- await client.exec({
325
- query: `USE ` + database
357
+ query: `CREATE DATABASE IF NOT EXISTS ` + database + databaseOnClusterClause + databaseEngineClause
326
358
  });
327
359
  await Promise.all(entities.map(entityConfig => client.exec({
328
- query: makeCreateHistoryTableQuery(entityConfig, database, replicated)
360
+ query: makeCreateHistoryTableQuery(entityConfig, database, replicated, ddlOnCluster)
329
361
  })));
330
362
  await client.exec({
331
- query: makeCreateCheckpointsTableQuery(database, replicated)
363
+ query: makeCreateCheckpointsTableQuery(database, replicated, ddlOnCluster)
332
364
  });
365
+ if (hasReplicatedDatabaseEngine) {
366
+ await client.exec({
367
+ query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ` + database
368
+ });
369
+ }
333
370
  await Promise.all(entities.map(entityConfig => client.exec({
334
- query: makeCreateViewQuery(entityConfig, database)
371
+ query: makeCreateViewQuery(entityConfig, database, ddlOnCluster)
335
372
  })));
336
373
  return Logging.trace("ClickHouse storage initialization completed successfully");
337
374
  } catch (raw_exn) {
@@ -382,6 +419,8 @@ export {
382
419
  insertWithRetry,
383
420
  setCheckpointsOrThrow,
384
421
  setUpdatesOrThrow,
422
+ onClusterClause,
423
+ databaseEngineName,
385
424
  makeCreateHistoryTableQuery,
386
425
  makeCreateCheckpointsTableQuery,
387
426
  makeCreateViewQuery,
@@ -46,6 +46,70 @@ module Process = {
46
46
  @module external process: t = "process"
47
47
  @module("process") external cwd: unit => string = "cwd"
48
48
  @get external execPath: t => string = "execPath"
49
+
50
+ type memoryUsage = {
51
+ rss: float,
52
+ heapTotal: float,
53
+ heapUsed: float,
54
+ @as("external") external_: float,
55
+ arrayBuffers: float,
56
+ }
57
+ @module("process") external memoryUsage: unit => memoryUsage = "memoryUsage"
58
+
59
+ // Microseconds since process start.
60
+ type cpuUsage = {user: float, system: float}
61
+ @module("process") external cpuUsage: unit => cpuUsage = "cpuUsage"
62
+
63
+ @module("process") external uptime: unit => float = "uptime"
64
+ @module("process") external version: string = "version"
65
+ @module("process")
66
+ external getActiveResourcesInfo: unit => array<string> = "getActiveResourcesInfo"
67
+ }
68
+
69
+ module V8 = {
70
+ type heapSpaceStatistics = {
71
+ @as("space_name") spaceName: string,
72
+ @as("space_size") spaceSize: float,
73
+ @as("space_used_size") spaceUsedSize: float,
74
+ @as("space_available_size") spaceAvailableSize: float,
75
+ }
76
+ @module("v8")
77
+ external getHeapSpaceStatistics: unit => array<heapSpaceStatistics> = "getHeapSpaceStatistics"
78
+ }
79
+
80
+ module PerfHooks = {
81
+ type eventLoopUtilization = {idle: float, active: float, utilization: float}
82
+ type performance
83
+ @module("perf_hooks") external performance: performance = "performance"
84
+ @send external eventLoopUtilization: performance => eventLoopUtilization = "eventLoopUtilization"
85
+
86
+ // Sampled event-loop delay histogram; values are nanoseconds.
87
+ type intervalHistogram = {
88
+ mean: float,
89
+ min: float,
90
+ max: float,
91
+ stddev: float,
92
+ }
93
+ type monitorOptions = {resolution?: int}
94
+ @module("perf_hooks")
95
+ external monitorEventLoopDelay: (~options: monitorOptions=?) => intervalHistogram =
96
+ "monitorEventLoopDelay"
97
+ @send external enable: intervalHistogram => bool = "enable"
98
+ @send external reset: intervalHistogram => unit = "reset"
99
+ @send external percentile: (intervalHistogram, int) => float = "percentile"
100
+
101
+ type performanceEntry = {
102
+ duration: float, // milliseconds
103
+ detail: {"kind": int},
104
+ }
105
+ type observerList
106
+ @send external getEntries: observerList => array<performanceEntry> = "getEntries"
107
+ type performanceObserver
108
+ type observeOptions = {entryTypes: array<string>}
109
+ @new @module("perf_hooks")
110
+ external makePerformanceObserver: (observerList => unit) => performanceObserver =
111
+ "PerformanceObserver"
112
+ @send external observe: (performanceObserver, observeOptions) => unit = "observe"
49
113
  }
50
114
 
51
115
  module ChildProcess = {
@@ -18,6 +18,10 @@ let Util$1 = {
18
18
 
19
19
  let Process = {};
20
20
 
21
+ let V8 = {};
22
+
23
+ let PerfHooks = {};
24
+
21
25
  let ChildProcess = {};
22
26
 
23
27
  let Url$1 = {};
@@ -45,6 +49,8 @@ let Fs = {
45
49
  export {
46
50
  Util$1 as Util,
47
51
  Process,
52
+ V8,
53
+ PerfHooks,
48
54
  ChildProcess,
49
55
  Url$1 as Url,
50
56
  ImportMeta,
@@ -269,8 +269,8 @@ module Registration = {
269
269
  }
270
270
 
271
271
  let fromOnEventRegistrations = (
272
- onEventRegistrations: array<Internal.evmOnEventRegistration>,
273
- ): array<input> => {
272
+ onEventRegistrations: array<Internal.evmOnEventRegistration>,
273
+ ): array<input> => {
274
274
  onEventRegistrations->Array.map(reg => {
275
275
  let event = reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
276
276
  {
@@ -282,9 +282,7 @@ module Registration = {
282
282
  isWildcard: reg.isWildcard,
283
283
  dependsOnAddresses: reg.dependsOnAddresses,
284
284
  params: event.paramsMetadata,
285
- topicSelections: reg.resolvedWhere.topicSelections->Array.map((
286
- ts
287
- ): topicSelectionInput => {
285
+ topicSelections: reg.resolvedWhere.topicSelections->Array.map((ts): topicSelectionInput => {
288
286
  topic0: ts.topic0->EvmTypes.Hex.toStrings,
289
287
  topic1: ts.topic1->toTopicFilterInput,
290
288
  topic2: ts.topic2->toTopicFilterInput,
@@ -360,12 +358,8 @@ type t = {
360
358
  }
361
359
 
362
360
  @send
363
- external classNew: (
364
- Core.evmHypersyncClientCtor,
365
- cfg,
366
- string,
367
- array<Registration.input>,
368
- ) => t = "new"
361
+ external classNew: (Core.evmHypersyncClientCtor, cfg, string, array<Registration.input>) => t =
362
+ "new"
369
363
 
370
364
  let makeWithAgent = (cfg, ~userAgent, ~eventRegistrations) =>
371
365
  Core.getAddon().evmHypersyncClient->classNew(cfg, userAgent, eventRegistrations)