envio 3.15.0-subgraph → 3.16.0-subgraph

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 (114) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +3 -3
  3. package/fuel.schema.json +2 -2
  4. package/index.d.ts +3 -3
  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/ChainFetching.res +1 -6
  11. package/src/ChainFetching.res.mjs +1 -1
  12. package/src/ChainState.res +48 -7
  13. package/src/ChainState.res.mjs +21 -8
  14. package/src/Clock.res +57 -0
  15. package/src/Clock.res.mjs +72 -0
  16. package/src/Config.res +16 -4
  17. package/src/Config.res.mjs +5 -3
  18. package/src/Core.res +4 -40
  19. package/src/CrossChainState.res +10 -6
  20. package/src/CrossChainState.res.mjs +1 -1
  21. package/src/EffectState.res +10 -2
  22. package/src/EffectState.res.mjs +8 -4
  23. package/src/EffectState.resi +3 -1
  24. package/src/EntityId.res +2 -7
  25. package/src/EntityId.res.mjs +1 -8
  26. package/src/EntityTables.res +4 -1
  27. package/src/EntityTables.res.mjs +1 -1
  28. package/src/Env.res +4 -0
  29. package/src/Env.res.mjs +5 -0
  30. package/src/FetchState.res +157 -69
  31. package/src/FetchState.res.mjs +112 -47
  32. package/src/FinalizeBackfill.res +2 -4
  33. package/src/FinalizeBackfill.res.mjs +3 -4
  34. package/src/InMemoryTable.res +17 -16
  35. package/src/InMemoryTable.res.mjs +14 -15
  36. package/src/IndexerState.res +28 -4
  37. package/src/IndexerState.res.mjs +19 -6
  38. package/src/IndexerState.resi +4 -0
  39. package/src/Internal.res +0 -49
  40. package/src/Internal.res.mjs +1 -80
  41. package/src/LoadLayer.res +17 -10
  42. package/src/LoadLayer.res.mjs +11 -8
  43. package/src/Main.res.mjs +1 -1
  44. package/src/Persistence.res +3 -23
  45. package/src/Persistence.res.mjs +3 -3
  46. package/src/PreloadLookahead.res +42 -24
  47. package/src/PreloadLookahead.res.mjs +51 -30
  48. package/src/Rollback.res +0 -1
  49. package/src/Rollback.res.mjs +1 -1
  50. package/src/SafeCheckpointTracking.res +4 -4
  51. package/src/SafeCheckpointTracking.res.mjs +1 -1
  52. package/src/StagedRows.res +106 -0
  53. package/src/StagedRows.res.mjs +102 -0
  54. package/src/Staging.res +32 -14
  55. package/src/Staging.res.mjs +17 -20
  56. package/src/Storage.res +278 -973
  57. package/src/Storage.res.mjs +218 -720
  58. package/src/Supervisor.res +75 -56
  59. package/src/Supervisor.res.mjs +55 -26
  60. package/src/SyncWrites.res +24 -18
  61. package/src/SyncWrites.res.mjs +11 -11
  62. package/src/TestIndexer.res +22 -27
  63. package/src/TestIndexer.res.mjs +20 -17
  64. package/src/Throttler.res +15 -8
  65. package/src/Throttler.res.mjs +9 -6
  66. package/src/Throttler.resi +1 -1
  67. package/src/UserContext.res +13 -4
  68. package/src/UserContext.res.mjs +5 -5
  69. package/src/Utils.res +0 -6
  70. package/src/Utils.res.mjs +0 -5
  71. package/src/Writing.res +0 -1
  72. package/src/Writing.res.mjs +1 -1
  73. package/src/bindings/ClickHouse.res +0 -322
  74. package/src/bindings/ClickHouse.res.mjs +0 -338
  75. package/src/bindings/StorageClient.res +107 -174
  76. package/src/bindings/StorageClient.res.mjs +19 -8
  77. package/src/bindings/Vitest.res +0 -26
  78. package/src/bindings/Vitest.res.mjs +0 -3
  79. package/src/db/Table.res +24 -150
  80. package/src/db/Table.res.mjs +38 -272
  81. package/src/sources/ChainSources.res +2 -0
  82. package/src/sources/ChainSources.res.mjs +2 -2
  83. package/src/sources/EvmChain.res +3 -0
  84. package/src/sources/EvmChain.res.mjs +4 -2
  85. package/src/sources/EvmHyperSyncSource.res +2 -2
  86. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  87. package/src/sources/EvmRpcClient.res +13 -3
  88. package/src/sources/EvmRpcClient.res.mjs +4 -3
  89. package/src/sources/EvmRpcWs.res +4 -3
  90. package/src/sources/EvmRpcWs.res.mjs +2 -2
  91. package/src/sources/HeightFeed.res +31 -10
  92. package/src/sources/HeightFeed.res.mjs +10 -6
  93. package/src/sources/HeightStream.res +32 -17
  94. package/src/sources/HeightStream.res.mjs +10 -10
  95. package/src/sources/HyperSyncSSE.res +4 -6
  96. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  97. package/src/sources/RpcSource.res +10 -1
  98. package/src/sources/RpcSource.res.mjs +4 -3
  99. package/src/sources/Source.res +3 -0
  100. package/src/sources/SourceManager.res +85 -104
  101. package/src/sources/SourceManager.res.mjs +18 -37
  102. package/src/sources/SourceManager.resi +3 -5
  103. package/src/sources/StartBlockResolver.res +1 -0
  104. package/src/sources/StartBlockResolver.res.mjs +4 -3
  105. package/src/sources/SvmHyperSyncSource.res +2 -2
  106. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  107. package/src/subgraph/graph-ts.ts +87 -28
  108. package/src/subgraph/runtime.ts +0 -1
  109. package/src/subgraph/scope.ts +0 -1
  110. package/svm.schema.json +3 -3
  111. package/src/PgWriting.res +0 -36
  112. package/src/PgWriting.res.mjs +0 -34
  113. package/src/db/IndexDefinition.res +0 -47
  114. package/src/db/IndexDefinition.res.mjs +0 -60
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Clock from "./Clock.res.mjs";
3
4
  import * as Utils from "./Utils.res.mjs";
4
5
  import * as Logger from "./Logger.res.mjs";
5
6
  import * as ChainId from "./ChainId.res.mjs";
@@ -28,7 +29,8 @@ function scopeKeySuffix(scope) {
28
29
 
29
30
  function loadById(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, entityId) {
30
31
  let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
31
- let change = inMemTable.latestEntityChangeById[entityId];
32
+ let entityIdToKey = inMemTable.entityIdToKey;
33
+ let change = inMemTable.latestEntityChangeById[entityIdToKey(entityId)];
32
34
  if (change !== undefined) {
33
35
  return Promise.resolve(InMemoryTable.Entity.mapChangeToEntity(change));
34
36
  }
@@ -51,13 +53,13 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
51
53
  let entitiesMap = {};
52
54
  for (let idx = 0, idx_finish = dbEntities.length; idx < idx_finish; ++idx) {
53
55
  let entity = dbEntities[idx];
54
- entitiesMap[entity.id] = entity;
56
+ entitiesMap[entityIdToKey(entity.id)] = entity;
55
57
  }
56
58
  let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
57
- idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entityId, entitiesMap[entityId]));
59
+ idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entityId, entitiesMap[entityIdToKey(entityId)]));
58
60
  return IndexerState.endStorageLoad(indexerState, timerRef, storageName, key, idsToLoad.length, dbEntities.length);
59
61
  };
60
- return LoadManager.call(loadManager, entityId, key, load, LoadManager.noopHasher, shouldGroup, hash => hash in inMemTable.latestEntityChangeById, InMemoryTable.Entity.getUnsafe(inMemTable));
62
+ return LoadManager.call(loadManager, entityId, key, load, entityIdToKey, shouldGroup, hash => hash in inMemTable.latestEntityChangeById, InMemoryTable.Entity.getUnsafe(inMemTable));
61
63
  }
62
64
 
63
65
  function callEffect(effect, arg, inMemTable, timerRef, onError) {
@@ -70,7 +72,7 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
70
72
  let promises = [];
71
73
  let state = inMemTable.rateLimitState;
72
74
  if (state !== undefined) {
73
- let now = Date.now();
75
+ let now = state.clock.now();
74
76
  if (now >= state.windowStartTime + state.durationMs) {
75
77
  state.availableCalls = state.callsPerDuration;
76
78
  state.windowStartTime = now;
@@ -99,7 +101,7 @@ function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQue
99
101
  nextWindowPromise = p;
100
102
  } else {
101
103
  millisUntilReset.contents = state.windowStartTime + state.durationMs - now | 0;
102
- let p$1 = Utils.delay(millisUntilReset.contents);
104
+ let p$1 = Clock.delay(state.clock, "effect rate limit window", millisUntilReset.contents);
103
105
  state.nextWindowPromise = p$1;
104
106
  nextWindowPromise = p$1;
105
107
  }
@@ -195,8 +197,9 @@ function indexFilter(inMemTable, filter, table) {
195
197
 
196
198
  function getByIdInMemory(entityConfig, scope, indexerState, entityId) {
197
199
  let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
198
- if (entityId in inMemTable.latestEntityChangeById) {
199
- return Primitive_option.some(InMemoryTable.Entity.getUnsafe(inMemTable)(entityId));
200
+ let entityKey = inMemTable.entityIdToKey(entityId);
201
+ if (entityKey in inMemTable.latestEntityChangeById) {
202
+ return Primitive_option.some(InMemoryTable.Entity.getUnsafe(inMemTable)(entityKey));
200
203
  }
201
204
  }
202
205
 
package/src/Main.res.mjs CHANGED
@@ -414,7 +414,7 @@ async function startIndexer(config, logger, persistence, resetOpt, isTestOpt, ex
414
414
  Metrics.startRuntimeCollectors();
415
415
  Server.startServer(logger, getMetrics, envioVersion, () => Persistence.getInitializedStorageOrThrow(persistence$1).dumpEffectCache().then(prim => {}), Metrics.collectRuntime, isDevelopmentMode);
416
416
  }
417
- let state = IndexerState.makeFromDbState(config$2, logger, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, Stdlib_Option.mapOr(Worker.config, false, worker => worker.holdRealtime), onError, undefined);
417
+ let state = IndexerState.makeFromDbState(config$2, logger, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, Stdlib_Option.mapOr(Worker.config, false, worker => worker.holdRealtime), onError, undefined, undefined);
418
418
  if (shouldUseTui) {
419
419
  Tui.start(config$2, logger, () => IndexerState.toMetrics(state));
420
420
  }
@@ -117,8 +117,6 @@ type storage = {
117
117
  // carries a matching row — storage doesn't interpret it.
118
118
  initialize: (
119
119
  ~chainConfigs: array<Config.chain>=?,
120
- ~entities: array<Internal.entityConfig>=?,
121
- ~enums: array<Table.enumConfig<Table.enum>>=?,
122
120
  ~contractMapping: ContractMapping.t,
123
121
  ~envioInfo: JSON.t,
124
122
  ) => promise<initialState>,
@@ -129,16 +127,11 @@ type storage = {
129
127
  // Brings an initialized storage a chain it doesn't have yet: its row, its
130
128
  // partitions and its config addresses, as `initialize` would have created
131
129
  // them. The chains already there are left untouched.
132
- addChain: (
133
- ~chainConfig: Config.chain,
134
- ~entities: array<Internal.entityConfig>,
135
- ~contractMapping: ContractMapping.t,
136
- ) => promise<unit>,
130
+ addChain: (~chainConfig: Config.chain, ~contractMapping: ContractMapping.t) => promise<unit>,
137
131
  // `chainIds` is what this run drives. An isolated run resumes a subset of
138
132
  // the stored chains, and ClickHouse's resume trims past each resumed chain's
139
133
  // checkpoint, so chains a sibling process is still writing must stay out.
140
134
  resumeInitialState: (
141
- ~entities: array<Internal.entityConfig>,
142
135
  ~chainIds: array<ChainId.t>,
143
136
  ~contractMapping: ContractMapping.t,
144
137
  ) => promise<initialState>,
@@ -159,19 +152,12 @@ type storage = {
159
152
  // never runs `finalizeBackfill`: an index dropped or invalidated while it was
160
153
  // down would otherwise never be rebuilt. Best-effort, and safe to run with
161
154
  // indexing live.
162
- ensureSchemaIndexes: (
163
- ~entities: array<Internal.entityConfig>,
164
- ~chainIds: array<ChainId.t>,
165
- ) => promise<unit>,
155
+ ensureSchemaIndexes: (~chainIds: array<ChainId.t>) => promise<unit>,
166
156
  // Creates every index the schema promises for the given chains, then stamps
167
157
  // `ready_at` on them. Called once those chains finish backfill. The indexes
168
158
  // are committed one at a time so a failure part way through doesn't undo the
169
159
  // ones already built; `ready_at` is only written once they all verify.
170
- finalizeBackfill: (
171
- ~entities: array<Internal.entityConfig>,
172
- ~chainIds: array<ChainId.t>,
173
- ~readyAt: Date.t,
174
- ) => promise<unit>,
160
+ finalizeBackfill: (~chainIds: array<ChainId.t>, ~readyAt: Date.t) => promise<unit>,
175
161
  // This is to download cache from the database to .envio/cache
176
162
  dumpEffectCache: unit => promise<unit>,
177
163
  reset: unit => promise<unit>,
@@ -190,7 +176,6 @@ type storage = {
190
176
  getRollbackTargetCheckpoint: (
191
177
  ~reorgChainId: ChainId.t,
192
178
  ~lastKnownValidBlockNumber: int,
193
- ~sequence: CheckpointSequence.t,
194
179
  ) => promise<option<Internal.checkpointId>>,
195
180
  // Get rollback progress diff
196
181
  getRollbackProgressDiff: (
@@ -214,7 +199,6 @@ type storage = {
214
199
  ~batch: Batch.t,
215
200
  ~rollback: option<rollback>,
216
201
  ~config: Config.t,
217
- ~allEntities: array<Internal.entityConfig>,
218
202
  ~updatedEffectsCache: array<updatedEffectCache>,
219
203
  ~updatedEntities: array<updatedEntity>,
220
204
  // Addresses this batch registered, with the checkpoint that covers them.
@@ -339,8 +323,6 @@ let init = {
339
323
  throw(Refused(Config.setUpMeanwhileMessage))
340
324
  }
341
325
  let initialState = await persistence.storage.initialize(
342
- ~entities=persistence.allEntities,
343
- ~enums=persistence.allEnums,
344
326
  ~chainConfigs,
345
327
  ~contractMapping,
346
328
  ~envioInfo,
@@ -397,13 +379,11 @@ let init = {
397
379
  )->Array.getUnsafe(0)
398
380
  await persistence.storage.addChain(
399
381
  ~chainConfig,
400
- ~entities=persistence.allEntities,
401
382
  ~contractMapping=stored.contractMapping,
402
383
  )
403
384
  }
404
385
  if resume {
405
386
  let initialState = await persistence.storage.resumeInitialState(
406
- ~entities=persistence.allEntities,
407
387
  ~chainIds=chainConfigs->Array.map(chain => chain.id),
408
388
  ~contractMapping=stored.contractMapping,
409
389
  )
@@ -94,7 +94,7 @@ async function init(persistence, logger, chainConfigs, contractMapping, envioInf
94
94
  Error: new Error()
95
95
  };
96
96
  }
97
- let initialState = await persistence.storage.initialize(chainConfigs$1, persistence.allEntities, persistence.allEnums, contractMapping, envioInfo);
97
+ let initialState = await persistence.storage.initialize(chainConfigs$1, contractMapping, envioInfo);
98
98
  Logger.info(logger, `The indexer storage is ready. Starting indexing!`, undefined);
99
99
  persistence.storageStatus = {
100
100
  TAG: "Ready",
@@ -136,11 +136,11 @@ async function init(persistence, logger, chainConfigs, contractMapping, envioInf
136
136
  chainId: chainConfig.id
137
137
  });
138
138
  let chainConfig$1 = (await StartBlockResolver.resolveAllOrThrow([chainConfig], logger, lowercaseAddresses, startBlockRetry, sourceTiming))[0];
139
- await persistence.storage.addChain(chainConfig$1, persistence.allEntities, stored.contractMapping);
139
+ await persistence.storage.addChain(chainConfig$1, stored.contractMapping);
140
140
  }
141
141
  }
142
142
  if (resume) {
143
- let initialState$1 = await persistence.storage.resumeInitialState(persistence.allEntities, chainConfigs.map(chain => chain.id), stored.contractMapping);
143
+ let initialState$1 = await persistence.storage.resumeInitialState(chainConfigs.map(chain => chain.id), stored.contractMapping);
144
144
  persistence.storageStatus = {
145
145
  TAG: "Ready",
146
146
  _0: initialState$1
@@ -26,12 +26,12 @@
26
26
  type t = {
27
27
  items: array<Internal.item>,
28
28
  indexOf: Utils.Map.t<Internal.item, int>,
29
- absentReaders: dict<array<int>>,
29
+ absentReaders: Map.t<string, array<int>>,
30
30
  dirty: array<bool>,
31
31
  // Milliseconds the item's preload rounds have run for, all runs together.
32
32
  preloadMs: array<float>,
33
33
  // By entity key, the writes items' preloads made: (item, entity or deleted).
34
- overlay: dict<array<(int, option<Internal.entity>)>>,
34
+ overlay: Map.t<string, array<(int, option<Internal.entity>)>>,
35
35
  window: int,
36
36
  // The last item execute finished; reads and writes at or before it are history.
37
37
  mutable executed: int,
@@ -51,10 +51,10 @@ let start = (~items: array<Internal.item>) => {
51
51
  Some({
52
52
  items,
53
53
  indexOf,
54
- absentReaders: Dict.make(),
54
+ absentReaders: Map.make(),
55
55
  dirty: Array.make(~length=items->Array.length, false),
56
56
  preloadMs: Array.make(~length=items->Array.length, 0.),
57
- overlay: Dict.make(),
57
+ overlay: Map.make(),
58
58
  window,
59
59
  executed: -1,
60
60
  })
@@ -102,9 +102,9 @@ let recordAbsent = (~item: Internal.item, ~entityName, ~scope, ~entityId) =>
102
102
  switch t.indexOf->Utils.Map.get(item) {
103
103
  | Some(idx) if idx > t.executed =>
104
104
  let key = entityKey(~entityName, ~scope, ~entityId)
105
- switch t.absentReaders->Utils.Dict.dangerouslyGetNonOption(key) {
105
+ switch t.absentReaders->Map.get(key) {
106
106
  | Some(readers) => readers->Array.push(idx)
107
- | None => t.absentReaders->Dict.set(key, [idx])
107
+ | None => t.absentReaders->Map.set(key, [idx])
108
108
  }
109
109
  | _ => ()
110
110
  }
@@ -113,22 +113,42 @@ let recordAbsent = (~item: Internal.item, ~entityName, ~scope, ~entityId) =>
113
113
  let onSet = (~entityName, ~scope, ~entityId) =>
114
114
  switch active.contents {
115
115
  | None => ()
116
- | Some(t) =>
116
+ // Every write execute makes lands here, and most batches have no absent read.
117
+ | Some(t) if t.absentReaders->Map.size > 0 =>
117
118
  let key = entityKey(~entityName, ~scope, ~entityId)
118
- switch t.absentReaders->Utils.Dict.dangerouslyGetNonOption(key) {
119
+ switch t.absentReaders->Map.get(key) {
119
120
  | None => ()
120
121
  | Some(readers) =>
121
- t.absentReaders->Utils.Dict.deleteInPlace(key)
122
+ t.absentReaders->Map.delete(key)->ignore
122
123
  readers->Array.forEach(idx =>
123
124
  if idx > t.executed {
124
125
  t.dirty->Array.setUnsafe(idx, true)
125
126
  }
126
127
  )
127
128
  }
129
+ | Some(_) => ()
128
130
  }
129
131
 
130
132
  let isActive = () => active.contents->Option.isSome
131
133
 
134
+ // How many of `writes` (sorted by item) are by an item before `item`. An entity
135
+ // every event of a batch touches (a popular holder) gathers a write per event,
136
+ // and a scan of them per read was quadratic in the batch.
137
+ let countWritesBeforeItem = (writes: array<(int, option<Internal.entity>)>, item) => {
138
+ let low = ref(0)
139
+ let high = ref(writes->Array.length)
140
+ while low.contents < high.contents {
141
+ let mid = (low.contents + high.contents) / 2
142
+ let (writer, _) = writes->Array.getUnsafe(mid)
143
+ if writer < item {
144
+ low := mid + 1
145
+ } else {
146
+ high := mid
147
+ }
148
+ }
149
+ low.contents
150
+ }
151
+
132
152
  let overlaySet = (~item: Internal.item, ~entityName, ~scope, ~entityId, ~entity) =>
133
153
  switch active.contents {
134
154
  | None => ()
@@ -136,12 +156,15 @@ let overlaySet = (~item: Internal.item, ~entityName, ~scope, ~entityId, ~entity)
136
156
  switch t.indexOf->Utils.Map.get(item) {
137
157
  | Some(writer) =>
138
158
  let key = entityKey(~entityName, ~scope, ~entityId)
139
- let writes = switch t.overlay->Utils.Dict.dangerouslyGetNonOption(key) {
140
- | Some(writes) => writes->Array.filter(((idx, _)) => idx !== writer)
141
- | None => []
159
+ switch t.overlay->Map.get(key) {
160
+ | Some(writes) =>
161
+ let at = writes->countWritesBeforeItem(writer)
162
+ switch writes->Array.get(at) {
163
+ | Some((idx, _)) if idx === writer => writes->Array.setUnsafe(at, (writer, entity))
164
+ | _ => writes->Array.splice(~start=at, ~remove=0, ~insert=[(writer, entity)])
165
+ }
166
+ | None => t.overlay->Map.set(key, [(writer, entity)])
142
167
  }
143
- writes->Array.push((writer, entity))
144
- t.overlay->Dict.set(key, writes)
145
168
  | None => ()
146
169
  }
147
170
  }
@@ -154,18 +177,13 @@ let overlayGet = (~item: Internal.item, ~entityName, ~scope, ~entityId) =>
154
177
  | Some(t) =>
155
178
  switch (
156
179
  t.indexOf->Utils.Map.get(item),
157
- t.overlay->Utils.Dict.dangerouslyGetNonOption(entityKey(~entityName, ~scope, ~entityId)),
180
+ t.overlay->Map.get(entityKey(~entityName, ~scope, ~entityId)),
158
181
  ) {
159
182
  | (Some(reader), Some(writes)) =>
160
- let found = ref(None)
161
- let foundAt = ref(t.executed)
162
- writes->Array.forEach(((writer, entity)) =>
163
- if writer > foundAt.contents && writer < reader {
164
- foundAt := writer
165
- found := Some(entity)
166
- }
167
- )
168
- found.contents
183
+ switch writes->Array.get(writes->countWritesBeforeItem(reader) - 1) {
184
+ | Some((writer, entity)) if writer > t.executed => Some(entity)
185
+ | _ => None
186
+ }
169
187
  | _ => None
170
188
  }
171
189
  }
@@ -1,7 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
- import * as Utils from "./Utils.res.mjs";
5
4
  import * as ChainId from "./ChainId.res.mjs";
6
5
  import * as Performance from "./bindings/Performance.res.mjs";
7
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -24,10 +23,10 @@ function start(items) {
24
23
  active.contents = {
25
24
  items: items,
26
25
  indexOf: indexOf,
27
- absentReaders: {},
26
+ absentReaders: new Map(),
28
27
  dirty: Stdlib_Array.make(items.length, false),
29
28
  preloadMs: Stdlib_Array.make(items.length, 0),
30
- overlay: {},
29
+ overlay: new Map(),
31
30
  window: Env.subgraphPreloadLookahead,
32
31
  executed: -1
33
32
  };
@@ -86,11 +85,11 @@ function recordAbsent(item, entityName, scope, entityId) {
86
85
  return;
87
86
  }
88
87
  let key = entityKey(entityName, scope, entityId);
89
- let readers = t.absentReaders[key];
88
+ let readers = t.absentReaders.get(key);
90
89
  if (readers !== undefined) {
91
90
  readers.push(idx);
92
91
  } else {
93
- t.absentReaders[key] = [idx];
92
+ t.absentReaders.set(key, [idx]);
94
93
  }
95
94
  }
96
95
 
@@ -99,10 +98,13 @@ function onSet(entityName, scope, entityId) {
99
98
  if (t === undefined) {
100
99
  return;
101
100
  }
101
+ if (t.absentReaders.size <= 0) {
102
+ return;
103
+ }
102
104
  let key = entityKey(entityName, scope, entityId);
103
- let readers = t.absentReaders[key];
105
+ let readers = t.absentReaders.get(key);
104
106
  if (readers !== undefined) {
105
- Utils.Dict.deleteInPlace(t.absentReaders, key);
107
+ t.absentReaders.delete(key);
106
108
  readers.forEach(idx => {
107
109
  if (idx > t.executed) {
108
110
  t.dirty[idx] = true;
@@ -117,6 +119,21 @@ function isActive() {
117
119
  return Stdlib_Option.isSome(active.contents);
118
120
  }
119
121
 
122
+ function countWritesBeforeItem(writes, item) {
123
+ let low = 0;
124
+ let high = writes.length;
125
+ while (low < high) {
126
+ let mid = (low + high | 0) / 2 | 0;
127
+ let match = writes[mid];
128
+ if (match[0] < item) {
129
+ low = mid + 1 | 0;
130
+ } else {
131
+ high = mid;
132
+ }
133
+ };
134
+ return low;
135
+ }
136
+
120
137
  function overlaySet(item, entityName, scope, entityId, entity) {
121
138
  let t = active.contents;
122
139
  if (t === undefined) {
@@ -127,13 +144,27 @@ function overlaySet(item, entityName, scope, entityId, entity) {
127
144
  return;
128
145
  }
129
146
  let key = entityKey(entityName, scope, entityId);
130
- let writes = t.overlay[key];
131
- let writes$1 = writes !== undefined ? writes.filter(param => param[0] !== writer) : [];
132
- writes$1.push([
133
- writer,
134
- entity
135
- ]);
136
- t.overlay[key] = writes$1;
147
+ let writes = t.overlay.get(key);
148
+ if (writes !== undefined) {
149
+ let at = countWritesBeforeItem(writes, writer);
150
+ let match = writes[at];
151
+ if (match !== undefined && match[0] === writer) {
152
+ writes[at] = [
153
+ writer,
154
+ entity
155
+ ];
156
+ return;
157
+ }
158
+ writes.splice(at, 0, [
159
+ writer,
160
+ entity
161
+ ]);
162
+ return;
163
+ }
164
+ t.overlay.set(key, [[
165
+ writer,
166
+ entity
167
+ ]]);
137
168
  }
138
169
 
139
170
  function overlayGet(item, entityName, scope, entityId) {
@@ -142,28 +173,17 @@ function overlayGet(item, entityName, scope, entityId) {
142
173
  return;
143
174
  }
144
175
  let match = t.indexOf.get(item);
145
- let match$1 = t.overlay[entityKey(entityName, scope, entityId)];
176
+ let match$1 = t.overlay.get(entityKey(entityName, scope, entityId));
146
177
  if (match === undefined) {
147
178
  return;
148
179
  }
149
180
  if (match$1 === undefined) {
150
181
  return;
151
182
  }
152
- let found = {
153
- contents: undefined
154
- };
155
- let foundAt = {
156
- contents: t.executed
157
- };
158
- match$1.forEach(param => {
159
- let writer = param[0];
160
- if (writer > foundAt.contents && writer < match) {
161
- foundAt.contents = writer;
162
- found.contents = Primitive_option.some(param[1]);
163
- return;
164
- }
165
- });
166
- return found.contents;
183
+ let match$2 = match$1[countWritesBeforeItem(match$1, match) - 1 | 0];
184
+ if (match$2 !== undefined && match$2[0] > t.executed) {
185
+ return Primitive_option.some(match$2[1]);
186
+ }
167
187
  }
168
188
 
169
189
  function afterItem(t, index, preload) {
@@ -190,6 +210,7 @@ export {
190
210
  recordAbsent,
191
211
  onSet,
192
212
  isActive,
213
+ countWritesBeforeItem,
193
214
  overlaySet,
194
215
  overlayGet,
195
216
  afterItem,
package/src/Rollback.res CHANGED
@@ -130,7 +130,6 @@ and executeRollback = async (
130
130
  switch await (state->IndexerState.persistence).storage.getRollbackTargetCheckpoint(
131
131
  ~reorgChainId=reorgChain,
132
132
  ~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
133
- ~sequence=(state->IndexerState.config).checkpointSequence,
134
133
  ) {
135
134
  | Some(checkpointId) => checkpointId
136
135
  | None => 0n
@@ -42,7 +42,7 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
42
42
  Logger.info(logger, "Started rollback on reorg", undefined);
43
43
  ChainState.setRollbackTargetBlock(IndexerState.getChainState(state, reorgChain), rollbackTargetBlockNumber);
44
44
  await Writing.flush(state);
45
- let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber, IndexerState.config(state).checkpointSequence);
45
+ let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber);
46
46
  let rollbackTargetCheckpointId = checkpointId !== undefined ? checkpointId : 0n;
47
47
  let config = IndexerState.config(state);
48
48
  let next = RollbackFloors.make(config.checkpointSequence, ChainMap.keys(config.chainMap), reorgChain, rollbackTargetCheckpointId, rollbackTargetBlockNumber);
@@ -16,9 +16,7 @@ let make = (
16
16
  ) => {
17
17
  if maxReorgDepth > 0 && shouldRollbackOnReorg {
18
18
  let checkpointIds = Utils.Array.jsArrayCreate(chainReorgCheckpoints->Array.length)
19
- let checkpointBlockNumbers = Utils.Array.jsArrayCreate(
20
- chainReorgCheckpoints->Array.length,
21
- )
19
+ let checkpointBlockNumbers = Utils.Array.jsArrayCreate(chainReorgCheckpoints->Array.length)
22
20
  chainReorgCheckpoints->Array.forEachWithIndex((checkpoint, idx) => {
23
21
  checkpointIds->Array.setUnsafe(idx, checkpoint.checkpointId)
24
22
  checkpointBlockNumbers->Array.setUnsafe(idx, checkpoint.blockNumber)
@@ -36,7 +34,9 @@ let make = (
36
34
  let getSafeCheckpointId = (safeCheckpointTracking: t, ~sourceBlockNumber: int): option<
37
35
  Internal.checkpointId,
38
36
  > => {
39
- let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth
37
+ // The first block reorg detection compares is itself reorgable, so the safe
38
+ // block is the one below it.
39
+ let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth - 1
40
40
 
41
41
  switch safeCheckpointTracking.checkpointIds {
42
42
  | [] => None
@@ -20,7 +20,7 @@ function make(maxReorgDepth, shouldRollbackOnReorg, chainReorgCheckpoints) {
20
20
  }
21
21
 
22
22
  function getSafeCheckpointId(safeCheckpointTracking, sourceBlockNumber) {
23
- let safeBlockNumber = sourceBlockNumber - safeCheckpointTracking.maxReorgDepth | 0;
23
+ let safeBlockNumber = (sourceBlockNumber - safeCheckpointTracking.maxReorgDepth | 0) - 1 | 0;
24
24
  let match = safeCheckpointTracking.checkpointIds;
25
25
  if (match.length === 0) {
26
26
  return;
@@ -0,0 +1,106 @@
1
+ // Values as the addon declares each staged field has to arrive, compiled once
2
+ // per layout and written into a stage.
3
+
4
+ @send external getTime: unknown => float = "getTime"
5
+ @send external toFixed: unknown => string = "toFixed"
6
+ @val @scope("Array") external isArray: unknown => bool = "isArray"
7
+
8
+ // A list element as its JSON: the encoding a scalar of its type would take,
9
+ // except that a boolean stays one and bytes become their values.
10
+ %%private(
11
+ let elementOf = (encoding: Staging.encoding, ~column): (unknown => unknown) =>
12
+ switch encoding {
13
+ | Number =>
14
+ value =>
15
+ value->Staging.toNumber->Staging.finiteOrThrow(~column)->(Utils.magic: float => unknown)
16
+ | Date => value => value->getTime->(Utils.magic: float => unknown)
17
+ | Digits => value => value->Staging.stringOf->(Utils.magic: string => unknown)
18
+ | Decimal => value => value->toFixed->(Utils.magic: string => unknown)
19
+ | Document => value => value->Sql.canonicalDocument->(Utils.magic: string => unknown)
20
+ | Bytes =>
21
+ value =>
22
+ value
23
+ ->(Utils.magic: unknown => Array.arrayLike<int>)
24
+ ->Array.fromArrayLike
25
+ ->(Utils.magic: array<int> => unknown)
26
+ | Bool | Text | List => value => value
27
+ }
28
+ )
29
+
30
+ %%private(
31
+ let rec mapList = (value: unknown, element: unknown => unknown): unknown =>
32
+ if value->isArray {
33
+ value
34
+ ->(Utils.magic: unknown => array<unknown>)
35
+ ->Array.map(item => item->mapList(element))
36
+ ->(Utils.magic: array<unknown> => unknown)
37
+ } else if value === %raw(`undefined`) || value === %raw(`null`) {
38
+ %raw(`null`)
39
+ } else {
40
+ element(value)
41
+ }
42
+ )
43
+
44
+ // What a present value becomes before it is written to its slot.
45
+ %%private(
46
+ let encoderOf = ({name, encoding, element}: Staging.stagedField): (unknown => unknown) =>
47
+ switch encoding {
48
+ | Bool =>
49
+ value => (value->(Utils.magic: unknown => bool) ? 1. : 0.)->(Utils.magic: float => unknown)
50
+ | Date => value => value->getTime->(Utils.magic: float => unknown)
51
+ | Decimal => value => value->toFixed->(Utils.magic: string => unknown)
52
+ | Document => value => value->Sql.canonicalDocument->(Utils.magic: string => unknown)
53
+ | List =>
54
+ let encode = elementOf(element, ~column=name)
55
+ // A document can be a list itself, so a list of them is one level deep.
56
+ let mapList = switch element {
57
+ | Document =>
58
+ (value, encode) =>
59
+ value
60
+ ->(Utils.magic: unknown => array<unknown>)
61
+ ->Array.map(item => item->Staging.isAbsent ? %raw(`null`) : encode(item))
62
+ ->(Utils.magic: array<unknown> => unknown)
63
+ | _ => mapList
64
+ }
65
+ value =>
66
+ value
67
+ ->mapList(encode)
68
+ ->(Utils.magic: unknown => JSON.t)
69
+ ->JSON.stringify
70
+ ->Utils.replaceLoneSurrogateEscapes
71
+ ->(Utils.magic: string => unknown)
72
+ | Number | Text | Digits | Bytes => value => value
73
+ }
74
+ )
75
+
76
+ // A layout's fields compiled once: the slot each takes and how a value of it
77
+ // is written there.
78
+ type writer = {field: Staging.stagedField, encode: unknown => unknown}
79
+
80
+ let writersOf = (fields: array<Staging.stagedField>) =>
81
+ fields->Array.map(field => {field, encode: encoderOf(field)})
82
+
83
+ let columnsOf = (writers: array<writer>): array<Staging.column> =>
84
+ writers->Array.map(({field}) => {
85
+ Staging.name: field.name,
86
+ kind: field.encoding->Staging.kindOf,
87
+ isNullable: field.isNullable,
88
+ })
89
+
90
+ // JSON `null` is a document of its own, which a column that can't be NULL
91
+ // stores as one. Left out, the value is no document and is refused like any
92
+ // other.
93
+ let writeField = (stage, ~writers: array<writer>, ~column, ~row, value: unknown) => {
94
+ let {field, encode} = writers->Array.getUnsafe(column)
95
+ if value->Staging.isAbsent {
96
+ stage->Staging.writeValue(
97
+ ~column,
98
+ ~row,
99
+ field.encoding === Staging.Document && !field.isNullable && value === %raw(`null`)
100
+ ? "null"->(Utils.magic: string => unknown)
101
+ : value,
102
+ )
103
+ } else {
104
+ stage->Staging.writeValue(~column, ~row, encode(value))
105
+ }
106
+ }