envio 3.14.0-subgraph → 3.14.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 (63) hide show
  1. package/evm.schema.json +1 -1
  2. package/package.json +6 -6
  3. package/src/Config.res +0 -9
  4. package/src/Config.res.mjs +2 -6
  5. package/src/Core.res +0 -21
  6. package/src/Core.res.mjs +0 -17
  7. package/src/EventProcessing.res +6 -6
  8. package/src/EventProcessing.res.mjs +6 -8
  9. package/src/HandlerLoader.res +22 -48
  10. package/src/HandlerLoader.res.mjs +15 -41
  11. package/src/HandlerTypeCheck.res +207 -0
  12. package/src/HandlerTypeCheck.res.mjs +262 -0
  13. package/src/LoadLayer.res +7 -39
  14. package/src/LoadLayer.res.mjs +38 -65
  15. package/src/LoadLayer.resi +0 -28
  16. package/src/PgStorage.res +5 -24
  17. package/src/PgStorage.res.mjs +1 -18
  18. package/src/UserContext.res +62 -377
  19. package/src/UserContext.res.mjs +39 -297
  20. package/src/bindings/NodeJs.res +31 -0
  21. package/src/bindings/NodeJs.res.mjs +3 -0
  22. package/src/db/Table.res +0 -10
  23. package/src/db/Table.res.mjs +11 -22
  24. package/src/sources/EvmRpcClient.res +2 -0
  25. package/src/sources/RpcSource.res +17 -4
  26. package/src/sources/RpcSource.res.mjs +23 -5
  27. package/src/sources/SourceManager.res +63 -40
  28. package/src/sources/SourceManager.res.mjs +32 -20
  29. package/src/HandlerTypeCheck.mjs +0 -14
  30. package/src/HandlerTypeCheckWorker.mjs +0 -68
  31. package/src/SyncWrites.res +0 -86
  32. package/src/SyncWrites.res.mjs +0 -85
  33. package/src/subgraph/abi-types.ts +0 -63
  34. package/src/subgraph/assemblyscript.ts +0 -85
  35. package/src/subgraph/blocks.ts +0 -183
  36. package/src/subgraph/calls.ts +0 -149
  37. package/src/subgraph/conformance.ts +0 -83
  38. package/src/subgraph/errors.ts +0 -96
  39. package/src/subgraph/graph-cli-worker.mjs +0 -26
  40. package/src/subgraph/graph-cli.ts +0 -190
  41. package/src/subgraph/graph-ts-enums.ts +0 -28
  42. package/src/subgraph/graph-ts-types/VERSION +0 -2
  43. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  44. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  45. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  46. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  47. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  48. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  49. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  50. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  51. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  52. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  53. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  54. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  55. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  56. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  57. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  58. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  59. package/src/subgraph/graph-ts.ts +0 -2285
  60. package/src/subgraph/hosts.ts +0 -158
  61. package/src/subgraph/rpc.ts +0 -65
  62. package/src/subgraph/runtime.ts +0 -595
  63. package/src/subgraph/scope.ts +0 -68
package/src/PgStorage.res CHANGED
@@ -602,7 +602,7 @@ let makeInsertValuesSetQuery = (
602
602
  ~itemsCount,
603
603
  ~chainIdMode: ChainId.mode=Int32,
604
604
  ) => {
605
- let {quotedFieldNames, quotedNonPrimaryFieldNames, booleanArrayColumnIndexes} =
605
+ let {quotedFieldNames, quotedNonPrimaryFieldNames} =
606
606
  table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
607
607
 
608
608
  let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table)
@@ -620,9 +620,6 @@ let makeInsertValuesSetQuery = (
620
620
  placeholders := placeholders.contents ++ ","
621
621
  }
622
622
  placeholders := placeholders.contents ++ `$${(fieldIdx * itemsCount + idx)->Int.toString}`
623
- if booleanArrayColumnIndexes->Array.includes(fieldIdx) {
624
- placeholders := placeholders.contents ++ "::integer[]::boolean[]"
625
- }
626
623
  }
627
624
  placeholders := placeholders.contents ++ ")"
628
625
  }
@@ -655,7 +652,7 @@ let makeTableBatchSetQuery = (
655
652
  ~itemSchema: S.t<'item>,
656
653
  ~chainIdMode: ChainId.mode=Int32,
657
654
  ) => {
658
- let {dbSchema, hasArrayField, byteaColumnIndexes, booleanArrayColumnIndexes} =
655
+ let {dbSchema, hasArrayField, byteaColumnIndexes} =
659
656
  table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode)
660
657
 
661
658
  // Should move this to a better place
@@ -716,25 +713,9 @@ let makeTableBatchSetQuery = (
716
713
  ),
717
714
  "convertOrThrow": S.compile(
718
715
  S.unnest(itemSchema)->S.preprocess(_ => {
719
- serializer: columns => {
720
- let columns = columns->(Utils.magic: unknown => array<array<Nullable.t<array<bool>>>>)
721
- booleanArrayColumnIndexes->Array.forEach(index =>
722
- columns->Array.setUnsafe(
723
- index,
724
- columns
725
- ->Array.getUnsafe(index)
726
- ->Array.map(
727
- value =>
728
- value
729
- ->Nullable.map(flags => flags->Array.map(flag => flag ? 1 : 0))
730
- ->(Utils.magic: Nullable.t<array<int>> => Nullable.t<array<bool>>),
731
- ),
732
- )
733
- )
734
- columns
735
- ->Utils.Array.flatten
736
- ->(Utils.magic: array<Nullable.t<array<bool>>> => unknown)
737
- },
716
+ serializer: Utils.Array.flatten->(
717
+ Utils.magic: (array<array<'a>> => array<'a>) => unknown => unknown
718
+ ),
738
719
  }),
739
720
  ~input=Value,
740
721
  ~output=Unknown,
@@ -32,7 +32,6 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
32
32
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
33
33
  import * as ContractMapping from "./ContractMapping.res.mjs";
34
34
  import * as IndexDefinition from "./db/IndexDefinition.res.mjs";
35
- import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
36
35
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
37
36
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
38
37
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -449,7 +448,6 @@ SELECT * FROM unnest(` + match.arrayFieldTypes.map((arrayFieldType, idx) => `$`
449
448
  function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chainIdModeOpt) {
450
449
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
451
450
  let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
452
- let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
453
451
  let quotedNonPrimaryFieldNames = match.quotedNonPrimaryFieldNames;
454
452
  let quotedFieldNames = match.quotedFieldNames;
455
453
  let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table);
@@ -465,9 +463,6 @@ function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chain
465
463
  placeholders = placeholders + ",";
466
464
  }
467
465
  placeholders = placeholders + (`$` + ((fieldIdx * itemsCount | 0) + idx | 0).toString());
468
- if (booleanArrayColumnIndexes.includes(fieldIdx)) {
469
- placeholders = placeholders + "::integer[]::boolean[]";
470
- }
471
466
  }
472
467
  placeholders = placeholders + ")";
473
468
  }
@@ -482,7 +477,6 @@ VALUES` + placeholders + (
482
477
  function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
483
478
  let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
484
479
  let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
485
- let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
486
480
  let byteaColumnIndexes = match.byteaColumnIndexes;
487
481
  let isRawEvents = table.tableName === InternalTable.RawEvents.table.tableName;
488
482
  let isHistoryUpdate = table.tableName.startsWith(EntityHistory.historyTablePrefix);
@@ -503,18 +497,7 @@ function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
503
497
  return {
504
498
  query: makeInsertValuesSetQuery(pgSchema, table, itemSchema, 500, chainIdMode),
505
499
  convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.preprocess(S$RescriptSchema.unnest(itemSchema), param => ({
506
- s: columns => {
507
- booleanArrayColumnIndexes.forEach(index => {
508
- columns[index] = columns[index].map(value => Stdlib_Nullable.map(value, flags => flags.map(flag => {
509
- if (flag) {
510
- return 1;
511
- } else {
512
- return 0;
513
- }
514
- })));
515
- });
516
- return columns.flat(1);
517
- }
500
+ s: prim => prim.flat(1)
518
501
  })), "Output", "Input", "Sync", false),
519
502
  isInsertValues: true
520
503
  };
@@ -1,38 +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 traps
4
- // keep re-throwing it while the context stays aborted.
5
- exception Suspend
6
-
7
- let isSuspend = exn =>
8
- switch exn {
9
- | Suspend => true
10
- | _ => false
11
- }
12
-
13
- type contextStatus =
14
- | Active
15
- // Set by a suspended sync op. Every trap access and every op closure
16
- // re-throws the stored error, so even a caught suspend stops the handler
17
- // at its next context interaction.
18
- | Aborted(exn)
19
- | Resolved
20
-
21
- // Held by reference so the entity sub-proxies, which copy the rest of the
22
- // params by value, observe the same lifecycle as the handler context.
23
- type syncState = {
24
- mutable status: contextStatus,
25
- // Ops scheduled by this round's suspended reads, awaited before the replay.
26
- mutable pending: option<array<promise<unit>>>,
27
- // Effect outputs read from the in-memory table during this handler
28
- // invocation, reused by its later replay rounds.
29
- mutable memo: option<dict<Internal.effectOutput>>,
30
- // Only while a synchronous round runs: its writes, held until it completes.
31
- mutable writes: option<SyncWrites.t>,
32
- }
33
-
34
- let makeSyncState = () => {status: Active, pending: None, memo: None, writes: None}
35
-
36
1
  type contextParams = {
37
2
  item: Internal.item,
38
3
  checkpointId: Internal.checkpointId,
@@ -42,45 +7,7 @@ type contextParams = {
42
7
  isPreload: bool,
43
8
  chains: Internal.chains,
44
9
  config: Config.t,
45
- sync: syncState,
46
- }
47
-
48
- let getPending = (sync: syncState) =>
49
- switch sync.pending {
50
- | Some(pending) => pending
51
- | None =>
52
- let pending = []
53
- sync.pending = Some(pending)
54
- pending
55
- }
56
-
57
- let getMemo = (sync: syncState) =>
58
- switch sync.memo {
59
- | Some(memo) => memo
60
- | None =>
61
- let memo = Dict.make()
62
- sync.memo = Some(memo)
63
- memo
64
- }
65
-
66
- let checkStatusOrThrow = (params: contextParams, ~access: string) =>
67
- switch params.sync.status {
68
- | Active => ()
69
- | Aborted(exn) => throw(exn)
70
- | Resolved =>
71
- Utils.Error.make(
72
- `Impossible to access ${access} after the handler is resolved. Make sure you didn't miss an await in the handler.`,
73
- )->ErrorHandling.mkLogAndRaise(
74
- ~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
75
- )
76
- }
77
-
78
- // Fires the async op behind a sync miss, records it for the replay loop and
79
- // aborts the context.
80
- let scheduleAndSuspend = (params: contextParams, promise: promise<'a>): 'b => {
81
- params.sync->getPending->Array.push(promise->Utils.Promise.ignoreValue)
82
- params.sync.status = Aborted(Suspend)
83
- throw(Suspend)
10
+ mutable isResolved: bool,
84
11
  }
85
12
 
86
13
  // We don't want to expose the params to the user
@@ -131,15 +58,8 @@ external makeEffectContext: (
131
58
  ~callEffect: (Internal.effect, Internal.effectInput) => promise<Internal.effectOutput>,
132
59
  ) => Internal.effectContext = "EffectContext"
133
60
 
134
- // Builds the scope and args a call to `effect` resolves against. Split out of
135
- // `initEffect` so the sync caller derives the same cache key and scope without
136
- // duplicating the nested-caller rules.
137
- let rec prepareEffectCall = (
138
- params: contextParams,
139
- ~effect: Internal.effect,
140
- ~input: Internal.effectInput,
141
- ~caller: option<Internal.effect>,
142
- ) => {
61
+ let initEffect = (params: contextParams) => {
62
+ let handlerChainId = params.item->Internal.getItemChainId
143
63
  // An effect that didn't state a scope follows the config: cross-chain by
144
64
  // default, per-chain under `disable_default_cross_chain`.
145
65
  let isCrossChain = (effect: Internal.effect) =>
@@ -147,103 +67,51 @@ let rec prepareEffectCall = (
147
67
  // A chain-scoped effect always resolves against the chain of the handler that
148
68
  // triggered the call, even several effects deep, so the chain id is captured
149
69
  // once from the item and reused for the whole nested-call tree.
150
- let scope =
151
- effect->isCrossChain
152
- ? Internal.CrossChain
153
- : Internal.Chain(params.item->Internal.getItemChainId)
154
-
155
- switch caller {
156
- | Some(callerEffect) if callerEffect->isCrossChain && !(effect->isCrossChain) =>
157
- // A cross-chain effect isn't tied to a single chain, so it has no chain
158
- // to resolve a chain-scoped child against. Reject before any cache work.
159
- JsError.throwWithMessage(
160
- `The cross-chain effect "${callerEffect.name}" cannot call the chain-scoped effect "${effect.name}", because a cross-chain effect isn't tied to a single chain. Make "${effect.name}" cross-chain (\`crossChain: true\`), or make "${callerEffect.name}" chain-scoped (\`crossChain: false\`).`,
161
- )
162
- | _ => ()
163
- }
164
-
165
- let effectContext = makeEffectContext(
166
- params,
167
- ~chainId=switch scope {
168
- | Internal.Chain(chainId) => Some(chainId)
169
- | Internal.CrossChain => None
170
- },
171
- ~effectName=effect.name,
172
- ~defaultShouldCache=effect.defaultShouldCache,
173
- // Nested calls made by the effect handler itself stay async: only the
174
- // handler that started the sync run needs a sync answer.
175
- ~callEffect=(nested, nestedInput) =>
176
- params->callEffectAsync(~effect=nested, ~input=nestedInput, ~caller=Some(effect)),
177
- )
178
- let effectArgs: Internal.effectArgs = {
179
- input,
180
- context: effectContext,
181
- cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
182
- chainId: params.item->Internal.getItemChainId,
183
- checkpointId: params.checkpointId,
184
- }
185
- (scope, effectArgs)
186
- }
187
-
188
- and callEffectAsync = (params: contextParams, ~effect, ~input, ~caller) => {
189
- let (scope, effectArgs) = params->prepareEffectCall(~effect, ~input, ~caller)
190
- LoadLayer.loadEffect(
191
- ~loadManager=params.loadManager,
192
- ~persistence=params.persistence,
193
- ~effect,
194
- ~effectArgs,
195
- ~scope,
196
- ~indexerState=params.indexerState,
197
- ~shouldGroup=params.isPreload,
198
- ~item=params.item,
199
- ~ecosystem=params.config.ecosystem,
200
- )
201
- }
202
-
203
- let initEffect = (params: contextParams) => {
204
- (effect: Internal.effect, input: Internal.effectInput) => {
205
- params->checkStatusOrThrow(~access="context.effect")
206
- params->callEffectAsync(~effect, ~input, ~caller=None)
207
- }
208
- }
209
-
210
- let effectMemoKey = (~effect: Internal.effect, ~scope: Internal.chainScope, ~cacheKey) =>
211
- switch scope {
212
- | CrossChain => `${effect.name}.${cacheKey}`
213
- | Chain(chainId) => `${effect.name}.${chainId->ChainId.toString}.${cacheKey}`
214
- }
215
-
216
- let initEffectSync = (params: contextParams) => {
217
- (effect: Internal.effect, input: Internal.effectInput) => {
218
- params->checkStatusOrThrow(~access="context.effectSync")
219
- let (scope, effectArgs) = params->prepareEffectCall(~effect, ~input, ~caller=None)
220
- let memo = params.sync->getMemo
221
- let memoKey = effectMemoKey(~effect, ~scope, ~cacheKey=effectArgs.cacheKey)
222
- switch memo->Utils.Dict.dangerouslyGetNonOption(memoKey) {
223
- | Some(output) => output
224
- | None =>
225
- let inMemTable = params.indexerState->InMemoryStore.getEffectInMemTable(~effect, ~scope)
226
- if inMemTable->InMemoryStore.hasEffectOutput(effectArgs.cacheKey) {
227
- let output = inMemTable->InMemoryStore.getEffectOutputUnsafe(effectArgs.cacheKey)
228
- memo->Dict.set(memoKey, output)
229
- output
230
- } else {
231
- params->scheduleAndSuspend(
232
- LoadLayer.loadEffect(
233
- ~loadManager=params.loadManager,
234
- ~persistence=params.persistence,
235
- ~effect,
236
- ~effectArgs,
237
- ~scope,
238
- ~indexerState=params.indexerState,
239
- ~shouldGroup=params.isPreload,
240
- ~item=params.item,
241
- ~ecosystem=params.config.ecosystem,
242
- ),
70
+ let rec makeCaller = (~caller: option<Internal.effect>) => {
71
+ (effect: Internal.effect, input: Internal.effectInput) => {
72
+ let scope = effect->isCrossChain ? Internal.CrossChain : Internal.Chain(handlerChainId)
73
+
74
+ switch caller {
75
+ | Some(callerEffect) if callerEffect->isCrossChain && !(effect->isCrossChain) =>
76
+ // A cross-chain effect isn't tied to a single chain, so it has no chain
77
+ // to resolve a chain-scoped child against. Reject before any cache work.
78
+ JsError.throwWithMessage(
79
+ `The cross-chain effect "${callerEffect.name}" cannot call the chain-scoped effect "${effect.name}", because a cross-chain effect isn't tied to a single chain. Make "${effect.name}" cross-chain (\`crossChain: true\`), or make "${callerEffect.name}" chain-scoped (\`crossChain: false\`).`,
243
80
  )
81
+ | _ => ()
82
+ }
83
+
84
+ let effectContext = makeEffectContext(
85
+ params,
86
+ ~chainId=switch scope {
87
+ | Internal.Chain(chainId) => Some(chainId)
88
+ | Internal.CrossChain => None
89
+ },
90
+ ~effectName=effect.name,
91
+ ~defaultShouldCache=effect.defaultShouldCache,
92
+ ~callEffect=makeCaller(~caller=Some(effect)),
93
+ )
94
+ let effectArgs: Internal.effectArgs = {
95
+ input,
96
+ context: effectContext,
97
+ cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
98
+ chainId: handlerChainId,
99
+ checkpointId: params.checkpointId,
244
100
  }
101
+ LoadLayer.loadEffect(
102
+ ~loadManager=params.loadManager,
103
+ ~persistence=params.persistence,
104
+ ~effect,
105
+ ~effectArgs,
106
+ ~scope,
107
+ ~indexerState=params.indexerState,
108
+ ~shouldGroup=params.isPreload,
109
+ ~item=params.item,
110
+ ~ecosystem=params.config.ecosystem,
111
+ )
245
112
  }
246
113
  }
114
+ makeCaller(~caller=None)
247
115
  }
248
116
 
249
117
  type entityContextParams = {
@@ -280,117 +148,21 @@ let throwClickHouseReadOnly = (entityConfig: Internal.entityConfig, op: string)
280
148
  `context.${entityConfig.name}.${op}() is unavailable: ClickHouse storage is currently write-only. Follow Envio releases to be notified when ClickHouse supports both reads and writes from handlers.`,
281
149
  )
282
150
 
283
- let writeChange = (params: entityContextParams, change) => {
284
- let scope = params->entityScope
285
- switch params.sync.writes {
286
- | Some(writes) => writes->SyncWrites.record(~entityConfig=params.entityConfig, ~scope, change)
287
- | None => params.indexerState->SyncWrites.write(~entityConfig=params.entityConfig, ~scope, change)
288
- }
289
- }
290
-
291
- let getRoundWrite = (params: entityContextParams, entityId) =>
292
- switch params.sync.writes {
293
- | Some(writes) => writes->SyncWrites.getById(~entityConfig=params.entityConfig, ~entityId)
294
- | None => None
295
- }
296
-
297
- let getSyncHandler = (params: entityContextParams, entityId: string) =>
298
- switch params->getRoundWrite(entityId) {
299
- | Some(entity) => entity
300
- | None =>
301
- switch LoadLayer.getByIdInMemory(
302
- ~entityConfig=params.entityConfig,
303
- ~scope=params->entityScope,
304
- ~indexerState=params.indexerState,
305
- ~entityId,
306
- ) {
307
- | Some(entity) => entity
308
- | None =>
309
- (params :> contextParams)->scheduleAndSuspend(
310
- LoadLayer.loadById(
311
- ~loadManager=params.loadManager,
312
- ~persistence=params.persistence,
313
- ~entityConfig=params.entityConfig,
314
- ~scope=params->entityScope,
315
- ~indexerState=params.indexerState,
316
- ~shouldGroup=params.isPreload,
317
- ~item=params.item,
318
- ~ecosystem=params.config.ecosystem,
319
- ~entityId,
320
- ),
321
- )
322
- }
323
- }
324
-
325
- let getWhereSyncHandler = (params: entityContextParams, filter: dict<dict<unknown>>) => {
326
- let entityConfig = params.entityConfig
327
- let filter =
328
- filter->EntityFilter.parseOrThrow(~entityName=entityConfig.name, ~table=entityConfig.table)
329
- switch LoadLayer.getByFilterInMemory(
330
- ~entityConfig,
331
- ~scope=params->entityScope,
332
- ~indexerState=params.indexerState,
333
- ~filter,
334
- ) {
335
- | Some(entities) =>
336
- switch params.sync.writes {
337
- | Some(writes) => writes->SyncWrites.overlayFilter(entities, ~entityConfig, ~filter)
338
- | None => entities
339
- }
340
- | None =>
341
- (params :> contextParams)->scheduleAndSuspend(
342
- LoadLayer.loadByParsedFilter(
343
- ~loadManager=params.loadManager,
344
- ~persistence=params.persistence,
345
- ~entityConfig,
346
- ~scope=params->entityScope,
347
- ~indexerState=params.indexerState,
348
- ~shouldGroup=params.isPreload,
349
- ~item=params.item,
350
- ~ecosystem=params.config.ecosystem,
351
- ~filter,
352
- ),
353
- )
354
- }
355
- }
356
-
357
- // Never suspends: the in-memory table spans the whole batch, so a change only
358
- // counts as "in this block" when it was written at this handler's checkpoint.
359
- let getInBlockSyncHandler = (params: entityContextParams, entityId: string) =>
360
- switch params->getRoundWrite(entityId) {
361
- | Some(entity) => entity
362
- | None =>
363
- let inMemTable =
364
- params.indexerState->InMemoryStore.getInMemTable(
365
- ~entityConfig=params.entityConfig,
366
- ~scope=params->entityScope,
367
- )
368
- switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
369
- | Some(change) if change->Change.getCheckpointId == params.checkpointId =>
370
- change->InMemoryTable.Entity.mapChangeToEntity
371
- | _ => None
372
- }
373
- }
374
-
375
151
  let entityTraps: Utils.Proxy.traps<entityContextParams> = {
376
152
  get: (~target as params, ~prop: unknown) => {
377
153
  let prop = prop->(Utils.magic: unknown => string)
378
154
 
379
- (params :> contextParams)->checkStatusOrThrow(
380
- ~access=`context.${params.entityConfig.name}.${prop}`,
381
- )
382
-
383
155
  let isClickHouseOnly = !params.entityConfig.storage.postgres
156
+ let scope = params->entityScope
157
+ let committedCheckpointId = params.indexerState->IndexerState.committedCheckpointIdFor(~scope)
384
158
 
385
159
  let set = params.isPreload
386
160
  ? noopSet
387
161
  : (entity: Internal.entity) => {
388
- // The check lives inside the closure too: a handler that grabbed
389
- // `context.X.set` before a suspend must not keep writing.
390
- (params :> contextParams)->checkStatusOrThrow(
391
- ~access=`context.${params.entityConfig.name}.set`,
392
- )
393
- params->writeChange(
162
+ params.indexerState
163
+ ->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
164
+ ->InMemoryTable.Entity.set(
165
+ ~committedCheckpointId,
394
166
  Set({
395
167
  entityId: entity.id->EntityId.unsafeOfString,
396
168
  checkpointId: params.checkpointId,
@@ -400,20 +172,6 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
400
172
  }
401
173
 
402
174
  switch prop {
403
- | "getSync" =>
404
- (entityId => params->getSyncHandler(entityId))->(
405
- Utils.magic: (string => option<Internal.entity>) => unknown
406
- )
407
-
408
- | "getWhereSync" =>
409
- (
410
- filter => params->getWhereSyncHandler(filter->(Utils.magic: unknown => dict<dict<unknown>>))
411
- )->(Utils.magic: (unknown => array<Internal.entity>) => unknown)
412
- | "getInBlockSync" =>
413
- (entityId => params->getInBlockSyncHandler(entityId))->(
414
- Utils.magic: (string => option<Internal.entity>) => unknown
415
- )
416
-
417
175
  | "get" =>
418
176
  if isClickHouseOnly {
419
177
  ((_entityId: string) => throwClickHouseReadOnly(params.entityConfig, "get"))->(
@@ -513,10 +271,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
513
271
  noopDeleteUnsafe
514
272
  } else {
515
273
  entityId => {
516
- (params :> contextParams)->checkStatusOrThrow(
517
- ~access=`context.${params.entityConfig.name}.deleteUnsafe`,
518
- )
519
- params->writeChange(
274
+ params.indexerState
275
+ ->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope)
276
+ ->InMemoryTable.Entity.set(
277
+ ~committedCheckpointId,
520
278
  Delete({
521
279
  entityId,
522
280
  checkpointId: params.checkpointId,
@@ -530,90 +288,17 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
530
288
  },
531
289
  }
532
290
 
533
- // Deterministic mappings always make progress, so the cap only exists to turn
534
- // a non-deterministic one into a clear error instead of a hang.
535
- let maxSyncRounds = 10000
536
-
537
- // Runs a synchronous body, replaying it from the top each time it suspends on
538
- // a read that wasn't in memory yet.
539
- let rec runSyncRound = async (params: contextParams, fn: unit => unit, ~round) => {
540
- if round > maxSyncRounds {
541
- JsError.throwWithMessage(
542
- `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.`,
543
- )
544
- }
545
- params.sync.status = Active
546
- params.sync.pending = None
547
- let writes = SyncWrites.make()
548
- params.sync.writes = Some(writes)
549
-
550
- let suspended = switch fn() {
551
- | () =>
552
- params.sync.writes = None
553
- switch params.sync.status {
554
- // The body caught the suspend and returned anyway, so it didn't see the
555
- // reads it asked for: it is replayed like any other suspended round.
556
- | Aborted(_) => true
557
- | Active | Resolved =>
558
- writes->SyncWrites.commit(~indexerState=params.indexerState)
559
- false
560
- }
561
- | exception exn =>
562
- // Neither a suspended round nor a failed one keeps what it wrote.
563
- params.sync.writes = None
564
- if exn->isSuspend {
565
- true
566
- } else {
567
- // The body threw its own error after scheduling a read. Those ops are
568
- // nobody's result now, and an unhandled rejection would replace the
569
- // error the handler actually raised.
570
- switch params.sync.pending {
571
- | None => ()
572
- | Some(pending) => {
573
- params.sync.pending = None
574
- pending->Array.forEach(promise => promise->Utils.Promise.silentCatch->ignore)
575
- }
576
- }
577
- throw(exn)
578
- }
579
- }
580
-
581
- if suspended {
582
- // A round only suspends by scheduling the read it missed.
583
- let pending = params.sync.pending->Option.getOr([])
584
- params.sync.pending = None
585
- let errors = []
586
- let _ = await pending
587
- ->Array.map(promise =>
588
- promise->Promise.catch(exn => {
589
- errors->Array.push(exn)
590
- Promise.resolve()
591
- })
592
- )
593
- ->Promise.all
594
- switch errors->Array.get(0) {
595
- | Some(exn) => throw(exn)
596
- | None => ()
597
- }
598
- await params->runSyncRound(fn, ~round=round + 1)
599
- }
600
- }
601
-
602
291
  let handlerTraps: Utils.Proxy.traps<contextParams> = {
603
292
  get: (~target as params, ~prop: unknown) => {
604
293
  let prop = prop->(Utils.magic: unknown => string)
605
- params->checkStatusOrThrow(~access=`context.${prop}`)
606
- switch prop {
607
- | "effectSync" =>
608
- initEffectSync((params :> contextParams))->(
609
- Utils.magic: ((Internal.effect, Internal.effectInput) => Internal.effectOutput) => unknown
610
- )
611
-
612
- | "runSync" =>
613
- (fn => params->runSyncRound(fn, ~round=1))->(
614
- Utils.magic: ((unit => unit) => promise<unit>) => unknown
294
+ if params.isResolved {
295
+ Utils.Error.make(
296
+ `Impossible to access context.${prop} after the handler is resolved. Make sure you didn't miss an await in the handler.`,
297
+ )->ErrorHandling.mkLogAndRaise(
298
+ ~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
615
299
  )
616
-
300
+ }
301
+ switch prop {
617
302
  | "log" =>
618
303
  (
619
304
  params.isPreload
@@ -645,7 +330,7 @@ let handlerTraps: Utils.Proxy.traps<contextParams> = {
645
330
  persistence: params.persistence,
646
331
  checkpointId: params.checkpointId,
647
332
  chains: params.chains,
648
- sync: params.sync,
333
+ isResolved: params.isResolved,
649
334
  config: params.config,
650
335
  entityConfig,
651
336
  }