envio 3.10.0-subgraph → 3.11.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.
- package/evm.schema.json +21 -4
- package/fuel.schema.json +21 -4
- package/index.d.ts +1 -1
- package/package.json +6 -6
- package/src/ChainState.res +9 -73
- package/src/ChainState.res.mjs +6 -58
- package/src/Config.res +73 -27
- package/src/Config.res.mjs +71 -21
- package/src/Core.res +0 -15
- package/src/Core.res.mjs +0 -11
- package/src/EventConfigBuilder.res +14 -10
- package/src/EventConfigBuilder.res.mjs +6 -4
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -8
- package/src/HandlerLoader.res +8 -20
- package/src/HandlerLoader.res.mjs +2 -11
- package/src/HandlerRegister.res +12 -11
- package/src/HandlerRegister.res.mjs +6 -5
- package/src/Internal.res +20 -4
- package/src/Internal.res.mjs +9 -0
- package/src/Main.res +8 -1
- package/src/Main.res.mjs +3 -3
- package/src/Persistence.res +11 -1
- package/src/Persistence.res.mjs +6 -2
- package/src/PgStorage.res +1 -1
- package/src/PgStorage.res.mjs +1 -1
- package/src/SimulateItems.res +13 -6
- package/src/SimulateItems.res.mjs +7 -6
- package/src/TestIndexer.res +6 -5
- package/src/TestIndexer.res.mjs +5 -4
- package/src/UserContext.res +51 -358
- package/src/UserContext.res.mjs +35 -271
- package/src/db/InternalTable.res +1 -1
- package/src/db/InternalTable.res.mjs +2 -1
- package/src/sources/ChainSources.res +72 -0
- package/src/sources/ChainSources.res.mjs +55 -0
- package/src/sources/EvmHyperSyncSource.res +10 -22
- package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
- package/src/sources/FuelHyperSyncSource.res +10 -18
- package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
- package/src/sources/HyperSync.res +31 -0
- package/src/sources/HyperSync.res.mjs +31 -0
- package/src/sources/HyperSync.resi +16 -0
- package/src/sources/StartBlockResolver.res +173 -0
- package/src/sources/StartBlockResolver.res.mjs +134 -0
- package/src/sources/Svm.res +0 -51
- package/src/sources/Svm.res.mjs +0 -46
- package/src/sources/SvmHyperSyncSource.res +20 -9
- package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
- package/svm.schema.json +78 -63
- package/src/subgraph/blocks.ts +0 -176
- package/src/subgraph/calls.ts +0 -217
- package/src/subgraph/conformance.ts +0 -102
- package/src/subgraph/division.ts +0 -133
- package/src/subgraph/errors.ts +0 -90
- package/src/subgraph/graph-ts-types/VERSION +0 -2
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
- package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
- package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
- package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
- package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
- package/src/subgraph/graph-ts-types/index.d.ts +0 -102
- package/src/subgraph/graph-ts.ts +0 -1948
- package/src/subgraph/hosts.ts +0 -148
- package/src/subgraph/runtime.ts +0 -863
- package/src/subgraph/scope.ts +0 -66
package/src/UserContext.res
CHANGED
|
@@ -1,37 +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 already resolved for this handler invocation. Replay rounds
|
|
28
|
-
// and the preload -> execute transition reuse them even when the in-memory
|
|
29
|
-
// effect table drops the entry (`cache: false`).
|
|
30
|
-
mutable memo: option<dict<Internal.effectOutput>>,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let makeSyncState = () => {status: Active, pending: None, memo: None}
|
|
34
|
-
|
|
35
1
|
type contextParams = {
|
|
36
2
|
item: Internal.item,
|
|
37
3
|
checkpointId: Internal.checkpointId,
|
|
@@ -41,45 +7,7 @@ type contextParams = {
|
|
|
41
7
|
isPreload: bool,
|
|
42
8
|
chains: Internal.chains,
|
|
43
9
|
config: Config.t,
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let getPending = (sync: syncState) =>
|
|
48
|
-
switch sync.pending {
|
|
49
|
-
| Some(pending) => pending
|
|
50
|
-
| None =>
|
|
51
|
-
let pending = []
|
|
52
|
-
sync.pending = Some(pending)
|
|
53
|
-
pending
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let getMemo = (sync: syncState) =>
|
|
57
|
-
switch sync.memo {
|
|
58
|
-
| Some(memo) => memo
|
|
59
|
-
| None =>
|
|
60
|
-
let memo = Dict.make()
|
|
61
|
-
sync.memo = Some(memo)
|
|
62
|
-
memo
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let checkStatusOrThrow = (params: contextParams, ~access: string) =>
|
|
66
|
-
switch params.sync.status {
|
|
67
|
-
| Active => ()
|
|
68
|
-
| Aborted(exn) => throw(exn)
|
|
69
|
-
| Resolved =>
|
|
70
|
-
Utils.Error.make(
|
|
71
|
-
`Impossible to access ${access} after the handler is resolved. Make sure you didn't miss an await in the handler.`,
|
|
72
|
-
)->ErrorHandling.mkLogAndRaise(
|
|
73
|
-
~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Fires the async op behind a sync miss, records it for the replay loop and
|
|
78
|
-
// aborts the context.
|
|
79
|
-
let scheduleAndSuspend = (params: contextParams, promise: promise<'a>): 'b => {
|
|
80
|
-
params.sync->getPending->Array.push(promise->Utils.Promise.ignoreValue)
|
|
81
|
-
params.sync.status = Aborted(Suspend)
|
|
82
|
-
throw(Suspend)
|
|
10
|
+
mutable isResolved: bool,
|
|
83
11
|
}
|
|
84
12
|
|
|
85
13
|
// We don't want to expose the params to the user
|
|
@@ -130,15 +58,8 @@ external makeEffectContext: (
|
|
|
130
58
|
~callEffect: (Internal.effect, Internal.effectInput) => promise<Internal.effectOutput>,
|
|
131
59
|
) => Internal.effectContext = "EffectContext"
|
|
132
60
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// duplicating the nested-caller rules.
|
|
136
|
-
let rec prepareEffectCall = (
|
|
137
|
-
params: contextParams,
|
|
138
|
-
~effect: Internal.effect,
|
|
139
|
-
~input: Internal.effectInput,
|
|
140
|
-
~caller: option<Internal.effect>,
|
|
141
|
-
) => {
|
|
61
|
+
let initEffect = (params: contextParams) => {
|
|
62
|
+
let handlerChainId = params.item->Internal.getItemChainId
|
|
142
63
|
// An effect that didn't state a scope follows the config: cross-chain by
|
|
143
64
|
// default, per-chain under `disable_default_cross_chain`.
|
|
144
65
|
let isCrossChain = (effect: Internal.effect) =>
|
|
@@ -146,101 +67,50 @@ let rec prepareEffectCall = (
|
|
|
146
67
|
// A chain-scoped effect always resolves against the chain of the handler that
|
|
147
68
|
// triggered the call, even several effects deep, so the chain id is captured
|
|
148
69
|
// once from the item and reused for the whole nested-call tree.
|
|
149
|
-
let
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
| _ => ()
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let effectContext = makeEffectContext(
|
|
164
|
-
params,
|
|
165
|
-
~chainId=switch scope {
|
|
166
|
-
| Internal.Chain(chainId) => Some(chainId)
|
|
167
|
-
| Internal.CrossChain => None
|
|
168
|
-
},
|
|
169
|
-
~effectName=effect.name,
|
|
170
|
-
~defaultShouldCache=effect.defaultShouldCache,
|
|
171
|
-
// Nested calls made by the effect handler itself stay async: only the
|
|
172
|
-
// handler that started the sync run needs a sync answer.
|
|
173
|
-
~callEffect=(nested, nestedInput) =>
|
|
174
|
-
params->callEffectAsync(~effect=nested, ~input=nestedInput, ~caller=Some(effect)),
|
|
175
|
-
)
|
|
176
|
-
let effectArgs: Internal.effectArgs = {
|
|
177
|
-
input,
|
|
178
|
-
context: effectContext,
|
|
179
|
-
cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
|
|
180
|
-
checkpointId: params.checkpointId,
|
|
181
|
-
}
|
|
182
|
-
(scope, effectArgs)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
and callEffectAsync = (params: contextParams, ~effect, ~input, ~caller) => {
|
|
186
|
-
let (scope, effectArgs) = params->prepareEffectCall(~effect, ~input, ~caller)
|
|
187
|
-
LoadLayer.loadEffect(
|
|
188
|
-
~loadManager=params.loadManager,
|
|
189
|
-
~persistence=params.persistence,
|
|
190
|
-
~effect,
|
|
191
|
-
~effectArgs,
|
|
192
|
-
~scope,
|
|
193
|
-
~indexerState=params.indexerState,
|
|
194
|
-
~shouldGroup=params.isPreload,
|
|
195
|
-
~item=params.item,
|
|
196
|
-
~ecosystem=params.config.ecosystem,
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
let initEffect = (params: contextParams) => {
|
|
201
|
-
(effect: Internal.effect, input: Internal.effectInput) => {
|
|
202
|
-
params->checkStatusOrThrow(~access="context.effect")
|
|
203
|
-
params->callEffectAsync(~effect, ~input, ~caller=None)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let effectMemoKey = (~effect: Internal.effect, ~scope: Internal.chainScope, ~cacheKey) =>
|
|
208
|
-
switch scope {
|
|
209
|
-
| CrossChain => `${effect.name}.${cacheKey}`
|
|
210
|
-
| Chain(chainId) => `${effect.name}.${chainId->ChainId.toString}.${cacheKey}`
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
let initEffectSync = (params: contextParams) => {
|
|
214
|
-
(effect: Internal.effect, input: Internal.effectInput) => {
|
|
215
|
-
params->checkStatusOrThrow(~access="context.effectSync")
|
|
216
|
-
let (scope, effectArgs) = params->prepareEffectCall(~effect, ~input, ~caller=None)
|
|
217
|
-
let memo = params.sync->getMemo
|
|
218
|
-
let memoKey = effectMemoKey(~effect, ~scope, ~cacheKey=effectArgs.cacheKey)
|
|
219
|
-
switch memo->Utils.Dict.dangerouslyGetNonOption(memoKey) {
|
|
220
|
-
| Some(output) => output
|
|
221
|
-
| None =>
|
|
222
|
-
let inMemTable = params.indexerState->InMemoryStore.getEffectInMemTable(~effect, ~scope)
|
|
223
|
-
if inMemTable->InMemoryStore.hasEffectOutput(effectArgs.cacheKey) {
|
|
224
|
-
let output = inMemTable->InMemoryStore.getEffectOutputUnsafe(effectArgs.cacheKey)
|
|
225
|
-
memo->Dict.set(memoKey, output)
|
|
226
|
-
output
|
|
227
|
-
} else {
|
|
228
|
-
params->scheduleAndSuspend(
|
|
229
|
-
LoadLayer.loadEffect(
|
|
230
|
-
~loadManager=params.loadManager,
|
|
231
|
-
~persistence=params.persistence,
|
|
232
|
-
~effect,
|
|
233
|
-
~effectArgs,
|
|
234
|
-
~scope,
|
|
235
|
-
~indexerState=params.indexerState,
|
|
236
|
-
~shouldGroup=params.isPreload,
|
|
237
|
-
~item=params.item,
|
|
238
|
-
~ecosystem=params.config.ecosystem,
|
|
239
|
-
),
|
|
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\`).`,
|
|
240
80
|
)
|
|
81
|
+
| _ => ()
|
|
241
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
|
+
checkpointId: params.checkpointId,
|
|
99
|
+
}
|
|
100
|
+
LoadLayer.loadEffect(
|
|
101
|
+
~loadManager=params.loadManager,
|
|
102
|
+
~persistence=params.persistence,
|
|
103
|
+
~effect,
|
|
104
|
+
~effectArgs,
|
|
105
|
+
~scope,
|
|
106
|
+
~indexerState=params.indexerState,
|
|
107
|
+
~shouldGroup=params.isPreload,
|
|
108
|
+
~item=params.item,
|
|
109
|
+
~ecosystem=params.config.ecosystem,
|
|
110
|
+
)
|
|
242
111
|
}
|
|
243
112
|
}
|
|
113
|
+
makeCaller(~caller=None)
|
|
244
114
|
}
|
|
245
115
|
|
|
246
116
|
type entityContextParams = {
|
|
@@ -296,111 +166,15 @@ let throwClickHouseReadOnly = (entityConfig: Internal.entityConfig, op: string)
|
|
|
296
166
|
`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.`,
|
|
297
167
|
)
|
|
298
168
|
|
|
299
|
-
// A sync read against the in-memory entity table: a hit is returned as-is
|
|
300
|
-
// (including a recorded absence), a miss schedules the async load and suspends.
|
|
301
|
-
let getSyncHandler = (params: entityContextParams, entityId: string) => {
|
|
302
|
-
let inMemTable =
|
|
303
|
-
params.indexerState->InMemoryStore.getInMemTable(
|
|
304
|
-
~entityConfig=params.entityConfig,
|
|
305
|
-
~scope=params->entityScope,
|
|
306
|
-
)
|
|
307
|
-
if inMemTable.latestEntityChangeById->Dict.has(entityId) {
|
|
308
|
-
(inMemTable->InMemoryTable.Entity.getUnsafe)(entityId)
|
|
309
|
-
} else {
|
|
310
|
-
(params :> contextParams)->scheduleAndSuspend(
|
|
311
|
-
LoadLayer.loadById(
|
|
312
|
-
~loadManager=params.loadManager,
|
|
313
|
-
~persistence=params.persistence,
|
|
314
|
-
~entityConfig=params.entityConfig,
|
|
315
|
-
~scope=params->entityScope,
|
|
316
|
-
~indexerState=params.indexerState,
|
|
317
|
-
~shouldGroup=params.isPreload,
|
|
318
|
-
~item=params.item,
|
|
319
|
-
~ecosystem=params.config.ecosystem,
|
|
320
|
-
~entityId,
|
|
321
|
-
),
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
let getWhereSyncHandler = (params: entityContextParams, filter: dict<dict<unknown>>) => {
|
|
327
|
-
let entityConfig = params.entityConfig
|
|
328
|
-
let inMemTable =
|
|
329
|
-
params.indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope=params->entityScope)
|
|
330
|
-
let hasIndex = inMemTable->InMemoryTable.Entity.hasIndex
|
|
331
|
-
let getOnIndex = inMemTable->InMemoryTable.Entity.getUnsafeOnIndex
|
|
332
|
-
|
|
333
|
-
let filters =
|
|
334
|
-
filter->EntityFilter.parseGetWhereOrThrow(
|
|
335
|
-
~entityName=entityConfig.name,
|
|
336
|
-
~table=entityConfig.table,
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
let missing = []
|
|
340
|
-
let entities = []
|
|
341
|
-
filters->Array.forEach(filter => {
|
|
342
|
-
let filterKey = filter->EntityFilter.toString
|
|
343
|
-
if hasIndex(filterKey) {
|
|
344
|
-
entities->Array.pushMany(getOnIndex(filterKey))
|
|
345
|
-
} else {
|
|
346
|
-
missing->Array.push(
|
|
347
|
-
LoadLayer.loadByFilter(
|
|
348
|
-
~loadManager=params.loadManager,
|
|
349
|
-
~persistence=params.persistence,
|
|
350
|
-
~entityConfig,
|
|
351
|
-
~scope=params->entityScope,
|
|
352
|
-
~indexerState=params.indexerState,
|
|
353
|
-
~shouldGroup=params.isPreload,
|
|
354
|
-
~item=params.item,
|
|
355
|
-
~ecosystem=params.config.ecosystem,
|
|
356
|
-
~filter,
|
|
357
|
-
)->Utils.Promise.ignoreValue,
|
|
358
|
-
)
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
if missing->Utils.Array.notEmpty {
|
|
363
|
-
let pending = params.sync->getPending
|
|
364
|
-
missing->Array.forEach(promise => pending->Array.push(promise))
|
|
365
|
-
params.sync.status = Aborted(Suspend)
|
|
366
|
-
throw(Suspend)
|
|
367
|
-
}
|
|
368
|
-
entities
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Never suspends: the in-memory table spans the whole batch, so a change only
|
|
372
|
-
// counts as "in this block" when it was written at this handler's checkpoint.
|
|
373
|
-
let getInBlockSyncHandler = (params: entityContextParams, entityId: string) => {
|
|
374
|
-
let inMemTable =
|
|
375
|
-
params.indexerState->InMemoryStore.getInMemTable(
|
|
376
|
-
~entityConfig=params.entityConfig,
|
|
377
|
-
~scope=params->entityScope,
|
|
378
|
-
)
|
|
379
|
-
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(entityId) {
|
|
380
|
-
| Some(change) if change->Change.getCheckpointId == params.checkpointId =>
|
|
381
|
-
change->InMemoryTable.Entity.mapChangeToEntity
|
|
382
|
-
| _ => None
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
169
|
let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
387
170
|
get: (~target as params, ~prop: unknown) => {
|
|
388
171
|
let prop = prop->(Utils.magic: unknown => string)
|
|
389
172
|
|
|
390
|
-
(params :> contextParams)->checkStatusOrThrow(
|
|
391
|
-
~access=`context.${params.entityConfig.name}.${prop}`,
|
|
392
|
-
)
|
|
393
|
-
|
|
394
173
|
let isClickHouseOnly = !params.entityConfig.storage.postgres
|
|
395
174
|
|
|
396
175
|
let set = params.isPreload
|
|
397
176
|
? noopSet
|
|
398
177
|
: (entity: Internal.entity) => {
|
|
399
|
-
// The check lives inside the closure too: a handler that grabbed
|
|
400
|
-
// `context.X.set` before a suspend must not keep writing.
|
|
401
|
-
(params :> contextParams)->checkStatusOrThrow(
|
|
402
|
-
~access=`context.${params.entityConfig.name}.set`,
|
|
403
|
-
)
|
|
404
178
|
params.indexerState
|
|
405
179
|
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope=params->entityScope)
|
|
406
180
|
->InMemoryTable.Entity.set(
|
|
@@ -414,18 +188,6 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
414
188
|
}
|
|
415
189
|
|
|
416
190
|
switch prop {
|
|
417
|
-
| "getSync" =>
|
|
418
|
-
(entityId => params->getSyncHandler(entityId))->(
|
|
419
|
-
Utils.magic: (string => option<Internal.entity>) => unknown
|
|
420
|
-
)
|
|
421
|
-
| "getWhereSync" =>
|
|
422
|
-
(
|
|
423
|
-
filter => params->getWhereSyncHandler(filter->(Utils.magic: unknown => dict<dict<unknown>>))
|
|
424
|
-
)->(Utils.magic: (unknown => array<Internal.entity>) => unknown)
|
|
425
|
-
| "getInBlockSync" =>
|
|
426
|
-
(entityId => params->getInBlockSyncHandler(entityId))->(
|
|
427
|
-
Utils.magic: (string => option<Internal.entity>) => unknown
|
|
428
|
-
)
|
|
429
191
|
| "get" =>
|
|
430
192
|
if isClickHouseOnly {
|
|
431
193
|
((_entityId: string) => throwClickHouseReadOnly(params.entityConfig, "get"))->(
|
|
@@ -525,9 +287,6 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
525
287
|
noopDeleteUnsafe
|
|
526
288
|
} else {
|
|
527
289
|
entityId => {
|
|
528
|
-
(params :> contextParams)->checkStatusOrThrow(
|
|
529
|
-
~access=`context.${params.entityConfig.name}.deleteUnsafe`,
|
|
530
|
-
)
|
|
531
290
|
params.indexerState
|
|
532
291
|
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig, ~scope=params->entityScope)
|
|
533
292
|
->InMemoryTable.Entity.set(
|
|
@@ -545,83 +304,17 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
545
304
|
},
|
|
546
305
|
}
|
|
547
306
|
|
|
548
|
-
// Deterministic mappings always make progress, so the cap only exists to turn
|
|
549
|
-
// a non-deterministic one into a clear error instead of a hang.
|
|
550
|
-
let maxSyncRounds = 10000
|
|
551
|
-
|
|
552
|
-
// Runs a synchronous body, replaying it from the top each time it suspends on
|
|
553
|
-
// a read that wasn't in memory yet.
|
|
554
|
-
let rec runSyncRound = async (params: contextParams, fn: unit => unit, ~round) => {
|
|
555
|
-
if round > maxSyncRounds {
|
|
556
|
-
JsError.throwWithMessage(
|
|
557
|
-
`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.`,
|
|
558
|
-
)
|
|
559
|
-
}
|
|
560
|
-
params.sync.status = Active
|
|
561
|
-
params.sync.pending = None
|
|
562
|
-
|
|
563
|
-
let suspended = switch fn() {
|
|
564
|
-
| () => false
|
|
565
|
-
| exception exn =>
|
|
566
|
-
if exn->isSuspend {
|
|
567
|
-
true
|
|
568
|
-
} else {
|
|
569
|
-
// The body threw its own error after scheduling a read. Those ops are
|
|
570
|
-
// nobody's result now, and an unhandled rejection would replace the
|
|
571
|
-
// error the handler actually raised.
|
|
572
|
-
switch params.sync.pending {
|
|
573
|
-
| None => ()
|
|
574
|
-
| Some(pending) => {
|
|
575
|
-
params.sync.pending = None
|
|
576
|
-
pending->Array.forEach(promise => promise->Utils.Promise.silentCatch->ignore)
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
throw(exn)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
switch params.sync.pending {
|
|
584
|
-
| None => ()
|
|
585
|
-
| Some(pending) =>
|
|
586
|
-
params.sync.pending = None
|
|
587
|
-
if suspended {
|
|
588
|
-
let errors = []
|
|
589
|
-
let _ =
|
|
590
|
-
await pending
|
|
591
|
-
->Array.map(promise =>
|
|
592
|
-
promise->Promise.catch(exn => {
|
|
593
|
-
errors->Array.push(exn)
|
|
594
|
-
Promise.resolve()
|
|
595
|
-
})
|
|
596
|
-
)
|
|
597
|
-
->Promise.all
|
|
598
|
-
switch errors->Array.get(0) {
|
|
599
|
-
| Some(exn) => throw(exn)
|
|
600
|
-
| None => ()
|
|
601
|
-
}
|
|
602
|
-
await params->runSyncRound(fn, ~round=round + 1)
|
|
603
|
-
} else {
|
|
604
|
-
// The body swallowed the suspend and returned anyway. The scheduled ops
|
|
605
|
-
// are nobody's result now, but they must not surface as unhandled
|
|
606
|
-
// rejections.
|
|
607
|
-
pending->Array.forEach(promise => promise->Utils.Promise.silentCatch->ignore)
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
307
|
let handlerTraps: Utils.Proxy.traps<contextParams> = {
|
|
613
308
|
get: (~target as params, ~prop: unknown) => {
|
|
614
309
|
let prop = prop->(Utils.magic: unknown => string)
|
|
615
|
-
params
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
)
|
|
621
|
-
| "runSync" =>
|
|
622
|
-
(fn => params->runSyncRound(fn, ~round=1))->(
|
|
623
|
-
Utils.magic: ((unit => unit) => promise<unit>) => unknown
|
|
310
|
+
if params.isResolved {
|
|
311
|
+
Utils.Error.make(
|
|
312
|
+
`Impossible to access context.${prop} after the handler is resolved. Make sure you didn't miss an await in the handler.`,
|
|
313
|
+
)->ErrorHandling.mkLogAndRaise(
|
|
314
|
+
~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
|
|
624
315
|
)
|
|
316
|
+
}
|
|
317
|
+
switch prop {
|
|
625
318
|
| "log" =>
|
|
626
319
|
(
|
|
627
320
|
params.isPreload
|
|
@@ -653,7 +346,7 @@ let handlerTraps: Utils.Proxy.traps<contextParams> = {
|
|
|
653
346
|
persistence: params.persistence,
|
|
654
347
|
checkpointId: params.checkpointId,
|
|
655
348
|
chains: params.chains,
|
|
656
|
-
|
|
349
|
+
isResolved: params.isResolved,
|
|
657
350
|
config: params.config,
|
|
658
351
|
entityConfig,
|
|
659
352
|
}
|