envio 3.0.0-alpha.1 → 3.0.0-alpha.3

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 (71) hide show
  1. package/evm.schema.json +63 -48
  2. package/fuel.schema.json +35 -31
  3. package/index.d.ts +1 -0
  4. package/package.json +15 -11
  5. package/rescript.json +1 -1
  6. package/src/Batch.res.mjs +1 -1
  7. package/src/Benchmark.res +394 -0
  8. package/src/Benchmark.res.mjs +398 -0
  9. package/src/ChainFetcher.res +459 -0
  10. package/src/ChainFetcher.res.mjs +281 -0
  11. package/src/ChainManager.res +179 -0
  12. package/src/ChainManager.res.mjs +139 -0
  13. package/src/Config.res +18 -7
  14. package/src/Config.res.mjs +28 -7
  15. package/src/Ecosystem.res +25 -0
  16. package/src/Ecosystem.res.mjs +29 -0
  17. package/src/Env.res +243 -0
  18. package/src/Env.res.mjs +270 -0
  19. package/src/Envio.gen.ts +9 -1
  20. package/src/Envio.res +12 -9
  21. package/src/EventProcessing.res +476 -0
  22. package/src/EventProcessing.res.mjs +341 -0
  23. package/src/EventRegister.res +4 -15
  24. package/src/EventRegister.res.mjs +3 -9
  25. package/src/EventRegister.resi +2 -8
  26. package/src/FetchState.res +54 -29
  27. package/src/FetchState.res.mjs +62 -35
  28. package/src/GlobalState.res +1169 -0
  29. package/src/GlobalState.res.mjs +1196 -0
  30. package/src/Internal.gen.ts +3 -14
  31. package/src/Internal.res +4 -12
  32. package/src/LoadLayer.res +444 -0
  33. package/src/LoadLayer.res.mjs +296 -0
  34. package/src/LoadLayer.resi +32 -0
  35. package/src/Prometheus.res +8 -8
  36. package/src/Prometheus.res.mjs +10 -10
  37. package/src/ReorgDetection.res +6 -10
  38. package/src/ReorgDetection.res.mjs +6 -6
  39. package/src/UserContext.res +356 -0
  40. package/src/UserContext.res.mjs +238 -0
  41. package/src/bindings/DateFns.res +71 -0
  42. package/src/bindings/DateFns.res.mjs +22 -0
  43. package/src/bindings/EventSource.res +13 -0
  44. package/src/bindings/EventSource.res.mjs +2 -0
  45. package/src/sources/Evm.res +87 -0
  46. package/src/sources/Evm.res.mjs +105 -0
  47. package/src/sources/EvmChain.res +95 -0
  48. package/src/sources/EvmChain.res.mjs +61 -0
  49. package/src/sources/Fuel.res +19 -34
  50. package/src/sources/Fuel.res.mjs +34 -16
  51. package/src/sources/FuelSDK.res +37 -0
  52. package/src/sources/FuelSDK.res.mjs +29 -0
  53. package/src/sources/HyperFuel.res +2 -2
  54. package/src/sources/HyperFuel.resi +1 -1
  55. package/src/sources/HyperFuelClient.res +2 -2
  56. package/src/sources/HyperFuelSource.res +8 -8
  57. package/src/sources/HyperFuelSource.res.mjs +5 -5
  58. package/src/sources/HyperSyncHeightStream.res +179 -0
  59. package/src/sources/HyperSyncHeightStream.res.mjs +127 -0
  60. package/src/sources/HyperSyncSource.res +7 -65
  61. package/src/sources/HyperSyncSource.res.mjs +10 -66
  62. package/src/sources/RpcSource.res +4 -4
  63. package/src/sources/RpcSource.res.mjs +3 -3
  64. package/src/sources/Solana.res +59 -0
  65. package/src/sources/Solana.res.mjs +79 -0
  66. package/src/sources/Source.res +2 -2
  67. package/src/sources/SourceManager.res +24 -32
  68. package/src/sources/SourceManager.res.mjs +20 -20
  69. package/src/sources/SourceManager.resi +4 -5
  70. package/src/Platform.res +0 -140
  71. package/src/Platform.res.mjs +0 -170
@@ -26,11 +26,7 @@ export type genericContractRegisterArgs<event,context> = { readonly event: event
26
26
 
27
27
  export type genericContractRegister<args> = $$genericContractRegister<args>;
28
28
 
29
- export type genericHandlerArgs<event,context,loaderReturn> = {
30
- readonly event: event;
31
- readonly context: context;
32
- readonly loaderReturn: loaderReturn
33
- };
29
+ export type genericHandlerArgs<event,context> = { readonly event: event; readonly context: context };
34
30
 
35
31
  export type genericHandler<args> = (_1:args) => Promise<void>;
36
32
 
@@ -46,17 +42,10 @@ export type genericHandlerWithLoader<loader,handler,eventFilters> = {
46
42
  readonly loader: loader;
47
43
  readonly handler: handler;
48
44
  readonly wildcard?: boolean;
49
- readonly eventFilters?: eventFilters;
50
- /** @deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration. */
51
- readonly preRegisterDynamicContracts?: boolean
45
+ readonly eventFilters?: eventFilters
52
46
  };
53
47
 
54
- export type eventOptions<eventFilters> = {
55
- readonly wildcard?: boolean;
56
- readonly eventFilters?: eventFilters;
57
- /** @deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration. */
58
- readonly preRegisterDynamicContracts?: boolean
59
- };
48
+ export type eventOptions<eventFilters> = { readonly wildcard?: boolean; readonly eventFilters?: eventFilters };
60
49
 
61
50
  export type fuelSupplyParams = { readonly subId: string; readonly amount: bigint };
62
51
 
package/src/Internal.res CHANGED
@@ -37,10 +37,9 @@ type contractRegisterArgs = genericContractRegisterArgs<event, contractRegisterC
37
37
  type contractRegister = genericContractRegister<contractRegisterArgs>
38
38
 
39
39
  @genType
40
- type genericHandlerArgs<'event, 'context, 'loaderReturn> = {
40
+ type genericHandlerArgs<'event, 'context> = {
41
41
  event: 'event,
42
42
  context: 'context,
43
- loaderReturn: 'loaderReturn,
44
43
  }
45
44
  @genType
46
45
  type genericHandler<'args> = 'args => promise<unit>
@@ -81,10 +80,6 @@ type genericHandlerWithLoader<'loader, 'handler, 'eventFilters> = {
81
80
  handler: 'handler,
82
81
  wildcard?: bool,
83
82
  eventFilters?: 'eventFilters,
84
- /**
85
- @deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration.
86
- */
87
- preRegisterDynamicContracts?: bool,
88
83
  }
89
84
 
90
85
  // This is private so it's not manually constructed internally
@@ -170,11 +165,12 @@ type eventItem = private {
170
165
  event: event,
171
166
  }
172
167
 
173
- // Opaque type to support both EVM and Fuel platforms
168
+ // Opaque type to support both EVM and other ecosystems
174
169
  type blockEvent
175
170
 
176
171
  type onBlockArgs = {
177
- block: blockEvent,
172
+ slot?: int,
173
+ block?: blockEvent,
178
174
  context: handlerContext,
179
175
  }
180
176
 
@@ -225,10 +221,6 @@ external setItemDcs: (item, dcs) => unit = "dcs"
225
221
  type eventOptions<'eventFilters> = {
226
222
  wildcard?: bool,
227
223
  eventFilters?: 'eventFilters,
228
- /**
229
- @deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration.
230
- */
231
- preRegisterDynamicContracts?: bool,
232
224
  }
233
225
 
234
226
  @genType
@@ -0,0 +1,444 @@
1
+ open Belt
2
+
3
+ let loadById = (
4
+ ~loadManager,
5
+ ~persistence: Persistence.t,
6
+ ~entityConfig: Internal.entityConfig,
7
+ ~inMemoryStore,
8
+ ~shouldGroup,
9
+ ~item,
10
+ ~entityId,
11
+ ) => {
12
+ let key = `${entityConfig.name}.get`
13
+ let inMemTable = inMemoryStore->InMemoryStore.getInMemTable(~entityConfig)
14
+
15
+ let load = async (idsToLoad, ~onError as _) => {
16
+ let timerRef = Prometheus.StorageLoad.startOperation(~operation=key)
17
+
18
+ // Since LoadManager.call prevents registerign entities already existing in the inMemoryStore,
19
+ // we can be sure that we load only the new ones.
20
+ let dbEntities = try {
21
+ await (persistence->Persistence.getInitializedStorageOrThrow).loadByIdsOrThrow(
22
+ ~table=entityConfig.table,
23
+ ~rowsSchema=entityConfig.rowsSchema,
24
+ ~ids=idsToLoad,
25
+ )
26
+ } catch {
27
+ | Persistence.StorageError({message, reason}) =>
28
+ reason->ErrorHandling.mkLogAndRaise(~logger=item->Logging.getItemLogger, ~msg=message)
29
+ }
30
+
31
+ let entitiesMap = Js.Dict.empty()
32
+ for idx in 0 to dbEntities->Array.length - 1 {
33
+ let entity = dbEntities->Js.Array2.unsafe_get(idx)
34
+ entitiesMap->Js.Dict.set(entity.id, entity)
35
+ }
36
+ idsToLoad->Js.Array2.forEach(entityId => {
37
+ // Set the entity in the in memory store
38
+ // without overwriting existing values
39
+ // which might be newer than what we got from db
40
+ inMemTable->InMemoryTable.Entity.initValue(
41
+ ~allowOverWriteEntity=false,
42
+ ~key=entityId,
43
+ ~entity=entitiesMap->Utils.Dict.dangerouslyGetNonOption(entityId),
44
+ )
45
+ })
46
+
47
+ timerRef->Prometheus.StorageLoad.endOperation(
48
+ ~operation=key,
49
+ ~whereSize=idsToLoad->Array.length,
50
+ ~size=dbEntities->Array.length,
51
+ )
52
+ }
53
+
54
+ loadManager->LoadManager.call(
55
+ ~key,
56
+ ~load,
57
+ ~shouldGroup,
58
+ ~hasher=LoadManager.noopHasher,
59
+ ~getUnsafeInMemory=inMemTable->InMemoryTable.Entity.getUnsafe,
60
+ ~hasInMemory=hash => inMemTable.table->InMemoryTable.hasByHash(hash),
61
+ ~input=entityId,
62
+ )
63
+ }
64
+
65
+ let callEffect = (
66
+ ~effect: Internal.effect,
67
+ ~arg: Internal.effectArgs,
68
+ ~inMemTable: InMemoryStore.effectCacheInMemTable,
69
+ ~timerRef,
70
+ ~onError,
71
+ ) => {
72
+ let effectName = effect.name
73
+ let hadActiveCalls = effect.activeCallsCount > 0
74
+ effect.activeCallsCount = effect.activeCallsCount + 1
75
+ Prometheus.EffectCalls.activeCallsCount->Prometheus.SafeGauge.handleInt(
76
+ ~labels=effectName,
77
+ ~value=effect.activeCallsCount,
78
+ )
79
+
80
+ if hadActiveCalls {
81
+ let elapsed = Hrtime.millisBetween(~from=effect.prevCallStartTimerRef, ~to=timerRef)
82
+ if elapsed > 0 {
83
+ Prometheus.EffectCalls.timeCounter->Prometheus.SafeCounter.incrementMany(
84
+ ~labels=effectName,
85
+ ~value=Hrtime.millisBetween(~from=effect.prevCallStartTimerRef, ~to=timerRef),
86
+ )
87
+ }
88
+ }
89
+ effect.prevCallStartTimerRef = timerRef
90
+
91
+ effect.handler(arg)
92
+ ->Promise.thenResolve(output => {
93
+ inMemTable.dict->Js.Dict.set(arg.cacheKey, output)
94
+ if arg.context.cache {
95
+ inMemTable.idsToStore->Array.push(arg.cacheKey)->ignore
96
+ }
97
+ })
98
+ ->Promise.catchResolve(exn => {
99
+ onError(~inputKey=arg.cacheKey, ~exn)
100
+ })
101
+ ->Promise.finally(() => {
102
+ effect.activeCallsCount = effect.activeCallsCount - 1
103
+ Prometheus.EffectCalls.activeCallsCount->Prometheus.SafeGauge.handleInt(
104
+ ~labels=effectName,
105
+ ~value=effect.activeCallsCount,
106
+ )
107
+ let newTimer = Hrtime.makeTimer()
108
+ Prometheus.EffectCalls.timeCounter->Prometheus.SafeCounter.incrementMany(
109
+ ~labels=effectName,
110
+ ~value=Hrtime.millisBetween(~from=effect.prevCallStartTimerRef, ~to=newTimer),
111
+ )
112
+ effect.prevCallStartTimerRef = newTimer
113
+
114
+ Prometheus.EffectCalls.totalCallsCount->Prometheus.SafeCounter.increment(~labels=effectName)
115
+ Prometheus.EffectCalls.sumTimeCounter->Prometheus.SafeCounter.incrementMany(
116
+ ~labels=effectName,
117
+ ~value=timerRef->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis,
118
+ )
119
+ })
120
+ }
121
+
122
+ let rec executeWithRateLimit = (
123
+ ~effect: Internal.effect,
124
+ ~effectArgs: array<Internal.effectArgs>,
125
+ ~inMemTable,
126
+ ~onError,
127
+ ~isFromQueue: bool,
128
+ ) => {
129
+ let effectName = effect.name
130
+
131
+ let timerRef = Hrtime.makeTimer()
132
+ let promises = []
133
+
134
+ switch effect.rateLimit {
135
+ | None =>
136
+ // No rate limiting - execute all immediately
137
+ for idx in 0 to effectArgs->Array.length - 1 {
138
+ promises
139
+ ->Array.push(
140
+ callEffect(
141
+ ~effect,
142
+ ~arg=effectArgs->Array.getUnsafe(idx),
143
+ ~inMemTable,
144
+ ~timerRef,
145
+ ~onError,
146
+ )->Promise.ignoreValue,
147
+ )
148
+ ->ignore
149
+ }
150
+
151
+ | Some(state) =>
152
+ let now = Js.Date.now()
153
+
154
+ // Check if we need to reset the window
155
+ if now >= state.windowStartTime +. state.durationMs->Int.toFloat {
156
+ state.availableCalls = state.callsPerDuration
157
+ state.windowStartTime = now
158
+ state.nextWindowPromise = None
159
+ }
160
+
161
+ // Split into immediate and queued
162
+ let immediateCount = Js.Math.min_int(state.availableCalls, effectArgs->Array.length)
163
+ let immediateArgs = effectArgs->Array.slice(~offset=0, ~len=immediateCount)
164
+ let queuedArgs = effectArgs->Array.sliceToEnd(immediateCount)
165
+
166
+ // Update available calls
167
+ state.availableCalls = state.availableCalls - immediateCount
168
+
169
+ // Call immediate effects
170
+ for idx in 0 to immediateArgs->Array.length - 1 {
171
+ promises
172
+ ->Array.push(
173
+ callEffect(
174
+ ~effect,
175
+ ~arg=immediateArgs->Array.getUnsafe(idx),
176
+ ~inMemTable,
177
+ ~timerRef,
178
+ ~onError,
179
+ )->Promise.ignoreValue,
180
+ )
181
+ ->ignore
182
+ }
183
+
184
+ if immediateCount > 0 && isFromQueue {
185
+ // Update queue count metric
186
+ state.queueCount = state.queueCount - immediateCount
187
+ Prometheus.EffectQueueCount.set(~count=state.queueCount, ~effectName)
188
+ }
189
+
190
+ // Handle queued items
191
+ if queuedArgs->Utils.Array.notEmpty {
192
+ if !isFromQueue {
193
+ // Update queue count metric
194
+ state.queueCount = state.queueCount + queuedArgs->Array.length
195
+ Prometheus.EffectQueueCount.set(~count=state.queueCount, ~effectName)
196
+ }
197
+
198
+ let millisUntilReset = ref(0)
199
+ let nextWindowPromise = switch state.nextWindowPromise {
200
+ | Some(p) => p
201
+ | None =>
202
+ millisUntilReset :=
203
+ (state.windowStartTime +. state.durationMs->Int.toFloat -. now)->Float.toInt
204
+ let p = Utils.delay(millisUntilReset.contents)
205
+ state.nextWindowPromise = Some(p)
206
+ p
207
+ }
208
+
209
+ // Wait for next window and recursively process queue
210
+ promises
211
+ ->Array.push(
212
+ nextWindowPromise
213
+ ->Promise.then(() => {
214
+ if millisUntilReset.contents > 0 {
215
+ Prometheus.EffectQueueCount.timeCounter->Prometheus.SafeCounter.incrementMany(
216
+ ~labels=effectName,
217
+ ~value=millisUntilReset.contents,
218
+ )
219
+ }
220
+ executeWithRateLimit(
221
+ ~effect,
222
+ ~effectArgs=queuedArgs,
223
+ ~inMemTable,
224
+ ~onError,
225
+ ~isFromQueue=true,
226
+ )
227
+ })
228
+ ->Promise.ignoreValue,
229
+ )
230
+ ->ignore
231
+ }
232
+ }
233
+
234
+ // Wait for all to complete
235
+ promises->Promise.all
236
+ }
237
+
238
+ let loadEffect = (
239
+ ~loadManager,
240
+ ~persistence: Persistence.t,
241
+ ~effect: Internal.effect,
242
+ ~effectArgs,
243
+ ~inMemoryStore,
244
+ ~shouldGroup,
245
+ ~item,
246
+ ) => {
247
+ let effectName = effect.name
248
+ let key = `${effectName}.effect`
249
+ let inMemTable = inMemoryStore->InMemoryStore.getEffectInMemTable(~effect)
250
+
251
+ let load = async (args, ~onError) => {
252
+ let idsToLoad = args->Js.Array2.map((arg: Internal.effectArgs) => arg.cacheKey)
253
+ let idsFromCache = Utils.Set.make()
254
+
255
+ if (
256
+ switch persistence.storageStatus {
257
+ | Ready({cache}) => cache->Utils.Dict.has(effectName)
258
+ | _ => false
259
+ }
260
+ ) {
261
+ let timerRef = Prometheus.StorageLoad.startOperation(~operation=key)
262
+ let {table, outputSchema} = effect.storageMeta
263
+
264
+ let dbEntities = try {
265
+ await (persistence->Persistence.getInitializedStorageOrThrow).loadByIdsOrThrow(
266
+ ~table,
267
+ ~rowsSchema=Internal.effectCacheItemRowsSchema,
268
+ ~ids=idsToLoad,
269
+ )
270
+ } catch {
271
+ | exn =>
272
+ item
273
+ ->Logging.getItemLogger
274
+ ->Logging.childWarn({
275
+ "msg": `Failed to load cache effect cache. The indexer will continue working, but the effect will not be able to use the cache.`,
276
+ "err": exn->Utils.prettifyExn,
277
+ "effect": effectName,
278
+ })
279
+ []
280
+ }
281
+
282
+ dbEntities->Js.Array2.forEach(dbEntity => {
283
+ try {
284
+ let output = dbEntity.output->S.parseOrThrow(outputSchema)
285
+ idsFromCache->Utils.Set.add(dbEntity.id)->ignore
286
+ inMemTable.dict->Js.Dict.set(dbEntity.id, output)
287
+ } catch {
288
+ | S.Raised(error) =>
289
+ inMemTable.invalidationsCount = inMemTable.invalidationsCount + 1
290
+ Prometheus.EffectCacheInvalidationsCount.increment(~effectName)
291
+ item
292
+ ->Logging.getItemLogger
293
+ ->Logging.childTrace({
294
+ "msg": "Invalidated effect cache",
295
+ "input": dbEntity.id,
296
+ "effect": effectName,
297
+ "err": error->S.Error.message,
298
+ })
299
+ }
300
+ })
301
+
302
+ timerRef->Prometheus.StorageLoad.endOperation(
303
+ ~operation=key,
304
+ ~whereSize=idsToLoad->Array.length,
305
+ ~size=dbEntities->Array.length,
306
+ )
307
+ }
308
+
309
+ let remainingCallsCount = idsToLoad->Array.length - idsFromCache->Utils.Set.size
310
+ if remainingCallsCount > 0 {
311
+ let argsToCall = []
312
+ for idx in 0 to args->Array.length - 1 {
313
+ let arg = args->Array.getUnsafe(idx)
314
+ if !(idsFromCache->Utils.Set.has(arg.cacheKey)) {
315
+ argsToCall->Array.push(arg)->ignore
316
+ }
317
+ }
318
+
319
+ if argsToCall->Utils.Array.notEmpty {
320
+ await executeWithRateLimit(
321
+ ~effect,
322
+ ~effectArgs=argsToCall,
323
+ ~inMemTable,
324
+ ~onError,
325
+ ~isFromQueue=false,
326
+ )->Promise.ignoreValue
327
+ }
328
+ }
329
+ }
330
+
331
+ loadManager->LoadManager.call(
332
+ ~key,
333
+ ~load,
334
+ ~shouldGroup,
335
+ ~hasher=args => args.cacheKey,
336
+ ~getUnsafeInMemory=hash => inMemTable.dict->Js.Dict.unsafeGet(hash),
337
+ ~hasInMemory=hash => inMemTable.dict->Utils.Dict.has(hash),
338
+ ~input=effectArgs,
339
+ )
340
+ }
341
+
342
+ let loadByField = (
343
+ ~loadManager,
344
+ ~persistence: Persistence.t,
345
+ ~operator: TableIndices.Operator.t,
346
+ ~entityConfig: Internal.entityConfig,
347
+ ~inMemoryStore,
348
+ ~fieldName,
349
+ ~fieldValueSchema,
350
+ ~shouldGroup,
351
+ ~item,
352
+ ~fieldValue,
353
+ ) => {
354
+ let operatorCallName = switch operator {
355
+ | Eq => "eq"
356
+ | Gt => "gt"
357
+ | Lt => "lt"
358
+ }
359
+ let key = `${entityConfig.name}.getWhere.${fieldName}.${operatorCallName}`
360
+ let inMemTable = inMemoryStore->InMemoryStore.getInMemTable(~entityConfig)
361
+
362
+ let load = async (fieldValues: array<'fieldValue>, ~onError as _) => {
363
+ let timerRef = Prometheus.StorageLoad.startOperation(~operation=key)
364
+
365
+ let size = ref(0)
366
+
367
+ let indiciesToLoad = fieldValues->Js.Array2.map((fieldValue): TableIndices.Index.t => {
368
+ Single({
369
+ fieldName,
370
+ fieldValue: TableIndices.FieldValue.castFrom(fieldValue),
371
+ operator,
372
+ })
373
+ })
374
+
375
+ let _ =
376
+ await indiciesToLoad
377
+ ->Js.Array2.map(async index => {
378
+ inMemTable->InMemoryTable.Entity.addEmptyIndex(~index)
379
+ try {
380
+ let entities = await (
381
+ persistence->Persistence.getInitializedStorageOrThrow
382
+ ).loadByFieldOrThrow(
383
+ ~operator=switch index {
384
+ | Single({operator: Gt}) => #">"
385
+ | Single({operator: Eq}) => #"="
386
+ | Single({operator: Lt}) => #"<"
387
+ },
388
+ ~table=entityConfig.table,
389
+ ~rowsSchema=entityConfig.rowsSchema,
390
+ ~fieldName=index->TableIndices.Index.getFieldName,
391
+ ~fieldValue=switch index {
392
+ | Single({fieldValue}) => fieldValue
393
+ },
394
+ ~fieldSchema=fieldValueSchema->(
395
+ Utils.magic: S.t<'fieldValue> => S.t<TableIndices.FieldValue.t>
396
+ ),
397
+ )
398
+
399
+ entities->Array.forEach(entity => {
400
+ //Set the entity in the in memory store
401
+ inMemTable->InMemoryTable.Entity.initValue(
402
+ ~allowOverWriteEntity=false,
403
+ ~key=entity.id,
404
+ ~entity=Some(entity),
405
+ )
406
+ })
407
+
408
+ size := size.contents + entities->Array.length
409
+ } catch {
410
+ | Persistence.StorageError({message, reason}) =>
411
+ reason->ErrorHandling.mkLogAndRaise(
412
+ ~logger=Logging.createChildFrom(
413
+ ~logger=item->Logging.getItemLogger,
414
+ ~params={
415
+ "operator": operatorCallName,
416
+ "tableName": entityConfig.table.tableName,
417
+ "fieldName": fieldName,
418
+ "fieldValue": fieldValue,
419
+ },
420
+ ),
421
+ ~msg=message,
422
+ )
423
+ }
424
+ })
425
+ ->Promise.all
426
+
427
+ timerRef->Prometheus.StorageLoad.endOperation(
428
+ ~operation=key,
429
+ ~whereSize=fieldValues->Array.length,
430
+ ~size=size.contents,
431
+ )
432
+ }
433
+
434
+ loadManager->LoadManager.call(
435
+ ~key,
436
+ ~load,
437
+ ~input=fieldValue,
438
+ ~shouldGroup,
439
+ ~hasher=fieldValue =>
440
+ fieldValue->TableIndices.FieldValue.castFrom->TableIndices.FieldValue.toString,
441
+ ~getUnsafeInMemory=inMemTable->InMemoryTable.Entity.getUnsafeOnIndex(~fieldName, ~operator),
442
+ ~hasInMemory=inMemTable->InMemoryTable.Entity.hasIndex(~fieldName, ~operator),
443
+ )
444
+ }