envio 3.9.0 → 3.10.0-resolvers.1

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 (133) hide show
  1. package/evm.schema.json +33 -0
  2. package/fuel.schema.json +33 -0
  3. package/index.d.ts +300 -14
  4. package/index.js +10 -0
  5. package/package.json +6 -7
  6. package/src/Batch.res +9 -0
  7. package/src/Bin.res +47 -0
  8. package/src/Bin.res.mjs +34 -1
  9. package/src/ChainState.res +35 -39
  10. package/src/ChainState.res.mjs +14 -11
  11. package/src/ChainState.resi +15 -7
  12. package/src/Config.res +82 -49
  13. package/src/Config.res.mjs +82 -18
  14. package/src/Core.res +4 -0
  15. package/src/CrossChainState.res +15 -23
  16. package/src/CrossChainState.res.mjs +10 -18
  17. package/src/CrossChainState.resi +4 -1
  18. package/src/Ecosystem.res +7 -2
  19. package/src/Env.res +141 -1
  20. package/src/Env.res.mjs +133 -1
  21. package/src/Envio.res +6 -4
  22. package/src/EventConfigBuilder.res +174 -27
  23. package/src/EventConfigBuilder.res.mjs +126 -15
  24. package/src/HandlerRegister.res +24 -4
  25. package/src/HandlerRegister.res.mjs +24 -5
  26. package/src/InMemoryStore.res +9 -7
  27. package/src/InMemoryStore.res.mjs +3 -3
  28. package/src/IndexerState.res +43 -6
  29. package/src/IndexerState.res.mjs +36 -7
  30. package/src/IndexerState.resi +4 -4
  31. package/src/Internal.res +36 -21
  32. package/src/Internal.res.mjs +9 -0
  33. package/src/Metrics.res +72 -6
  34. package/src/Metrics.res.mjs +30 -2
  35. package/src/Persistence.res +36 -30
  36. package/src/Persistence.res.mjs +1 -20
  37. package/src/PgStorage.res +104 -56
  38. package/src/PgStorage.res.mjs +57 -35
  39. package/src/PruneStaleHistory.res +85 -52
  40. package/src/PruneStaleHistory.res.mjs +63 -37
  41. package/src/Rollback.res +68 -45
  42. package/src/Rollback.res.mjs +38 -19
  43. package/src/RollbackCommit.res +7 -9
  44. package/src/RollbackCommit.res.mjs +5 -6
  45. package/src/SafeCheckpointTracking.res +11 -11
  46. package/src/SafeCheckpointTracking.res.mjs +7 -6
  47. package/src/SimulateItems.res +44 -22
  48. package/src/SimulateItems.res.mjs +32 -27
  49. package/src/Sink.res +38 -26
  50. package/src/Sink.res.mjs +21 -16
  51. package/src/TestIndexer.res +5 -5
  52. package/src/TestIndexer.res.mjs +1 -1
  53. package/src/Utils.res +170 -0
  54. package/src/Utils.res.mjs +153 -0
  55. package/src/Writing.res +2 -2
  56. package/src/Writing.res.mjs +1 -1
  57. package/src/bindings/ClickHouse.res +366 -698
  58. package/src/bindings/ClickHouse.res.mjs +371 -421
  59. package/src/bindings/ClickHouseSink.res +337 -0
  60. package/src/bindings/ClickHouseSink.res.mjs +246 -0
  61. package/src/bindings/EventSource.res +8 -2
  62. package/src/bindings/NodeJs.res +6 -0
  63. package/src/bindings/Vitest.res +23 -0
  64. package/src/bindings/Vitest.res.mjs +3 -0
  65. package/src/bindings/WebSocket.res +9 -10
  66. package/src/db/CheckpointBounds.res +53 -0
  67. package/src/db/CheckpointBounds.res.mjs +44 -0
  68. package/src/db/EntityFilter.res +36 -17
  69. package/src/db/EntityFilter.res.mjs +25 -14
  70. package/src/db/EntityHistory.res +47 -22
  71. package/src/db/EntityHistory.res.mjs +19 -12
  72. package/src/db/InternalTable.res +84 -35
  73. package/src/db/InternalTable.res.mjs +56 -23
  74. package/src/db/RollbackFloors.res +55 -0
  75. package/src/db/RollbackFloors.res.mjs +92 -0
  76. package/src/db/Table.res +22 -4
  77. package/src/db/Table.res.mjs +30 -9
  78. package/src/resolvers/ResolverProcess.res +811 -0
  79. package/src/resolvers/ResolverProcess.res.mjs +668 -0
  80. package/src/resolvers/ResolverQuery.res +317 -0
  81. package/src/resolvers/ResolverQuery.res.mjs +220 -0
  82. package/src/resolvers/collisions.js +108 -0
  83. package/src/resolvers/db.js +363 -0
  84. package/src/resolvers/dispatch.js +259 -0
  85. package/src/resolvers/errors.js +30 -0
  86. package/src/resolvers/graphqlSelection.js +292 -0
  87. package/src/resolvers/hasuraAction.js +84 -0
  88. package/src/resolvers/hasuraApply.js +333 -0
  89. package/src/resolvers/hasuraMetadata.js +137 -0
  90. package/src/resolvers/index.js +123 -0
  91. package/src/resolvers/manifest.js +376 -0
  92. package/src/resolvers/server.js +304 -0
  93. package/src/sources/Evm.res +2 -0
  94. package/src/sources/Evm.res.mjs +2 -0
  95. package/src/sources/EvmHyperSyncSource.res +4 -10
  96. package/src/sources/EvmHyperSyncSource.res.mjs +5 -9
  97. package/src/sources/EvmRpcWs.res +124 -0
  98. package/src/sources/EvmRpcWs.res.mjs +117 -0
  99. package/src/sources/Fuel.res +2 -0
  100. package/src/sources/Fuel.res.mjs +2 -0
  101. package/src/sources/FuelHyperSyncSource.res +2 -8
  102. package/src/sources/FuelHyperSyncSource.res.mjs +4 -6
  103. package/src/sources/HeightFeed.res +568 -0
  104. package/src/sources/HeightFeed.res.mjs +452 -0
  105. package/src/sources/HeightStream.res +257 -0
  106. package/src/sources/HeightStream.res.mjs +194 -0
  107. package/src/sources/HyperSync.res +14 -0
  108. package/src/sources/HyperSync.res.mjs +16 -0
  109. package/src/sources/HyperSync.resi +9 -0
  110. package/src/sources/HyperSyncSSE.res +49 -0
  111. package/src/sources/HyperSyncSSE.res.mjs +79 -0
  112. package/src/sources/RpcSource.res +1 -1
  113. package/src/sources/RpcSource.res.mjs +2 -2
  114. package/src/sources/Source.res +54 -1
  115. package/src/sources/Source.res.mjs +25 -0
  116. package/src/sources/SourceManager.res +220 -227
  117. package/src/sources/SourceManager.res.mjs +139 -164
  118. package/src/sources/SourceManager.resi +8 -0
  119. package/src/sources/Svm.res +2 -0
  120. package/src/sources/Svm.res.mjs +2 -0
  121. package/src/sources/SvmHyperSyncClient.res +70 -127
  122. package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
  123. package/src/sources/SvmHyperSyncSource.res +32 -18
  124. package/src/sources/SvmHyperSyncSource.res.mjs +39 -16
  125. package/src/tui/Tui.res +1 -1
  126. package/src/tui/Tui.res.mjs +1 -1
  127. package/svm.schema.json +29 -101
  128. package/src/MemoryStorage.res +0 -729
  129. package/src/MemoryStorage.res.mjs +0 -586
  130. package/src/sources/HyperSyncHeightStream.res +0 -129
  131. package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
  132. package/src/sources/RpcWebSocketHeightStream.res +0 -175
  133. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
package/src/Bin.res.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Env from "./Env.res.mjs";
3
4
  import * as Core from "./Core.res.mjs";
4
5
  import * as Main from "./Main.res.mjs";
5
6
  import * as Utils from "./Utils.res.mjs";
@@ -11,6 +12,7 @@ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
11
12
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
12
13
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
13
14
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
15
+ import * as ResolverProcess from "./resolvers/ResolverProcess.res.mjs";
14
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
15
17
 
16
18
  let setEnvVar = ((k, v) => { process.env[k] = v; });
@@ -36,6 +38,11 @@ function decodeCommand(json) {
36
38
  TAG: "Migrate",
37
39
  _0: json
38
40
  };
41
+ case "resolvers" :
42
+ return {
43
+ TAG: "Resolvers",
44
+ _0: json
45
+ };
39
46
  case "start" :
40
47
  return {
41
48
  TAG: "Start",
@@ -65,9 +72,14 @@ async function run(args) {
65
72
  switch (match.TAG) {
66
73
  case "Start" :
67
74
  let match$1 = match._0;
75
+ let cwd = match$1.cwd;
68
76
  Config.prime(match$1.config);
69
- process.chdir(match$1.cwd);
77
+ process.chdir(cwd);
70
78
  applyEnv(match$1.env);
79
+ let loaded = Config.load();
80
+ if (loaded.isDev) {
81
+ await ResolverProcess.startForDev(loaded, cwd);
82
+ }
71
83
  return await Main.start(undefined, match$1.reset, undefined, undefined, undefined);
72
84
  case "Migrate" :
73
85
  let match$2 = match._0;
@@ -76,6 +88,27 @@ async function run(args) {
76
88
  case "DropSchema" :
77
89
  Config.prime(match._0.config);
78
90
  return await Main.dropSchema();
91
+ case "Resolvers" :
92
+ let match$3 = match._0;
93
+ let cwd$1 = match$3.cwd;
94
+ Config.prime(match$3.config);
95
+ process.chdir(cwd$1);
96
+ applyEnv(match$3.env);
97
+ switch (match$3.mode) {
98
+ case "manifest" :
99
+ return await ResolverProcess.writeManifest(Config.load(), cwd$1);
100
+ case "metadata" :
101
+ let url = Env.Resolvers.publicUrl();
102
+ let handlerUrl = url !== undefined ? url : Stdlib_JsError.throwWithMessage("ENVIO_RESOLVERS_PUBLIC_URL is not set. It is the URL Hasura posts to and is baked into every action, so the metadata cannot be printed without it.");
103
+ let metadata = await ResolverProcess.metadataJson(Config.load(), cwd$1, handlerUrl);
104
+ console.log(JSON.stringify(metadata, undefined, 2));
105
+ return;
106
+ case "migrate" :
107
+ let match$4 = await ResolverProcess.migrate(Config.load(), cwd$1);
108
+ return Logging.info(match$4.applied ? `Updated Hasura to match these resolvers: ` + match$4.reasons.join("; ") : "Hasura already matches these resolvers; nothing to update");
109
+ default:
110
+ return ResolverProcess.handleSignals(await ResolverProcess.serve(Config.load(), cwd$1, undefined, undefined, undefined, undefined));
111
+ }
79
112
  }
80
113
  } catch (raw_exn) {
81
114
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -310,7 +310,11 @@ let makeInternal = (
310
310
  chainId,
311
311
  endpointUrl: hypersyncUrl,
312
312
  apiToken,
313
- onEventRegistrations,
313
+ onEventRegistrations: onEventRegistrations->(
314
+ Utils.magic: array<Internal.onEventRegistration> => array<
315
+ Internal.svmOnEventRegistration,
316
+ >
317
+ ),
314
318
  clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
315
319
  addressStore,
316
320
  }),
@@ -1205,21 +1209,17 @@ let markReady = (cs: t, ~readyAt) =>
1205
1209
  cs.timestampCaughtUpToHeadOrEndblock = Some(readyAt)
1206
1210
  }
1207
1211
 
1208
- // Roll a chain back to a reorg target. With a progress diff, restore fetch/
1209
- // safe-checkpoint/progress state to `newProgressBlockNumber`. A reorg chain
1210
- // with no diff entry still rewinds fetch state + stores to the target -
1211
- // otherwise the stale block hash stays in the store and re-triggers the same
1212
- // reorg.
1213
- // Returns the address registrations the storage has to delete along with it —
1214
- // the store is what decides which ones died, so the two halves of a rollback
1215
- // can't disagree.
1216
- let rollback = (
1217
- cs: t,
1218
- ~newProgressBlockNumber,
1219
- ~eventsProcessedDiff,
1220
- ~rollbackTargetBlockNumber,
1221
- ~isReorgChain,
1222
- ): array<AddressRows.key> => {
1212
+ type progressDiff = {blockNumber: int, eventsProcessed: float}
1213
+
1214
+ type rolledBackTo =
1215
+ | RecomputedProgress(progressDiff)
1216
+ | ForkBlock(int)
1217
+ | Untouched
1218
+
1219
+ // Returns the address registrations the storage has to delete along with the
1220
+ // chain — the store is what decides which ones died, so the two halves of a
1221
+ // rollback can't disagree.
1222
+ let rollback = (cs: t, ~rolledBackTo: rolledBackTo): array<AddressRows.key> => {
1223
1223
  let rollbackTo = targetBlockNumber => {
1224
1224
  let {fetchState, rolledBackAddresses} =
1225
1225
  cs.fetchState->FetchState.rollback(
@@ -1234,15 +1234,15 @@ let rollback = (
1234
1234
  })
1235
1235
  }
1236
1236
 
1237
- switch newProgressBlockNumber {
1238
- | Some(newProgressBlockNumber) =>
1239
- let newTotalEventsProcessed =
1240
- cs.numEventsProcessed -.
1241
- // Both dicts are populated together per progress-diff row, so a chain with
1242
- // a progress diff always has an events-processed diff too.
1243
- eventsProcessedDiff->Option.getOrThrow(
1244
- ~message="Missing events-processed diff for rolled-back chain",
1245
- )
1237
+ switch rolledBackTo {
1238
+ | RecomputedProgress({blockNumber, eventsProcessed}) =>
1239
+ // A rollback only ever takes a chain back. The diff recomputes progress from
1240
+ // the checkpoints still in the database, so a chain that an unwritten
1241
+ // rollback already took below them — to a fork block only that rollback
1242
+ // knew — would be handed their MIN back and moved forward onto blocks it
1243
+ // never re-indexed.
1244
+ let newProgressBlockNumber = Pervasives.min(blockNumber, cs.committedProgressBlockNumber)
1245
+ let newTotalEventsProcessed = cs.numEventsProcessed -. eventsProcessed
1246
1246
 
1247
1247
  switch cs.safeCheckpointTracking {
1248
1248
  | Some(safeCheckpointTracking) =>
@@ -1260,19 +1260,15 @@ let rollback = (
1260
1260
  cs.processingBlockNumber = newProgressBlockNumber
1261
1261
  cs.numEventsProcessed = newTotalEventsProcessed
1262
1262
  rolledBackAddresses
1263
- | None =>
1264
- if isReorgChain {
1265
- let rolledBackAddresses = rollbackTo(rollbackTargetBlockNumber)
1266
- cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
1267
- cs.blockStore->BlockStore.rollback(rollbackTargetBlockNumber)
1268
- cs.committedProgressBlockNumber = Pervasives.min(
1269
- cs.committedProgressBlockNumber,
1270
- rollbackTargetBlockNumber,
1271
- )
1272
- cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, rollbackTargetBlockNumber)
1273
- rolledBackAddresses
1274
- } else {
1275
- []
1276
- }
1263
+ | ForkBlock(forkBlock) =>
1264
+ // Rewinds the stores too, not just progress: otherwise the stale block hash
1265
+ // survives and re-triggers the same reorg.
1266
+ let rolledBackAddresses = rollbackTo(forkBlock)
1267
+ cs.transactionStore->TransactionStore.rollback(forkBlock)
1268
+ cs.blockStore->BlockStore.rollback(forkBlock)
1269
+ cs.committedProgressBlockNumber = Pervasives.min(cs.committedProgressBlockNumber, forkBlock)
1270
+ cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, forkBlock)
1271
+ rolledBackAddresses
1272
+ | Untouched => []
1277
1273
  }
1278
1274
  }
@@ -857,7 +857,7 @@ function markReady(cs, readyAt) {
857
857
  }
858
858
  }
859
859
 
860
- function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTargetBlockNumber, isReorgChain) {
860
+ function rollback(cs, rolledBackTo) {
861
861
  let rollbackTo = targetBlockNumber => {
862
862
  let match = FetchState.rollback(cs.fetchState, cs.addressStore, targetBlockNumber);
863
863
  cs.fetchState = match.fetchState;
@@ -867,8 +867,13 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
867
867
  contractId: param.contractId
868
868
  }));
869
869
  };
870
- if (newProgressBlockNumber !== undefined) {
871
- let newTotalEventsProcessed = cs.numEventsProcessed - Stdlib_Option.getOrThrow(eventsProcessedDiff, "Missing events-processed diff for rolled-back chain");
870
+ if (typeof rolledBackTo !== "object") {
871
+ return [];
872
+ }
873
+ if (rolledBackTo.TAG === "RecomputedProgress") {
874
+ let match = rolledBackTo._0;
875
+ let newProgressBlockNumber = Primitive_int.min(match.blockNumber, cs.committedProgressBlockNumber);
876
+ let newTotalEventsProcessed = cs.numEventsProcessed - match.eventsProcessed;
872
877
  let safeCheckpointTracking = cs.safeCheckpointTracking;
873
878
  if (safeCheckpointTracking !== undefined) {
874
879
  cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
@@ -881,14 +886,12 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
881
886
  cs.numEventsProcessed = newTotalEventsProcessed;
882
887
  return rolledBackAddresses;
883
888
  }
884
- if (!isReorgChain) {
885
- return [];
886
- }
887
- let rolledBackAddresses$1 = rollbackTo(rollbackTargetBlockNumber);
888
- cs.transactionStore.rollback(rollbackTargetBlockNumber);
889
- cs.blockStore.rollback(rollbackTargetBlockNumber);
890
- cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
891
- cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
889
+ let forkBlock = rolledBackTo._0;
890
+ let rolledBackAddresses$1 = rollbackTo(forkBlock);
891
+ cs.transactionStore.rollback(forkBlock);
892
+ cs.blockStore.rollback(forkBlock);
893
+ cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, forkBlock);
894
+ cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, forkBlock);
892
895
  return rolledBackAddresses$1;
893
896
  }
894
897
 
@@ -157,10 +157,18 @@ let enterReorgThreshold: t => unit
157
157
  let advanceAfterBatch: (t, ~batch: Batch.t, ~enteringReorgThreshold: bool) => unit
158
158
  let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
159
159
  let markReady: (t, ~readyAt: Date.t) => unit
160
- let rollback: (
161
- t,
162
- ~newProgressBlockNumber: option<int>,
163
- ~eventsProcessedDiff: option<float>,
164
- ~rollbackTargetBlockNumber: int,
165
- ~isReorgChain: bool,
166
- ) => array<AddressRows.key>
160
+ // What a rollback's progress diff says about one chain: the block its surviving
161
+ // checkpoints put it back to, and the events the deleted ones carried away. One
162
+ // record because one diff row yields both — they are never known separately.
163
+ type progressDiff = {blockNumber: int, eventsProcessed: float}
164
+
165
+ // Where a rollback leaves one chain.
166
+ type rolledBackTo =
167
+ | RecomputedProgress(progressDiff)
168
+ // The reorg chain with no checkpoint left at or below the target, so only the
169
+ // fork block says where it goes.
170
+ | ForkBlock(int)
171
+ // A chain this rollback doesn't reach.
172
+ | Untouched
173
+
174
+ let rollback: (t, ~rolledBackTo: rolledBackTo) => array<AddressRows.key>
package/src/Config.res CHANGED
@@ -94,6 +94,9 @@ type t = {
94
94
  name: string,
95
95
  description: option<string>,
96
96
  handlers: string,
97
+ // Relative path to the module declaring custom GraphQL resolvers, when the
98
+ // project ships them. None means entity fields only.
99
+ resolvers: option<string>,
97
100
  contractHandlers: array<contractHandler>,
98
101
  shouldRollbackOnReorg: bool,
99
102
  shouldSaveFullHistory: bool,
@@ -174,28 +177,26 @@ let publicConfigChainSchema = S.schema(s =>
174
177
  }
175
178
  )
176
179
 
180
+ type svmAccountSlotItem = {"name": option<string>, "optional": option<bool>}
181
+
182
+ let svmAccountSlotSchema: S.t<svmAccountSlotItem> = S.schema(s =>
183
+ {
184
+ "name": s.matches(S.option(S.string)),
185
+ "optional": s.matches(S.option(S.bool)),
186
+ }
187
+ )
188
+
189
+ let svmAccountSlotFromItem = (slot: svmAccountSlotItem): Internal.svmAccountSlot =>
190
+ switch (slot["name"], slot["optional"]) {
191
+ | (None, _) => Unnamed
192
+ | (Some(name), Some(true)) => Optional(name)
193
+ | (Some(name), _) => Required(name)
194
+ }
195
+
177
196
  let svmEventDescriptorSchema = S.schema(s =>
178
197
  {
179
198
  "discriminator": s.matches(S.option(S.string)),
180
- "discriminatorByteLen": s.matches(S.int),
181
- // An array of AND-groups OR-ed together: the CLI normalizes both the flat
182
- // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
183
- "accountFilters": s.matches(
184
- S.option(
185
- S.array(
186
- S.array(
187
- S.schema(s =>
188
- {
189
- "position": s.matches(S.int),
190
- "values": s.matches(S.array(S.string)),
191
- }
192
- ),
193
- ),
194
- ),
195
- ),
196
- ),
197
- "isInner": s.matches(S.option(S.bool)),
198
- "accounts": s.matches(S.option(S.array(S.string))),
199
+ "accounts": s.matches(S.option(S.array(svmAccountSlotSchema))),
199
200
  "args": s.matches(S.option(S.json(~validate=false))),
200
201
  }
201
202
  )
@@ -341,6 +342,7 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
341
342
 
342
343
  let (fieldType, baseSchema) = switch typ {
343
344
  | "string" => (Table.String, S.string->S.toUnknown)
345
+ | "bytes" => (Table.Bytea, Utils.Schema.bytes->S.toUnknown)
344
346
  | "boolean" => (Table.Boolean, S.bool->S.toUnknown)
345
347
  | "int" => (Table.Int32, S.int->S.toUnknown)
346
348
  | "bigint" => (Table.BigInt({precision: ?prop["precision"]}), Utils.BigInt.schema->S.toUnknown)
@@ -374,13 +376,13 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
374
376
  | other => JsError.throwWithMessage("Unknown field type in entity config: " ++ other)
375
377
  }
376
378
 
377
- let fieldSchema = if isArray {
378
- S.array(baseSchema)->S.toUnknown
379
- } else {
380
- baseSchema
379
+ let fieldSchema = switch (fieldType, isArray) {
380
+ | (Table.Bytea, true) => Utils.Schema.bytesArray->S.toUnknown
381
+ | (_, true) => S.array(baseSchema)->S.toUnknown
382
+ | (_, false) => baseSchema
381
383
  }
382
384
  let fieldSchema = if isNullable {
383
- S.null(fieldSchema)->S.toUnknown
385
+ Utils.Schema.nullTolerant(fieldSchema)->S.toUnknown
384
386
  } else {
385
387
  fieldSchema
386
388
  }
@@ -550,6 +552,7 @@ let publicConfigSchema = S.schema(s =>
550
552
  "name": s.matches(S.string),
551
553
  "description": s.matches(S.option(S.string)),
552
554
  "handlers": s.matches(S.option(S.string)),
555
+ "resolvers": s.matches(S.option(S.string)),
553
556
  "isDev": s.matches(S.option(S.bool)),
554
557
  "fullBatchSize": s.matches(S.option(S.int)),
555
558
  "rollbackOnReorg": s.matches(S.option(S.bool)),
@@ -753,12 +756,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
753
756
  Utils.magic: _ => {
754
757
  "svm": option<{
755
758
  "discriminator": option<string>,
756
- "discriminatorByteLen": int,
757
- "accountFilters": option<
758
- array<array<{"position": int, "values": array<string>}>>,
759
- >,
760
- "isInner": option<bool>,
761
- "accounts": option<array<string>>,
759
+ "accounts": option<array<svmAccountSlotItem>>,
762
760
  "args": option<JSON.t>,
763
761
  }>,
764
762
  }
@@ -770,26 +768,12 @@ let fromPublic = (publicConfigJson: JSON.t) => {
770
768
  `SVM instruction ${contractName}.${eventName} is missing the "svm" descriptor in internal config`,
771
769
  )
772
770
  }
773
- let accountFilters =
774
- svm["accountFilters"]
775
- ->Option.getOr([])
776
- ->Array.map(group =>
777
- group->Array.map(
778
- af => {
779
- Internal.position: af["position"],
780
- values: af["values"]->SvmTypes.Pubkey.fromStringsUnsafe,
781
- },
782
- )
783
- )
784
771
  (EventConfigBuilder.buildSvmInstructionEventConfig(
785
772
  ~contractName,
786
773
  ~instructionName=eventName,
787
774
  ~programId,
788
775
  ~discriminator=svm["discriminator"],
789
- ~discriminatorByteLen=svm["discriminatorByteLen"],
790
- ~accountFilters,
791
- ~isInner=svm["isInner"],
792
- ~accounts=svm["accounts"]->Option.getOr([]),
776
+ ~accounts=svm["accounts"]->Option.getOr([])->Array.map(svmAccountSlotFromItem),
793
777
  ~args=svm["args"]->Option.getOr(JSON.Null),
794
778
  ~definedTypes=svmDefinedTypes,
795
779
  ) :> Internal.eventConfig)
@@ -1047,6 +1031,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1047
1031
  name: publicConfig["name"],
1048
1032
  description: publicConfig["description"],
1049
1033
  handlers: publicConfig["handlers"]->Option.getOr("src/handlers"),
1034
+ resolvers: publicConfig["resolvers"],
1050
1035
  contractHandlers,
1051
1036
  shouldRollbackOnReorg: publicConfig["rollbackOnReorg"]->Option.getOr(true),
1052
1037
  shouldSaveFullHistory: publicConfig["saveFullHistory"]->Option.getOr(false),
@@ -1070,6 +1055,14 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1070
1055
  }
1071
1056
  }
1072
1057
 
1058
+ // With no cross-chain entity, a reorg on one chain can never have changed a row
1059
+ // another chain owns, so its rollback stays isolated to that chain instead of
1060
+ // dragging every sibling back with it. A single chain has no sibling to spare,
1061
+ // and narrowing its rollback would only buy it a predicate that always holds.
1062
+ let isIsolatedMultichain = (config: t) =>
1063
+ config.chainMap->ChainMap.keys->Array.length > 1 &&
1064
+ config.userEntities->Array.every(entityConfig => !entityConfig.crossChain)
1065
+
1073
1066
  // Canonicalize a user-provided address to the configured casing so it matches
1074
1067
  // addresses parsed from config.yaml during routing. HyperSync/RPC data arrives
1075
1068
  // already canonical; only user-land input (simulate srcAddress, contractRegister
@@ -1141,8 +1134,17 @@ let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option<Cha
1141
1134
  })
1142
1135
  }
1143
1136
 
1144
- let shouldSaveHistory = (config, ~isInReorgThreshold) =>
1145
- config.shouldSaveFullHistory || (config.shouldRollbackOnReorg && isInReorgThreshold)
1137
+ // A chain that can't be rolled back (maxReorgDepth = 0) has no history to keep,
1138
+ // unless a cross-chain entity lets another chain's rollback reach its rows.
1139
+ let shouldSaveHistory = (config, ~isInReorgThreshold, ~chainId: option<ChainId.t>=?) =>
1140
+ config.shouldSaveFullHistory ||
1141
+ (config.shouldRollbackOnReorg &&
1142
+ isInReorgThreshold &&
1143
+ switch chainId {
1144
+ | Some(chainId) if config->isIsolatedMultichain =>
1145
+ (config.chainMap->ChainMap.get(chainId)).maxReorgDepth > 0
1146
+ | _ => true
1147
+ })
1146
1148
 
1147
1149
  let shouldPruneHistory = (config, ~isInReorgThreshold) =>
1148
1150
  !config.shouldSaveFullHistory && (config.shouldRollbackOnReorg && isInReorgThreshold)
@@ -1172,8 +1174,11 @@ let getPublicConfigJson = () =>
1172
1174
 
1173
1175
  // Drops source URLs from each chain so RPC/hypersync edits don't trigger
1174
1176
  // the resume-time compat check (and don't end up in `envio_info`). Also
1175
- // drops `isDev`, which toggles between `envio dev` and `envio start` and
1176
- // has no bearing on schema/indexing compatibility.
1177
+ // drops two fields that have no bearing on schema/indexing compatibility:
1178
+ // `isDev`, which toggles between `envio dev` and `envio start`, and
1179
+ // `resolvers`, which names where the custom GraphQL resolvers live. Without
1180
+ // the latter, adding `resolvers:` to a running indexer reads as an
1181
+ // incompatible config change and it refuses to resume.
1177
1182
  let stripSensitiveData = (json: JSON.t): JSON.t => {
1178
1183
  let cloned = json->JSON.stringify->JSON.parseOrThrow
1179
1184
  let stripChains = (ecosystem: option<JSON.t>) =>
@@ -1200,6 +1205,7 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1200
1205
  switch cloned {
1201
1206
  | Object(obj) => {
1202
1207
  obj->Utils.Dict.deleteInPlace("isDev")
1208
+ obj->Utils.Dict.deleteInPlace("resolvers")
1203
1209
  stripChains(obj->Dict.get("evm"))
1204
1210
  stripChains(obj->Dict.get("fuel"))
1205
1211
  stripChains(obj->Dict.get("svm"))
@@ -1362,6 +1368,33 @@ let throwIfIncompatible = (
1362
1368
  }
1363
1369
  }
1364
1370
 
1371
+ let throwIfResumeIncompatible = (
1372
+ ~storedEnvioInfo: option<JSON.t>,
1373
+ ~storedContractMapping: ContractMapping.t,
1374
+ ~envioInfo: JSON.t,
1375
+ ~contractMapping: ContractMapping.t,
1376
+ ~resetCommand: string,
1377
+ ~runCommand: option<string>,
1378
+ ) => {
1379
+ let changedPaths = switch storedEnvioInfo {
1380
+ | None => ["storage was initialized by an older envio version"]
1381
+ | Some(stored) => diffPaths(~stored, ~current=envioInfo)
1382
+ }
1383
+ let changedPaths =
1384
+ storedContractMapping->ContractMapping.isEqual(contractMapping)
1385
+ ? changedPaths
1386
+ : changedPaths->Array.concat(["contracts"])
1387
+ let hasClickhouse = switch envioInfo {
1388
+ | Object(d) =>
1389
+ switch d->Dict.get("storage") {
1390
+ | Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
1391
+ | _ => false
1392
+ }
1393
+ | _ => false
1394
+ }
1395
+ throwIfIncompatible(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
1396
+ }
1397
+
1365
1398
  // The returned value is a pure function of the JSON: it holds only event
1366
1399
  // definitions, never handler/contractRegister/where state (that's layered on
1367
1400
  // separately as `HandlerRegister.registrationsByChainId`). That purity is
@@ -17,6 +17,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
17
17
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
18
18
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
19
19
  import * as ContractMapping from "./ContractMapping.res.mjs";
20
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
20
21
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
21
22
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
22
23
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -71,15 +72,34 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
71
72
  contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(chainContractSchema)))
72
73
  }));
73
74
 
75
+ let svmAccountSlotSchema = S$RescriptSchema.schema(s => ({
76
+ name: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
77
+ optional: s.m(S$RescriptSchema.option(S$RescriptSchema.bool))
78
+ }));
79
+
80
+ function svmAccountSlotFromItem(slot) {
81
+ let match = slot.name;
82
+ let match$1 = slot.optional;
83
+ if (match !== undefined) {
84
+ if (match$1 !== undefined && match$1) {
85
+ return {
86
+ TAG: "Optional",
87
+ _0: match
88
+ };
89
+ } else {
90
+ return {
91
+ TAG: "Required",
92
+ _0: match
93
+ };
94
+ }
95
+ } else {
96
+ return "Unnamed";
97
+ }
98
+ }
99
+
74
100
  let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
75
101
  discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
76
- discriminatorByteLen: s.m(S$RescriptSchema.int),
77
- accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
78
- position: s.m(S$RescriptSchema.int),
79
- values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
80
- })))))),
81
- isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
82
- accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
102
+ accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(svmAccountSlotSchema))),
83
103
  args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
84
104
  }));
85
105
 
@@ -226,6 +246,12 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
226
246
  S$RescriptSchema.bool
227
247
  ];
228
248
  break;
249
+ case "bytes" :
250
+ match = [
251
+ "Bytea",
252
+ Utils.Schema.bytes
253
+ ];
254
+ break;
229
255
  case "date" :
230
256
  match = [
231
257
  "Date",
@@ -277,10 +303,20 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
277
303
  match = Stdlib_JsError.throwWithMessage("Unknown field type in entity config: " + typ);
278
304
  }
279
305
  let baseSchema = match[1];
280
- let fieldSchema = isArray ? S$RescriptSchema.array(baseSchema) : baseSchema;
281
- let fieldSchema$1 = isNullable ? S$RescriptSchema.$$null(fieldSchema) : fieldSchema;
306
+ let fieldType = match[0];
307
+ let fieldSchema;
308
+ let exit = 0;
309
+ if (typeof fieldType !== "object" && fieldType === "Bytea" && isArray) {
310
+ fieldSchema = Utils.Schema.bytesArray;
311
+ } else {
312
+ exit = 1;
313
+ }
314
+ if (exit === 1) {
315
+ fieldSchema = isArray ? S$RescriptSchema.array(baseSchema) : baseSchema;
316
+ }
317
+ let fieldSchema$1 = isNullable ? Utils.Schema.nullTolerant(fieldSchema) : fieldSchema;
282
318
  return [
283
- match[0],
319
+ fieldType,
284
320
  fieldSchema$1,
285
321
  isNullable,
286
322
  isArray,
@@ -378,6 +414,7 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
378
414
  name: s.m(S$RescriptSchema.string),
379
415
  description: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
380
416
  handlers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
417
+ resolvers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
381
418
  isDev: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
382
419
  fullBatchSize: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
383
420
  rollbackOnReorg: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
@@ -527,11 +564,7 @@ function fromPublic(publicConfigJson) {
527
564
  ) : addresses[0];
528
565
  let s = eventItem.svm;
529
566
  let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
530
- let accountFilters = Stdlib_Option.getOr(svm.accountFilters, []).map(group => group.map(af => ({
531
- position: af.position,
532
- values: af.values
533
- })));
534
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, accountFilters, svm.isInner, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
567
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, Stdlib_Option.getOr(svm.accounts, []).map(svmAccountSlotFromItem), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
535
568
  }
536
569
  });
537
570
  } else {
@@ -703,6 +736,7 @@ function fromPublic(publicConfigJson) {
703
736
  name: publicConfig.name,
704
737
  description: publicConfig.description,
705
738
  handlers: Stdlib_Option.getOr(publicConfig.handlers, "src/handlers"),
739
+ resolvers: publicConfig.resolvers,
706
740
  contractHandlers: contractHandlers,
707
741
  shouldRollbackOnReorg: Stdlib_Option.getOr(publicConfig.rollbackOnReorg, true),
708
742
  shouldSaveFullHistory: Stdlib_Option.getOr(publicConfig.saveFullHistory, false),
@@ -726,6 +760,14 @@ function fromPublic(publicConfigJson) {
726
760
  };
727
761
  }
728
762
 
763
+ function isIsolatedMultichain(config) {
764
+ if (ChainMap.keys(config.chainMap).length > 1) {
765
+ return config.userEntities.every(entityConfig => !entityConfig.crossChain);
766
+ } else {
767
+ return false;
768
+ }
769
+ }
770
+
729
771
  function normalizeUserAddress(config, address) {
730
772
  let match = config.ecosystem.name;
731
773
  switch (match) {
@@ -789,11 +831,15 @@ function getEventConfig(config, contractName, eventName, chainId) {
789
831
  });
790
832
  }
791
833
 
792
- function shouldSaveHistory(config, isInReorgThreshold) {
834
+ function shouldSaveHistory(config, isInReorgThreshold, chainId) {
793
835
  if (config.shouldSaveFullHistory) {
794
836
  return true;
795
- } else if (config.shouldRollbackOnReorg) {
796
- return isInReorgThreshold;
837
+ } else if (config.shouldRollbackOnReorg && isInReorgThreshold) {
838
+ if (chainId !== undefined && isIsolatedMultichain(config)) {
839
+ return ChainMap.get(config.chainMap, Primitive_option.valFromOption(chainId)).maxReorgDepth > 0;
840
+ } else {
841
+ return true;
842
+ }
797
843
  } else {
798
844
  return false;
799
845
  }
@@ -864,6 +910,7 @@ function stripSensitiveData(json) {
864
910
  };
865
911
  if (typeof cloned === "object" && cloned !== null && !Array.isArray(cloned)) {
866
912
  Utils.Dict.deleteInPlace(cloned, "isDev");
913
+ Utils.Dict.deleteInPlace(cloned, "resolvers");
867
914
  stripChains(cloned["evm"]);
868
915
  stripChains(cloned["fuel"]);
869
916
  stripChains(cloned["svm"]);
@@ -1031,6 +1078,19 @@ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhou
1031
1078
  Stdlib_JsError.throwWithMessage(`The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3);
1032
1079
  }
1033
1080
 
1081
+ function throwIfResumeIncompatible(storedEnvioInfo, storedContractMapping, envioInfo, contractMapping, resetCommand, runCommand) {
1082
+ let changedPaths = storedEnvioInfo !== undefined ? diffPaths(storedEnvioInfo, envioInfo) : ["storage was initialized by an older envio version"];
1083
+ let changedPaths$1 = ContractMapping.isEqual(storedContractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
1084
+ let hasClickhouse;
1085
+ if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
1086
+ let match = envioInfo["storage"];
1087
+ hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
1088
+ } else {
1089
+ hasClickhouse = false;
1090
+ }
1091
+ throwIfIncompatible(changedPaths$1, resetCommand, runCommand, hasClickhouse);
1092
+ }
1093
+
1034
1094
  function load() {
1035
1095
  let c = cached.contents;
1036
1096
  if (c !== undefined) {
@@ -1052,6 +1112,8 @@ export {
1052
1112
  rpcConfigSchema,
1053
1113
  chainContractSchema,
1054
1114
  publicConfigChainSchema,
1115
+ svmAccountSlotSchema,
1116
+ svmAccountSlotFromItem,
1055
1117
  svmEventDescriptorSchema,
1056
1118
  svmAbiSchema,
1057
1119
  contractEventItemSchema,
@@ -1075,6 +1137,7 @@ export {
1075
1137
  publicConfigSchema,
1076
1138
  contractMappingOf,
1077
1139
  fromPublic,
1140
+ isIsolatedMultichain,
1078
1141
  normalizeUserAddress,
1079
1142
  normalizeSimulateAddress,
1080
1143
  getEventConfig,
@@ -1087,6 +1150,7 @@ export {
1087
1150
  canonicalJson,
1088
1151
  diffPaths,
1089
1152
  throwIfIncompatible,
1153
+ throwIfResumeIncompatible,
1090
1154
  load,
1091
1155
  getPgUserEntities,
1092
1156
  }