envio 3.15.0-subgraph → 3.16.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 (186) 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 +9 -10
  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/Bin.res +0 -7
  11. package/src/Bin.res.mjs +0 -8
  12. package/src/ChainFetching.res +17 -456
  13. package/src/ChainFetching.res.mjs +33 -404
  14. package/src/ChainState.res +48 -83
  15. package/src/ChainState.res.mjs +83 -115
  16. package/src/ChainState.resi +0 -26
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +7 -16
  20. package/src/Config.res.mjs +4 -11
  21. package/src/ContractRegisterContext.res +6 -3
  22. package/src/ContractRegisterContext.res.mjs +2 -4
  23. package/src/Core.res +5 -136
  24. package/src/Core.res.mjs +1 -90
  25. package/src/CrossChainState.res +2 -8
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +4 -7
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectState.res +10 -2
  30. package/src/EffectState.res.mjs +8 -4
  31. package/src/EffectState.resi +3 -1
  32. package/src/EntityId.res +2 -7
  33. package/src/EntityId.res.mjs +1 -8
  34. package/src/EntityTables.res +4 -1
  35. package/src/EntityTables.res.mjs +1 -1
  36. package/src/Env.res +0 -16
  37. package/src/Env.res.mjs +0 -11
  38. package/src/EventProcessing.res +1 -38
  39. package/src/EventProcessing.res.mjs +1 -28
  40. package/src/FetchState.res +160 -319
  41. package/src/FetchState.res.mjs +182 -292
  42. package/src/FinalizeBackfill.res +2 -4
  43. package/src/FinalizeBackfill.res.mjs +3 -4
  44. package/src/HandlerLoader.res +15 -39
  45. package/src/HandlerLoader.res.mjs +8 -27
  46. package/src/HandlerRegister.res +0 -4
  47. package/src/HandlerRegister.res.mjs +2 -5
  48. package/src/HandlerRegister.resi +0 -2
  49. package/src/InMemoryTable.res +17 -16
  50. package/src/InMemoryTable.res.mjs +14 -15
  51. package/src/IndexerState.res +28 -4
  52. package/src/IndexerState.res.mjs +19 -6
  53. package/src/IndexerState.resi +4 -0
  54. package/src/Internal.res +0 -110
  55. package/src/Internal.res.mjs +1 -115
  56. package/src/LoadLayer.res +21 -47
  57. package/src/LoadLayer.res.mjs +47 -72
  58. package/src/LoadLayer.resi +0 -26
  59. package/src/Main.res +1 -14
  60. package/src/Main.res.mjs +3 -4
  61. package/src/Persistence.res +3 -23
  62. package/src/Persistence.res.mjs +3 -3
  63. package/src/Rollback.res +0 -1
  64. package/src/Rollback.res.mjs +1 -1
  65. package/src/RunScope.res +0 -3
  66. package/src/SafeCheckpointTracking.res +4 -4
  67. package/src/SafeCheckpointTracking.res.mjs +1 -1
  68. package/src/SimulateItems.res +26 -42
  69. package/src/SimulateItems.res.mjs +16 -40
  70. package/src/StagedRows.res +106 -0
  71. package/src/StagedRows.res.mjs +102 -0
  72. package/src/Staging.res +32 -14
  73. package/src/Staging.res.mjs +17 -20
  74. package/src/Storage.res +277 -998
  75. package/src/Storage.res.mjs +216 -731
  76. package/src/Supervisor.res +75 -56
  77. package/src/Supervisor.res.mjs +55 -26
  78. package/src/TestIndexer.res +22 -27
  79. package/src/TestIndexer.res.mjs +20 -17
  80. package/src/Throttler.res +15 -8
  81. package/src/Throttler.res.mjs +9 -6
  82. package/src/Throttler.resi +1 -1
  83. package/src/UserContext.res +46 -410
  84. package/src/UserContext.res.mjs +30 -318
  85. package/src/Utils.res +0 -23
  86. package/src/Utils.res.mjs +1 -17
  87. package/src/Writing.res +0 -1
  88. package/src/Writing.res.mjs +1 -1
  89. package/src/bindings/ClickHouse.res +0 -322
  90. package/src/bindings/ClickHouse.res.mjs +0 -338
  91. package/src/bindings/StorageClient.res +107 -175
  92. package/src/bindings/StorageClient.res.mjs +19 -8
  93. package/src/bindings/Vitest.res +0 -26
  94. package/src/bindings/Vitest.res.mjs +0 -3
  95. package/src/db/Table.res +24 -150
  96. package/src/db/Table.res.mjs +38 -272
  97. package/src/sources/AddressSet.res +0 -2
  98. package/src/sources/AddressStore.res +0 -3
  99. package/src/sources/ChainSources.res +2 -0
  100. package/src/sources/ChainSources.res.mjs +2 -2
  101. package/src/sources/EvmChain.res +4 -7
  102. package/src/sources/EvmChain.res.mjs +5 -4
  103. package/src/sources/EvmHyperSyncSource.res +2 -2
  104. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  105. package/src/sources/EvmRpcClient.res +13 -3
  106. package/src/sources/EvmRpcClient.res.mjs +4 -3
  107. package/src/sources/EvmRpcWs.res +4 -3
  108. package/src/sources/EvmRpcWs.res.mjs +2 -2
  109. package/src/sources/HeightFeed.res +31 -10
  110. package/src/sources/HeightFeed.res.mjs +10 -6
  111. package/src/sources/HeightStream.res +32 -17
  112. package/src/sources/HeightStream.res.mjs +10 -10
  113. package/src/sources/HyperSync.res +1 -2
  114. package/src/sources/HyperSync.res.mjs +1 -2
  115. package/src/sources/HyperSyncSSE.res +4 -6
  116. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  117. package/src/sources/RpcSource.res +10 -1
  118. package/src/sources/RpcSource.res.mjs +4 -3
  119. package/src/sources/Source.res +3 -0
  120. package/src/sources/SourceManager.res +85 -104
  121. package/src/sources/SourceManager.res.mjs +18 -37
  122. package/src/sources/SourceManager.resi +3 -5
  123. package/src/sources/StartBlockResolver.res +1 -0
  124. package/src/sources/StartBlockResolver.res.mjs +4 -3
  125. package/src/sources/SvmHyperSyncSource.res +2 -2
  126. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  127. package/svm.schema.json +3 -3
  128. package/src/EffectCacheLookup.res +0 -98
  129. package/src/EffectCacheLookup.res.mjs +0 -116
  130. package/src/PgWriting.res +0 -36
  131. package/src/PgWriting.res.mjs +0 -34
  132. package/src/PreloadLookahead.res +0 -184
  133. package/src/PreloadLookahead.res.mjs +0 -197
  134. package/src/SubgraphLog.res +0 -19
  135. package/src/SubgraphLog.res.mjs +0 -26
  136. package/src/SyncWrites.res +0 -101
  137. package/src/SyncWrites.res.mjs +0 -98
  138. package/src/db/IndexDefinition.res +0 -47
  139. package/src/db/IndexDefinition.res.mjs +0 -60
  140. package/src/sources/CallSource.res +0 -274
  141. package/src/sources/CallSource.res.mjs +0 -196
  142. package/src/subgraph/abi-types.ts +0 -84
  143. package/src/subgraph/aggregations.ts +0 -745
  144. package/src/subgraph/as-package.ts +0 -143
  145. package/src/subgraph/assemblyscript-regex.ts +0 -67
  146. package/src/subgraph/assemblyscript.ts +0 -731
  147. package/src/subgraph/blocks.ts +0 -398
  148. package/src/subgraph/calls-source.ts +0 -702
  149. package/src/subgraph/calls.ts +0 -258
  150. package/src/subgraph/conformance.ts +0 -106
  151. package/src/subgraph/errors.ts +0 -85
  152. package/src/subgraph/graft.ts +0 -73
  153. package/src/subgraph/graph-cli-worker.mjs +0 -35
  154. package/src/subgraph/graph-cli.ts +0 -900
  155. package/src/subgraph/graph-node-decimal.ts +0 -199
  156. package/src/subgraph/graph-ts-enums.ts +0 -28
  157. package/src/subgraph/graph-ts-types/VERSION +0 -2
  158. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  159. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  160. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  161. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  162. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  163. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  164. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  165. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  166. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  167. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  168. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  169. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  170. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  171. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  172. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  173. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  174. package/src/subgraph/graph-ts.ts +0 -4280
  175. package/src/subgraph/hosts.ts +0 -554
  176. package/src/subgraph/log.ts +0 -28
  177. package/src/subgraph/receipts.ts +0 -184
  178. package/src/subgraph/register-slots.ts +0 -93
  179. package/src/subgraph/rollup.ts +0 -109
  180. package/src/subgraph/rpc.ts +0 -337
  181. package/src/subgraph/runtime.ts +0 -2060
  182. package/src/subgraph/scope.ts +0 -177
  183. package/src/subgraph/stats.ts +0 -42
  184. package/src/subgraph/template-instances.ts +0 -66
  185. package/src/subgraph/toolchain.ts +0 -327
  186. package/src/subgraph/transient.ts +0 -176
@@ -1,91 +1,3 @@
1
- // Thrown by a sync op that can't be served from memory. Owned by envio so the
2
- // subgraph runtime's replay loop can tell it apart from a real handler error.
3
- // User code never swallows it: AssemblyScript has no try/catch, and the ops
4
- // keep re-throwing it while the round stays aborted.
5
- exception Suspend
6
-
7
- let isSuspend = exn =>
8
- switch exn {
9
- | Suspend => true
10
- | _ => false
11
- }
12
-
13
- // Every suspend throws this one value: `throw(Suspend)` builds a fresh Error,
14
- // stack trace and all, and a mapping suspends several times per event.
15
- let suspend: exn = Suspend
16
-
17
- type roundStatus =
18
- | Active
19
- // Set by a suspended sync op. Every context access re-throws the stored
20
- // error, so even a caught suspend stops the round at its next one.
21
- | Aborted(exn)
22
-
23
- // One `context.runSync` call. Its body is replayed from the top each time it
24
- // suspends on a read that wasn't in memory yet.
25
- type syncRun = {
26
- run: RunScope.handlerRun,
27
- mutable status: roundStatus,
28
- // Ops scheduled by this round's suspended reads, awaited before the replay.
29
- mutable pending: array<promise<unit>>,
30
- // Effect outputs read from the in-memory table during this call, reused by
31
- // its later rounds.
32
- memo: dict<Internal.effectOutput>,
33
- // This round's writes, held until it completes.
34
- mutable writes: SyncWrites.t,
35
- // Preload reads this round answered as absent while their load was in flight.
36
- mutable speculated: int,
37
- }
38
-
39
- // The round running right now. A round's body runs synchronously from start to
40
- // end, so no other run's code can observe it.
41
- let currentSync: ref<option<syncRun>> = ref(None)
42
-
43
- let syncOf = (run: RunScope.handlerRun) =>
44
- switch currentSync.contents {
45
- | Some(sync) if sync.run === run => Some(sync)
46
- | _ => None
47
- }
48
-
49
- // The handler run a `context.${prop}` access belongs to, refused while its
50
- // round is aborted.
51
- let currentRunOrThrow = (~prop) => {
52
- let run = RunScope.currentHandlerRunOrThrow(~prop)
53
- switch run->syncOf {
54
- | Some({status: Aborted(exn)}) => throw(exn)
55
- | _ => ()
56
- }
57
- run
58
- }
59
-
60
- let syncOrThrow = (run, ~prop) =>
61
- switch run->syncOf {
62
- | Some(sync) => sync
63
- | None => JsError.throwWithMessage(`context.${prop} is only available inside context.runSync.`)
64
- }
65
-
66
- // Fires the async op behind a sync miss, records it for the replay and aborts
67
- // the round.
68
- let scheduleAndSuspend = (sync: syncRun, promise: promise<'a>): 'b => {
69
- sync.pending->Array.push(promise->Utils.Promise.ignoreValue)
70
- sync.status = Aborted(suspend)
71
- throw(suspend)
72
- }
73
-
74
- // Preload reads past a miss: the read answers as absent and the round runs on,
75
- // so the reads after it that don't depend on it join this round's batch rather
76
- // than taking a round each. Nothing preload computes is kept, and the round is
77
- // replayed once its reads land. Bounded, for a mapping that loops on a read.
78
- let maxSpeculatedReads = 32
79
-
80
- let missOrSuspend = (sync: syncRun, promise: promise<'a>, ~absent: 'b): 'b =>
81
- if sync.run.isPreload && sync.speculated < maxSpeculatedReads {
82
- sync.pending->Array.push(promise->Utils.Promise.ignoreValue)
83
- sync.speculated = sync.speculated + 1
84
- absent
85
- } else {
86
- sync->scheduleAndSuspend(promise)
87
- }
88
-
89
1
  // What every handler run of an indexer shares. The run itself — its item,
90
2
  // checkpoint and whether it's a preload — is read from the RunScope it was
91
3
  // entered with, so one context object serves every run, concurrent ones and
@@ -146,7 +58,7 @@ let isCrossChain = (params: params, effect: Internal.effect) =>
146
58
 
147
59
  // A chain-scoped effect always resolves against the chain of the handler run
148
60
  // that triggered the call, even several effects deep.
149
- let rec prepareEffectCall = (
61
+ let rec callEffect = (
150
62
  params: params,
151
63
  ~run: RunScope.handlerRun,
152
64
  ~caller: option<Internal.effect>,
@@ -176,73 +88,23 @@ let rec prepareEffectCall = (
176
88
  ~callEffect=(child, input) => params->callEffect(~run, ~caller=Some(effect), child, input),
177
89
  )
178
90
  effectContext->RunScope.attachEffectRun({caller: run, effectName: effect.name})
179
- let effectArgs: Internal.effectArgs = {
180
- input,
181
- context: effectContext,
182
- cacheKey: input
183
- ->S.reverseConvertOrThrow(effect.input)
184
- ->Utils.Hash.makeOrThrow
185
- ->Utils.Hash.boundCacheKey,
186
- chainId: handlerChainId,
187
- checkpointId: run.checkpointId,
188
- }
189
- (scope, effectArgs)
190
- }
191
-
192
- and callEffect = (params: params, ~run: RunScope.handlerRun, ~caller, effect, input) => {
193
- let (scope, effectArgs) = params->prepareEffectCall(~run, ~caller, effect, input)
194
91
  LoadLayer.loadEffect(
195
92
  ~loadManager=params.loadManager,
196
93
  ~persistence=params.persistence,
197
94
  ~effect,
198
- ~effectArgs,
95
+ ~effectArgs={
96
+ input,
97
+ context: effectContext,
98
+ cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
99
+ chainId: handlerChainId,
100
+ checkpointId: run.checkpointId,
101
+ },
199
102
  ~scope,
200
103
  ~indexerState=params.indexerState,
201
104
  ~shouldGroup=run.isPreload,
202
105
  )
203
106
  }
204
107
 
205
- let effectMemoKey = (~effect: Internal.effect, ~scope: Internal.chainScope, ~cacheKey) =>
206
- switch scope {
207
- | CrossChain => `${effect.name}.${cacheKey}`
208
- | Chain(chainId) => `${effect.name}.${chainId->ChainId.toString}.${cacheKey}`
209
- }
210
-
211
- // An effect's output when it's in memory; otherwise the round suspends on it.
212
- let effectSync = (params: params, effect: Internal.effect, input: Internal.effectInput) => {
213
- let run = currentRunOrThrow(~prop="effectSync")
214
- let sync = run->syncOrThrow(~prop="effectSync")
215
- let (scope, effectArgs) = params->prepareEffectCall(~run, ~caller=None, effect, input)
216
- let memoKey = effectMemoKey(~effect, ~scope, ~cacheKey=effectArgs.cacheKey)
217
- switch sync.memo->Utils.Dict.dangerouslyGetNonOption(memoKey) {
218
- | Some(output) => output
219
- | None =>
220
- let inMemTable = params.indexerState->InMemoryStore.getEffectInMemTable(~effect, ~scope)
221
- if inMemTable->InMemoryStore.hasEffectOutput(effectArgs.cacheKey) {
222
- let output = inMemTable->InMemoryStore.getEffectOutputUnsafe(effectArgs.cacheKey)
223
- sync.memo->Dict.set(memoKey, output)
224
- output
225
- } else if sync.speculated > 0 {
226
- // The input may be computed from a speculated read: an effect can cost
227
- // an RPC call, so it waits to be asked with the real values.
228
- sync.status = Aborted(suspend)
229
- throw(suspend)
230
- } else {
231
- sync->scheduleAndSuspend(
232
- LoadLayer.loadEffect(
233
- ~loadManager=params.loadManager,
234
- ~persistence=params.persistence,
235
- ~effect,
236
- ~effectArgs,
237
- ~scope,
238
- ~indexerState=params.indexerState,
239
- ~shouldGroup=run.isPreload,
240
- ),
241
- )
242
- }
243
- }
244
- }
245
-
246
108
  // A TTL drops an entity's rows whenever ClickHouse merges them, so for one kept
247
109
  // there alone a read would turn on merge timing rather than on the events.
248
110
  let expiresUnread = (entityConfig: Internal.entityConfig) =>
@@ -267,15 +129,8 @@ type entityContext = {
267
129
  deleteUnsafe: EntityId.t => unit,
268
130
  }
269
131
 
270
- type syncEntityContext = {
271
- getSync: string => option<Internal.entity>,
272
- getWhereSync: dict<dict<unknown>> => array<Internal.entity>,
273
- getInBlockSync: string => option<Internal.entity>,
274
- }
275
-
276
132
  let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) => {
277
133
  let prop = entityConfig.name
278
- let entityName = entityConfig.name
279
134
  // The handler context is always chain-scoped, so a per-chain entity resolves
280
135
  // to the chain the handler runs on and a cross-chain one to the shared
281
136
  // partition.
@@ -283,7 +138,7 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
283
138
  entityConfig->InMemoryStore.entityScope(~chainId=run.item->Internal.getItemChainId)
284
139
 
285
140
  let loadById = entityId => {
286
- let run = currentRunOrThrow(~prop)
141
+ let run = RunScope.currentHandlerRunOrThrow(~prop)
287
142
  LoadLayer.loadById(
288
143
  ~loadManager=params.loadManager,
289
144
  ~persistence=params.persistence,
@@ -295,34 +150,35 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
295
150
  )
296
151
  }
297
152
 
298
- // A preload run's writes are no-ops: its run proper makes them. Inside a sync
299
- // round they are held until the round completes, a preload's only for the
300
- // batch's later preloads to read (PreloadLookahead), never in the store.
301
- let write = (run: RunScope.handlerRun, change: Change.t<Internal.entity>) =>
302
- switch (run->syncOf, run.isPreload) {
303
- | (Some(sync), false) =>
304
- sync.writes->SyncWrites.record(~entityConfig, ~scope=run->scopeOf, change)
305
- | (Some(sync), true) if PreloadLookahead.isActive() =>
306
- sync.writes->SyncWrites.record(~entityConfig, ~scope=run->scopeOf, change)
307
- | (None, false) =>
308
- params.indexerState->SyncWrites.write(~entityConfig, ~scope=run->scopeOf, change)
309
- | (_, true) => ()
310
- }
153
+ let write = (run, change: Change.t<Internal.entity>) => {
154
+ let scope = run->scopeOf
155
+ params.indexerState
156
+ ->InMemoryStore.getInMemTable(~entityConfig, ~scope)
157
+ ->InMemoryTable.Entity.set(
158
+ ~committedCheckpointId=params.indexerState->IndexerState.committedCheckpointIdFor(~scope),
159
+ change,
160
+ )
161
+ }
311
162
 
163
+ // A preload run's writes are no-ops: its run proper makes them.
312
164
  let set = (entity: Internal.entity) => {
313
- let run = currentRunOrThrow(~prop)
314
- run->write(
315
- Set({
316
- entityId: entity.id->EntityId.unsafeOfString,
317
- checkpointId: run.checkpointId,
318
- entity,
319
- }),
320
- )
165
+ let run = RunScope.currentHandlerRunOrThrow(~prop)
166
+ if !run.isPreload {
167
+ run->write(
168
+ Set({
169
+ entityId: entity.id->EntityId.unsafeOfString,
170
+ checkpointId: run.checkpointId,
171
+ entity,
172
+ }),
173
+ )
174
+ }
321
175
  }
322
176
 
323
177
  let deleteUnsafe = entityId => {
324
- let run = currentRunOrThrow(~prop)
325
- run->write(Delete({entityId, checkpointId: run.checkpointId}))
178
+ let run = RunScope.currentHandlerRunOrThrow(~prop)
179
+ if !run.isPreload {
180
+ run->write(Delete({entityId, checkpointId: run.checkpointId}))
181
+ }
326
182
  }
327
183
 
328
184
  let entityContext: entityContext = if entityConfig->expiresUnread {
@@ -338,7 +194,7 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
338
194
  {
339
195
  get: loadById,
340
196
  getWhere: filter => {
341
- let run = currentRunOrThrow(~prop)
197
+ let run = RunScope.currentHandlerRunOrThrow(~prop)
342
198
  LoadLayer.loadByFilter(
343
199
  ~loadManager=params.loadManager,
344
200
  ~persistence=params.persistence,
@@ -375,198 +231,11 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
375
231
  }
376
232
  }
377
233
 
378
- let object =
379
- Utils.StrictObject.make(
380
- Utils.StrictObject.makePrototype(op =>
381
- JsError.throwWithMessage(`Invalid context.${entityConfig.name}.${op} operation.`)
382
- ),
383
- )->Utils.StrictObject.assign(entityContext)
384
-
385
- // The subgraph runtime's synchronous reads: answered from memory, or the
386
- // round suspends on the load and is replayed once it lands.
387
- if Env.subgraphMode.contents {
388
- let syncEntityContext: syncEntityContext = {
389
- getSync: entityId => {
390
- let run = currentRunOrThrow(~prop)
391
- let sync = run->syncOrThrow(~prop=`${prop}.getSync`)
392
- let scope = run->scopeOf
393
- switch sync.writes->SyncWrites.getById(~entityConfig, ~entityId) {
394
- | Some(entity) => entity
395
- | None =>
396
- switch run.isPreload
397
- ? PreloadLookahead.overlayGet(~item=run.item, ~entityName, ~scope, ~entityId)
398
- : None {
399
- | Some(entity) => entity
400
- | None =>
401
- switch LoadLayer.getByIdInMemory(
402
- ~entityConfig,
403
- ~scope,
404
- ~indexerState=params.indexerState,
405
- ~entityId,
406
- ) {
407
- | Some(entity) =>
408
- if run.isPreload && entity->Option.isNone {
409
- PreloadLookahead.recordAbsent(~item=run.item, ~entityName, ~scope, ~entityId)
410
- }
411
- entity
412
- | None =>
413
- sync->missOrSuspend(
414
- ~absent=None,
415
- LoadLayer.loadById(
416
- ~loadManager=params.loadManager,
417
- ~persistence=params.persistence,
418
- ~entityConfig,
419
- ~scope,
420
- ~indexerState=params.indexerState,
421
- ~shouldGroup=run.isPreload,
422
- ~entityId,
423
- ),
424
- )
425
- }
426
- }
427
- }
428
- },
429
- getWhereSync: filter => {
430
- let run = currentRunOrThrow(~prop)
431
- let sync = run->syncOrThrow(~prop=`${prop}.getWhereSync`)
432
- let scope = run->scopeOf
433
- let filter = filter->EntityFilter.parseOrThrow(~entityName, ~table=entityConfig.table)
434
- switch LoadLayer.getByFilterInMemory(
435
- ~entityConfig,
436
- ~scope,
437
- ~indexerState=params.indexerState,
438
- ~filter,
439
- ) {
440
- | Some(entities) => sync.writes->SyncWrites.overlayFilter(entities, ~entityConfig, ~filter)
441
- | None =>
442
- sync->missOrSuspend(
443
- ~absent=[],
444
- LoadLayer.loadByParsedFilter(
445
- ~loadManager=params.loadManager,
446
- ~persistence=params.persistence,
447
- ~entityConfig,
448
- ~scope,
449
- ~indexerState=params.indexerState,
450
- ~shouldGroup=run.isPreload,
451
- ~filter,
452
- ),
453
- )
454
- }
455
- },
456
- // Never suspends: the in-memory table spans the whole batch, so a change
457
- // only counts as "in this block" when it was written at this run's
458
- // checkpoint.
459
- getInBlockSync: entityId => {
460
- let run = currentRunOrThrow(~prop)
461
- let sync = run->syncOrThrow(~prop=`${prop}.getInBlockSync`)
462
- switch sync.writes->SyncWrites.getById(~entityConfig, ~entityId) {
463
- | Some(entity) => entity
464
- | None =>
465
- let inMemTable =
466
- params.indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope=run->scopeOf)
467
- switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
468
- | Some(change) if change->Change.getCheckpointId == run.checkpointId =>
469
- change->InMemoryTable.Entity.mapChangeToEntity
470
- | _ => None
471
- }
472
- }
473
- },
474
- }
475
- object->Utils.StrictObject.assign(
476
- syncEntityContext->(Utils.magic: syncEntityContext => entityContext),
477
- )
478
- } else {
479
- object
480
- }
481
- }
482
-
483
- // Deterministic mappings always make progress, so the cap only exists to turn
484
- // a non-deterministic one into a clear error instead of a hang.
485
- let maxSyncRounds = 10000
486
-
487
- let rec runSyncRound = async (params: params, sync: syncRun, fn: unit => unit, ~round) => {
488
- if round > maxSyncRounds {
489
- JsError.throwWithMessage(
490
- `The handler suspended on a synchronous read too many times: gave up after ${maxSyncRounds->Int.toString} rounds. This usually means the code isn't deterministic across reruns.`,
491
- )
492
- }
493
- let run = sync.run
494
- sync.status = Active
495
- sync.pending = []
496
- sync.speculated = 0
497
- sync.writes = SyncWrites.make()
498
- let startedAt = PreloadLookahead.roundStarted(~isPreload=run.isPreload)
499
-
500
- let previous = currentSync.contents
501
- currentSync := Some(sync)
502
- let outcome = try {
503
- fn()
504
- Ok()
505
- } catch {
506
- | exn => Error(exn)
507
- }
508
- currentSync := previous
509
- PreloadLookahead.roundEnded(~item=run.item, ~startedAt)
510
- let writes = sync.writes
511
-
512
- // Neither a suspended round nor a failed one keeps what it wrote.
513
- sync.writes = SyncWrites.make()
514
-
515
- let suspended = switch outcome {
516
- | Ok() =>
517
- switch sync.status {
518
- // The body caught the suspend and returned anyway, so it didn't see the
519
- // reads it asked for: it is replayed like any other suspended round.
520
- | Aborted(_) => true
521
- // Ran on reads it was told were absent.
522
- | Active if sync.speculated > 0 => true
523
- | Active =>
524
- if run.isPreload {
525
- writes->SyncWrites.forEachChange((~entityConfig, ~scope, change) =>
526
- PreloadLookahead.overlaySet(
527
- ~item=run.item,
528
- ~entityName=entityConfig.name,
529
- ~scope,
530
- ~entityId=change->Change.getEntityId->EntityId.toKey,
531
- ~entity=change->InMemoryTable.Entity.mapChangeToEntity,
532
- )
533
- )
534
- } else {
535
- writes->SyncWrites.commit(~indexerState=params.indexerState)
536
- }
537
- false
538
- }
539
- // A round handed a speculated absence may well throw on it: the replay that
540
- // sees the real value decides whether the error is the handler's.
541
- | Error(exn) if exn->isSuspend || sync.speculated > 0 => true
542
- | Error(exn) =>
543
- // The body threw its own error after scheduling a read. Those ops are
544
- // nobody's result now, and an unhandled rejection would replace the error
545
- // the handler actually raised.
546
- sync.pending->Array.forEach(promise => promise->Utils.Promise.catchResolve(_ => ())->ignore)
547
- sync.pending = []
548
- throw(exn)
549
- }
550
-
551
- if suspended {
552
- // A round only suspends by scheduling the read it missed.
553
- let pending = sync.pending
554
- sync.pending = []
555
- let errors = []
556
- let _ = await pending
557
- ->Array.map(promise =>
558
- promise->Promise.catch(exn => {
559
- errors->Array.push(exn)
560
- Promise.resolve()
561
- })
562
- )
563
- ->Promise.all
564
- switch errors->Array.get(0) {
565
- | Some(exn) => throw(exn)
566
- | None => ()
567
- }
568
- await params->runSyncRound(sync, fn, ~round=round + 1)
569
- }
234
+ Utils.StrictObject.make(
235
+ Utils.StrictObject.makePrototype(op =>
236
+ JsError.throwWithMessage(`Invalid context.${entityConfig.name}.${op} operation.`)
237
+ ),
238
+ )->Utils.StrictObject.assign(entityContext)
570
239
  }
571
240
 
572
241
  // One context for every handler run of an indexer.
@@ -582,7 +251,7 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
582
251
  context
583
252
  ->Utils.Object.defineProperty(
584
253
  name,
585
- {enumerable: true, get: () => get(currentRunOrThrow(~prop=name))},
254
+ {enumerable: true, get: () => get(RunScope.currentHandlerRunOrThrow(~prop=name))},
586
255
  )
587
256
  ->ignore
588
257
 
@@ -594,7 +263,12 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
594
263
  // Reads the run when called, so a kept `context.effect` is refused once its
595
264
  // handler resolved, like a kept entity accessor.
596
265
  let effect = (effect, input) =>
597
- params->callEffect(~run=currentRunOrThrow(~prop="effect"), ~caller=None, effect, input)
266
+ params->callEffect(
267
+ ~run=RunScope.currentHandlerRunOrThrow(~prop="effect"),
268
+ ~caller=None,
269
+ effect,
270
+ input,
271
+ )
598
272
  context
599
273
  ->Utils.Object.definePropertyWithValue(
600
274
  "effect",
@@ -608,44 +282,6 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
608
282
  },
609
283
  )
610
284
  ->ignore
611
- if Env.subgraphMode.contents {
612
- context
613
- ->Utils.Object.definePropertyWithValue(
614
- "effectSync",
615
- {
616
- enumerable: true,
617
- value: ((effect, input) => params->effectSync(effect, input))->(
618
- Utils.magic: ((Internal.effect, Internal.effectInput) => Internal.effectOutput) => unknown
619
- ),
620
- },
621
- )
622
- ->ignore
623
- context
624
- ->Utils.Object.definePropertyWithValue(
625
- "runSync",
626
- {
627
- enumerable: true,
628
- value: (
629
- fn => {
630
- let run = currentRunOrThrow(~prop="runSync")
631
- params->runSyncRound(
632
- {
633
- run,
634
- status: Active,
635
- pending: [],
636
- memo: Dict.make(),
637
- writes: SyncWrites.make(),
638
- speculated: 0,
639
- },
640
- fn,
641
- ~round=1,
642
- )
643
- }
644
- )->(Utils.magic: ((unit => unit) => promise<unit>) => unknown),
645
- },
646
- )
647
- ->ignore
648
- }
649
285
  define("isPreload", run => run.isPreload->(Utils.magic: bool => unknown))
650
286
  define("chain", run =>
651
287
  params.chains